问题
计算机已经接入了键盘,为啥按键没反应啊?
因为键盘是外设,当按键被按下时会向处理器发送中断,而我们并没有编写相应的中断处理函数。
键盘的本质
键盘是一种计算机外部设备
键盘与计算机的通信 (数据交互) 需要借助中断完成
键盘的中断引脚接在 主8259A 的 IRQ1 引脚上。
键盘驱动步骤
1. 使能 8259A 引脚 IRQ1 (启用键盘中断)
2. 编写中断服务程序,并注册到中断向量表 (0x21号中断)
键盘中断
interrupt.c
void IntModInit()
{
SetIntHandler(AddrOff(gIdtInfo.entry, 0x0D), (uint)SegmentFaultHandlerEntry);
SetIntHandler(AddrOff(gIdtInfo.entry, 0x0E), (uint)PageFaultHandlerEntry);
SetIntHandler(AddrOff(gIdtInfo.entry, 0x20), (uint)TimerHandlerEntry);
SetIntHandler(AddrOff(gIdtInfo.entry, 0x21), (uint)KeyboardHandlerEntry);
SetIntHandler(AddrOff(gIdtInfo.entry, 0x80), (uint)SysCallHandlerEntry);
InitInterrupt();
}
loader.asm
RunTask:
push ebp
mov ebp, esp
mov esp, [ebp + 8]
lldt word [esp + 96]
ltr word [esp + 98]
pop gs
pop fs
pop es
pop ds
popad
add esp, 4
mov dx, MASTER_IMR_PORT
in ax, dx
%rep 5
nop
%endrep
and ax, 0xFC ; Enable Timer and Keyboard Interrupt
out dx, al
%rep 5
nop
%endrep
mov eax, PageDirBase ; Open Page Mode
mov cr3, eax
mov eax, cr0
or eax, 0x80000000
mov cr0, eax
iret
kentry.asm
;
;
KeyboardHandlerEntry:
BeginISR
call KeyboardHandler
EndISR
ihandler.c
void KeyboardHandler()
{
PrintChar('*');
SendEOI(MASTER_EOI_PORT);
}
我们在中断向量中注册了 0x21 号中断 (键盘中断),并将中断屏蔽寄存器的值设置为 0xFD,时钟中断和键盘中断分别为 IRQ0 和 IRQ1,这两个中断现在可以被处理器接收。
在键盘中断服务程序中,我们打印 '*' 字符。
当第一次按下键盘的一个按键时,屏幕上显示了 '*' 字符,但之后在按下键盘时,屏幕上就不会再显示 '*' 字符了。
问题
为什么键盘中断只发生一次?如何获取键盘按键的值?
键盘工作原理简介
当我们按下字符 ‘a’ 时,8048把按键信息发送给8042,8042把键位信息存储在缓冲区中,发送中断请求;然后 8259A 通知处理器来处理8042的中断。当处理器读取8042的缓冲区时,8042的缓冲区会被清空;而我们的中断处理函数并没有读取8042缓冲区的信息,所以第二次按下按键的时候,8042就接收不了8048的键位信息了。
键盘扫描码 (键位信息)
处理器接收到按键中断后,从 0x60 端口读取键盘扫描码
扫描码指的是硬件电路对键位的编码
- Make Code - 按下键时产生的扫描码
- Break Code - 释放键时产生的扫描码
- Break Code = Make Code + 0x80
小贴士
C语言中的函数是怎么返回值的?
汇编程序可以通过将该函数的返回值写入 AX 寄存器,AX 寄存器会作为这个函数的返回值。
端口数据读取
读取键盘扫描码
kentry.asm
;
; byte ReadPort(ushort port)
;
ReadPort:
push ebp
mov ebp, esp
xor eax, eax
mov dx, [esp + 8]
in al, dx
nop
nop
nop
leave
ret
;
; void WritePort(ushort port, byte value)
;
WritePort:
push ebp
mov ebp, esp
xor eax, eax
mov al, [esp + 8]
mov dx, [esp + 12]
out dx, al
nop
nop
nop
leave
ret
void KeyboardHandler()
{
byte kc = ReadPort(0x60);
PrintIntHex(kc);
PrintString(" ");
SendEOI(MASTER_EOI_PORT);
}
在键盘中断服务程序中,我们读取 0x60 端口上的键盘扫描码,以便可以接收到下一次按键中断。
当按下一个按键时,屏幕上会显示两个数字,一个是按下键时产生的扫描码,另一个是松开键时产生的扫描码,二者相差 0x80。并且我们每按下一次按键,都会读到两个按键信息。