linux内核中断、异常 3.................
linux内核中断、异常 3.................
清楚了中断机制和内核中有关数据结构的初始化以后,我们就从中断请求的发生到CPU的相应,再到中断服务程序的调用与返回,沿着CPU所经过的路线走一遍。
当某个外设已经产生了依次中断请求后,该请求通过中断控制器i8259A到达CPU的“中断请求”引线INTR。由于中断时开着的,所以CPU在执行完当前指令后就来相应该次中断请求。
中断向量的设置和初始化主要在setup.s文件中设置了一部分(上面已经介绍),在start_kernel函数中init_IRQ函数最终调用函数
view plaincopy to clipboardprint?01.void __init native_init_IRQ(void)
02.{
03. int i;
04.
05. /* Execute any quirks before the call gates are initialised: */
06. x86_init.irqs.pre_vector_init();
07.
08. apic_intr_init();
09.
10. /*
11. * Cover the whole vector space, no vector can escape
12. * us. (some of these will be overridden and become
13. * 'special' SMP interrupts)
14. *//*更新外部中断(IRQ)的IDT表项*/
15. for (i = FIRST_EXTERNAL_VECTOR; i < NR_VECTORS; i++) {
16. /* IA32_SYSCALL_VECTOR could be used in trap_init already. */
17. if (!test_bit(i, used_vectors))/*跳过系统调用(trap)使用过的槽位*/
18. set_intr_gate(i, interrupt);
19. }
20.
21. if (!acpi_ioapic)
22. setup_irq(2, &irq2);
23.
24.#ifdef CONFIG_X86_32
25. /*
26. * External FPU? Set up irq13 if so, for
27. * original braindamaged IBM FERR coupling.
28. */
29. if (boot_cpu_data.hard_math && !cpu_has_fpu)
30. setup_irq(FPU_IRQ, &fpu_irq);
31.
32. irq_ctx_init(smp_processor_id());
33.#endif
34.}
void __init native_init_IRQ(void)
{
int i;
/* Execute any quirks before the call gates are initialised: */
x86_init.irqs.pre_vector_init();
apic_intr_init();
/*
* Cover the whole vector space, no vector can escape
* us. (some of these will be overridden and become
* 'special' SMP interrupts)
*//*更新外部中断(IRQ)的IDT表项*/
for (i = FIRST_EXTERNAL_VECTOR; i < NR_VECTORS; i++) {
/* IA32_SYSCALL_VECTOR could be used in trap_init already. */
if (!test_bit(i, used_vectors))/*跳过系统调用(trap)使用过的槽位*/
set_intr_gate(i, interrupt);
}
if (!acpi_ioapic)
setup_irq(2, &irq2);
#ifdef CONFIG_X86_32
/*
* External FPU? Set up irq13 if so, for
* original braindamaged IBM FERR coupling.
*/
if (boot_cpu_data.hard_math && !cpu_has_fpu)
setup_irq(FPU_IRQ, &fpu_irq);
irq_ctx_init(smp_processor_id());
#endif
}可以看到这里将interrupt[]数组中的值设置为中断服务函数而i-FIRST_EXTERNAL_VECTOR中的i为中断号,interrupt[]数组在汇编中实现
view plaincopy to clipboardprint?01.ENTRY(interrupt)
02..text
03. .p2align 5
04. .p2align CONFIG_X86_L1_CACHE_SHIFT
05.ENTRY(irq_entries_start)
06. RING0_INT_FRAME
07.vector=FIRST_EXTERNAL_VECTOR
08..rept (NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7
09. .balign 32
10..rept 7
11. .if vector < NR_VECTORS
12. .if vector <> FIRST_EXTERNAL_VECTOR
13. CFI_ADJUST_CFA_OFFSET -4
14. .endif
15.1:pushl $(~vector+0x80) /* Note: always in signed byte range */
16. CFI_ADJUST_CFA_OFFSET 4
17. .if ((vector-FIRST_EXTERNAL_VECTOR)%7) <> 6
18. jmp 2f
19. .endif
20. .previous
21. .long 1b
22. .text
23.vector=vector+1
24. .endif
25..endr
26.2:jmp common_interrupt
27..endr
28.END(irq_entries_start)
29.
30..previous
31.END(interrupt)
ENTRY(interrupt)
.text
.p2align 5
.p2align CONFIG_X86_L1_CACHE_SHIFT
ENTRY(irq_entries_start)
RING0_INT_FRAME
vector=FIRST_EXTERNAL_VECTOR
.rept (NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7
.balign 32
.rept 7
.if vector < NR_VECTORS
.if vector <> FIRST_EXTERNAL_VECTOR
CFI_ADJUST_CFA_OFFSET -4
.endif
1: pushl $(~vector+0x80) /* Note: always in signed byte range */
CFI_ADJUST_CFA_OFFSET 4
.if ((vector-FIRST_EXTERNAL_VECTOR)%7) <> 6
jmp 2f
.endif
.previous
.long 1b
.text
vector=vector+1
.endif
.endr
2: jmp common_interrupt
.endr
END(irq_entries_start)
.previous
END(interrupt)上面的代码相当于下面代码
view plaincopy to clipboardprint?
01.346 ENTRY(irq_entries_start)
02.347 .rept NR_IRQS /*348-354行重复NR_IRQS次,会被gcc编译时展开,不需手写这么多行重复的代码 */
03.348 ALIGN
04.349 1: pushl $vector-256 /*vector在354行递增 */
05.350 jmp common_interrupt /*所有的外部中断处理函数的统一部分,以后再讲述*/
06.351 .data
07.352 .long 1b /*存储着指向349行的地址,但是随着348行-354被gcc展开,每次的值都不同 */
08.353 .text
09.354 vector=vector+1
10.355 .endr /*与347行呼应*/
11.356
12.357 ALIGN13./*首先342行和352行都处于.data段,虽然看起来它们是隔开的,但实际上被gcc安排在了连续的数据段内存中,同理在代码段内存中,354行与350行的指令序列也是连续存储的。另外,348-354行会被gcc展开NR_IRQS次,因此每次352行都会存储一个新的指针,该指针指向每个349行展开的新对象。最后在代码段内存中连续存储了NR_IRQS个代码片断,首地址由irq_entries_start指向。而在数据段内存中连续存储了NR_IRQS个指针,首址存储在interrupt这个全局变量中。这样,例如IRQ号是0 (从init_IRQ()的404行知道,它对应的中断向量是FIRST_EXTERNAL_VECTOR)的中断通过中断门后会触发interrput,从而执行:14.pushl 0-256
15.jmp common_interrupt
16.的代码片断,进入到Linux内核安排好的中断入口路径 */
346 ENTRY(irq_entries_start)
347 .rept NR_IRQS /*348-354行重复NR_IRQS次,会被gcc编译时展开,不需手写这么多行重复的代码 */
348 ALIGN
349 1: pushl $vector-256 /*vector在354行递增 */
350 jmp common_interrupt /*所有的外部中断处理函数的统一部分,以后再讲述*/
351 .data
352 .long 1b /*存储着指向349行的地址,但是随着348行-354被gcc展开,每次的值都不同 */
353 .text
354 vector=vector+1
355 .endr /*与347行呼应*/
356
357 ALIGN/*首先342行和352行都处于.data段,虽然看起来它们是隔开的,但实际上被gcc安排在了连续的数据段内存中,同理在代码段内存中,354行与350行的指令序列也是连续存储的。另外,348-354行会被gcc展开NR_IRQS次,因此每次352行都会存储一个新的指针,该指针指向每个349行展开的新对象。最后在代码段内存中连续存储了NR_IRQS个代码片断,首地址由irq_entries_start指向。而在数据段内存中连续存储了NR_IRQS个指针,首址存储在interrupt这个全局变量中。这样,例如IRQ号是0 (从init_IRQ()的404行知道,它对应的中断向量是FIRST_EXTERNAL_VECTOR)的中断通过中断门后会触发interrput,从而执行:
pushl 0-256
jmp common_interrupt
的代码片断,进入到Linux内核安排好的中断入口路径 */
common_interrupt:
view plaincopy to clipboardprint?01.common_interrupt:
02. /*将中断向量号减256。内核用负数表示所有的中断*/
03. addl $-0x80,(%esp)/* Adjust vector into the [-256,-1] range */
04. /*调用SAVE_ALL宏保存寄存器的值*/
05. SAVE_ALL
06. TRACE_IRQS_OFF
07. /*保存栈顶地址*/
08. movl %esp,%eax
09. /*调用do_IRQ函数*/
10. call do_IRQ
11. /*从中断返回*/
12. jmp ret_from_intr
13.ENDPROC(common_interrupt)
14. CFI_ENDPROC
common_interrupt:
/*将中断向量号减256。内核用负数表示所有的中断*/
addl $-0x80,(%esp) /* Adjust vector into the [-256,-1] range */
/*调用SAVE_ALL宏保存寄存器的值*/
SAVE_ALL
TRACE_IRQS_OFF
/*保存栈顶地址*/
movl %esp,%eax
/*调用do_IRQ函数*/
call do_IRQ
/*从中断返回*/
jmp ret_from_intr
ENDPROC(common_interrupt)CFI_ENDPROC保存现场后调用do_IRQ函数进入c语言中执行中断函数
view plaincopy to clipboardprint?01.unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
02.{
03. /*取得原来的寄存器*/
04. struct pt_regs *old_regs = set_irq_regs(regs);
05.
06. /* high bit used in ret_from_ code*/
07. /*取得中断向量号,通过汇编代码中栈中保存
08. 的寄存器的顺序和前面c函数中的idt的初始化
09. 可以得知这里返回的值再取补
10. 正好是我们要的中断号*/
11. unsigned vector = ~regs->orig_ax;
12. unsigned irq;
13. /*退出idle进程*/
14. exit_idle();
15. /*进入中断*/
16. irq_enter();
17. /*中断线号与设备的中断号之间对应关系
18. ,由系统分派,分派表是一个per-cpu变量vector_irq*/
19. irq = __get_cpu_var(vector_irq);
20.
21. if (!handle_irq(irq, regs)) {/*处理*/
22. ack_APIC_irq();/*应答apic*/
23.
24. if (printk_ratelimit())
25. pr_emerg("%s: %d.%d No irq handler for vector (irq %d)\n",
26. __func__, smp_processor_id(), vector, irq);
27. }
28.
29. irq_exit();
30.
31. set_irq_regs(old_regs);
32. return 1;
33.}
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*/
/*取得中断向量号,通过汇编代码中栈中保存
的寄存器的顺序和前面c函数中的idt的初始化
可以得知这里返回的值再取补
正好是我们要的中断号*/
unsigned vector = ~regs->orig_ax;
unsigned irq;
/*退出idle进程*/
exit_idle();
/*进入中断*/
irq_enter();
/*中断线号与设备的中断号之间对应关系
,由系统分派,分派表是一个per-cpu变量vector_irq*/
irq = __get_cpu_var(vector_irq);
if (!handle_irq(irq, regs)) {/*处理*/
ack_APIC_irq();/*应答apic*/
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;
}view plaincopy to clipboardprint?01./*下面是处理函数。函数根据中断号,查找相应的desc结构*/
02.bool handle_irq(unsigned irq, struct pt_regs *regs)
03.{
04. struct irq_desc *desc;
05. int overflow;
06.
07. overflow = check_stack_overflow();
08.
09. desc = irq_to_desc(irq);/*取得irq对应的中断描述符*/
10. if (unlikely(!desc))
11. return false;
12. /*如果是在中断栈上调用,则稍微复杂一点
13. ,需要先构造一个中断栈,再调用handle_irq*/
14. if (!execute_on_irq_stack(overflow, desc, irq)) {
15. if (unlikely(overflow))
16. print_stack_overflow();
17. /*handle_irq函数指针,指向了handle_level_irq,
18. 或者是handle_edge_irq。不论是哪一种,
19. 中断电流处理函数在会调用handle_IRQ_event
20. 进一步处理,handle_IRQ_event函数的本质是
21. 遍历中断号上所有的action,调用其handler。
22. 这是在设备驱动初始化时向中断子系统注册的
23. */
24. desc->handle_irq(irq, desc);
25. }
26.
27. return true;
28.}
/*下面是处理函数。函数根据中断号,查找相应的desc结构*/
bool handle_irq(unsigned irq, struct pt_regs *regs)
{
struct irq_desc *desc;
int overflow;
overflow = check_stack_overflow();
desc = irq_to_desc(irq);/*取得irq对应的中断描述符*/
if (unlikely(!desc))
return false;
/*如果是在中断栈上调用,则稍微复杂一点
,需要先构造一个中断栈,再调用handle_irq*/
if (!execute_on_irq_stack(overflow, desc, irq)) {
if (unlikely(overflow))
print_stack_overflow();
/*handle_irq函数指针,指向了handle_level_irq,
或者是handle_edge_irq。不论是哪一种,
中断电流处理函数在会调用handle_IRQ_event
进一步处理,handle_IRQ_event函数的本质是
遍历中断号上所有的action,调用其handler。
这是在设备驱动初始化时向中断子系统注册的
*/
desc->handle_irq(irq, desc);
}
return true;
}由上面的注释知,我们直接看handle_IRQ_event函数
view plaincopy to clipboardprint?01.irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
02.{
03. irqreturn_t ret, retval = IRQ_NONE;
04. unsigned int status = 0;
05. /*为CPU会禁止中断,这里将其打开,如果没有指定
06. IRQF_DISABLED标志的话,它表示处理程序在中断禁止
07. 情况下运行*/
08. if (!(action->flags & IRQF_DISABLED))
09. local_irq_enable_in_hardirq();
10.
11. do {/*遍历当前irq的action链表中的所有action,调用之*/
12. trace_irq_handler_entry(irq, action);/*打开中断跟踪*/
13. ret = action->handler(irq, action->dev_id);/*调用中断函数*/
14. trace_irq_handler_exit(irq, action, ret);/*关闭中断跟踪*/
15.
16. switch (ret) {
17. case IRQ_WAKE_THREAD:
18. /*
19. * Set result to handled so the spurious check
20. * does not trigger.
21. */
22. ret = IRQ_HANDLED;
23.
24. /*
25. * Catch drivers which return WAKE_THREAD but
26. * did not set up a thread function
27. */
28. if (unlikely(!action->thread_fn)) {
29. warn_no_thread(irq, action);
30. break;
31. }
32.
33. /*
34. * Wake up the handler thread for this
35. * action. In case the thread crashed and was
36. * killed we just pretend that we handled the
37. * interrupt. The hardirq handler above has
38. * disabled the device interrupt, so no irq
39. * storm is lurking.
40. */
41. if (likely(!test_bit(IRQTF_DIED,
42. &action->thread_flags))) {
43. set_bit(IRQTF_RUNTHREAD, &action->thread_flags);
44. wake_up_process(action->thread);
45. }
46.
47. /* Fall through to add to randomness */
48. case IRQ_HANDLED:
49. status |= action->flags;
50. break;
51.
52. default:
53. break;
54. }
55.
56. retval |= ret;
57. action = action->next;/*取得下一个action,如果有的话*/
58. } while (action);
59. /*如果指定了标志,则使用中断间隔时间为随机数产生器产生熵*/
60. if (status & IRQF_SAMPLE_RANDOM)
61. add_interrupt_randomness(irq);
62. /*关闭中断,do_IRQ进入下一轮循环——等待新的中断到来*/
63. local_irq_disable();
64.
65. return retval;
66.}
irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
{
irqreturn_t ret, retval = IRQ_NONE;
unsigned int status = 0;
/*为CPU会禁止中断,这里将其打开,如果没有指定
IRQF_DISABLED标志的话,它表示处理程序在中断禁止
情况下运行*/
if (!(action->flags & IRQF_DISABLED))
local_irq_enable_in_hardirq();
do {/*遍历当前irq的action链表中的所有action,调用之*/
trace_irq_handler_entry(irq, action);/*打开中断跟踪*/
ret = action->handler(irq, action->dev_id);/*调用中断函数*/
trace_irq_handler_exit(irq, action, ret);/*关闭中断跟踪*/
switch (ret) {
case IRQ_WAKE_THREAD:
/*
* Set result to handled so the spurious check
* does not trigger.
*/
ret = IRQ_HANDLED;
/*
* Catch drivers which return WAKE_THREAD but
* did not set up a thread function
*/
if (unlikely(!action->thread_fn)) {
warn_no_thread(irq, action);
break;
}
/*
* Wake up the handler thread for this
* action. In case the thread crashed and was
* killed we just pretend that we handled the
* interrupt. The hardirq handler above has
* disabled the device interrupt, so no irq
* storm is lurking.
*/
if (likely(!test_bit(IRQTF_DIED,
&action->thread_flags))) {
set_bit(IRQTF_RUNTHREAD, &action->thread_flags);
wake_up_process(action->thread);
}
/* Fall through to add to randomness */
case IRQ_HANDLED:
status |= action->flags;
break;
default:
break;
}
retval |= ret;
action = action->next;/*取得下一个action,如果有的话*/
} while (action);
/*如果指定了标志,则使用中断间隔时间为随机数产生器产生熵*/
if (status & IRQF_SAMPLE_RANDOM)
add_interrupt_randomness(irq);
/*关闭中断,do_IRQ进入下一轮循环——等待新的中断到来*/
local_irq_disable();
return retval;
}可知,在handle_event_IRQ函数中完成了具体的中断函数响应执行工作。也在这里我们看出,多个中断服务函数共享同一个中断号时,在每次中断到来时,在中断向量上的服务函数队列中所有的服务函数将执行一遍。而对于中断线程也会在这里得到执行。
到这里中断的初始化、中断服务的相应的分析完了,对于中断返回的退出,基本和异常的返回是一样的,我们从代码中可以看出
view plaincopy to clipboardprint?01.ret_from_exception:
02. preempt_stop(CLBR_ANY)
03.ret_from_intr:
04. GET_THREAD_INFO(%ebp)
05. ....
ret_from_exception:
preempt_stop(CLBR_ANY)
ret_from_intr:
GET_THREAD_INFO(%ebp)....
至此,中断和异常的分析基本上就这样了,中间有很多细节是值得去理解的。包括汇编和c的实现技巧。对于软中断和任务队列将在后面给予介绍。
谢谢分享
页:
[1]