一、介绍

1、中断本质是一种特殊的电信号,由硬件设备产生,交由中断控制器后传递给cpu处理。
其基本传递流程如下:

  • 硬件设备产生中断电信号,并将信号输入中断控制器引脚
  • 中断控制器发信号给处理器,处理器中断当前工作
  • 处理器通知操作系统产生了中断,操作系统进行处理。

下面介绍中断处理机制时有中断从硬件到内核的路由。

  • 该问提及的中断都是属于中断的上半部,也即硬中断,执行在硬中断上下文中。
  • 中断的下半部是中断处理的推后处理的工作,内核代码里叫软中断部分,但是中断下半部不止软中断,还有tasklet和工作队列,这些后文会讲。
  • 下半部:内核中凡是属于推后处理的工作都叫下半部。


2、中断线与中断号
中断不需要与系统时钟保持同步_,随时都能产生中断。
不同设备对应不同中断,每个中断通过唯一的数字标志。
中断值也称中断请求线(IRQ),每个IRQ都会被关联一个数字。

x86-32位系统中断号共256个。
前0x20=32个用于内中断,供内核自己使用,初始化在arch/x86/kernel/traps.c。
0x80是用于系统调用然后陷入内核。
0×20~0x7F、0×81~0xFF用于外中断,初始化在arch/x86/kernel/irqinit.c。

3、异常与中断

异常:是cpu主动引起,并且必须要与处理器时钟同步,也叫同步中断。 中断:是cpu除外的硬件产生

因为内核处理异常跟处理中断的机制差不多,所以讨论中断时就包含了异常的情况。

4、上半部

  • 上半部等价于中断处理程序的那部分,是要快速执行完的,而上半部也只能由中断处理程序来实现。
  • 下半部是需要推后执行的那部分中断处理部分。有很多种实现方式,2.5以后再用的有:软中断、tasklet和工作队列。
  • 内核中所有可以推后执行的程序都可以叫下半部。

    内中断 = 异常

    1、异常(exception)

    cpu在执行进程时产生。
    包括陷入(trap)、出错(fault)和 终止(abort)等
    陷入:比如除0错误,就会陷入异常,陷入返回执行的是下条指令;调试时打断点也是这种模式,断点执行后,执行的是下条指令。
    出错:比如缺页异常,就会出错进入异常,出错处理后执行的是当前指令,也就是当前指令会被再次执行。

2、可编程中断(programmable exception)

可编程中断:由int指令触发,比如linux中唯一的一个中断号int 0x80,作为系统调用。

二、外中断 = 中断

初始化代码:arch/x86/kernel/irqinit.c
硬中断:由硬件产生的信号传给cpu产生的上半部处理。
软中断:由硬件产生中断后交由下半部中断处理程序处理。

三、中断控制

中断控制方法列表:

序号 函数 说明 统计 范围
1 local_irq_disable() 禁止本地中断传递 x86中,仅仅是执行了cli和sti指令,对应的操作是clear和set,即只修改允许中断标志(allow interrupt)。 当前cpu
2 local_irq_enable() 激活本地中断传递
3 local_irq_save() 保存本地中断传递的当前状态,然后禁止本地中断传递。 保存或恢复状态,并修改标志
4 local_irq_restore() 恢复本地中断传递到给定的状态
5 disable_irq(irq) 禁止给定的本地中断线,并等到该中断线上执行的处理程序结束后才返回,即返回时该线上不会有运行的处理程序。
- 这四个函数可以嵌套。


- 5和6的分别调用几次,8就要调用几次才能最终激活该中断线。


- 5、6、7可以在中断或进程上下文中执行,而且不会睡眠,即不会产生上下文切换。(在中断上下文中调用时,要注意当中断线上有处理程序在运行时,并不能激活它)。
给定中断线
6 disable_irq_nosync(irq) 禁止给定本地中断线,不管该中断线上是否有在执行的处理程序。
7 synchronize_irq(irq) 等待一个特定的中断处理程序的退出,一直等。
8 enable_irq(irq) 激活给定中断线
9 irqs_disabled() 本地中断传递被禁止,就返回非0,否则是0。 判断中断系统的状态 当前cpu
10 in_interrupt() 在中断上下文中就返回非0,进程上下文中返回0。
11 in_irq() 当前正在运行中断处理程序就返回非0,否则返回0。
12 in_softirq() 在下半部上下文中就返回非0,否则返回0。

四、中断处理机制

一)、中断路由

二)、以x86为例

1、内核汇编代码
接收中断的代码定义在arch/x86/kernel/entry_32.S和entry_64.S中。
image.png

