计算机通常有很多输入输出设备,当这些设备需要服务时就向处理器提出要求,处理器在收到要求后就为这些设备提供服务。
当设备向处理器提出服务要求时,处理器会在执行完当前一条指令后相应设备要求,转向并执行相应的服务程序(中断服务程序ISR),在执行完毕后,处理器回返回到执行ISR之前的程序继续执行,这种处理方式即为中断方法,设备提出的请求称为中断请求(IRQ)。
在微处理器中,由可编程控制器(Programmable Interrupt Controller,PIC)负责管理系统中的中断请求。它通过连接到设备的中断请求引脚来接受设备发车的中断请求,当设备需要服务时,就激活中断请求引脚信号,PIC检测到信号后即向CPU发出中断信号。此时有三种情况:
1)PIC仅仅接收到1个中断请求信号,而此时CPU没有执行中断请求,那么PIC就直接向CPU发出中断信号。
2)PIC同时收到多个中断请求信号,而此时CPU没有执行中断请求,那么PIC比较中断请求信号中的优先级,选择优先级最高的向CPU发出中断信号。
3)PIC收到多个中断请求信号,此时CPU正在执行中断服务程序,那么PIC选出的最高优先级的中断请求信号然后再与正在执行的中断服务进行比较,基于比较结果来决定是否向CPU立刻发送中断信号。
但PIC向CPU的INT引脚发送中断信号时,CPU会立刻停下来并询问PIC要执行哪个中断服务请求。PIC则通过向数据总线发送与中断请求对应的中断号告知CPU执行哪个中断服务程序。CPU通过在数据总线读取的中断号,查询中断向量表,取得相应的中断服务程序的地址并开始执行中断服务程序,执行完毕后,处理器就接着执行被中断的程序。
上面所述是描述的I/O设备的中断处理过程,属于硬件中断的范畴,但是中断并非只有硬件,也可用于软件,通过使用int指令并使用其操作数指明中断号,就可以让CPU执行相应的ISR。
两片8259A芯片共可处理15级中断,从芯片的INT引脚连接到主芯片的IR2引脚上,即从芯片发出的中断信号做为主芯片IRQ2输入信号。主芯片的端口地址是0x20,从芯片的端口基地址是0xA0,IRQ9引脚的作用与PC/XT的IRQ2相同(PC/AT和PC/XT对应8086系列的不同数据总线,PC/XT是早期的型号,数据总线为8位,而PC/AT的数据总线为16位),即PC/AT机利用硬件电路把使用IRQ2的设备的IRQ2引脚重新定性到了PIC的IRQ9引脚上,并利用BIOS中的软件把IRQ9的中断int 71重新定向到IRQ2的中断 int 0x0A,这样使得使用IRQ2的PC/XT的8位设配卡在PC/AT机下仍能正常使用,做到了向下兼容。
在总线控制器的控制下,芯片有两种状态:编程状态和操作状态。编程状态是CPU通过IN和OUT指令对芯片进行初始化编程的状态,完成了初始化编程后,芯片就进入操作状态。此时芯片可随时响应外部设备提出的中断请求(IRQ0-IRQ15)。通过中断判优选择,芯片选择最高优先级的中断请求作为服务对象,通过CPU的INT引脚向CPU发出中断请求,CPU响应后,芯片从数据总线D7-D0将编程设定的对应中断请求的中断号送出,CPU接收到中断号,查找中断向量表,从而响应中断。
8086中断子系统
8086的微型机系统中采用的是8259A可编程中断控制器芯片,每个芯片可以管理8个中断源,通过多级相连,可以管理多个中断向量。PC/AT系列使用了两片上述芯片,如下图所示:
中断向量表
中断向量表有时也称为中断描述符表,它的作用就是通过中断号来确定对应中断的ISR的物理地址。8086微机支持256个中断,在实模式下,每个中断向量由4个字节组成,这四个字节指明了中断服务程序的段值和段内偏移,因此整个中断向量表的大小为256*4字节=1024字节。当8086微机启动时,ROM BIOS中的程序会在物理内存的0x0000:0x0000处初始化并设置中断向量表,各中断的默认ISR在BIOS中给出。中断向量表中的向量是按照中断号顺序排列,因此给定中断号N,对应的物理地址则为:0x0000:N*4(字节)。
BIOS的初始化操作设置了8259A芯片支持的16个硬件中断向量和BIOS提供的中断号为0x10-0x1f的中断调用功能向量等。对于实际没有使用的向量则填入临时的哑中断服务程序的地址,在系统引导加载操作系统时会根据实际需要修改某些中断向量的值。
对于Linux操作系统,在加载内核时需要用到BIOS提供的显示和磁盘读操作中断功能,在内核正常工作之前则会在setup.s程序中重新初始化8259A芯片并且在head.s程序中重新设置一张中断向量表(即中断描述符),完全抛弃了在BIOS中提供的中断服务功能。
在intel CPU运行在32位保护模式下时,需要使用中断描述符表IDT来管理中断或者异常。IDT是中断向量表的替代物,作用类似于中断向量,只不过除了含有中断服务程序ISR的地址外,还包括了有关特权级和描述符类别等信息。
Linux内核的中断处理
对linux内核来说,中断信号分为两类:硬件中断和软件中断(异常),中断用0-255之间的数字标示,其中int0-int31(0x00-0x1f)每个中断的功能由intel公司固定设定或者保留,属于软件中断(异常),这些中断是在CPU执行指令时探测到异常情况而引起的。通常还可分为故障和陷阱两类,中断int32-int255(0x20-0xff)可以由用户自己设定。
中断int32-int47对应8259A中断控制芯片发出的硬件中断请求信号IRQ0-IRQ15。
程序编程发出的系统调用中断设置为int128(0x80),系统调用中断是用户程序使用操作系统资源的唯一界面接口。
系统在初始化时,内核首先在head.s中使用一个“哑中断向量(中断描述符)”对中断描述符表中的所有的256个描述符进行默认设置。这个哑中断向量执行一个默认的“无中断”处理程序。此时发生中断而执行此程序时会显示信息:“未知中断(Unknown interrupt)”。这样使用哑中断向量的目的是为了防止出现一般保护性错误。
而对于系统中需要使用的中断则会在初始化过程中的init/main.c程序中重新设置这些中断描述符(即中断向量),让他们指向对应的实际处理程序。通常对int0-int31的初始化设置在traps.c程序中进行重新设置;系统调用中断int128的重新设置在kernel/sched.c中进行。
Linux内核设置IDT时使用了中断门和陷阱门两种描述符。区别是对标志寄存器EFLAGS中的“中断允许标志”IF的影响,中断门执行的中断会复位IF标志,可以避免其他中断干扰当前中断的处理,随后的中断结束指令iret会从堆栈上恢复IF标志的原值,而陷阱门执行的中断不会影响IF标志。