代码、内容参考来自于包括《操作系统真象还原》、《一个64位操作系统的设计与实现》以及《ORANGE’S:一个操作系统的实现》。
1.概述
8259A的作用是负责所有来自外设的中断,其中就包括来自时钟的中断,以后我们要通过它完成进程调度。
中断代理:负责对中断仲裁,决定哪个中断优先被CPU受理。Intel 8259A芯片就是一种可编程的中断代理。
8259A的功能:用于管理和控制可屏蔽中断,它表现为屏蔽外设中断,对它们实行优先级判决,向CPU提供中断向量号等。
每个独立运行的外设都是一个中断源,它们发出的中断只有接在中断请求(IRQ:Interrupt ReQuest)信号线上才能被CPU知晓。
Intel处理器共支持256个中断,而8259A可管理8个中断,一个8259A被称为一片。
将n片级联在一起,一片为主片(master),其余为从片(slave),一共可以处理8(n-1)+(8-(n-1))=7n+1个中断。
来自从片的中断只能传递给主片,由主片向上传递给CPU。只有主片才能向CPU发送int中断信号。
一个主片最多级联8个从片,因为级联一个从片要占用主片的一个IRQ接口,而从片上的IRQ接口不会被占用,从片上有专门的接口用于级联。所以9个片最多可以处理64个中断。
每个独立运行的外部设备都是一个中断源,它们所发出的中断,只有接在中断请求(IRQ: InterruptReQuest)信号线上才能被CPU大神知晓,这也就是大家在开机时,电脑屏幕上会看到的IRQ1… IRQn,这些都是为外部设备所分配的中断号。
在我们的个人电脑中只有两片 8259A 芯片,也就是说,一共 16个IRQ 接口。在我们的个人电脑中只有两片8259A芯片,也就是说,一共16个IRQ接口。
不过,单独使用哪个芯片都只能支持8个中断源,它们只有通过级联后才能都利用上,根据前面所说的公式,最多也只是支持7*2+1=15个中断。
不是2*8-16个,因为级联一个从片,要占用主片一个IRQ接口,而从片上的IRQ接口不被占用,从片上有专门的接口用于级联(相当于从片上向CPU发送INT信号的接口插在了主片上的某个IRQ),这和级联交换机的原理是一样的,交换机上通向网关的接口是单独的,下级交换机必须用该接口通过网线接在核心交换机的某个普通网卡接口上。

外部设备向CPU发中断,它们并不知道中间还隔着个中断代理,8259A 在收到了中断后,对中断判优,将优先级最高的中断转发给 CPU 处理。
信号端口和寄存器:
- INT:8259A选出优先级最高的中断请求后,发信号通知CPU
- INTA:INT Acknowledge,中断响应信号。位于8259A中的INTA接收来自CPU的INTA接口的中断响应信号。
- IMR:Interrupt Mask Register,中断屏蔽寄存器,宽度是8位,用来屏蔽某个外设的中断。
- IRR:Interrupt Request Register,中断请求寄存器,宽度是8位。它的作用是接受经过IMR寄存器过滤后的中断信号并锁存,此寄存器中全是等待处理的中断,“相当于”5259A维护的未处理中断信号队列。
- PR:Priority Resolver,优先级仲裁器。当有多个中断同时发生,或当有新的中断请求进来时,将它与当前正在处理的中断进行比较,找出优先级更高的中断。
- ISR:In-Service Register,中断服务寄存器,宽度是8位。当某个中断正在被处理时,保存在此寄存器中。

