X86下的8259A子系统
声明
本文会用尽量朴素非术语的语言整体介绍x86架构下中断处理的大概过程,希望能够帮助读者对PC的工作模式有一个简单的形象化的认识。
为了帮助理解,示意图经过简化后不能保证完全正确。
背景
在描述8259A的工作方式之前,我们先明确几个基础的前提
- cpu的工作方式是“取址->执行”,每执行一条指令,IP(Instruction Pointer)指令寄存器会自动移动到下一条指令
- 有的时候cpu执行的指令可能是一个io指令,比如读写磁盘,这个工作要磁盘来完成,比如这里的一个out指令,向磁盘控制器发出一个信号
- 在没有其它部件协助的情况下,我们的程序为了获得磁盘读取/写入数据的结果,必须写一个while循环去不断询问磁盘是否执行成功,这明显是非常浪费cpu的宝贵时间的
- 于是我们想,能不能有一种机制使得cpu不需要去不断询问磁盘的状态,当磁盘执行完成之后主动通知cpu,这时候cpu再做接下来的处理,在此之前,cpu可以继续做其他事情
- 于是人们设计了一个叫PIC(Programmable Interrupt Controller)可编程中断控制器 的东西来解决cpu轮询的问题,在磁盘处理完任务后通过一个电信号通知PIC芯片,PIC芯片再告知cpu磁盘读写成功,这时cpu会放下手中处理的事情,来处理磁盘读写完成的收尾工作,再处理完收尾工作之后又回到刚才手头上的工作上去。这样的工程就是我们常说的中断机制。
- 这时读者可能会问,磁盘为什么不能直接告诉cpu我已经处理完了,而是要通过一个PIC芯片间接的告诉cpu这件事情呢,原因是和cpu协同工作的硬件不止磁盘一个,还有键盘,鼠标,各种协处理设备,这每个设备都与PIC的引脚相连,每个设备通知PIC自己有要通知cpu的消息的这个动作称为中断请求
- 一般的X86架构所在的主板上的PIC是由两块能接收8位信号的8259A芯片及联而成的,所以一共能接受16种设备中断,而cpu是如何区分中断请求是由哪个设备发出的呢
8259A
协同工作
在一般运行的状态下cpu不断执行正常代码,每执行一条指令,IP(Instruction Pointer)指令寄存器会自动移动到下一条指令,外部设备与PIC的IR0-IR5引脚相连
CPU的INTR引脚提供给外部电路一个功能,当外部电路给INTR一个信号的时候,cpu会在执行完当前IP指向的指令之后,从数据总线上取一个中断号,这个中断号是PIC在向CPU发送中断信号的时候放在数据总线上的,通过这个中断号,cpu可以区分不同的设备请求,从而进入不同的中断处理程序
PIC除了能告诉cpu是哪个设备发出的中断之外,还能够判断中断来临的优先级,比如在一个中断请求正在被cpu执行的时候,PIC会记录这个请求的是谁,当又有一个中断发生的时候,PIC会比较这两个请求谁的优先级更高,如果新来的请求优先级更高,则PIC向cpu发出一个中断信号,cpu从原来的中断处理程序中被中断出来,执行新的中断处理程序,当执行完新的中断处理程序之后,再返回原来的中断处理程序
另外PIC还有能力屏蔽一些外部中断请求
而cpu也可个通过设置自己的EFLAGS寄存器中的一个标志位来屏蔽INTR引脚上由PIC传来的信号,在老版本的linux代码中我们可以看到cli sti这样的指令来临时关闭和打开中断功能来实现临界区的功能
Intel保留中断号
转自《linux内核源码剖析》
-
如上图可以看到,Intel的cpu在遇到各种故障的时候会给自己发出各种中断号,cpu收到这些中断号所产生的行为和从PIC收到中断号的行为是一致的,都是要找相应的中断处理程序来收尾
-
比如当一个用户的程序除以了0,cpu就会给自己发出一个中断信号,并且中断号为0,这是如果我们给0注册了一个中断处理程序,我们就能捕获这个信息并且在屏幕上显示一些文本告诉用户错误
-
所以cpu正常的工作场景应该是这样的
-
cpu不断的取址执行
-
当有硬件中断的时候处理相应的收尾工作,比如接收磁盘发来的数据,接收网卡发来的数据
-
当有自己程序发生错误的时候进入错误处理的中断处理程序,以比较优雅的方式通知用户,并关闭用户进程
-
中断处理程序在内存中应该这样排列
- 0-31 留给cpu自己会产生的中断
- 32开始往后可以选16个中断号给硬件
-
猪队友IBM
- 8259A是可编程芯片,就是说我们可个在芯片初始化的时候规定它的工作行为,这里面最重要的是规定引脚接收到硬件传来的信号后要放在数据总线上的中断号
- IBM在自己出场的BIOS中对8259A做了一次初始化,把许多硬件中断号设置成了从0x08开始,这就与intel的保留的功能相冲突了
- 如果我们不重新初始化8259A会发生什么情况呢,那就是cpu收到了一个中断号,可能是由硬件产生的,也可能是由cpu产生的。这时候cpu就没法做收尾工作了
Linux中的处理
以下代码摘自linux0.12源码中的setup.s文件,这段代码在初始化两块8259A,
mov al,#0x20 ! start of hardware int's (0x20)
mov al,#0x28 ! start of hardware int's 2 (0x28)
我们能看到这两句话在重置硬件中断的中断号
! well, that went ok, I hope. Now we have to reprogram the interrupts :-(
! we put them right after the intel-reserved hardware interrupts, at
! int 0x20-0x2F. There they won't mess up anything. Sadly IBM really
! messed this up with the original PC, and they haven't been able to
! rectify it afterwards. Thus the bios puts interrupts at 0x08-0x0f,
! which is used for the internal hardware interrupts as well. We just
! have to reprogram the 8259's, and it isn't fun.
mov al,#0x11 ! initialization sequence
out #0x20,al ! send it to 8259A-1
.word 0x00eb,0x00eb ! jmp $+2, jmp $+2
out #0xA0,al ! and to 8259A-2
.word 0x00eb,0x00eb
mov al,#0x20 ! start of hardware int's (0x20)
out #0x21,al
.word 0x00eb,0x00eb
mov al,#0x28 ! start of hardware int's 2 (0x28)
out #0xA1,al
.word 0x00eb,0x00eb
mov al,#0x04 ! 8259-1 is master
out #0x21,al
.word 0x00eb,0x00eb
mov al,#0x02 ! 8259-2 is slave
out #0xA1,al
.word 0x00eb,0x00eb
mov al,#0x01 ! 8086 mode for both
out #0x21,al
.word 0x00eb,0x00eb
out #0xA1,al
.word 0x00eb,0x00eb
mov al,#0xFF ! mask off all interrupts for now
out #0x21,al
.word 0x00eb,0x00eb
out #0xA1,al
! well, that certainly wasn't fun :-(. Hopefully it works, and we don't
! need no steenking BIOS anyway (except for the initial loading :-).
! The BIOS-routine wants lots of unnecessary data, and it's less
! "interesting" anyway. This is how REAL programmers do it.