使能中断与禁止中断策略比较
Posted 研究是为了理解
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使能中断与禁止中断策略比较相关的知识,希望对你有一定的参考价值。
本文描述的内容仅适用于
Cortex-M3/M4
架构、Keil MDK
编译环境。
在阅读 RT-Thread 源码和 FreeRTOS 源码时,发现二者使能中断和禁止中断的方式不同,再加上 CMSIS 也提供了使能中断和禁止中断的函数,这些代码有何不同,各自的方法有什么优势所在?
SMSIS
使用以下函数使能全局中断或者禁止全局中断:
void __enable_irq(void); //使能中断
void __disable_irq(void); //禁止中断
查看反汇编,这两个函数分别对应着汇编代码:
CPSIE I ;使能中断
CPSID I ;禁止中断
这两句汇编代码操作的是 PRIMASK
寄存器,CPSIE I
将寄存器清零,CPSID I
设置寄存器为 1 ,该寄存详细描述如下表:
名称 | 功能 |
---|---|
PRIMASK | 只有 1 个位,可读写。 = 0(默认):使能中断 =1:禁止中断 (硬 Fault 和 NMI 异常除外) |
RT-Thread
使用以下函数使能全局中断或者禁止全局中断:
void rt_hw_interrupt_enable(rt_base_t level); //使能中断
rt_base_t rt_hw_interrupt_disable(void); //禁止中断
这两个函数的实现位于 \\rt-thread\\libcpu\\arm\\cortex-m3\\context_rvds.S
中,对于 使能中断:
;/*
; * void rt_hw_interrupt_enable(rt_base_t level);
; */
rt_hw_interrupt_enable PROC
EXPORT rt_hw_interrupt_enable
MSR PRIMASK, r0
BX LR
ENDP
对于 禁止中断:
;/*
; * rt_base_t rt_hw_interrupt_disable();
; */
rt_hw_interrupt_disable PROC
EXPORT rt_hw_interrupt_disable
MRS r0, PRIMASK
CPSID I
BX LR
ENDP
对于禁止中断,先将 PRIMASK
寄存器的内容保存为返回值,再用 CPSID I
指令禁止中断;
对于使能中断,则是将参数 level
写入 PRIMASK
寄存器。
为什么 RT-Thread
不使用 CMSIS
那种方式来使能中断和禁止中断,而是绕了这么一个大圈呢?
答案是为了嵌套!
我们先用 CMSIS
提供的方法写一个嵌套的例子:
...
__disable_irq(); //进入临界区
...
//调用一个函数,在这个函数中也出现了开关中断操作(嵌套)
__disable_irq(); //再次进入临界区,这一步无错误
...
__enable_irq(); //退出临界区,开中断,这步提前打开了中断
//函数调用完毕
...
__enable_irq(); //期望在这里打开中断
...
可见 CMSIS
提供的方法并不能用于嵌套,我们再来看一下 RT-Thread
嵌套的例子:
...
level1 = rt_hw_interrupt_disable(); //进入临界区
...
//调用一个函数,在这个函数中也出现了开关中断操作(嵌套)
level2 = rt_hw_interrupt_disable(); //再次进入临界区
...
rt_hw_interrupt_enable(level2); //因为 level2 保存值为禁止中断,所以中断并没有开启
//函数调用完毕
...
rt_hw_interrupt_enable(level1); //因为 level1 保存值为使能中断,所以到这里才开启中断
...
可见 RT-Thread
提供的方法可以用于中断嵌套。
FreeRTOS
FreeRTOS 并不会关闭所有中断,因为 FreeRTOS 把所有中断分成两部分:FreeRTOS 可以管控的中断和 FreeRTOS 不能管控的中断。
这样区分后,FreeRTOS 允许用户保留一些中断,这些中断不会被 FreeROTS 关闭,从而具有可控、稳定的响应速度。这些中断的优先级要比 FreeRTOS 可以管控的中断优先级高,而且不能在这些中断在中调用任何 FreeRTOS API 函数。FreeRTOS 能管控的最高中断优先级用宏 configMAX_SYSCALL_INTERRUPT_PRIORITY
指定。
所以 FreeRTOS 使能中断和禁止中断的方式与 RT-Thread 有所不同。FreeRTOS 使用一组宏来使能和禁止中断:
taskENABLE_INTERRUPTS() //使能中断
taskDISABLE_INTERRUPTS() //禁止中断
对于使能中断,实际调用函数 vPortSetBASEPRI(0)
,这个函数代码为:
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
__asm
/* Barrier instructions are not used as this function is only used to lower the BASEPRI value. */
msr basepri, ulBASEPRI
相当于将寄存器 BASEPRI
设置为 0 。
0
是寄存器 BASEPRI
的默认值,表示不关闭任何中断,也就是使能中断。该寄存详细描述如下表:
名称 | 功能 |
---|---|
BASEPRI | 最多有 9 个位,可读写,定义被屏蔽优先级的阈值。 = n:优先级号 ≥ n 的中断禁止 注:优先级号越大,优先级越低,n = 0(默认值)表示不关闭任何中断 |
对于禁止中断,实际调用函数 vPortRaiseBASEPRI()
,这个函数代码为:
static portFORCE_INLINE void vPortRaiseBASEPRI( void )
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
__asm
/* Set BASEPRI to the max syscall priority to effect a critical section. */
msr basepri, ulNewBASEPRI
dsb
isb
这个函数将宏 configMAX_SYSCALL_INTERRUPT_PRIORITY
写入寄存器BASEPRI
,表示优先级号 ≥ configMAX_SYSCALL_INTERRUPT_PRIORITY
的中断禁止,而那些优先级号小于此值的,也就是更高优先级的中断不受影响。
很容易分析出,FreeRTOS 提供的使能中断和禁止中断不能嵌套使用,这也意味着它们也不能用于中断服务函数中。原因如下:
- 一旦中断中也使用这些宏,则有嵌套使用的可能,这可能会不正确的提前开启中断;
- 中断不可控,如果使能中断嵌套,则在中断服务函数中也可能出现嵌套
因此,FreeRTOS 提供另外的宏,专用于在中断函数中禁止和使能中断:
portSET_INTERRUPT_MASK_FROM_ISR() //禁止中断
portCLEAR_INTERRUPT_MASK_FROM_ISR(x) //使能中断
其思想和 RT-Thread 相同,只不过使用的是 BASEPRI
寄存器。禁止中断实际调用 ulPortRaiseBASEPRI
函数,代码为:
static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
__asm
/* Set BASEPRI to the max syscall priority to effect a critical section. */
mrs ulReturn, basepri
msr basepri, ulNewBASEPRI
dsb
isb
return ulReturn;
在禁止中断前,先保存 BASEPRI
寄存器的值并返回,也就是记录当时的中断环境。
使能中断实际调用的函数与非中断环境下调用的函数相同,也是 vPortSetBASEPRI(x)
,只不过参数不是 0
,而是禁止中断时保存的那个BASEPRI
寄存器值。
这里举一个可以在中断中调用的 API 函数,其中有这两个宏的用法:
UBaseType_t uxTaskPriorityGetFromISR( const TaskHandle_t xTask )
TCB_t const *pxTCB;
UBaseType_t uxReturn, uxSavedInterruptState;
portASSERT_IF_INTERRUPT_PRIORITY_INVALID();
uxSavedInterruptState = portSET_INTERRUPT_MASK_FROM_ISR(); //禁止中断
/* If null is passed in here then it is the priority of the calling task that is being queried. */
pxTCB = prvGetTCBFromHandle( xTask );
uxReturn = pxTCB->uxPriority;
portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptState ); //使能中断
return uxReturn;
可嵌套的使能和禁止中断方法
实际上,CMSIS 给出了 4 个操作 PRIMASK
寄存器的函数:
void __enable_irq(void); //使能中断
void __disable_irq(void); //禁止中断
uint32_t __get_PRIMASK(void); //读取
void __set_PRIMASK(uint32_t priMask); //设置
这里用后面两个函数演示可以嵌套使能和禁止中断:
#include "core_cm3.h" //包含 CMSIS 头文件
uint32_t level;
...
level = __get_PRIMASK(); //关中断
__disable_irq();
//... //被保护的代码
__set_PRIMASK(levle); //开中断
...
以上是关于使能中断与禁止中断策略比较的主要内容,如果未能解决你的问题,请参考以下文章