本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。
作者:gfree.wind@gmail.com
博客:linuxfocus.blog.chinaunix.net
最近一直对Linux中的中断处理过程一直很纠结,觉得很明确的概念和过程,仔细想起来还是有不明白的地方。比如中断嵌套这一问题,上学的时候,很明确的知道CPU是支持中断嵌套的。可是在看Linux的代码时,对于Linux是否支持中断嵌套产生了疑问。且在和同事的讨论中,更是从Intel的手册上看到了意想不到的说明。
好,闲话少说,开始讨论今天的正文,Linux是否支持中断嵌套!
如果操作系统要想支持中断嵌套,那么底层的平台也就是CPU必须要支持中断嵌套,否则无论操作系统如何努力,也是无法做到的——唉,这就是软件的悲哀。那么我选中的平台是Intel x86平台,这应该是应用最广泛的CPU了。
根据Intel的开发手册volume 3,6.12.1.2,
The only difference between an interrupt gate and a trap gate is the way the
processor handles the IF flag in the EFLAGS register. When accessing an exception-
or interrupt-handling procedure through an interrupt gate, the processor clears the
IF flag to prevent other interrupts from interfering with the current interrupt handler.
A subsequent IRET instruction restores the IF flag to its value in the saved contents of EFFLAGS register on the stack. Accessing a handler through a trap gate does not affect the IF flag.
这段话说明了在通过interrupt gate去调用中断处理时,CPU会自动清除IF 标志,当中断处理例程结束并返还时,通过IRET指令又将IF标志恢复。而EFLAGS的IF标志使用设置该CPU中断是否enable的标志。看到这里大家可能会有一些奇怪,按照intel的说明,岂不是说intel的CPU不支持中断嵌套。我来说一下我的理解,对于中断的定义,Intel定义的中断与咱们平时说的中断不大一样。咱们平时说的中断,应该说是Intel定义的中断的一种。而trap也被Intel认为是一种中断。这么说吧,Intel眼中的中断就是只要打断了正常的处理流程,都可以称为中断——这是我下的定义,不太准确,但大概意思是这样。所以exception和系统trap都被看完中断。而通过trap gate去调用处理函数时,CPU是不会自动禁掉中断的,这时如果发生高优先级的中断,即发生了中断嵌套。
在Linux kernel中,中断处理使用的时interrupt gate,所以从平台上看,当Linux运行在x86平台时,中断就已经不支持嵌套了——当然,要是中断函数里面非要把中断再次打开,也没办法,此处不考虑这种情况。
现在看Linux kernel本身的代码是否支持中断嵌套。下面查看do_IRQ的实现,因为这个函数是依赖于平台实现的。首先查看x86平台的实现
- unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
- {
- struct pt_regs *old_regs = set_irq_regs(regs);
- /* high bit used in ret_from_ code */
- unsigned vector = ~regs->orig_ax;
- unsigned irq;
- exit_idle();
- irq_enter();
- irq = __this_cpu_read(vector_irq[vector]);
- if (!handle_irq(irq, regs)) {
- ack_APIC_irq();
- if (printk_ratelimit())
- pr_emerg("%s: %d.%d No irq handler for vector (irq %d)\n",
- __func__, smp_processor_id(), vector, irq);
- }
- irq_exit();
- set_irq_regs(old_regs);
- return 1;
- }
这部分代码没有明显处理中断开关的代码,下面看其调用的handle_irq
- bool handle_irq(unsigned irq, struct pt_regs *regs)
- {
- struct irq_desc *desc;
- int overflow;
- overflow = check_stack_overflow();
- /* 得到中断描述符 */
- desc = irq_to_desc(irq);
- if (unlikely(!desc))
- return false;
- if (!execute_on_irq_stack(overflow, desc, irq)) {
- if (unlikely(overflow))
- print_stack_overflow();
- /* 调用中断处理函数 */
- desc->handle_irq(irq, desc);
- }
- return true;
- }
从上面的代码中,可以看出没有直接去对于中断使能位的任何处理。那么在x86平台上,完全依赖于x86的处理。这样的话,可以得出结论,Linux内核在x86平台上不支持中断嵌套。——当然,如果非得在中断处理函数中,enable中断标志,来允许中断嵌套。
我认为这种行为不是好的方法,因为x86既然本身限制了中断嵌套,那么必然有它的限制。如果人为的破坏这个设计,肯定会引发错误。
最后,在引用一段Intel手册中的一段话。
Because IA-32 architecture tasks are not re-entrant, an interrupt-handler task must disable interrupts between the time it completes handling the interrupt and the time it executes the IRET instruction. This action prevents another interrupt from occurring while the interrupt task’s TSS is still marked busy, which would cause a general-protection (#GP) exception.
这里明确说明了Intel的IA-32的架构,不支持中断嵌套(这里指的是可屏蔽中断)。
最后,感谢一下
amarant同学,他指出了我最早引用的handler_irq的代码不正确。我之前使用cscope跳到handler_irq的定义时,没有注意到是alpha平台的定义。现已改正博文。