INT 16h AH=00h takes the oldest key out of the BIOS keyboard buffer and returns it: AL = the ASCII character, AH = the scan code of the key. If the buffer is empty it waits, with interrupts enabled, until the keyboard handler puts something there. There is no timeout and no echo, and Ctrl+C is just character 03h: nothing aborts the program.

Because both bytes come back, one call tells apart keys that DOS would need two calls for. A key with a character has AL ≠ 0; a key without one (function keys, arrows, Alt+letter) has AL = 0 and is identified by AH alone. The example stores "A" and F1 in the buffer, then reads them back:

prints: 1E41 3B00
MOV AH, 05h ; put "A" in the buffer: scan 1Eh, ASCII 41h
MOV CX, 1E41h
INT 16h
MOV AH, 05h ; and F1: scan 3Bh, no ASCII
MOV CX, 3B00h
INT 16h
MOV AH, 00h ; BIOS: wait for a key (one is there)
INT 16h ; AH = scan code, AL = ASCII
CALL PHEX4 ; 1E41
MOV DL, " "
MOV AH, 02h ; DOS: print the character in DL
INT 21h
MOV AH, 00h
INT 16h ; F1: AL = 0 marks a key without a character
CALL PHEX4 ; 3B00
PHEX4 PROC NEAR ; print AX as four hex digits; keeps every register
PUSH AX
MOV AL, AH
CALL PHEX2 ; the high byte first
POP AX
CALL PHEX2 ; then the low byte
RET
PHEX4 ENDP
PHEX2 PROC NEAR ; print AL as two hex digits; keeps every register
PUSH AX
PUSH CX
PUSH DX
MOV DH, AL ; keep the byte
MOV CL, 4
SHR AL, CL ; the high nibble
CALL HEXN1
MOV AL, DH
AND AL, 0Fh ; the low nibble
CALL HEXN1
POP DX
POP CX
POP AX
RET
HEXN1:
ADD AL, "0"
CMP AL, "9"
JBE HEXN2
ADD AL, 7 ; past "9": "A".."F"
HEXN2:
MOV DL, AL
MOV AH, 02h
INT 21h
RET
PHEX2 ENDP

Telling keys apart

The usual dispatch tests AL first. Comparing AH instead makes the program independent of Shift and Caps Lock, which is what games want for movement keys; comparing AL is what a text editor wants for letters.

MOV AH, 00h
INT 16h
TEST AL, AL ; a character?
JZ SPECIAL1 ; no: look at the scan code
CMP AL, 1Bh ; Esc
JE QUIT1
; ... AL is a character ...
SPECIAL1:
CMP AH, 48h ; Up
JE UP1
CMP AH, 3Bh ; F1
JE HELP1

Pitfalls

The call blocks. A program that must keep running (a game loop, an animation) checks first with INT 16h AH=01h and only reads when a key is there. On a 101-key keyboard the grey arrows, F11 and F12 are filtered for compatibility: a grey arrow comes back as the keypad one (48E0h becomes 4800h) and F11/F12 are silently dropped while the call keeps waiting. Use INT 16h AH=10h to see them. DOS functions such as INT 21h AH=08h read the same buffer, but return an extended key in two steps: first 0, then the scan code on the next call.

In DOSBox-X the wait does not spin the emulated CPU in a tight loop; the BIOS idles until a key arrives, so a program sitting in AH=00h costs little host CPU.