分析中断,本质上是一种特殊的电信号,由硬件设备发向处理器,处理器接收到中断后,会马上向操作系统反应此信号的到来,然后就由OS负责处理这些新到来的数据,中断可以随时发生,才不用操心与处理器的时间同步问题。不同的设备对应的中断不同,他们之间的不同从操作系统级来看,差别就在于一个数字标识-----中断号,即中断请求(IRQ)。讨论中断就不得不提及异常,异常和中断不一样,它在产生时必须要考虑与处理器的时钟同步,实际上,异常也常常称为同步中断,在处理器执行到由于编程失误而导致的错误指令的时候,或者是在执行期间出现特殊情况,必须要靠内核来处理的时候,处理器就会产生一个异常。因为许多处理器体系结构处理异常以及处理中断的方式类似,因此,内核对它们的处理也很类似。
异常:异常通常指的是同步中断。是当指令执行时由CPU控制单元产生的,之所以成为同步,是因为只有在一条指令终止后CPU才会发出中断。当CPU执行到由于编程失误而导致的错误指令(比如被0除)的时候,或者在执行期间出现踢输情况(如缺叶)而必须靠内核来处理的时候,处理器就产生一个异常。
80x86发布了大约20中不同的异常,内核必须为每种异常提供一个专门的异常处理程序,对于某些异常,CPU在执行异常处理程序前会产生一个硬件出错码,并压入内核态堆栈。
下面列表给出了在80x86处理器中可以找到的异常的向量、名字及其简单描述。
IDT表中每一项对应一个中断或异常向量,每个向量由8个字节组成,因此最多需要256×8=2048字节来存放IDT。
IDT的地址存放在idtr寄存器中(idtr CPU寄存器使IDT可以位于内存的任何地方,它指定IDT的线性基地址及其限制。在允许中断前,必须用lidr汇编指令初始化idtr。)。中断发生时,内核就从IDT中查询相应中断的处理信息。
IDT包含三种类型的中断描述符,如下图总每种描述符的64位的含义。尤其值得注意的是,子啊40——43为的Type字段的值表示描述符的类型。
这些描述符是:
任务门(task gate):当任务发生时,必须取代当前进程的那个进程的TSS选择符存放在任务门中。
中断门(Interrupt gate):包含段选择符和中断或异常处理程序的段内偏移量。当控制权转移到一个适当的段时,处理器清IF标志,从而关闭将来会发生的可屏蔽中断。
陷阱门(Trap gate):与中断门相似,只是控制权传递到一个适当的段时处理器不修改IF标志。
每个中断和异常都会引起一个内核控制路径,而内核控制路径是可以任意嵌套的。也就是说,一个中断处理程序可以被另一个中断处理程序“中断”。为了允许这样的嵌套,中断处理程序就必须永不阻塞,换句话说,进程被中断,在中断程序运行期间,不能发生进程切换。这是因为,一个中断产生时,内核会把当前寄存器的内容保存在内核态堆栈中,这个内核态堆栈属于当前进程,嵌套中断时,上一个中断执行程序产生的寄存器内容同样也会保存在该内核态堆栈,然后从嵌套的下一个中断恢复时,又从内核态堆栈中取出来放进寄存器中。
传统的PIC是由两片8259A芯片级联成的,可以支持15个IRQ线。
每个IRQ都有自己的描述符:irq_desc_t,描述符中有字段指向PIC对象,有字段指向ISR的链表(因为每个IRQ线上可以注册多个中断处理程序)。所有的irq_desc_t合起来组成irq_desc数组。irq_desc数组是Linux内核中用于维护IRQ资源的管理单元,它存储了某IRQ号对应的哪些处理函数,属于哪个PIC管理、来自哪个设备、IRQ自身的属性、资源等,是内核中断子系统的一个核心数组。
有时候中断处理需要做的工作很多,而中断处理程序的性质要求它必须在尽量短的时 间内处理完毕,所以中断处理的过程可以分为两部分或者两半(half)。中断处理程序属 于“上半部(top half)”——接受到一个中断,立刻开始执行,但只做有严格时限的工作。 能够被允许稍微晚一点完成的工作会放到“下半部(bottom half)中去,下半部不会马上 执行,而是等到一个合适的时机调度执行。也就是说,关键而紧急的部分,内核立即执行,属于上半部;其余推迟的部分,内核随后执行,属于下半部。
比如说当网卡接收到数据包时,会产生一个中断,中断处理程序首要进行的工作是通知硬件拷贝最新的网络数据包到内存,然后读取网卡更多的数据包。这样网卡缓存就不会溢出 。至于对数据包的处理和其他随后工作,则放到下半部进行。关于下半部的细节,我们后面会讨论。
/* 定义在中 */
typedef irqreturn_t (*irq_handler_t)(int, void *);
int request_irq(ussigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *name,
void *dev );
irq //要分配的中断号
handler //是指向中断处理程序的指针
flags //设置中断处理程序的一些属性,可能的值如下:
IRQF_DISABLED 在本次中断处理程序本身期间,禁止所有其他中断。
IRQF_SAMPLE_RANDOM 这个中断对内核的随机数产生源有贡献。
IRQF_TIMER 该标志是特别为系统定时器的中断处理准备的。
IRQF_SHARED 表明多个中断处理程序可以共享这条中断线。也就是说这条中断线上可以注册多个中断处理程序,当中断发生时,所有注册到这条中断线上的handler都会被调用。
name: 是与中断相关设备的ASCII文本表示
dev: 类似于一个cookie,内核每次调用中断处理程序时,都会把这个指针传递给它, 指针的值用来表明到底是什么设备产生了这个中断,当中断线共享时,这条中断线上 的handler们就可以通过dev来判断自己是否需要处理。
void free_irq(unsigned int irq, void *dev);
参数和request_irq的参数类似。当一条中断线上注册了多个中断处理程序时,就需要
dev来说明想要注销的是哪一个handler。
当执行一条指令后,cs和eip这对寄存器包含下一条将要执行的指令的逻辑地址。在处理这条指令之前,控制单元会检查在运行前一条指令是是否已经发生了一个中断或异常。如果发生,那么控制单元执行以下操作:
控制单元所执行的最后一步就是跳转到中断或异常处理程序,相应的处理程序必须产生一条iret指令,把控制权交给被中断的进程,这将迫使控制单元。