- 论坛徽章:
- 0
|
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[i-FIRST_EXTERNAL_VECTOR]);
- 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[i-FIRST_EXTERNAL_VECTOR]);
- }
- 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 ALIGN
复制代码 13./*首先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[0],从而执行:- 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[0],从而执行:
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)[vector];
- 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)[vector];
- 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的实现技巧。对于软中断和任务队列将在后面给予介绍。
|
|