中断服务函数是为响应硬件或软件中断而异步执行的函数。ISR通常会抢占当前线程的执行,从而允许以非常低的开销进行响应。线程执行仅在所有ISR工作完成后恢复。

概念

可以定义任意数量的 ISR(仅受可用 RAM 的限制),但要遵守底层硬件施加的约束
ISR 具有以下关键属性:

  • 触发ISR的中断请求 (IRQ) 信号。
  • 与 IRQ 关联的优先级。
  • 调用以处理中断的中断处理函数。
  • 传递给该函数的参数值。

IDT向量表用于将给定的中断源与给定的ISR相关联。在任何给定时间,只有单个ISR可以与特定的 IRQ相关联。
多个ISR可以利用相同的功能来处理中断,从而允许单个功能为生成多种类型的中断的设备提供服务,或为多个设备(通常为相同类型)提供服务。传递给 ISR 函数的参数值允许该函数确定已发出信号的中断。
内核为所有未使用的IDT条目提供默认ISR。如果发出意外中断信号,此ISR将生成致命的系统错误。
内核支持中断嵌套。这允许在收到更高优先级中断信号时在执行过程中抢占 ISR。一旦优先级较高的 ISR 完成其处理,优先级较低的 ISR 就会恢复执行。
ISR 的中断处理程序函数在内核的中断上下文中执行。此上下文具有自己的专用堆栈区域。如果启用了中断嵌套支持,则中断上下文堆栈的大小必须能够处理多个并发 ISR 的执行。

多级中断控制器

硬件平台可以通过使用一个或多个嵌套中断控制器来支持比本机提供的更多的中断线路。硬件中断源被组合成一行,然后路由到父控制器。
如果支持嵌套中断控制器,则应将CONFIG_MULTI_LEVEL_INTERRUPTS设置为 1,并根据硬件体系结构配置CONFIG_2ND_LEVEL_INTERRUPTSCONFIG_3RD_LEVEL_INTERRUPTS
分配一个唯一的32位中断编号,其中包含嵌入的信息,以选择和调用正确的中断服务例程 (ISR)。每个中断级别在此32位数字中都有一个字节,使用此架构最多支持四个中断级别,如下图所示:

  1. 9 2 0
  2. _ _ _ _ _ _ _ _ _ _ _ _ _ (LEVEL 1)
  3. 5 | A |
  4. _ _ _ _ _ _ _ _ _ _ _ _ _ _ (LEVEL 2)
  5. | C B
  6. _ _ _ _ _ _ _ (LEVEL 3)
  7. D

此处显示了三个中断级别。

  • -表示中断行,编号为0(最右边)。
  • LEVEL 1有12条中断线,其中两条线路2和9连接到嵌套控制器,一条设备A连接到线路 4。
  • 其中一个2级控制器具有连接到3级嵌套控制器的中断线路5和线路3上的一个设备C
  • 另一个2级控制器没有嵌套控制器,但第2行有一个设备B
  • LEVEL 3控制器在第2行有一个设备D

下面介绍了如何为每个硬件中断生成唯一的中断编号。让我们考虑上面显示的四个中断,如 A、B、C 和 D:

  1. A -> 0x00000004
  2. B -> 0x00000302
  3. C -> 0x00000409
  4. D -> 0x00030609

LEVEL 2 及更高级别的位位置偏移 1,因为 0 表示该级别不存在中断编号。对于我们的示例,LEVEL 3 控制器在第 2 行上有设备 D,连接到 LEVEL 2 控制器的线路 5,即连接到 LEVEL 1 控制器的线路 9 (2 -> 5 -> 9)。由于 LEVEL 2 及更高级别的编码偏移量,设备 D 被赋予0x00030609数。

中断禁用

在某些情况下,当前线程可能需要在执行时间敏感或关键操作时阻止 ISR 执行。
线程可能会使用IRQ锁暂时阻止系统中的所有IRQ处理。即使此锁已经生效,也可以应用它,因此例程可以使用它,而不必知道它是否已经生效。线程必须解锁IRQ 锁的次数与它被锁定的次数相同,然后内核才能在线程运行时再次处理中断。

