16、中断管理
1、异常与中断的基本概念
异常是导致处理器脱离正常运行转向执行特殊代码的任何事件,如果不及时进行处理,轻则系统出错,重则会导致系统毁灭性瘫痪。所以正确地处理异常,避免错误的发生是提高软件鲁棒性(稳定性)非常重要的一环,对于实时系统更是如此。
异常是指任何打断处理器正常执行,并且迫使处理器进入一个由有特权的特殊指令执行的事件。
异常通常可以分成两类:同步异常和异步异常。
由内部事件(像处理器指令运行产生的事件)引起的异常称为同步异常,例如造成被零除的算术运算引发一个异常,又如在某些处理器体系结构中,对于确定的数据尺寸必须从内存的偶数地址进行读和写操作。从一个奇数内存地址的读或写操作将引起存储器存取一个错误事件并引起一个异常(称为校准异常) 。
异步异常主要是指由于外部异常源产生的异常,是一个由外部硬件装置产生的事件引起的异步异常。 同步异常不同于异步异常的地方是事件的来源,同步异常事件是由于执行某些指令而从处理器内部产生的, 而异步异常事件的来源是外部硬件装置。 例如按下设备某个按钮产生的事件。同步异常与异步异常的区别还在于,同步异常触发后,系统必须立刻进行处理而不能够依然执行原有的程序指令步骤;而异步异常则可以延缓处理甚至是忽略,例如按键中断异常,虽然中断异常触发了,但是系统可以忽略它继续运行(同样也忽略了相应的按键事件)。
中断,中断属于异步异常。 所谓中断是指中央处理器 CPU 正在处理某件事的时候,外部发生了某一事件,请求 CPU 迅速处理, CPU 暂时中断当前的工作,转入处理所发生的事件,处理完后,再回到原来被中断的地方,继续原来的工作,这样的过程称为中断。
中断能打断任务的运行,无论该任务具有什么样的优先级,因此中断一般用于处理比较紧急的事件,而且只做简单处理,例如标记该事件, 在使用 FreeRTOS 系统时,一般建议使用信号量、消息或事件标志组等标志中断的发生,将这些内核对象发布给处理任务,处理任务再做具体处理。
通过中断机制,在外设不需要 CPU 介入时, CPU 可以执行其他任务,而当外设需要CPU 时通过产生中断信号使 CPU 立即停止当前任务转而来响应中断请求。这样可以使CPU 避免把大量时间耗费在等待、查询外设状态的操作上,因此将大大提高系统实时性以及执行效率。
此处读者要知道一点, FreeRTOS 源码中有许多处临界段的地方,临界段虽然保护了关键代码的执行不被打断,但也会影响系统的实时,任何使用了操作系统的中断响应都不会比裸机快。比如,某个时候有一个任务在运行中,并且该任务部分程序将中断屏蔽掉,也就是进入临界段中,这个时候如果有一个紧急的中断事件被触发,这个中断就会被挂起,不能得到及时响应,必须等到中断开启才可以得到响应,如果屏蔽中断时间超断能够容忍的限度,危害是可想而知的。所以,操作系统的中断在某些时候会有适当的中断延迟,因此调用中断屏蔽函数进入临界段的时候,也需快进快出。 当然 FreeRTOS 也能允许一些高优先级的中断不被屏蔽掉,能够及时做出响应,不过这些中断就不受系统管理,也不允许调用 FreeRTOS 中与中断相关的任何 API 函数接口。
FreeRTOS 的中断管理支持:
- 开/关中断。
- 恢复中断。
- 中断使能。
- 中断屏蔽。
- 可选择系统管理的中断优先级。
1.1、中断的介绍
与中断相关的硬件可以划分为三类:外设、中断控制器、 CPU 本身。
外设:当外设需要请求 CPU 时,产生一个中断信号,该信号连接至中断控制器。
中断控制器:中断控制器是 CPU 众多外设中的一个,它一方面接收其他外设中断信号的输入,另一方面,它会发出中断信号给 CPU。可以通过对中断控制器编程实现对中断源的优先级、触发方式、打开和关闭源等设置操作。在 Cortex-M 系列控制器中常用的中断控制器是 NVIC(内嵌向量中断控制器 Nested Vectored Interrupt Controller) 。
CPU: CPU 会响应中断源的请求,中断当前正在执行的任务,转而执行中断处理程序。
NVIC 最多支持 240 个中断,每个中断最多 256 个优先级。
1.2 、和中断相关的名词解释
中断号:每个中断请求信号都会有特定的标志,使得计算机能够判断是哪个设备提出的中断请求,这个标志就是中断号。
中断请求:“紧急事件”需向 CPU 提出申请,要求 CPU 暂停当前执行的任务,转而处理该“紧急事件”,这一申请过程称为中断请求。
中断优先级:为使系统能够及时响应并处理所有中断,系统根据中断时间的重要性和紧迫程度,将中断源分为若干个级别,称作中断优先级。
中断处理程序:当外设产生中断请求后, CPU 暂停当前的任务,转而响应中断申请,即执行中断处理程序。
中断触发:中断源发出并送给 CPU 控制信号,将中断触发器置“1”,表明该中断源产生了中断,要求 CPU 去响应该中断, CPU 暂停当前任务,执行相应的中断处理程序。
中断触发类型:外部中断申请通过一个物理信号发送到 NVIC,可以是电平触发或边沿触发。
中断向量:中断服务程序的入口地址。
中断向量表:存储中断向量的存储区,中断向量与中断号对应,中断向量在中断向量表中按照中断号顺序存储。
临界段:代码的临界段也称为临界区,一旦这部分代码开始执行,则不允许任何中断打断。为确保临界段代码的执行不被中断,在进入临界段之前须关中断,而临界段代码执行完毕后,要立即开中断。
2、中断管理的运作机制
当中断产生时,处理机将按如下的顺序执行:
(1)保存当前处理机状态信息
(2)载入异常或中断处理函数到 PC 寄存器
(3)把控制权转交给处理函数并开始执行
(4)当处理函数执行完成时,恢复处理器状态信息
(5)从异常或中断中返回到前一个程序执行点
中断使得 CPU 可以在事件发生时才给予处理,而不必让 CPU 连续不断地查询是否有相应的事件发生。通过两条特殊指令:关中断和开中断可以让处理器不响应或响应中断,在关闭中断期间,通常处理器会把新产生的中断挂起,当中断打开时立刻进行响应,所以会有适当的延时响应中断,故用户在进入临界区的时候应快进快出。
中断发生的环境有两种情况:在任务的上下文中,在中断服务函数处理上下文中。
- 任务在工作的时候,如果此时发生了一个中断,无论中断的优先级是多大,都会打断当前任务的执行,从而转到对应的中断服务函数中执行,其过程具体见下图。
图 (1)、 (3):在任务运行的时候发生了中断,那么中断会打断任务的运行, 那么操作系统将先保存当前任务的上下文环境,转而去处理中断服务函数。
图 (2)、 (4):当且仅当中断服务函数处理完的时候才恢复任务的上下文环境,继续运行任务。
- 在执行中断服务例程的过程中,如果有更高优先级别的中断源触发中断,由于当前处于中断处理上下文环境中,根据不同的处理器构架可能有不同的处理方式,比如新的中断等待挂起直到当前中断处理离开后再行响应;或新的高优先级中断打断当前中断处理过程,而去直接响应这个更高优先级的新中断源。后面这种情况,称之为中断嵌套。在硬实时环境中,前一种情况是不允许发生的,不能使响应中断的时间尽量的短。而在软件处理(软实时环境)上, FreeRTOS 允许中断嵌套,即在一个中断服务例程期间,处理器可以响应另外一个优先级更高的中断,过程如下图所示。
图 (1):当中断 1 的服务函数在处理的时候发生了中断 2,由于中断 2 的优先级比中断 1 更高,所以发生了中断嵌套, 那么操作系统将先保存当前中断服务函数的上下文环境,并且转向处理中断 2,当且仅当中断 2 执行完的时候图 (2),才能继续执行中断 1。
3、中断延迟的概念
即使操作系统的响应很快了,但对于中断的处理仍然存在着中断延迟响应的问题,我们称之为中断延迟(Interrupt Latency) 。
中断延迟是指从硬件中断发生到开始执行中断处理程序第一条指令之间的这段时间。也就是系统接收到中断信号到操作系统作出响应,并完成换到转入中断服务程序的时间。也可以简单地理解为:(外部)硬件(设备)发生中断,到系统执行中断服务子程序(ISR)的第一条指令的时间。
中断的处理过程是:外界硬件发生了中断后, CPU 到中断处理器读取中断向量,并且查找中断向量表,找到对应的中断服务子程序( ISR)的首地址,然后跳转到对应的 ISR去做相应处理。这部分时间,我称之为:识别中断时间。
在允许中断嵌套的实时操作系统中,中断也是基于优先级的,允许高优先级中断抢断正在处理的低优先级中断,所以,如果当前正在处理更高优先级的中断,即使此时有低优先级的中断,也系统不会立刻响应,而是等到高优先级的中断处理完之后,才会响应。而即使在不支持中断嵌套,即中断是没有优先级的,中断是不允许被中断的,所以,如果当前系统正在处理一个中断,而此时另一个中断到来了,系统也是不会立即响应的,而只是等处理完当前的中断之后,才会处理后来的中断。此部分时间,我称其为:等待中断打开时间。
在操作系统中,很多时候我们会主动进入临界段,系统不允许当前状态被中断打断,故而在临界区发生的中断会被挂起,直到退出临界段时候打开中断。此部分时间,我称其为: 关闭中断时间。
中断延迟可以定义为,从中断开始的时刻到中断服务例程开始执行的时刻之间的时间段。中断延迟 = 识别中断时间 + [等待中断打开时间] + [关闭中断时间]。
注意:“[ ]”的时间是不一定都存在的,此处为最大可能的中断延迟时间。
4、 中断管理的应用场景
中断在嵌入式处理器中应用非常之多,没有中断的系统不是一个好系统,因为有中断,才能启动或者停止某件事情,从而转去做另一间事情。 我们可以举一个日常生活中的例子来说明,假如你正在给朋友写信,电话铃响了,这时你放下手中的笔去接电话,通话完毕再继续写信。这个例子就表现了中断及其处理的过程:电话铃声使你暂时中止当前的工作,而去处理更为急需处理的事情——接电话,当把急需处理的事情处理完毕之后,再回过头来继续原来的事情。在这个例子中,电话铃声就可以称为“中断请求”,而你暂停写信去接电话就叫作“中断响应”,那么接电话的过程就是“中断处理”。
由此我们可以看出,在计算机执行程序的过程中,由于出现某个特殊情况(或称为“特殊事件” ),使得系统暂时中止现行程序,而转去执行处理这一特殊事件的程序,处理完毕之后再回到原来程序的中断点继续向下执行。
为什么说没有中断的系统不是好系统呢? 我们可以再举一个例子来说明中断的作用。假设有一个朋友来拜访你,但是由于不知何时到达,你只能在门口等待,于是什么事情也干不了;但如果在门口装一个门铃,你就不必在门口等待而可以在家里去做其他的工作,朋友来了按门铃通知你,这时你才中断手中的工作去开门,这就避免了不必要的等待。
CPU 也是一样,如果时间都浪费在查询的事情上,那这个 CPU 啥也干不了,要他何用。在嵌入式系统中合理利用中断,能更好利用 CPU 的资源。
5、中断管理讲解
ARM Cortex-M 系列内核的中断是由硬件管理的, 而 FreeRTOS 是软件,它并不接管由硬件管理的相关中断(接管简单来说就是,所有的中断都由 RTOS 的软件管理,硬件来了中断时,由软件决定是否响应,可以挂起中断,延迟响应或者不响应) ,只支持简单的开关中断等,所以 FreeRTOS 中的中断使用其实跟裸机差不多的,需要我们自己配置中断,并且使能中断,编写中断服务函数,在中断服务函数中使用内核 IPC 通信机制,一般建议使用信号量、消息或事件标志组等标志事件的发生,将事件发布给处理任务,等退出中断后再由相关处理任务具体处理中断。
用 户 可 以 自 定 义 配 置 系 统 可 管 理 的 最 高 中 断 优 先 级 的 宏 定 义configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY , 它 是 用 于 配 置 内 核 中 的basepri 寄存器的,当 basepri 设置为某个值的时候, NVIC 不会响应比该优先级低的中断,而优先级比之更高的中断则不受影响。就是说当这个宏定义配置为 5 的时候,中断优先级数值在 0、 1、 2、 3、 4 的这些中断是不受 FreeRTOS 屏蔽的, 也就是说即使在系统进入临界段的时候,这些中断也能被触发而不是等到退出临界段的时候才被触发,当然,这些中断服务函数中也不能调用 FreeRTOS 提供的 API 函数接口,而中断优先级在 5 到 15 的这些中断是可以被屏蔽的,也能安全调用 FreeRTOS 提供的 API 函数接口。
ARM Cortex-M NVIC 支持中断嵌套功能:当一个中断触发并且系统进行响应时,处理器硬件会将当前运行的部分上下文寄存器自动压入中断栈中,这部分的寄存器包括 PSR,R0, R1, R2, R3 以及 R12 寄存器。当系统正在服务一个中断时,如果有一个更高优先级的中断触发,那么处理器同样的会打断当前运行的中断服务例程,然后把老的中断服务例程上下文的 PSR, R0, R1, R2, R3 和 R12 寄存器自动保存到中断栈中。这些部分上下文寄存器保存到中断栈的行为完全是硬件行为,这一点是与其他 ARM 处理器最大的区别(以往都需要依赖于软件保存上下文)。
另外,在 ARM Cortex-M 系列处理器上,所有中断都采用中断向量表的方式进行处理,即当一个中断触发时,处理器将直接判定是哪个中断源,然后直接跳转到相应的固定位置进行处理。而在 ARM7、 ARM9 中,一般是先跳转进入 IRQ 入口,然后再由软件进行判断是哪个中断源触发,获得了相对应的中断服务例程入口地址后,再进行后续的中断处理。ARM7、 ARM9 的好处在于,所有中断它们都有统一的入口地址,便于 OS 的统一管理。而ARM Cortex-M 系列处理器则恰恰相反,每个中断服务例程必须排列在一起放在统一的地址上(这个地址必须要设置到 NVIC 的中断向量偏移寄存器中)。中断向量表一般由一个数组定义(或在起始代码中给出),在 STM32 上,默认采用起始代码给出:具体见下列代码清单。
__Vectors DCD __initial_sp ; Top of Stack DCD Reset_Handler ; Reset Handler DCD NMI_Handler ; NMI Handler DCD HardFault_Handler ; Hard Fault Handler DCD MemManage_Handler ; MPU Fault Handler DCD BusFault_Handler ; Bus Fault Handler DCD UsageFault_Handler ; Usage Fault Handler DCD 0 ; Reserved DCD 0 ; Reserved DCD 0 ; Reserved DCD 0 ; Reserved DCD SVC_Handler ; SVCall Handler DCD DebugMon_Handler ; Debug Monitor Handler DCD 0 ; Reserved DCD PendSV_Handler ; PendSV Handler DCD SysTick_Handler ; SysTick Handler ; External Interrupts DCD WWDG_IRQHandler ; Window Watchdog DCD PVD_IRQHandler ; PVD through EXTI Line detect DCD TAMPER_IRQHandler ; Tamper DCD RTC_IRQHandler ; RTC DCD FLASH_IRQHandler ; Flash DCD RCC_IRQHandler ; RCC DCD EXTI0_IRQHandler ; EXTI Line 0 DCD EXTI1_IRQHandler ; EXTI Line 1 DCD EXTI2_IRQHandler ; EXTI Line 2 DCD EXTI3_IRQHandler ; EXTI Line 3 DCD EXTI4_IRQHandler ; EXTI Line 4 DCD DMA1_Channel1_IRQHandler ; DMA1 Channel 1 DCD DMA1_Channel2_IRQHandler ; DMA1 Channel 2 DCD DMA1_Channel3_IRQHandler ; DMA1 Channel 3 DCD DMA1_Channel4_IRQHandler ; DMA1 Channel 4 DCD DMA1_Channel5_IRQHandler ; DMA1 Channel 5 DCD DMA1_Channel6_IRQHandler ; DMA1 Channel 6 DCD DMA1_Channel7_IRQHandler ; DMA1 Channel 7 ………
FreeRTOS 在 Cortex-M 系列处理器上也遵循与裸机中断一致的方法,当用户需要使用自定义的中断服务例程时,只需要定义相同名称的函数覆盖弱化符号即可。所以,FreeRTOS 在 Cortex-M 系列处理器的中断控制其实与裸机没什么差别。