There are multiple layers of buffers that may be used by the BIOS, DOS, and the C library (including scanf
). When your machine starts up the interrupt vector table is modified to point IRQ1/INT 9h (the external keyboard interrupt) to a BIOS routine to handle characters as they are typed. At the lowest level there is usually a 32 byte6 circular buffer that is maintained in the BIOS Data Area (BDA) to keep track of the characters. You can use the Int 16h BIOS calls1 to interact with this low level keyboard buffer. If you remove characters from the BIOS keyboard buffer at interrupt time then DOS and the C library scanf
5 routine will never see them.
BIOS/中断级别消除重复字符的方法
It appears that the exercise is to eliminate all duplicate2 characters entered into scanf
3 by intercepting keystrokes via Interrupt 9 (IRQ1) and throwing duplicates away. One idea for a new keyboard interrupt handler to eliminate the duplicates before DOS (and eventually scanf
) ever see them:
- 跟踪变量中按下的前一个字符
- 调用原始(已保存)中断 9,以便 BIOS 更新键盘缓冲区和键盘标志,如 DOS 期望的那样。
- 查询键盘以查看某个字符是否可用累计16小时/AH=1小时如果没有可用字符,则零标志 (ZF) 将被设置;如果有可用字符,则将清除零标志。此键盘 BIOS 调用会查看键盘缓冲区的开头,而不会实际删除下一个可用字符。
- If a character is available then compare it with the previous character.
- 如果不同则用当前字符更新前一个字符并退出
- 如果它们相同,则使用整数 16 小时/AH=0 小时从键盘缓冲区中删除重复字符并退出
A Turbo-C 3.0x version of the code4:
#include <stdio.h>
#include <dos.h>
#include <string.h>
#include <conio.h>
volatile char key = 0;
void interrupt (*Int9save)(void);
void interrupt kill_multiple_press(void)
{
asm {
PUSHF
CALL DWORD PTR Int9save /* Fake an interrupt call to original handler */
MOV AH, 1 /* Peek at next key in buffer without removing it */
INT 16h
JZ noKey /* If no keystroke then we are finished */
/* If ZF=1 then no key */
CMP AL, [key] /* Compare key to previous key */
JNE updChar /* If characters are not same, update */
/* last character and finish */
/* Last character and current character are same (duplicate)
* Read keystroke from keyboard buffer and throw it away (ignore it)
* When it is thrown away DOS and eventually `scanf` will never see it */
XOR AH, AH /* AH = 0, Read keystroke BIOS Call */
INT 16h /* Read keystroke that has been identified as a */
/* duplicate in keyboard buffer and throw away */
JMP noKey /* We are finished */
}
updChar:
asm {
MOV [key], AL /* Update last character pressed */
}
noKey: /* We are finished */
}
void eliminate_multiple_press()
{
Int9save = getvect(9);
setvect(9, kill_multiple_press);
}
void uneliminate_multiple_press()
{
setvect(9, Int9save);
}
void main()
{
char str[1000];
clrscr();
eliminate_multiple_press();
printf("Enter your string: ");
/* Get a string terminated by a newline. Max 999 chars + newline */
scanf("%999[^\n]s", &str);
printf("\n%s", str);
uneliminate_multiple_press();
}
Notes
-
1Within the keyboard interrupt handler you want to avoid any keyboard BIOS call that will block waiting for keyboard input. If using Int 16h/AH=0 make sure there is a character available first with Int 16h/AH=1 otherwise Int 16h/AH=0 will block while waiting for another character to arrive.
-
2Removing duplicate characters is not the same as disabling the keyboard repeat rate.
-
3Because the duplicates are removed before DOS routines see them (and functions like
scanf
that rely on DOS), they will never be seen by scanf
.
-
4Some modifications may have to be made to be compatible with versions of Turbo-C other than 3.0x.
-
5This method only works because
scanf
will be indirectly making BIOS calls keeping the keyboard buffer clear. This code does't work in all generic cases especially where keystrokes may be buffered by the BIOS. To get around that the keyboard interrupt routine would have to remove all the duplicates in the keyboard buffer not just at the head as this code does.
-
6Each keystroke takes up 2 bytes of space in the BIOS keyboard buffer (in the BDA). 2 of the 32 bytes are lost because they are used to detect if the keyboard buffer is full or empty. This means the maximum number of keystrokes the BIOS can buffer is 15.