以上介绍的寄存器都是8位,8259A共8个IRQ接口,可以用8位寄存器中的每一位代表8259A的每个IRQ接口,类似于接口的位图,这样在后续的操作中,操作寄存器中的位便表示处理来自对应的IRQ接口的中断信号。
2.8259A处理过程
8259A处理过程如下:
- 当某个外设发出一个中断信号时,由于主板上已经将信号通路指向了8259A芯片的某个IRQ接口,所以该中断信号最终被送入了8259A.
- 8259A首先检查IMR寄存器中是否已经屏蔽了来自该IRQ接口的中断信号。IMR寄存器中的位为1则表示中断屏蔽,为0则表示中断放行。
- 如果该IRQ对应的相应位已经被置1,即表示来自该IRQ接口上的中断已经被屏蔽了,则将该中断信号丢弃,否则将其送入IRR寄存器,将该IRQ接口所在IRR寄存器中对应的BIT置1。
- IRR寄存器的作用”相当于”待处理中断队列,在某个恰当时机,优先级仲裁器PR会从IRR寄存器中挑选一个优先级最大的中断,此处的优先级决判很简单,就是IRQ接口号越低,优先级越大,所以IRQ0优先级最大。
- 之后,8259A会在控制电路中,通过INT接口向CPU发送INTR信号,信号被送入了CPU的INTR接口后,这样CPU便知道有新的中断到来了,于是CPU将手里的指令执行完后,马上通过自己的INTA接口向8259A的INTA接口回复一个中断响应信号,表示现在CPU我已准备好, 8259A在收到这个信号后,立即将刚才选出来的优先级最大的中断在ISR寄存器中对应的BIT置1,此寄存器表示当前正在处理的中断,同时要将该中断从”待处理中断队列”寄存器IRR中去掉,也就是在IRR中将该中断对应的BIT置0。
- 之后,CPU将再次发送INTA信号给8259A,这一次是想获取中断对应的中断向量号,就是我们前面所说的0~255的”整数”,由于大部分情况下8259A的起始中断向量号并不是0(起始中断向量号被修改,原因后面会说),所以用起始中断向量号+IRQ接口号便是该设备的中断向量号,由此可见,外部设备虽然会发中断信号,但它并不知道还有中断向量号这回事,不知道自己会被中断代理(如8259A)分配一个这样的整数。随后,8259A将此中断向量号通过系统数据总线发送给CPU,CPU从数据总线上拿到该中断向量号后,用它做中断向量表或中断描述符表中的索引,找到相应的中断处理程序并去执行。
- 如果8259A的”EOI通知(End Of Interrupt)”若被设置为非自动模式(手工模式),中断处理程序结束处必须有向8259A发送EOI的代码,8259A在收到 EOI 后,将当前正处理的中断在 ISR 寄存器中对应的 BIT 置 0。如果“EOI 通知”被设置为自动模式,在刚才8259A接收到第二个INTA信号后,也就是CPU向8259A要中断向量号的那个INTA,8259A会自动将此中断在ISR中对应的BIT置0.
8259A 内部结构逻辑示意图对应图如下:

并不是进入了ISR后的中断就高枕无忧等着CPU了,它还是有可能被换下来的,在8259A发送中断向量号给CPU之前,这时候又来了新的中断,如果它的来源IRQ接口号比ISR中的低,也就是优先级更高,原来ISR中准备上CPU处理的旧中断,其对应的BIT就得清0,同时将它所在的IRR中的相应BIT恢复为1,随后在ISR中将此优先级更高的新中断对应的BIT置1,然后将此新中断的中断向量号发给CPU。
所以我们要完成两步:
- 构造好IDT。
- 提供中断向量号。
3.8259A编程
表中Error code字段中,如果值为Y,表示相应中断会由CPU压入错误码。
在开机之后的实模式下,8259A的IRQ0~7已经被BIOS分配了0x8~0xf的中断向量号。而在保护模式下,中断向量号为0x8~0xf的范围已经被CPU占了,分配给了各种异常,所以还得重新为8259A芯片上的IRQ接口们分配中断向量号。

中断向量号是逻辑上的东西,它在物理上是8259A上的IRQ接口号。8259A上IRQ号的排列顺序是固定的,但其对应的中断向量号是不固定的,这其实是一种由硬件到软件的映射,通过设置8259A,可以将IRQ接口映射到不同的中断向量号。
在8259A内部有两组寄存器,一组是初始化命令寄存器组,用来保存初始化命令字(Initialization Command Words,ICW),ICW共4个,ICW1~ICW4。另一组寄存器是操作命令寄存器组,用来保存操作命令字(Operation Command Word,OCW),OCW共3个,OCW1~OCW3。所以,我们对8259A的编程,也分为初始化和操作两部分。
一部分是用 ICW 做初始化,用来确定是否需要级联,设置起始中断向量号,设置中断结束模式。其编程就是往8259A的端口发送一系列ICW。由于从一开始就要决定8259A的工作状态,所以要一次性写入很多设置,某些设置之间是具有关联、依赖性的,也许后面的某个设置会依赖前面某个ICW写入的设置,所以这部分要求严格的顺序,必须依次写入ICW1、ICW2、ICW3、ICW4。
另一部分是用OCW来操作控制8259A,前面所说的中断屏蔽和中断结束,就是通过往8259A端口发送OCW实现的。OCW的发送顺序不固定,3个之中先发送哪个都可以。
ICW1
ICW1用来初始化8259A的连接方式和中断信号的触发方式。连接方式是指用单片工作,还是用多片级联工作,触发方式是指中断请求信号是电平触发,还是边沿触发。
ICM1结构如图:

