0 回顾
- 上一讲开始是操作系统对设备的驱动管理,显示器和键盘是终端设备,看操作系统怎么驱动的
- 这是外设工作的基本原理
- CPU发送一个指令,读或写的指令,然后当外设工作完成的时候,向CPU做回一个中断
- 以上对于终端设备(不光是外设),其基本思路就是这样的
- 要想实现外设的驱动,要实现三件事:
- 核心:向外设控制器的某些寄存器/存储器端口发送读/写指令,通过out向外设发指令
- 操作系统通过文件形成一个统一的文件视图,将所有的外设都做成文件,根据文件所对应的文件名,文件所对应的结构(文件描述的结构当中的信息,inode)决定了要走哪条路,最终到了哪一段out
- 进行中断处理,最后回到文件系统
1 键盘
- 敲了就中断,所以就从中断开始
- 但是中断开始就得从中断初始化开始
- 我们得知道键盘中断被初始化成了什么函数
{set_trap_gate(0x21, &keyboard_interrupt);}
这里就是将0x21
设置成这个键盘中断- 有
out
就有in
- 可以看出这个与CPU交互最核心的指令往往是最简单的
in
指令把0x60
读入al
当中- 所有的键盘,每一个按键都对应一个码
- 因为这里是赋给
al
了,所以存在于ax
当中 - 然后根据不同的码,来调用一个table来执行相应的工作
- 根据扫描码来知道要干什么,因为键盘,从设备开始的
inb $0x60, %al//从端口0x60读扫描码
call key_table(,%eax,4)//调用key_table + eax*4
1.1 扫描码key_table + eax * 4
- 根据key_table来决定做什么
- do_self其实就是函数,汇编里的函数,函数无非就是地址
- 对一般的显式字符都要执行do_self
- 接下来载入key_map
1.2 key_map
- 可以看出存入的都是ASCII码,也就是之前的可显式字符
- 所以这个就是可显式字符的ASCII码表
- 这个表的起始地址已经赋给了ebx
lea key map, %ebx;
- 再加上刚才的扫描码,扫描码肯定对应的是这个表的偏移
- 有了这两个东西,就能找到你按下的这个键所对应的ASCII码赋给al
- 所以执行到这里已经得到ASCII码了,然后放到缓冲队列
- 现在放到缓冲队列等着上面的进程去拿,执行scanf的时候就要拿到这些东西
- 所以接下来是
put_queue
- 那么
put_queue
无非就是得到列表,上次是write_q现在是read_q
1.3 put_queue
- 现在得到ASCII码放到了队列
综上:键盘的驱动就是写个中断处理程序,根据扫描码得到ASCII码,或者根据扫描码进行相应的处理函数,或者得到扫描码,放到read_q这个队列中就完事,然后上层是和文件接在一起了,scanf(read)就从队列中取就行了,和写write_q的队列一样
1.4 键盘处理
- 从硬件开始,就是从中断开始
- 取出ASCII码放到read_q里
- 经过转译放到secondary(这里才是scanf真正取的)
- 之后回显write_q(回显的时候调用con_write即可回显)
1.5 回显
- 怎么回显?
- 得到c,放到write_q中写就行
- 再放到缓冲队列当中,写就行
2 总结
- 都是文件API
- 形成了一个统一的文件视图,最终落实到out(如果往下写的话)
- 设备处理落实到了中断处理
- 解答:
- 在tty_write当中把c变成了*号,那么按下F12得调用这个程序
- 所以只需要在相应的扫描码处理的时候,改变一下F12按键的功能,现在的F12肯定用的是func当中的API去处理的,只需要把func改成别的名字,例如
myFunc
{
if (flag == 0)//设置一个flag,按下一个F12输出全是*,再按下就正常,通过flag来判断
{
flag = 1;//等于0就置成1
}
tty_write()
{
if (flag == 1)
{
c = '*';
cout << "*";//所以write_q里面就是*号
}
}
}