Zephyr系列文章,翻译自 https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/zephyr

人肉翻译,英文水平有限,仅供参考。

本文目录:Zephyr API reference->kernel services ->interrupts

https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/zephyr/reference/kernel/other/interrupts.html


Interrupts 中断

中断服务程序(ISR)是一个响应硬件或软件中断而一部执行的函数。ISR通常抢先与当前线程执行,允许响应以非常低的开销产生。线程只有在所有ISR工作结束之后才执行。


Concepts 概念

可以定义任意数量的ISR,取决于底层硬件的约束。

ISR拥有以下关键特性:

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

 

中断描述表(IDT)或者向量表用于将给定的中断源和ISR相关联。只有1个ISR可以在任意给定的时间与特定的IRQ关联

多个ISR可以利用同样的函数处理中断,允许单个功能服务产生多种类型中断的设备,或服务多种设备(通常是同种类型)。传递给ISR的函数的参数使函数可以确定是哪个中断被置位了。

内核为所有没使用的IDT条目提供了默认的ISR。ISR会产生严重的系统错误,如果未知的中断置位了。

内核支持中断嵌套。这允许如果有高优先级的中断置位了,ISR在运行之中被抢占。一旦高优先级的ISR已经完成它的处理,低优先级的ISR恢复执行。

ISR的中断回调函数在内核的中断上下文中执行。这个上下文拥有其独立的栈区域(或在某些体系结构上为栈区域)。如果支持中断嵌套,则中断上下文的栈大小必须能够处理多个ISR并发的执行。

 

很多内核的API只能被线程使用,而不能被ISR使用。当一个程序需要被ISR和线程调用时,内核提供了API  k_is_in_isr()来允许这个程序根据它是线程的一部分或是ISR的一部分,来改变它的行为。

 

Multi-level Interrupt handling 多级的中断处理

 

通过使用一个或者多个嵌套中断控制器,硬件平台可以支持比原本更多的中断行数。

硬件中断源被合成一个行(line),然后路由到父控制器。

如果中断嵌套控制器使能了,CONFIG_MULTI_LEVEL_INTERRUPTS 需要被置1,CONFIG_2ND_LEVEL_INTERRUPTS 和 CONFIG_3RD_LEVEL_INTERRUPTS也同样需要根据硬件结构被设置好。

一个唯一的32bit中断号码被分配,其中嵌入了信息,用于选择和调用正确的ISR(中断服务程序)。每个中断等级都被给与了1个32bit的数字,使用此结构可以支持4个中断等级,图示如下:

      这里展示了3个中断等级。

  • “-”代表中断line,从0(最右边)开始编号。
  • LEVEL1有12个中断line,其中2个line(2和9)连接到嵌套控制器,并且1个设备“A”在line4上。
  • LEVEL2的1个控制器上,中断行5连接到LEVEL3嵌套控制器,并且1个设备“C”在line3上。
  • 剩余的LEVEL2 控制器没有嵌套,还只有一个设备“B”在line2上。
  • LEVEL3的控制器有1个设备“D”在line2上。

下面是如何为每个硬件中断生成唯一中断号的方法。

LEVEL2以及前面的位位置偏移了1,因为0代表中断号在那个级别不存在。例如,LEVEL3的控制器有设备“D”在line2上,连接到LEVEL2控制器的line5上,又连接到LEVEL1控制器的line9上(2->5->9)。因为LEVEL2和前面LEVEL的编码偏移,所以设备“D”分配的号码是0x00030609。(这个地方可以理解为,当需要向前面一级的LEVEL去寻找嵌套链路,如果前一级存在,就需要在本级别的line数上加1。)

 

Preventing Interruptions 防止中断

在某些情况下,可能会需要为当前的线程阻止ISR的运行,因为它在执行时间敏感或临界断的操作。

线程可以使用IRQ锁来暂时的阻止系统中所有的中断处理。即使它已经生效依然可以应用这个锁,所以程序可以不需要知道它是否已经生效的时候使用它。线程必须解锁它的IRQ锁,与前面它锁的次数相同,之后中断就可以在线程运行的时候被内核处理了。

IRQ锁是线程指定的。如果线程A锁住了中断,然后执行一个操作让线程B运行(例如给了1个信号量或休眠了N毫秒),一旦线程A切换出去,线程的IRQ锁将不再起作用。这表明在线程B运行的时候中断额可以被执行,除非线程B也使用自己的IRQ锁锁住了中断。(内核在2个使用了IRQ锁的线程间切换是否可以运行中断,是架构指定的。)

当线程A最终又变成当前线程,内核将重新建立线程A的IRQ锁。这确保了线程A将不会被中断,直到它明明确地解锁了它的IRQ锁。

或者,线程可以临时的关闭特定的IRQ,这样当IRQ置位时它关联的ISR就不会执行。IRQ必须在随后被使能以允许ISR运行。

关闭一个IRQ会保护系统里面所有的线程被其关联的ISR抢占,而不只是关闭IRQ的这个线程。

 

Zero Latency Interrupts 零延时中断

使用IRQ锁来防止中断,可能会显著的增加中断的延时。然而,高的中断延时,在低延时的应用中可能不被接受。

内核应对这种用户情况,通过允许有关键延时约束的中断在不被中断锁屏蔽的高优先级里执行。这些中断被定义为零延时中断。支持零延时中断需要使能CONFIG_ZERO_LATENCY_IRQS。除此之外,标志位IRQ_ZERO_LATENCY 必须被设置为IRQ_CONNECT 或者 IRQ_DIRECT_CONNECT宏定义来设置特定的零延时中断。