IRQ 锁是特定于线程的。如果线程 A 锁定中断,然后执行将自身置于休眠状态的操作(例如,休眠 N 毫秒),则一旦线程 A 换出并且下一个就绪的线程 B 开始运行,线程的 IRQ 锁定将不再适用。 这意味着可以在线程 B 运行时处理中断,除非线程 B 也使用自己的 IRQ 锁锁定中断。当内核在使用 IRQ 锁的两个线程之间切换时,是否可以处理中断是特定于体系结构的。 当线程 A 最终再次成为当前线程时,内核会重新建立线程 A 的 IRQ 锁。这可确保线程 A 在显式解锁其 IRQ 锁之前不会中断。 如果线程 A 未进入休眠状态,但使较高优先级的线程 B 准备就绪,则 IRQ 锁将禁止可能发生的任何抢占。线程 B在释放 IRQ 锁后才会得到执行。

线程可以暂时禁用指定的 IRQ,以便在发出 IRQ 信号时不执行其关联的 ISR。随后必须启用 IRQ 以允许执行 ISR。

零延迟中断

通过应用IRQ锁来防止中断可能会增加中断延迟。但是,对于某些低延迟中断,较高的中断延迟是不可接受的。
内核通过允许具有延迟约束的中断以中断锁无法阻止的优先级执行来解决此类用例。这些中断被定义为零延迟中断。对零延迟中断的支持需要启用CONFIG_ZERO_LATENCY_IRQS。除此之外,还必须将IRQ_ZERO_LATENCY标志传递给IRQ_CONNECTIRQ_DIRECT_CONNECT宏,以零延迟配置特定中断。

在特定于体系结构的基础上支持零延迟中断。该功能目前在ARM Cortex-M架构变体中实现。

中断下半部

ISR 应快速执行,以确保可预测的系统操作。如果需要耗时的处理,ISR应将部分或全部处理放到线程里,从而恢复内核响应其他中断的能力。
内核支持多种机制,用于将与中断相关的处理分配到线程。

  • ISR 可以向程序线程发出信号,以使用内核对象(如 FIFO、LIFO 或信号量)执行与中断相关的处理。
  • ISR 可以指示系统工作队列线程执行工作项。

当 ISR 将工作发送到线程时,当 ISR 完成时,通常会有一个上下文切换到该线程,从而允许与中断相关的处理几乎立即继续。但是,根据处理线程的优先级,当前正在执行的合作线程或其他更高优先级的线程可能会在调度处理的线程之前执行。

定义常规 ISR

ISR是在运行时通过调用IRQ_CONNECT来定义的。然后,必须通过调用irq_enable()来启用它。

  1. #define MY_DEV_IRQ 24 /* device uses IRQ 24 */
  2. #define MY_DEV_PRIO 2 /* device uses interrupt priority 2 */
  3. /* argument passed to my_isr(), in this case a pointer to the device */
  4. #define MY_ISR_ARG DEVICE_GET(my_device)
  5. #define MY_IRQ_FLAGS 0 /* IRQ flags */
  6. void my_isr(void *arg)
  7. {
  8. ... /* ISR code */
  9. }
  10. void my_isr_installer(void)
  11. {
  12. ...
  13. IRQ_CONNECT(MY_DEV_IRQ, MY_DEV_PRIO, my_isr, MY_ISR_ARG, MY_IRQ_FLAGS);
  14. irq_enable(MY_DEV_IRQ);
  15. ...
  16. }

由于IRQ_CONNECT宏要求在构建时知道其所有参数的值,因此在某些情况下,这是不可能的。因此可以使用irq_connect_dynamic()在运行时安装中断。它的使用方式与IRQ_CONNECT完全相同:

  1. void my_isr_installer(void)
  2. {
  3. ...
  4. irq_connect_dynamic(MY_DEV_IRQ, MY_DEV_PRIO, my_isr, MY_ISR_ARG,
  5. MY_IRQ_FLAGS);
  6. irq_enable(MY_DEV_IRQ);
  7. ...
  8. }

动态中断需要启用CONFIG_DYNAMIC_INTERRUPTS选项。当前不支持删除或重新配置动态中断。

定义直接ISR

常规的Zephyr中断会带来一些开销,这对于某些低延迟用例来说可能是不可接受的。具体说来:

  • 检索 ISR 的参数并将其传递给 ISR
  • 如果启用了电源管理并且系统处于空闲状态,则在执行 ISR 之前,所有硬件都将从低功耗状态恢复,这可能非常耗时
  • 虽然有些架构会在硬件中做到这一点,但其他架构需要在代码中切换到中断堆栈。
  • 在为中断提供服务后,操作系统会执行一些逻辑,以可能做出调度决策。