2、do_IRQ()
定义在x86/kernel/irq.c中:

  1. /*
  2. * do_IRQ会处理所有普通设备的中断响应,特殊的smp跨cpu中断有自己的指定的处理方式。
  3. * do_IRQ handles all normal device IRQ's (the special
  4. * SMP cross-CPU interrupts have their own specific
  5. * handlers).
  6. */
  7. unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
  8. {
  9. struct pt_regs *old_regs = set_irq_regs(regs);
  10. /* high bit used in ret_from_ code */
  11. unsigned vector = ~regs->orig_ax;
  12. unsigned irq;
  13. exit_idle();
  14. irq_enter(); // 主要是增加当前进程里硬中断的计数:thread_info->preempt_count
  15. // irq_enter()->__irq_enter()->add_preempt_count()【include/linux/hardirq.h】-> preempt_count()+=val【include/linux/preempt.h】 -> current_thread_info()->preempt_count + val
  16. irq = __get_cpu_var(vector_irq)[vector]; // 采用不加锁的方式获取当前cpu上的变量,这里是获取vector处的值,即上面所计算得到的中断号
  17. // 如果配合preempt_disable(); 可以禁止本地中断来获取加锁变量。
  18. // 执行中断,声明在x86/include/asm/irq.h,定义在x86/kernel/irq_32.c中。
  19. // 最终会调用到handle_IRQ_event(),定义在kernel/irq/handle.c中
  20. if (!handle_irq(irq, regs)) {
  21. ack_APIC_irq();
  22. if (printk_ratelimit())
  23. pr_emerg("%s: %d.%d No irq handler for vector (irq %d)\n",
  24. __func__, smp_processor_id(), vector, irq);
  25. }
  26. irq_exit(); // 减少硬中断的计数
  27. // 恢复中断前cpu状态
  28. set_irq_regs(old_regs);
  29. return 1;
  30. }

3、irq_enter()
进入硬中断上半部

  1. /*
  2. * 标识进入一个中断上下文
  3. * Enter an interrupt context.
  4. */
  5. void irq_enter(void)
  6. {
  7. int cpu = smp_processor_id(); //
  8. rcu_irq_enter();
  9. if (idle_cpu(cpu) && !in_interrupt()) {
  10. __irq_enter();
  11. tick_check_idle(cpu);
  12. } else
  13. __irq_enter();
  14. }

5、handle_IRQ_event()
运行中断线上所安装的所有中断处理程序
定义在kernel/irq/handle.c中:

  1. /**
  2. * 由每个体系架构的do_IRQ调用此函数来运行要处理的中断线上安装的中断处理程序。
  3. * - do_IRQ会判断该中断线上是否有处理程序并且已启动但没有执行。
  4. * - 在include/linux/irq.h中声明。
  5. * handle_IRQ_event - irq action chain handler
  6. * @irq: the interrupt number 指定的中断号(中断线)
  7. * @action: the interrupt action chain for this irq 该中断线上注册的处理程序,可能是多个或根本没注册就不执行
  8. *
  9. * Handles the action chain of an irq event
  10. */
  11. irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
  12. {
  13. irqreturn_t ret, retval = IRQ_NONE;
  14. unsigned int status = 0;
  15. // IRQF_DISABLED表示需要禁止中断的情况下才执行中断处理程序
  16. // 所以当前处理程序如果没指定禁止中断,那就先启用本地中断,以让cpu正常接收其他中断。
  17. if (!(action->flags & IRQF_DISABLED))
  18. local_irq_enable_in_hardirq();
  19. // 循环执行所有中断处理程序,除非是非共享的那就会直接结束循环。
  20. do {
  21. trace_irq_handler_entry(irq, action);
  22. ret = action->handler(irq, action->dev_id);
  23. trace_irq_handler_exit(irq, action, ret);
  24. switch (ret) {
  25. case IRQ_WAKE_THREAD:
  26. /*
  27. * 返回值设为已处理,以便可疑的检查不再触发
  28. * Set result to handled so the spurious check
  29. * does not trigger.
  30. */
  31. ret = IRQ_HANDLED;
  32. /*
  33. * 捕获返回值为WAKE_THREAD的驱动程序,但是不会创建执行线程
  34. * Catch drivers which return WAKE_THREAD but
  35. * did not set up a thread function
  36. */
  37. if (unlikely(!action->thread_fn)) {
  38. warn_no_thread(irq, action);
  39. break;
  40. }
  41. /* 为这次中断唤醒处理线程
  42. * - 万一线程崩溃且被杀死,只能假装中断处理完了
  43. * - 如果需要,硬中断在上面并没有打开还是禁止状态,防止中断再产生
  44. * Wake up the handler thread for this
  45. * action. In case the thread crashed and was
  46. * killed we just pretend that we handled the
  47. * interrupt. The hardirq handler above has
  48. * disabled the device interrupt, so no irq
  49. * storm is lurking.
  50. */
  51. if (likely(!test_bit(IRQTF_DIED,
  52. &action->thread_flags))) {
  53. set_bit(IRQTF_RUNTHREAD, &action->thread_flags); // 设置处理程序的线程正在运行的标识
  54. wake_up_process(action->thread); // 唤醒执行线程
  55. }
  56. /* Fall through to add to randomness */
  57. case IRQ_HANDLED:
  58. status |= action->flags;
  59. break;
  60. default:
  61. break;
  62. }
  63. // 执行下个处理程序
  64. retval |= ret;
  65. action = action->next;
  66. } while (action);
  67. // 如果设置了IRQF_SAMPLE_RANDOM,中断处理完后为随机数产生器产生熵。
  68. if (status & IRQF_SAMPLE_RANDOM)
  69. add_interrupt_randomness(irq);
  70. // 默认要禁止本地所有中断的,只修改禁止的标识
  71. local_irq_disable();
  72. return retval;
  73. }