简介
- 当CPU正在处理内部数据时,外界发生了紧急情况,要求CPU暂停当前的工作转去处理这个异步事件。处理完毕后回到原来被中断的地址,继续原来的工作,这样的过程称为中断。
- 实现这一功能的系统称为中断系统,申请CPU中断的请求源称为中断源。
- 中断是一种异常,异常导致处理器脱离正常运行专项执行特殊代码的任何事件,正确处理异常,提高软件鲁棒性非常重要。
- 下图所示为中断示意图:
RT-Thread中断工作机制
中断向量表
- 中断向量表是所有中断处理程序的入口;
- 下图所示是Cortex-M系列的中断处理过程:把一个函数(用户中断服务程序)同一个虚拟中断向量表中的中断向量联系在一起;
- 当中断向量对应中断发生的时候,被挂接的用户中断服务程序就会被调用执行。
在Cortex-M内核上,所有中断都采用中断向量表的方式进行处理,即当一个中断触发时,处理器将直接判定是哪个中断源,然后直接跳转到相应的固定位置进行处理,每个中断服务程序必须排列在一起放在统一的地址上。
中断向量表一般由一个数组定义或在起始代码中给出,默认采用起始代码为:
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset 处理函数
DCD NMI_Handler ; NMI 处理函数
DCD HardFault_Handler ; Hard Fault 处理函数
DCD MemManage_Handler ; MPU Fault 处理函数
DCD BusFault_Handler ; Bus Fault 处理函数
DCD UsageFault_Handler ; Usage Fault 处理函数
DCD 0 ; 保留
DCD 0 ; 保留
DCD 0 ; 保留
DCD 0 ; 保留
DCD SVC_Handler ; SVCall 处理函数
DCD DebugMon_Handler ; Debug Monitor 处理函数
DCD 0 ; 保留
DCD PendSV_Handler ; PendSV 处理函数
DCD SysTick_Handler ; SysTick 处理函数
… …
NMI_Handler PROC
EXPORT NMI_Handler [WEAK]
B .
ENDP
HardFault_Handler PROC
EXPORT HardFault_Handler [WEAK]
B .
ENDP
… …
代码后面的[WEAK]标识,它是符号弱化标识。如果整个代码在链接时遇到名称相同的符号,那么代码将使用未被弱化定义的符号(与NMI_Handler相同名称的函数),而与弱化符号相关的代码将被自动丢弃。
中断处理过程
RT-Thread 中断管理中,将中断处理程序分为中断前导程序、用户中断服务程序、中断后续程序三部分,如下图:
中断前导程序
主要工作包括:
- 保存 CPU 中断现场,这部分跟 CPU 架构相关,不同 CPU 架构的实现方式有差异。
- 通知内核进入中断状态,调用 rt_interrupt_enter() 函数,作用是把全局变量 rt_interrupt_nest 加 1,用它来记录中断嵌套的层数。
用户中断服务程序
在用户中断服务程序(ISR)中,分为两种情况:第一种是不进行线程切换,ISR和中断后续程序运行完毕后退出中断模式,返回被中断的线程;另一种是需要线程切换,会调用 rt_hw_context_switch_interrupt() 函数进行上下文切换,该函数跟 CPU 架构相关,不同 CPU 架构的实现方式有差异。rt_hw_context_switch_interrupt()的函数实现流程如下图所示:
在Cortex-M架构中,
中断后续程序
- 通知内核离开中断状态,通过调用 rt_interrupt_leave() 函数,将全局变量 rt_interrupt_nest 减 1,代码如下所示。
void rt_interrupt_leave(void)
{
rt_base_t level;
level = rt_hw_interrupt_disable();
rt_interrupt_nest --;
rt_hw_interrupt_enable(level);
}
- 恢复中断前的CPU上下文,如果在中断处理过程中未进行线程切换,那么恢复from线程的CPU上下文,如果在中断中进行了线程切换,那么恢复to线程的CPU上下文。
中断嵌套
下图所示为嵌套规则,通俗易懂:
中断栈
在中断处理过程中,在系统响应中断前,软件代码需要把当前线程的上下文保存下来,再调用中断服务程序进行中断响应、处理,这时需要中断栈来保存上下文和运行中断处理函数。
RT-Thread采用的方式是提供独立的中断栈,即中断发生时,中断的前期处理程序会将用户的栈指针更换到系统事先留出的中断栈空间中,等中断退出时再恢复用户的栈指针。
中断的底半处理
对于一些中断,中断服务程序在取得硬件状态或数据以后,还需要进行一系列更耗时的处理过程,通常需要将该中断分割为两部分,即上半部分(Top Half)和底半部分(Bottom Half)。
在上半部分中,取得硬件状态和数据后,打开被屏蔽的中断,给相关线程发送一条通知(可以是 RT-Thread 所提供的信号量、事件、邮箱或消息队列等方式),然后结束中断服务程序;而接下来,相关的线程在接收到通知后,接着对状态或数据进行进一步的处理,这一过程称之为底半处理。
RT-Thread中断管理接口
为了把操作系统和系统底层的异常、中断硬件隔离开来,RT-Thread把中断和异常封装为一组抽象接口,如下图所示:
中断与轮询
- 当驱动外设工作时,其变成模式到底采用中断模式触发还是轮询模式触发往往是驱动开发人员首先要考虑的问题。
- 轮询模式本身采用顺序执行的方式:查询到相应的事件然后进行对应的处理,相应的代码如下。
/* 轮询模式向串口写入数据 */
while (size)
{
/* 判断 UART 外设中数据是否发送完毕 */
while (!(uart->uart_device->SR & USART_FLAG_TXE));
/* 当所有数据发送完毕后,才发送下一个数据 */
uart->uart_device->DR = (*ptr & 0x1FF);
++ptr; --size;
}
在实时操作系统中轮询模式可能会出现非常大的问题,导致它所在的线程一直运行,比它优先级低的线程都不会得到运行。
所以通常,实时操作系统更多采用的是中断模式来驱动外设。当数据达到时,由中断唤醒相关的处理线程,再继续进行后续的动作。
发送数据量越小,发送速度越快,对于数据吞吐量的影响也将越大。归根结底取决于系统中产生中断的频度如何。当一个实时系统想要提升数据吞吐量时,可以考虑的几种方式:
1)增加每次数据量发送的长度,每次尽量让外设尽量多地发送数据;
2)必要情况下更改中断模式为轮询模式。同时为了解决轮询方式一直抢占处理机,其他低优先级线程得不到运行的情况,可以把轮询线程的优先级适当降低。