IC4表示是否要写入ICW4,这表示,并不是所有的ICW初始化控制字都需要用到。IC4为1时表示需要在后面写入ICW4,为0则不需要。注意,x86系统IC4必须为1。
SNGL 表示 single,若 SNGL 为 1,表示单片,若 SNGL 为 0,表示级联(cascade),若在级联模式下,这要涉及到主片(1个)和从片(多个)用哪个IRQ接口互相连接的问题,所以当SNGL为0时,主片和从片也是需要ICW3的。
ADI 表示 call address interval,用来设置 8085 的调用时间间隔,x86 不需要设置。
LTIM表示level/edge triggered mode,用来设置中断检测方式,LTIM为0表示边沿触发,LTIM为1 表示电平触发。
第 4 位的 1 是固定的,这是 ICW1 的标记。
第 5~7 位专用于 8085 处理器,x86 不需要,直接置为 0 即可。
ICW2
ICW2用来设置起始中断向量号,就是前面所说的硬件IRQ接口到逻辑中断向量号的映射。 由于每个8259A芯片上的IRQ接口是顺序排列的,所以咱们这里的设置就是指定IRQ0映射到的中断向量号,其他IRQ接口对应的中断向量号会顺着自动排下去。

由于咱们只需要设置IRQ0的中断向量号,IRQ1~IRQ7的中断向量号是IRQ0的顺延,所以,只负责填写高5位T3~T7,ID0~ID2这低3位不用我们负责。
由于咱们只填写高5位,所以任意数字都是8的倍数,这个数字表示的便是设定的起始中断向量号。 这是有意设计的,低3位能表示8个中断向量号,这由8259A根据8个IRQ接口的排列位次自行导入,IRQ0的值是000,IRQ1的值是001,IRQ2的值便是 010 …以此类推,这样高 5 位加低3位,便表示了任意一个IRQ接口实际分配的中断向量号。
ICW3
ICW3仅在级联的方式下才需要(如果ICW1中的SNGL为0),用来设置主片和从片用哪个IRQ接口互连。
由于主片和从片的级联方式不一样,对于这个ICW3,主片和从片都有自己不同的结构。


对于主片,ICW3 中置 1的那一位对应的 IRQ 接口用于连接从片,若为0 则表示接外部设备。
为主片,ICW3用于指示哪个中断输入引脚上有从片相连。
哪一位为1,表明哪一位上有从片相连,
哪一位为0,表明哪一位上无从片相连,则表示接外部设备。
比如,若主片IRQ2和IRQ5接有从片,则主片的ICW3为00100100。
对于从片,要设置与主片8259A的连接方式,“不需要”指定用自己的哪个IRQ接口与主片连接,从片上专门用于级联主片的接口并不是IRQ,如果从片用IRQ接口连接主片,若主片只级联一个从片,在从片上指定的IRQ接口便默认与主片上做级联的那个IRQ接口匹配了。但如果主片级联多个从片时,在从片上还要设置自己用于连接主片的IRQ接口与主片上连接从片的哪个IRQ接口(有多个)对接,所以,设置从片连接主片的方法是只需要在从片上指定主片用于连接自己的那个IRQ接口就行了。
在中断响应时,主片会发送与从片做级联的IRQ接口号,所有从片用自己的ICW3的低3位和它对比,若一致则认为是发给自己的。比如主片用IRQ2接口连接从片A,用IRQ5接口连接从片B,从片A的ICW3的值就应该设为00000010,从片B的ICW3的值应该设为00000101。所以,从片ICW3中的低3位ID0~ID2就够了,高5位不需要,为0即可。
ICW4
ICW4用于设置8259A的工作模式,当ICW1中的IC4为1时才需要ICW4。注意,ICW4需要写入主片的0x21及从片的0xA1端口。
如图所示:

