File: ThinkInt.txt
Name: 理解操作系统对中断的处理
Author: zyl910
Blog: http://blog.csdn.net/zyl910/
Version: V1.01
Updata: 2006-6-20
以前看《操作系统》时,总觉得书上说得太抽象,理解不了。但最近编写一个键盘处理的小程序时,慢慢的理解了操作系统对中断的处理的那些概念。
本来我是使用 Int 16h 中断来接收键盘输入的,但是该方法不能很好的解析组合键,而且无法获知某个按键是否按下。所以后来我决定 挂接IRQ0、访问60h端口,自己解析扫描码来处理键盘输入。
一、中断时只接收数据
最开始时为了研究扫描码,我在中断例程中用printf显示扫描码。但很快发现,会因printf带来的重入性问题导致程序出现许多莫名其妙的问题,而且在中断时占用太多时间会影响系统运行效率。
于是我修改了设计,定义了一个扫描码队列,每次中断发生时向扫描码队列添加项目。然后在主函数循环检查扫描码队列,并进行处理。
二、系统分层
最开始看到《操作系统》关于系统分层时总有点不太理解,觉得那是将简单的事情搞复杂。但是这次处理键盘扫描码时发现:必须分层!
有两个原因。一是因为扫描码牵涉到底层细节,比如 Ex 是扩展按键前导字节,x表示后面有几个字节的数据,处理起来极其复杂。二是由于中断时修改了扫描码队列,所以访问扫描码队列必须关中断,如果老是直接访问扫描码队列会影响系统运行效率,甚至可能会导致中断丢失。
最后觉得至少得分四层:
扫描码队列:底层的键盘扫描码。
键盘按键状态:记录每个键盘按键的状态。注意是指单独的按键,类似DirectInput的DK的按键处理。比如:方向键上与小键盘8是不同的按键。
功能按键队列:解析按键功能含义,类似Windows的虚拟键码。比如:无论是方向键上,还是小键盘8都是指“上”这个功能。
字符消息:字符消息是用于文本输入的,比如CapsLock、Shift用于切换字母大小写。为了支持多语言输入和输入法,建议使用DWord来存放31位Unicode编码。
三、内核线程
有两种按键状态,一种是键盘按键状态,一种是输入按键状态。先前我们按队列方式实现的实际上是输入按键状态,表示当前输入队列环境下的的按键状态;而键盘按键状态是指当前键盘上的按键状态。
比如当按下空格键时,空格键的扫描码进入扫描码队列。当程序分析扫描码队列时,肯定认为空格键时按下的,所以“输入按键状态”认为空格键处于按下状态。但是此时在键盘上的空格键很可能已经释放了,所以“键盘按键状态”认为空格键处于未按下状态。
所以我们需要在中断后立即分析数据,以实现“键盘按键状态”。
注意是8259发送EOI以通知中断事务结束的,所以我们可以在发送EOI后处理数据,然后再使用iret指令从中断例程中返回。
在发送EOI后的数据操作代码是什么?就是所谓的内核线程。内核态与用户态的定义并不是那么绝对,我们可以这样定义——非中断时的操作为用户态,在中断例程中的操作为内核态。其实保护模式跟这个差不多的,只不过它有明确的“环”概念及权限保护机制。
怎么解决内核线程重入问题呢,需要仔细设计。
变量:
bkbinkmd:处于键盘内核线程中
中断例程{
取得按键扫描码
向扫描码队列填充数据
if (bkbinkmd) {
发送EOI
/* 仍然由内核线程处理数据 */
iret
}else{
发送EOI
bkbinkmd = TRUE
call 键盘处理函数 //内核线程
iret
}
}
键盘处理函数{
do{
cli
取走扫描码队列中的所有数据。假设数据长度为cbBuf
if (cbBuf > 0) bkbinkmd = FALSE
sti
分析数据
}while(cbBuf > 0) // 尝试重复,因为中断可能修改了数据
}
四、总结
《操作系统》教材太过于浓缩,只讲设计思路,不讲设计细节。只有真正接触具体设计时,才能真正明白那些设计思路的作用。
Updata
~~~~~~
[V1.01]2006-6-20
重写“内核线程”部分
[V1.00]2006-6-17
基本内容