键盘是如何与操作系统交互的?
在显示器那一节,我们说过,CPU使用外设就是向外设写入一条指令,然后中断处理,中断处理就在键盘这一篇里,我们每按下一次键盘,就相当与向CPU发起一次中断
。
我们从键盘中断开始,看一下键盘中断的初始化
void con_init(void)
{
set_trap_gate(0x21, &keyboard_interrupt);
}
//在kernel/chr_drv/keyboard.s中
.global _keyboard_interrupt
_keyboard_interrupt:
inb $0x60, %al
//从端口0x60读扫描码,inb表示读入一个字节,扫描码(每一个按键对应一个码)
call key_table(,%eax,4) //调用keyboard+eax*4,根据不同的按键调用不同的函数
...
push $0
call _do_tty_interrupt
处理扫描码key_table+eax*4
key_table是一个函数数组
在kernel/chr_drv/keyboard.s中
key_table:
.long none,do_self,so_self,do_self //扫描码00-03
.long do_self,...,func,scroll,cursor 等
mode: .byte 0
do_self:
lea alt_map, %ebx
testb $0x20, mode //alt键是否同时按下 jne 1f
lea shift_map, %ebx testb $0x03, mode jne 1f
lea key_map, %ebx
1:
从key_map中取出ASCLL码
#if defined(KBD_US)
key_mpa: .byte 0, 27 .ascll "1234567890-=" ...
shift_map: .byte 0, 27 .ascll "!@#$%^&*()_+" ...
#elif defined(KBD_GR) ...
回到do_self函数,从1f开始,ebx存放的是map的起始地址
1: movb (%ebx,%eax), %al //扫描码索引,ASCLL码->al
orb %al, %al je none //找不到对应的ASCLL码
testb $0x4c, mode //看caps是否亮
je 2f capb $'a,%al jb 2f
cmpb $'}, %al ja 2f subb $32, %al //变大写
2: testb %??, mode //处理其他模式,如ctr1同时按下
3: andl $0xff, %eax call_put_queue
none:ret
do_self函数得到ASCLL码后就要把它放入缓冲队列中,等待CPU读取
put_queue:
movl _table_list, %edx //得到队列
movl head(%edx), %ecx //取出队列头部位置
1: movb %al,buf(%edx,%ecx) //将键盘输入的ASCLL码放入队列的头部
struct tty_queue *table_list[]={
&tty_table[0].read_q,
&tty_table[0].write_q;
...
};
上面已经成功将ASCLL码放入队列中,接下只要等待CPU读取并处理就可以了,但是还还要实现回显
回显:将键盘输入的显示到屏幕上,其实就是上节的内容,只要将输入的字符放入write_q队列中,就可以了
void do_tty_interrupt(int tty)
{copy_to_cooked(tty_table+tty);}
void copy_to_cooked(struct tty_struct *tty)
{
GETCH(tty->read_q,c);
if(L_ECHO(tty))
{
PUTCH(c,tty->write_q);
tty->write(tty);
}
PUTCH(c,tty->secondary);
...
wake_up(&tty->secondary.proc_list);
}
我们重新回顾一下操作系统是如何让CPU使用外设的
可以看到,这实际上就是一个文件系统,用文件系统形成统一接口,让外设工作起来。
可以看一下配套的实验按下F12使输出全部变成*号