Zephyr支持所谓的直接中断,这些中断是通过IRQ_DIRECT_CONNECT安装的。

  1. #define MY_DEV_IRQ 24 /* device uses IRQ 24 */
  2. #define MY_DEV_PRIO 2 /* device uses interrupt priority 2 */
  3. /* argument passed to my_isr(), in this case a pointer to the device */
  4. #define MY_IRQ_FLAGS 0 /* IRQ flags */
  5. ISR_DIRECT_DECLARE(my_isr)
  6. {
  7. do_stuff();
  8. ISR_DIRECT_PM(); /* PM done after servicing interrupt for best latency */
  9. return 1; /* We should check if scheduling decision should be made */
  10. }
  11. void my_isr_installer(void)
  12. {
  13. ...
  14. IRQ_DIRECT_CONNECT(MY_DEV_IRQ, MY_DEV_PRIO, my_isr, MY_IRQ_FLAGS);
  15. irq_enable(MY_DEV_IRQ);
  16. ...
  17. }

中断的实现原理

中断表是在编译时使用一些特殊的生成工具设置的。
IRQ_CONNECT的任何调用都将声明结构_isr_list的实例,该实例会存储位于特殊的.intList段中

  1. struct _isr_list {
  2. /** IRQ line number */
  3. int32_t irq;
  4. /** Flags for this IRQ, see ISR_FLAG_* definitions */
  5. int32_t flags;
  6. /** ISR to call */
  7. void *func;
  8. /** Parameter for non-direct IRQs */
  9. void *param;
  10. };

Zephyr分两个阶段建造;
构建的第一阶段生成.elf,其中包含.intList部分中的所有条目,前面有一个标头:${ZEPHYR_PREBUILT_EXECUTABLE}

  1. struct {
  2. void *spurious_irq_handler;
  3. void *sw_irq_handler;
  4. uint32_t num_isrs;
  5. uint32_t num_vectors;
  6. struct _isr_list isrs[]; <- of size num_isrs
  7. };

然后,gen_isr_tables.py脚本使用由.elf内部结构_isr_list标头和实例组成的数据生成定义向量表和软件ISR表的C文件,然后将其编译并链接到最终应用程序中。
任何中断的优先级都不编码在这些表中,而是IRQ_CONNECT还具有一个运行时组件,该组件将中断的所需优先级编程到中断控制器。某些体系结构不支持中断优先级的概念,在这种情况下,优先级参数将被忽略。

矢量表

启用CONFIG_GEN_IRQ_VECTOR_TABLE时,将生成一个矢量表。此数据结构由CPU本机使用,只是一个函数指针数组,其中每个元素对应于IRQ 处理程序,函数指针为:

  • 对于使用IRQ_DIRECT_CONNECT声明的直接中断,处理程序函数将放置在此处。
  • 对于使用IRQ_CONNECT声明的常规中断,通用软件 IRQ 处理程序的地址放在此处。此代码执行常见的内核中断记账,并从软件 ISR 表中查找 ISR 和参数。
  • 对于根本没有配置的中断线路,虚假IRQ 处理程序的地址将放在此处。如果遇到虚假的 IRQ 处理程序,则会导致系统致命错误。

某些架构(如 Nios II 内部中断控制器)具有所有中断的公共入口点,并且不支持矢量表,在这种情况下,应禁用CONFIG_GEN_IRQ_VECTOR_TABLE选项。
某些体系结构可能会为系统异常保留一些初始向量,并在其他位置的表中声明此向量,在这种情况下,需要将CONFIG_GEN_IRQ_START_VECTOR设置为正确偏移表中的索引。

软件 ISR 表

这是一个结构_isr_table_entry数组:

  1. struct _isr_table_entry {
  2. void *arg;
  3. void (*isr)(void *);
  4. };

通用软件 IRQ 处理程序使用它来查找 ISR 及其参数并执行它。

中断的Kconfig

Kconfig 描述
CONFIG_ISR_STACK_SIZE ISR 初始化堆栈大小(以字节为单位)
CONFIG_GEN_IRQ_VECTOR_TABLE 生成中断向量表
CONFIG_GEN_IRQ_START_VECTOR 中断向量表的偏移地址
CONFIG_DYNAMIC_INTERRUPTS 启用动态中断
CONFIG_ZERO_LATENCY_IRQS 启用零延迟中断
CONFIG_MULTI_LEVEL_INTERRUPTS 启用嵌套中断控制器