ICW4有些低位的选项基于高位,第7~5 位未定义,直接置为0 即可。
SFNM表示特殊全嵌套模式(Special Fully Nested Mode),若SFNM为0,则表示全嵌套模式,若SFNM 为1,则表示特殊全嵌套模式。
BUF表示本8259A芯片是否工作在缓冲模式。BUF为0,则工作非缓冲模式,BUF为1,则工作在缓冲模式。
当多个8259A级联时,如果工作在缓冲模式下,M/S用来规定本8259A是主片,还是从片。若M/S为1,则表示则表示是主片,若M/S为0,则表示是从片。若工作在非缓冲模式(BUF为0)下,M/S无效。
AEOI表示自动结束中断(Auto End Of interrupt),8259A在收到中断结束信号时才能继续处理下一个中断,此项用来设置是否要让8259A自动把中断结束。若AEOI为0,则表示非自动,即手动结束中断,咱们可以在中断处理程序中或主函数中手动向8259A的主、从片发送EOI信号。这种“操作”类命令,通过下面要介绍的 OCW 进行。若 AEOI 为 1,则表示自动结束中断。
uPM表示微处理器类型(microprocessor),此项是为了兼容老处理器。若uPM为0,则表示8080或8085处理器,若μPM为1,则表示x86处理器。
OCW1
OCW1用来屏蔽连接在8259A上的外部设备的中断信号,实际上就是把OCW1写入了IMR寄存器。这里的屏蔽是说是否把来自外部设备的中断信号转发给CPU。由于外部设备的中断都是可屏蔽中断,所以最终还是要受标志寄存器eflags中的IF位的管束,若IF为0,可屏蔽中断全部被屏蔽,也就是说,在IF为0的情况下,即使8259A把外部设备的中断向量号发过来,CPU也置之不理。
如图所示:

M0~M7对应8259A的IRQ0~IRQ7,某位为1,对应的IRQ上的中断信号就被屏蔽了。否则某位为0的话,对应的 IRQ中断信号则被放行。
OCW2
OCW2 用来设置中断结束方式和优先级模式。
OCW2 的配置比较复杂,各种属性位要配合在一起,组合出 8259A 的各种工作模式。
如图所示,由高3位R、SL、EOI可以定义多种中断结束方式和优先级循环方式。

R,Rotation,表示是否按照循环方式设置中断优先级。R为1表示优先级自动循环,R为0表示不自动循环,采用固定优先级方式。
SL,Specific Level,表示是否指定优先等级。等级是用低3位来指定的。此处的SL只是开启低3位的开关,所以SL也表示低3位的L2~L0是否有效。SL为1表示有效,SL为0表示无效。
EOI,End Of Interrupt,为中断结束命令位。令 EOI 为 1,则会令 ISR 寄存器中的相应位清 0,也就是将当前处理的中断清掉,表示处理结束。向8259A主动发送EOI是手工结束中断的做法,所以,使用此命令有个前提,就是ICW4中的AEOI位为0,非自动结束中断时才用。
值得注意的是在手动结束中断(AEOI位为0)的情况下,如果中断来自主片,只需要向主片发送EOI就行了,如果中断来自从片,除了向从片发送EOI以外,还要再向主片发送EOI。
第4~3 位的 00 是 OCW2 的标识。
L2~L0用来确定优先级的编码,这里分两种,一种用于EOI时,表示被中断的优先级别,另一种用于优先级循环时,指定起始最低的优先级别。
SL开关位可以针对某个特定优先级的中断进行操作,以下的优先级模式设置和中断结束都可以基于此开关做更细粒度的控制。
OCW2 其中的一个作用就是发 EOI 信号结束中断。
如果使 SL 为 1, 可以用 OCW2 的低 3 位(L2~L0) 来指定位于 ISR 寄存器中的哪一个中断被终止,也就是结束来自哪个 IRQ 接口的中断信号。 如果 SL 位为 0,L2~LO 便不起作用了,8259A 会自动将正在处理的中断结束,也就是把 ISR 寄存器中优先级最高的位清 0。
OCW2另一个作用就是设置优先级控制方式,这是用R位(第7位)来设置的。 为表述方便,IRQ 各个接口在此被表述为“IR 数字”的形式,这也是微机接口中的命名规则。 如果R为0,表示固定优先级方式,即IRQ接口号越低,优先级越高。 如果R为1,表明用循环优先级方式,这样优先级会在0~7内循环。
如果SL为0,初始的优先级次序为IR0>IR1>IR2>IR3>IR4>IR5>IR6>IR7。 当某级别的中断被处理完成后,它的优先级别将变成最低,将最高优先级传给之前较之低一级别的中断请求,其他依次类推。 所以,可循环方式多用于各中断源优先级相同的情况,优先级通过这种循环可以实现轮询处理。该循环可总结为如果IR (i)优先级最低,IR (i+1)则优先级最高。
其优先级关系如图所示:

