保护模式下8259A芯片编程及中断处理探究(上)
Version 0.02
哈尔滨工业大学 并行计算实验室 谢煜波[1][1]
简介
中断处理是操作系统必须完成的任务,在IBM PC中,常用一块中断控制芯片(PIC)——8259A来辅助CPU完成中断管理。在实模式下,中断控制芯片(PIC)8259A的初始化是由BIOS自动完成的,然而在保护模式下却需要我们自行编程初始化。本篇拟从操作系统的编写角度详细描述下笔者在此方向上所做的摸索,并在最后通过pyos进行实验验证。此是这部份内容的上篇,将详细描述8259A芯片的编程部份,对于操作系统中的中断处理以及程序验证将在下篇里面详细描述。
此文只是我在进行操作系统实验过程中的一点心得体会,记下来,避免自己忘记。对于其中可能出现的错误,欢迎你来信指证。
一、中断概述
相信大家对于中断一点都不陌生,这里也不准备详细介绍中断的所有内容,只简单做下概要介绍,这样使对中断没有概念的朋友能建立起一点概念。
计算机除了CPU外,还有很多外围设备,然而我们都知道CPU的运行速度是很快的,而外围设备的运行速度却不是很快了。假设我们现在需要从磁盘上读入十个字节,而这需要10秒钟(很夸张,但这只是一个例子),那么在这10秒钟之内,CPU就无所事事,不得不等待磁盘如蜗牛般的读入这十个字节,如果在这10秒钟之内,CPU转去运行其它的程序,不就可以防止浪费CPU的时间了吗?但是这就出现了一个问题,CPU怎么知道磁盘已经读完数据了呢?实际上,这时磁盘的控制器会向CPU发送一个信号,CPU收到信号之后,就知道磁盘已经读完数据了,于是它就中断正在运行的程序,重新回到原先等待磁盘输入的程序上来继续执行。这只是一个很简单的例子,也只是中断应用的一个很简单的方面,但基本上可以说明问题。可以这么认为:中断就是外部设备或程序同CPU通信的一种方式。CPU在接收到中断信号时,会中断正在运行的程序,转到对中断的处理上,而这个对中断的处理程序常常称为中断服务程序,当中断服务程序处理完中断后,CPU再返回到原先被中断的程序上继续执行。整个过程如下图所示:
(图1)
中断有很多类型,比如可屏蔽中断(顾名思义,对此种中断,CPU可以不响应)、不可屏蔽中断;软中断(一般由运行中的程序触发)、硬中断……等很多分类方法。中断可以完成的任务也很多,比如设备准备完毕、设备运行故障、程序运行故障……,这许多突发事件都可以以中断的方式通知CPU进行处理。
二、认识中断号及8259A芯片
我们都知道计算机可以挂接上许多外部设备,比如键盘、磁盘驱动器、鼠标、声卡……等等一系列设备,而这些设备都可能在同一时刻向CPU发出中断信号,那么CPU到底应当响应哪一个设备的中断信号呢?这都通过另外一个芯片来控制,在IBM PC机中,这个芯片常常被称作:可编程中断控制器(PIC)8259A,说它可编程,是因为我们可以通过编程来改变它的功能。比如我可以通过编程设定CPU应当优先响应哪一个中断,屏蔽哪些中断等等一系列事件。
一个8259A芯片共有中断请求(IRQ)信号线:IRQ0~IRQ7,共8根。在PC机中,共有两片8259A芯片,通过把它们联结起来使用,就有IRQ0~IRQ15,共16根中断信号线,每个外部设备使用一根或多个外部设备共用一根中断信号线,它们通过IRQ发送中断请求,8259A芯片接到中断请求后就对中断进行优先级选定,然后对多个中断中具有最高优先级的中断进行处理,将其所对应的中断向量送上通往CPU的数据线,并通知CPU有中断到来。
这里出现了一个中断向量的概念,其实它就是一个被送往CPU数据线的一个整数。CPU给每个IRQ分配了一个类型号,通过这个整数,CPU来识别不同类型的中断。这里可能很多朋友会寻问为什么还要弄个中断向量这么麻烦的东东?为什么不直接用IRQ0~IRQ15就完了?比如就让IRQ0为0,IRQ1为1……,这不是要简单的多么?其实这里体现了模块化设计规则以及节约规则。
首先我们先谈谈节约规则,所谓节约规则就是所使用的信号线数越少越好,这样如果每个IRQ都独立使用一根数据线,如IRQ0用0号线,IRQ1用1号线……这样,16个IRQ就会用16根线,这显然是一种浪费。那么也许马上就有朋友会说:那么只用4根线不就行了吗(24=16)?
对于这个问题,则体现了模块设计规则。我们在前面就说过中断有很多类,可能是外部硬件触发,也可能是由软件触发,然而对于CPU来说中断就是中断,只有一种,CPU不用管它到底是由外部硬件触发的还是由运行的软件本身触发的,因为对于CPU来说,中断处理的过程都是一样的:中断现行程序,转到中断服务程序处执行,回到被中断的程序继续执行。CPU总共可以处理256种中断,而并不知道,也不应当让CPU知道这是硬件来的中断还是软件来的中断,这样,就可以使CPU的设计独立于中断控制器的设计,因为CPU所需完成的工作就很单纯了。CPU对于其它的模块只提供了一种接口,这就是256个中断处理向量,也称为中断号。由这些中断控制器自行去使用这256个中断号中的一个与CPU进行交互。比如,硬件中断可以使用前128个号,软件中断使用后128个号,也可以软件中断使用前128个号,硬件中断使用后128个号,这于CPU完全无关了,当你需要处理的时候,只需告诉CPU你用的是哪个中断号就行,而不需告诉CPU你是来自哪儿的中断。这样也方便了以后的扩充,比如现在机器里又加了一片8259芯片,那么这个芯片就可以使用空闲的中断号,看哪一个空闲就使用哪一个,而不是必须要使用第0号,或第1号中断。其实这相当于一种映射机制,把IRQ信号映射到不同的中断号上,IRQ的排列或说编号是固定的,但通过改变映射机制,就可以让IRQ映射到不同的中断号,也可以说调用不同的中断服务程序,因为中断号是与中断服务程序一一对应的,这一点我们将在随后的内容中详细描述。8259A将中断号通知CPU后,它的任务就完成了,至于CPU使用此中断号去调用什么程序它就不管了。下图就是8259A芯片的结构:
(图2 来源《Linux 0.11 内核完全注释》)
上图就是PC机中两片8259A的联结及IRQ分配示意图。从图中我们可以看到,两片8259A芯片是级联工作的,一个为主片,一个为从片,从片的INT端口与主片的IRQ2相连。主片通过0x20及0x21两个端口访问,而从片通过0xA0及0xA1这两个端口访问。
至于为什么从片的INT需要与主片的IRQ2相连而不是与其它的IRQ相联,很遗憾,我目前无法回答这个问题:(,如果你知道答案,非常希望你能来信指教!不过幸运的是,我们只要知道计算机是的确是这样联的,并且这样连它就可以正常工作就行了!
三、8259A的编程
8259A常常称之为PIC(可编程中断控制器),因此,在使用的时候我们必须通过编程对它进行初始化,需要完成的工作是指定主片与从片怎样相连,怎样工作,怎样分配中断号。在实模式下,也就是计算机加电或重启时,这是由BIOS自动完成的,然而当转到保护模式下后,我们却不得不对它进行编程重新设定,这都是由该死的IBM与Intel为维护兼容性而搞出来的麻烦:(。
在BIOS初始化PIC的时候,IRQ0~IRQ7被分配了0x8~0xF的中断号,然而当CPU转到保护模式下工作的时候,0x8~0xF的中断号却被CPU用来处理错误!一点不奇怪,CPU是Intel生产的,而计算机却是由IBM生产的,两家公司没有协调好:(。
因此,我们不得不在保护模式下,重新对PIC进行编程,主要的目的就是重新分配中断号。幸好这不是一件太难的工作。
对8259A的编程是通过向其相应的端口发送一系列的ICW(初始化命令字)完成的。总共需要发送四个ICW,它们都分别有自己独特的格式,而且必须按次序发送,并且必须发送到相应的端口,下面我们先来看看第一个ICW1的结构:
ICW1:发送到0x20(主片)及0xa0(从片)端口
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
0 |
0 |
0 |
1 |
M |
0 |
C |
I |
I位:若置位,表示ICW4会被发送。(ICW4等下解释)
C位:若清零,表示工作在级联环境下。
M位:指出中断请求的电平触发模式,在PC机中,它应当被置零,表示采用“边沿触发模式”。
ICW2:发送到0x21(主片)及0xa1(从片)端口
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
A7 |
A6 |
A5 |
A4 |
A3 |
0 |
0 |
0 |
ICW2用来指示出IRQ0使用的中断号是什么,因为最后三位均是零,因此要求IRQ0的中断号必须是8的倍数,这又是一个很巧妙的设计。因为IRQ1的中断号就是IRQ0的中断号+1,IRQ2的中断号就是IRQ0的中断号+2,……,IRQ7的中断号就是IRQ0的中断号+7,刚好填满一个8个的中断向量号空间。
ICW3:发送到0x21(主片)及0xa1(从片)端口
ICW3只有在级联工作的时候才会被发送,它主要用来建立两个PIC之间的连接,对于主片与从片,它结构是不一样的。
(主片结构:)
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
IRQ7 |
IRQ6 |
IRQ5 |
IRQ4 |
IRQ3 |
IRQ2 |
IRQ1 |
IRQ0 |
上面,如果相应的位被置1,则相应的IRQ线就被用于与从片连接,若清零则表示被连接到外围设备。
(从片结构:)
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
IRQ |
上面的IRQ位指出了是主片的哪一个IRQ连到了从片,这需要同主片上发送的上面的主片结构字一致。
ICW4:发送到0x21(主片)及0xa1(从片)端口
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
EOI |
80x86 |
80x86位:若置位则表示工作在80x86架构下。
EOI位:若置位则表示自动清除中断请求信号。在PC上这位需要被清零。要理解这一位,我们需要详细了解一下8259A的内部中断处理流程。
三、8259A的内部中断处理流程
下面我们就来从一个系统程序员(System Programmer)的角度看看8259A的内部结构。
(图3)
首先,一个外部中断请求信号通过中断请求线IRQ,传输到IMR(中断屏蔽寄存器),IMR根据所设定的中断屏蔽字(OCW1),决定是将其丢弃还是接受。如果可以接受,则8259A将IRR(中断请求暂存寄存器)中代表此IRQ的位置位,以表示此IRQ有中断请求信号,并同时向CPU的INTR(中断请求)管脚发送一个信号,但CPU这时可能正在执行一条指令,因此CPU不会立即响应,而当这CPU正忙着执行某条指令时,还有可能有其余的IRQ线送来中断请求,这些请求都会接受IMR的挑选,如果没有被屏蔽,那么这些请求也会被放到IRR中,也即IRR中代表它们的IRQ的相应位会被置1。
当CPU执行完一条指令时后,会检查一下INTR管脚是否有信号,如果发现有信号,就会转到中断服务,此时,CPU会立即向8259A芯片的INTA(中断应答)管脚发送一个信号。当芯片收到此信号后,判优部件开始工作,它在IRR中,挑选优先级最高的中断,将中断请求送到ISR(中断服务寄存器),也即将ISR中代表此IRQ的位置位,并将IRR中相应位置零,表明此中断正在接受CPU的处理。同时,将它的编号写入中断向量寄存器IVR的低三位(IVR正是由ICW2所指定的,不知你是否还记得ICW2的最低三位在指定时都是0,而在这里,它们被利用了!)这时,CPU还会送来第二个INTA信号,当收到此信号后,芯片将IVR中的内容,也就是此中断的中断号送上通向CPU的数据线。
这个内容看起来仿佛十分复杂,但如果我们用一个很简单的比喻来解释就好理解了。CPU就相当于一个公司的老总,而8259A芯片就相当于这个老总的秘书,现在有很多人想见老总,但老总正在打电话,于是交由秘书先行接待。每个想见老总的人都需要把自己的名片交给秘书,秘书首先看看名片,有没有老总明确表示不愿见到的人,如果没有就把它放到一个盒子里面,这时老总的电话还没打完,但不停的有人递上名片求见老总,秘书就把符合要求的名片全放在盒子里了。这时,老总打完电话了,探出头来问秘书:有人想见我吗?这时,秘书就从盒子里挑选一个级别最高的,并把他的名片交给老总。
这里需要理解的是中断屏蔽与优先级判定并不是一回事,如果被屏蔽了,那么参加判定的机会也都没了。在默认情况下,IRQ0的优先级最高,IRQ7最低。当然我们可以更改这个设定,这样在下面有详细描述。
言归正传,当芯片把中断号送上通往CPU的数据线后,就会检测ICW4中的EOI是否被置位。如果EOI被置位表示需要自动清除中断请求信号,则芯片会自动将ISR中的相应位清零。如果EOI没有被置位,则需要中断处理程序向芯片发送EOI消息,芯片收到EOI消息后才会将ISR中的相应位清零。
这里的机关存在于这样一个地方。优先权判定是存在于8259A芯片中的,假如CPU正在处理IRQ1线来的中断,这时ISR中IRQ1所对应的位是置1的。这时来了一个IRQ2的中断请求,8259A会将其同ISR中的位进行比较,发现比它高的IRQ1所对应的位被置位,于是8259A会很遗憾的告诉IRQ2:你先在IRR中等等。而如果这时来的是IRQ0,芯片会马上让其进入ISR,即将ISR中的IRQ0所对应的位置位,并向CPU发送中断请求。这时由于IRQ1还在被CPU处理,所以ISR中IRQ1的位也还是被置位的,但由于IRQ0的优先级高,所以IRQ0的位也会被置位,并向CPU发送新的中断请求。此时ISR中IRQ0与IRQ1的位都是被置位的,这种情况在多重中断时常常发生,非常正常。
如果EOI被设为自动的,那么ISR中的位总是被清零的(在EOI被置位的情况下,8259A只要向CPU发送了中断号就会将ISR中的相应位清零),也就是如果有中断来,芯片就会马上再向CPU发出中断请求,即使CPU正在处理IRQ0的中断,CPU并不知道谁的优先级高,它只会简单的响应8259A送来的中断,因此,这种情况下低优先级的中断就可能会中断高优先级的中断服务程序。所以在PC中,我们总是将EOI位清零,而在中断服务程序结束的时候才发送EOI消息。
四、EOI消息及OCW操作命令字
上面所描述的内容几乎全与EOI消息有关,我们现在也已经知道了,应当在中断服务程序结束的时候发送EOI消息给芯片,让芯片在这个时候将其相应的位清零。那让我们现在来揭开EOI消息的神秘面纱。
要认识EOI消息,我们需要先行了解OCW(操作命令字),它们用来操作8259A的优先级、中断屏蔽及中断结束等控制。总共有三个OCW,它们也都有自己很独特的格式,不过它们的发送却不须按固定的顺序进行。
OCW1:中断屏蔽,发送到0x21(主片)或0xa1(从片)端口
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
IRQ7 |
IRQ6 |
IRQ5 |
IRQ4 |
IRQ3 |
IRQ2 |
IRQ1 |
IRQ0 |
如果相应的位置1,则表示屏蔽相应的IRQ请求。
OCW2:优先权控制及中断结束命令,发送到0x20(主片)及0xa0(从片)端口
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
R |
SL |
EOI |
0 |
0 |
L2 |
L1 |
L0 |
先来看看中断结束消息(EOI)
EOI也是OCW2型命令中的一种,当EOI位被置1,这就是一个EOI消息。SL是指定级别位,如果SL被置位,则表明这是一个指定级别的EOI消息,这个消息可以指定将ISR中的哪一位清零,即告诉8259A应当清除哪一个IRQ信号。L2、L1、L0就用来指定IRQ的编号。而在实际运用中我们却将SL及L2、L1、L0全置零。
SL置零表示这是一个不指定级别的EOI消息,则8259A芯片自动将ISR中所有被置位的IRQ里优先级最高的清零,因为它是正在被处理及等待处理的中断中优先级最高的,也就一定是CPU正在处理的中断。
现在再来看看优先级控制命令。
当R为0时,表明这是一个固定优先权方式,IRQ0最高,IRQ7最低。
当R为1时,表明这是一个循环优先权,比如,如指定IRQ2最低,则优先级顺序就为:
IRQ2 < IRQ1 < IRQ0 < IRQ7 < IRQ6 < IRQ5 < IRQ4 < IRQ3
(编者注:可以将其记忆为“IRQ7<IRQ6<IRQ5<IRQ4<IRQ3<IRQ2<IRQ1<IRQ0”进行循环左移后的结果)
也即,如果IRQ( i )最低,那么IRQ( i + 1 )就最高。
所以,在这种方式下需要先行指定一个最低优先级。如果SL被清零,则表示使用自动选择方式,那么正在被处理的中断服务在下一次,就被自动指定为最低优先级。如果SL被置位,那么L2、L1、L0所指定的IRQ就被指定为最低优先级。
在指定优先级的时候,也可通过置位EOI,即可以将指定优先级命令与中断结束消息同时发送。
上面的描述看起来很复杂,其实对于一般的操作系统编写来说大多就使用一种形式:0010 0000,我也很乐意将其称为EOI消息。
还有一个OCW3命令字,可以指定特殊的屏蔽方式及读出IRR与ISR寄存器,不过一般在操作系统中并不需要这样的操作。在操作系统编写中一般只用到前面两个命令字的格式(至少pyos是这样:))由于本文并非硬件手册,只想为操作系统的编写提供一点帮助,因此,如果你想了解完整的OCW3命令字格式,请查阅相应的硬件手册,或本文的参考资料1。
五、上篇结束语
在这一篇中,较为详细的描述了对8259A中断控制器芯片编程所需具备的基础知识,在编写操作系统的过程中,我们就需要向相应端口发送相应的ICW或OCW,完成对8259A的操作,具体的代码将在下篇中描述。
在下篇中将更主要的描述操作系统对中断服务程序的处理及安排,并以开发中的pyos做为例子进行实验。
参考文献
1. 张昆藏. 《IBM PC/XT微型计算机接口技术》. 清华大学出版社. 1991.3.