键盘中断也是一个非常重要的硬中断。当键盘中的一个按钮被按下或抬起时,将通过8359A芯片向CPU发送一个键盘中断的消息,这时CPU将转入键盘中断处理程序。键盘上的每个按键都对应一个扫描码,当有键盘中断时,这个扫描码被送入0x60端口。CPU通过读取0x60端口中的扫描码就可以得知是键盘中的哪个键盘被按下或抬起了。按键盘的扫描码的大小顺序定义的按键如下:
#define KEY_ESC 0X01 // ESC
#define KEY_1 0X02 // 1
#define KEY_2 0X03 // 2
#define KEY_3 0X04 // 3
#define KEY_4 0X05 // 4
#define KEY_5 0X06 // 5
#define KEY_6 0X07 // 6
#define KEY_7 0X08 // 7
#define KEY_8 0X09 // 8
#define KEY_9 0X0A // 9
#define KEY_0 0X0B // 0
#define KEY_DASH 0X0C // -
#define KEY_EQUAL 0X0D // =
#define KEY_BACKSPACE 0X0E // BACKSPACE
#define KEY_TAB 0X0F // TAB
#define KEY_Q 0X10 // Q
#define KEY_W 0X11 // W
#define KEY_E 0X12 // E
#define KEY_R 0X13 // R
#define KEY_T 0X14 // T
#define KEY_Y 0X15 // Y
#define KEY_U 0X16 // U
#define KEY_I 0X17 // I
#define KEY_O 0X18 // O
#define KEY_P 0X19 // P
#define KEY_LBRACKET 0X1A // [
#define KEY_RBRACKET 0X1B // ]
#define KEY_ENTER 0X1C // ENTER
#define KEY_CTRL 0X1D // CTRL
#define KEY_A 0X1E // A
#define KEY_S 0X1F // S
#define KEY_D 0X20 // D
#define KEY_F 0X21 // F
#define KEY_G 0X22 // G
#define KEY_H 0X23 // H
#define KEY_J 0X24 // J
#define KEY_K 0X25 // K
#define KEY_L 0X26 // L
#define KEY_SEMICOLON 0X27 // ;
#define KEY_RQUOTE 0X28 // '
#define KEY_LQUOTE 0X29 // `
#define KEY_LEFT_SHIFT 0X2A // LEFT SHIFT
#define KEY_BACKSLASH 0X2B // '\'
#define KEY_Z 0X2C // Z
#define KEY_X 0X2D // X
#define KEY_C 0X2E // C
#define KEY_V 0X2F // V
#define KEY_B 0X30 // B
#define KEY_N 0X31 // N
#define KEY_M 0X32 // M
#define KEY_COMMA 0X33 // ,
#define KEY_PERIOD 0X34 // .
#define KEY_SLASH 0X35 // /
#define KEY_RIGHT_SHIFT 0X36 // RIGHT SHIFT
#define KEY_PRTSC 0X37 // PRINT SCREEN
#define KEY_ALT 0X38 // ALT
#define KEY_SPACE 0X39 // SPACE
#define KEY_CAPS_LOCK 0X3A // CAPS LOCK
#define KEY_F1 0X3B // F1
#define KEY_F2 0X3C // F2
#define KEY_F3 0X3D // F3
#define KEY_F4 0X3E // F4
#define KEY_F5 0X3F // F5
#define KEY_F6 0X40 // F6
#define KEY_F7 0X41 // F7
#define KEY_F8 0X42 // F8
#define KEY_F9 0X43 // F9
#define KEY_F10 0X44 // F10
#define KEY_NUM_LOCK 0X45 // NUM LOCK
#define KEY_SCROLL_LOCK 0X46 // SCROLL LOCK
#define KEY_HOME 0X47 // HOME
#define KEY_UP 0X48 // UP
#define KEY_PAGE_UP 0X49 // PAGE UP
#define KEY_SUB 0X4A // SUB
#define KEY_LEFT 0X4B // LEFT
#define KEY_CENTER 0X4C // CENTER
#define KEY_RIGHT 0X4D // RIGHT
#define KEY_ADD 0X4E // ADD
#define KEY_END 0X4F // END
#define KEY_DOWN 0X50 // DOWN
#define KEY_PAGE_DOWN 0X51 // PAGE DOWN
#define KEY_INSERT 0X52 // INSERT
#define KEY_DEL 0X53 // DEL
另外,当一个键被按下时,我们希望在显示器上显示出这个字符,所以还要定义可显字符与扫描码的对应关系。比如当CPU得到一个扫描码为0x1e的按键,可以通过这个对应关系找到这个按键为字符'a',于是调用putchar函数在显示器上显示'a'。这个对应关系被定义为一个二维的字符数组,这个字符数组的行下标为扫描码,列下标为当shift按键按下时的下标。如下:
u8 keys[0x53][2] =
{
{ 0x0, 0x0 }, // ESC
{ '1', '!' }, // 1
{ '2', '@' }, // 2
{ '3', '#' }, // 3
{ '4', '$' }, // 4
{ '5', '%' }, // 5
{ '6', '^' }, // 6
{ '7', '&' }, // 7
{ '8', '*' }, // 8
{ '9', '(' }, // 9
{ '0', ')' }, // 0
{ '-', '_' }, // -
{ '=', '+' }, // =
{ 0x0, 0x0 }, // BACKSPACE
{ 0x0, 0x0 }, // TAB
{ 'q', 'Q' }, // Q
{ 'w', 'W' }, // W
{ 'e', 'E' }, // E
{ 'r', 'R' }, // R
{ 't', 'T' }, // T
{ 'y', 'Y' }, // Y
{ 'u', 'U' }, // U
{ 'i', 'I' }, // I
{ 'o', 'O' }, // O
{ 'p', 'P' }, // P
{ '[', '{' }, // [
{ ']', '}' }, // ]
{ '\n', 0x0 }, // ENTER
{ 0x0, 0x0 }, // CTRL
{ 'a', 'A' }, // A
{ 's', 'S' }, // S
{ 'd', 'D' }, // D
{ 'f', 'F' }, // F
{ 'g', 'G' }, // G
{ 'h', 'H' }, // H
{ 'j', 'J' }, // J
{ 'k', 'K' }, // K
{ 'l', 'L' }, // L
{ ';', ':' }, // ;
{ '\'', '"' }, // '
{ '`', '~' }, // `
{ 0x0, 0x0 }, // LEFTSHIFT
{ '\\', '|' }, // '\'
{ 'a', 'Z' }, // Z
{ 'x', 'X' }, // X
{ 'c', 'C' }, // C
{ 'v', 'V' }, // V
{ 'b', 'B' }, // B
{ 'n', 'N' }, // N
{ 'm', 'M' }, // M
{ ',', '<' }, // ,
{ '.', '>' }, // .
{ '/', '?' }, // /
{ 0x0, 0x0 }, // RIGHTSHIFT
{ 0x0, 0x0 }, // PRINTSCREEN
{ 0x0, 0x0 }, // ALT
{ 0x0, 0x0 }, // SPACE
{ 0x0, 0x0 }, // CAPSLOCK
{ 0x0, 0x0 }, // F1
{ 0x0, 0x0 }, // F2
{ 0x0, 0x0 }, // F3
{ 0x0, 0x0 }, // F4
{ 0x0, 0x0 }, // F5
{ 0x0, 0x0 }, // F6
{ 0x0, 0x0 }, // F7
{ 0x0, 0x0 }, // F8
{ 0x0, 0x0 }, // F9
{ 0x0, 0x0 }, // F10
{ 0x0, 0x0 }, // NUMLOCK
{ 0x0, 0x0 }, // SCROLLLOCK
{ 0x0, 0x0 }, // HOME
{ 0x0, 0x0 }, // UP
{ 0x0, 0x0 }, // PAGEUP
{ 0x0, 0x0 }, // SUB
{ 0x0, 0x0 }, // LEFT
{ 0x0, 0x0 }, // CENTER
{ 0x0, 0x0 }, // RIGHT
{ 0x0, 0x0 }, // ADD
{ 0x0, 0x0 }, // END
{ 0x0, 0x0 }, // DOWN
{ 0x0, 0x0 }, // PAGEDOWN
{ 0x0, 0x0 }, // INSERT
{ 0x0, 0x0 } // DEL
};
可以看到可显字符只有0-9、a-z、A-Z以及一些符号,其中很多功能按键并不是可显字符,也就是说这些如Esc、Home、Left、ScrollLock、PrintScreen等这些按键并不是ascii中可显字符中的有效值。但是为了能够让可显字符的扫描码方便的作为keys行下标而使用,这里也保留了这些非可显字符的按键。接下来就可以使用这个扫描码了,通过打开8259A中断控制器的IRQ1脚来打开键盘中断:
//打开IRQ1的键盘中断
outb_p(inb_p(0x21) & 0xfd, 0x21);
另外,键盘存放按键扫描码的端口为0x60,还有一个键盘按键控制端口0x61,向它写入相应的控制字节,此字节每一位格式如下:
对于0-6暂时不去管它们,只需要将IRQ复位即可。如果IRQ不复位,键盘中断只会被8259A响应一次,之后就不再触发键盘中断了。
//清除键盘状态可以接受新按键
outb_p(0x7f, 0x61);
在int.S中处理键盘服务程序,调用int_keyborad函数来处理键盘按键:
//键盘中断
_int_0x21:
cli
pushal
pushfl
//调用键盘中断处理函数
call int_keyboard
popfl
popal
sti
iret
在int_keyborad函数中处理按下键盘的一个键时显示这个键所对应的字符:
/*
* int_keyboard : 键盘中断
* return : void
*/
void int_keyboard()
{
//取得扫描码
u8 scan_code = inb_p(0x60);
//取得按下、抬起状态
u8 status = scan_code >> 7;
//扫描码的索引
u8 key_ind = scan_code & 0x7f;
//shift按下
if ((key_ind == KEY_LEFT_SHIFT || key_ind == KEY_RIGHT_SHIFT) && status == 0)
{
kb_key_shift = 0x1;
}
//shift抬起
else if ((key_ind == KEY_LEFT_SHIFT || key_ind == KEY_RIGHT_SHIFT) && status == 1)
{
kb_key_shift = 0x0;
}
else if (status == 0)
{
//显示字符
putchar(keys[key_ind - 1][kb_key_shift]);
}
//清除键盘状态可以接受新按键
outb_p(scan_code & 0x7f, 0x61);
//通知PIC1可以接受新中断
outb_p(0x20, 0x20);
}
最后在start_kernel中加入安装键盘中断:
//安装键盘中断
install_kb();
在Makefile中加入key.c的编译代码,编译并运行结果:
运行结果中显示了一个叫作main的C语言主函数,千万不要被它吓到。这只是通过键盘输入在显示器上显示的一些字符罢了,并不是什么C的源代码和编译器。不过这已经说明我们的键盘中断已经可以正常工作了。
源代码的下载地址为:
https https://github.com/magicworldos/lidqos.git
git git@github.com:magicworldos/lidqos.git
subverion https://github.com/magicworldos/lidqos
branch v0.13
Copyright © 2015-2023 问渠网 辽ICP备15013245号