顺时针方向的优先级是逐级减小,反之逆时针方向的优先级是逐渐增大。比如,当前IR3为最高级别中断请求,处理完成后,IR3将变成最低级别,IR4 变成最高级别,这一组循环之后的优先级变成了:IR4>IR5>IR6>IR7>IR0>IR1>IR2>IR3
另外,还可以打开SL开关,使SL为1,再通过L2~L1指定最低优先级是哪个IRQ 接口。
在R 和SL都等于1的情况下,若想指定IR5为最低的优先级,需要将L2~L0置为101。
这样新的初始优先级循环是:IR6>IR7>IRO>IR1>IR2>IR3>IR4>IR5
整个OCW2就是各种关键字属性的配合使用组合如下:

OCW3
OCW3 用来设定特殊屏蔽方式及查询方式,
如图所示:

第7位未用到。
ESMM (Enable Special Mask Mode)和第5位的SMM (Special Mask Mode)是组合在一起用的,用来启用或禁用特殊屏蔽模式。 ESMM是特殊屏蔽模式允许位,是个开关。
SMM是特殊屏蔽模式位。 只有在启用特殊屏蔽模式时,特殊屏蔽模式才有效。 也就是若ESMM为0,则SMM无效。 若ESMM为1,SMM为0,表示未工作在特殊屏蔽模式。 若ESMM和SMM都为1,这才正式工作在特殊屏蔽模式下。
第4~3位的01是OCW3的标识,8259A通过这两位判断是哪个控制字。
P,Poll command,查询命令,当P为1时,设置8259A为中断查询方式,这样就可以通过读取寄存器,如IRS,来查看当前的中断处理情况。
RR,Read Register,读取寄存器命令。 它和RIS位是配合在一起使用的。 当RR 为1时才可以读取寄存器。
RIS,Read Interrupt register Select,读取中断寄存器选择位,就是用此位选择待读取的寄存器。 有点类似显卡寄存器中的索引的意思。 若RIS为1,表示选择ISR寄存器,若RIS为0,表示选择IRR 寄存器。 这两个寄存器能否读取,前提是RR的值为1。
总结
8259A虽然就2个端口地址,但是8259A有一套方法可识别 4 个ICW 和 3 个OCW 的。
ICW1和OCW2、OCW3是用偶地址端口0x20 (主片)或0xA0 (从片)写入。
ICW2~ICW4和OCW1是用奇地址端口0x21 (主片)或0xA1 (从片)写入。
OCW的写入与顺序无关,并且ICW1和OCW2、OCW3的写入端口是一致的,8259A辨识它们通过各控制字中的第 4~3 标识位,通过这两位的组合来唯一确定某个控制字。

OCW是在初始化之后才有效的,所以在初始化之后写入奇地址端口的数据便被认为是OCW1。
8259A的编程就是写入ICW和OCW,对于8259A的初始化必须最先完成,步骤如下:
- 无论8259A是否级联,ICW1和ICW2是必须要有的,并且要顺序写入。
- 只有当ICW1中的SNGL位为0时,这表示级联,级联就需要设置主片和从片,这才需要在主片和从片中各写入ICW3,注意,ICW3的格式在主片和从片中是不同的。
- 只能当 ICW1 中的 IC4 为 1 时,才需要写入 ICW4。不过,x86 系统 IC4 必须为 1。
在x86系统中,对于初始化级联8259A,4个ICW都需要,初始化单片8259A, ICw3不要,其余全要。
4.参考
郑钢著操作系统真象还原
田宇著一个64位操作系统的设计与实现
丁渊著ORANGE’S:一个操作系统的实现