零延时中断预期是用来直接管理硬件事件的,而不是与内核代码互操作。它们应该将所有的内核API视为未定义行为(例如,在零延时中断上下文里使用这些API的应用程序,是负责直接验证正确的行为)。零延时中断可能不会修改从正常zephyr上下文调用的内核API检查过的任何数据,并且不会生成需要同步处理的异常(例如,内核恐慌? Kernel panic)。

零延时中断在特定体系结构上被支持。这个功能当前在ARM Cortex-M体系结构变体中实现。

 

Offloading ISR Work 卸载(移交)ISR工作

ISR应该快速执行,以确保可预测的系统操作。如果是需要耗时的处理,ISR需要卸载(转移)部分或全部的工作给线程,从而恢复内核对其它中断的响应能力。

内核支持几种机制,可以将中断有关的处理卸载到线程中。

  • ISR可以使用内核对象来向辅助线程发出信号,例如FIFO,LIFO,或者信号量。
  • 内核可以指示系统工作队列来执行工作项目。

当ISR卸载(移交)工作给线程,ISR完成后,有一个典型的上下文切换到线程,允许中断相关的处理立刻处理。然而,这依赖于你处理卸载的线程的优先级,当前执行的合作线程或其他高优先级的线程,有可能会在执行卸载的线程之前被调度。


Implementation 实现

Defining a regular ISR 定义一个常规的ISR

ISR在运行时使用IRQ_CONNECT来定义。然后它必须使用irq_enable()来使能。

IRQ_CONNECT不是C语言函数,它是后台执行了一些内联汇编魔术。它所有的参数在编译的时候必须已知。拥有多个实例的驱动,可能会需要定义每个实例的配置函数,来配置各个实例的中断。

下面的代码定义和使能了ISR。

         因为IRQ_CONNECT宏定义需要在编译的时候知道它所有的参数,在某些情况下,这可能无法接受。同样也允许在运行的时候安装中断,使用irq_connect_dynamic()。它与IRQ_CONNECT的使用完全相同:

       动态中断需要使能CONFIG_DYNAMIC_INTERRUPTS选项。移除或者重新配置一个动态中断,目前还不支持。

Defining a “direct” ISR 定义“direct”(直接)中断

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

  • ISR的参数被取回和传递给ISR
  • 如果电源管理使能了且系统处于idle时,所有的硬件会在ISR执行之前,从低功耗状态恢复,这可能会很耗时。
  • 尽管有些结构会在硬件中做这些,但其他框架需要用代码切换到中断栈。
  • 服务中断之后,操作系统随后会执行一些逻辑来潜在的做出调度决策。

 

Zephyr支持被称为“direct”的中断,通过使用IRQ_DIRECT_CONNECT来安装。这些直接中断具有一些特殊的实现要求和简化的功能集,查看IRQ_DIRECT_CONNECT来看详细情况。

下面的代码演示了直接ISR:

      安装动态直接中断在某些特殊的架构下是支持的(这个功能目前在ARM-Cortex-M架构变体上实现了。动态直接中断特性只通过ARM的API向用户暴露)。

 

Implementation Details  实现细节

中断表在编译的时候使用一些特殊的编译工具建立。详细的放在下面,它应用于除了x86之外的全部架构。

任何IRQ_CONNECT的调用,将声明一个在special.intList 中的结构体 _isr_list的实例。

       Zephyr编译有两个步骤,编译第一步产生${ZEPHYR_PREBUILT_EXECUTABLE}.elf ,它包含了所有.intList 区域的所有条目,并带有头(header):

        然后,由gen_isr_tables.py脚本使用${ZEPHYR_PREBUILT_EXECUTABLE}.elf中由结构 _isr_list的标头和实例组成的数据,由gen_isr_tables.py脚本使用生成一个C文件,该C文件定义向量表和软件ISR表,然后将其编译并链接到最终的应用程序中。

任何中断的优先级都没有编在这些表中,取而代之的是IRQ_CONNECT运行时的组件,组件编程了中断控制器需求的中断优先级。一些架不支持中断优先级的架构,这时优先级参数可以被忽略。

 

Vector Table 向量表

向量表在CONFIG_GEN_IRQ_VECTOR_TABLE使能的时候生成。这个数据结构本身就被CPU使用,并且是简单的函数数组指针,其中每个元素n对应于IRQ第n行的IRQ处理程序,并且函数指针为:

1.对于“直接”中断使用IRQ_DIRECT_CONNECT声明,在此放置回调函数。

2.对于常规中断使用IRQ_CONNECT声明,通用软件IRQ回调的地址被发在这里。这些代码执行常见的内核中断记账,并从软件ISR表中查找ISR和参数。

3.对于根本没有配置的中断线(lines),伪IRQ处理程序的地址放置在这。如果遇到伪IRQ处理程序,则程序会导致致命错误。

一些架构(例如Nios II 内部的中断控制器)有一个给所有中断的通用入口指针,并且不支持中断向量表,这时CONFIG_GEN_IRQ_VECTOR_TABLE选项应该被关闭。

一些架构可能会为系统异常保留一些初始向量,并在其他表中声明,这时CONFIG_GEN_IRQ_START_VECTOR需要设置为适当的偏移索引。

 

SW ISR Table 软件中断表

这是一个结构体数组 _isr_table_entry:

 

x86 Details x86细节

此章节省略

 


Suggested Uses 建议用法

使用常规的或直接的ISR来执行中断处理,需要非常快的相应,可以在不被屏蔽/阻塞下很快的完成。

耗费时间或涉及阻塞的中断处理,应该移交给线程。有关可在应用程序中使用的各种技术描述,查看Offloading ISR Work章节。


Configuration Option 配置选项

相关的配置选项:

  • CONFIG_ISR_STACK_SIZE

附加的指定结构和指定设备配置选项也存在。


API Reference API参考

此章节省略

发表评论