使能中断与禁止中断策略比较

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);					//开中断
...

以上是关于使能中断与禁止中断策略比较的主要内容,如果未能解决你的问题,请参考以下文章

关于通讯断帧策略

linux 中断

5.2 SW1控制LED1亮灭(中断功能)

对于CPSR寄存器,选择用户模式且使用快速中断FIQ,禁止iqr中断,Thumb状态,则cpsr的

预装装载使能

Linux驱动开发中断