您的位置:首页 > 其它

【中断异常】中断的响应和服务

2014-05-09 20:27 239 查看
这里,我们假定外设的驱动程序都已经完成了初始化后,并且已把相应的中断服务程序挂入到特定的中断请求队列中,系统正在用户空间正常运行(此时中断必然是开着),并且某个外设已经产生了一次中断请求。该请求通过中断控制器i8259A达到了CPU的中断请求引线INTR;由于中断是开着的,所以CPU在执行完当前指令后就来响应该次中断请求;

(1)CPU根据中断控制器取得中断向量,然后根据中断向量从中断向量表IDT中找到相应的表项,而该表项应该是个中断门;这样CPU就根据中断门的设置而达到了该通道的总服务程序的入口,假定为IRQ0x03_interrupt。由于中断是用户空间发生的,运行级别CPL为3,;而中断服务程序属于内核,其运行级别DPL为0;所以,CPU要从寄存器TR所指的当前TSS中取出内核(0级)的堆栈指针,并把堆栈切换到内核堆栈,即当前进程的系统空间堆栈;而每次从系统空间返回时要返回到用户空间时堆栈一定回到其原点;也就是说CPU进入IRQ0x03_interrupt时,堆栈中除寄存器EFLAGS的内容以及返回地址,一无所有;穿过中断门(非陷阱门)后,中断被关闭;

(2)所有公用中断请求的服务程序总入口是由gcc的预处理阶段生成的,包括IRQ0x03_interrupt;代码具体就是将一个与中断请求号相关的数值压入堆栈,使得 common_interrupt()中可以通过这个数值来确定该次中断的来源;这块堆栈本来是要存放系统调用号的,为了与系统调用的调用号相区别(因为都调用同一个公共的一部分子程序),故将中断请求号减去256变为负数,变为负数主要是为了效率起见,少一次访存;

(3)在common_interrupt()中,用宏操作SAVE_ALL,就是保存现场(保存中断发生前夕所有寄存器的内容到堆栈中,这样返回时可恢复现场);其中EFLAGS的内容并不是在SAVE_ALL中保存的,这是因为CPU进入中断服务时,已经将它们的内容压入堆栈中了;原来的堆栈寄存器ss以及堆栈指针sp要么是不需要变化,要么是不需保存;SAVE_ALL执行完后,堆栈的内容如下:

(4)在common_interrupt()中,在SAVE_ALL在之后,还压入了一个标号,因为common_interrupt(),SAVE_ALL都是宏定义,不是函数,因此do_IRQ()要返回时需要压入标号的地址;

(5)在do_IRQ()中,通过存入一个上述寄存器组的一个备份struct pt_regs,这样可为do_IRQ模拟了一个子程序调用的环境,后面只需要传入一个指向pt_regs的指针(指向系统堆栈的那块地方);这样也可以从pt_regs得到请求号,就从数组irq_desc[]中找到相应的中断请求队列;通过desc->handler->ack(irq)给CPU一个确认,然后请IRQ_PENDING标志位清零;如果中断请求队列的服务时关着的即IRQ_DISABLED位置为1,或action为NULL,无法执行都得返回;IRQ_PENDING保证了同一个中断通道上的中断处理“串行化”,同一个CPU不允许中断嵌套,不同的CPU不允许进入同一个中断服务程序,在handle_IRQ_event()中,如果返回时IRQ_PENDING总是为1,那这个就是内核的一个bug了;

(6)在handle_IRQ_event()中,依次执行队列中的各个中断处理服务程序;在这边若果request_irq中flag标识中设置了可开中断,这边可以将其打开;最后循环执行每一个具体的服务程序;最后需要执行do_softirq()软中断,就可返回到do_IRQ()要返回时需要压入标号的地址处;

(7)在common_interrupt()中,首先将当前进程的task_struct结构的指针置入寄存器EBX中;经过判断是否在VM86模式下还是在DOS软件下,若CPU在中断前夕在用户空间,就需执行ret_with_reschedule()看是否需要进程调度,是否有信号处理,最终都要进入restore_all中;最后使用RESTORE_ALL将SAVE_ALL中保存的寄存器出栈,除了ORIG_EAX,因为它不需要保存了,也没有可用的寄存器给它保存了;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: