文章目录
- 摘要
- 二、FreeRTOS临界区管理使用的理解与源码分析
-
- [2.1、任务级与中断级临界区 屏蔽的中断类型](#2.1、任务级与中断级临界区 屏蔽的中断类型)
摘要
二、FreeRTOS临界区管理使用的理解与源码分析
2.1、任务级与中断级临界区 屏蔽的中断类型
不管是任务级或者是中断级,他们操作的本质都是BASEPRI寄存器,通过该寄存器进行管理。
任务级使用的时候都是配对使用,只不过来说任务级的没有那么复杂,或者说简单、自动一些,那相对来说功能就没有那么复杂。
FreeRTOS 的设计哲学是将中断分为两个明确的阵营:
-
不受管理的中断 (优先级 0-4) :用于处理最紧急的硬件事件(如看门狗、电源故障、高速采集)。这些中断完全不受 FreeRTOS 控制,拥有最高的实时性,但相应地,绝对禁止调用任何 FreeRTOS API。
-
受管理的中断 (优先级 5-15) :用于处理普通外设事务(如串口、定时器)。这些中断可以安全地 调用
xxxFromISR
系列的 FreeRTOS API,但需要接受 FreeRTOS 的调度管理。
taskENTER_CRITICAL_FROM_ISR()
的固定屏蔽阈值 正是这个边界的体现。它的作用是保护 FreeRTOS 内核的数据结构在"受管理中断"的临界区内不被并发访问。而"不受管理的中断"被认为不会也不应该去访问这些资源,因此无需屏蔽它们。
2.1.1、任务级
以实际应用代码为例(一)
c
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
taskENTER_CRITICAL();
{
xInheritanceOccurred = xTaskPriorityInherit( ( void * ) pxQueue->pxMutexHolder );
}
taskEXIT_CRITICAL();
}
可以看出taskENTER_CRITICAL();
和taskEXIT_CRITICAL();
都是成对出现的,一个是进入,一个是离开。
进入:
c
task.h文件(对应的就是task.c文件)
#define taskENTER_CRITICAL() portENTER_CRITICAL()
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
离开:
c
task.h文件(对应的就是task.c文件)
#define taskEXIT_CRITICAL() portEXIT_CRITICAL()
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )
可以看出,只要是FreeRTOS中属于是应用层的,就都是包含task
这个单词,这是因为FreeRTOS处理的都是任务相关的,因此大多数都是包含task
字样,另外一些就是一些信号量的关键字,例如是queue
等。
而驱动层使用的都是port
字样,这是表示封装的意思,也就是为了便于移植的时候使用。例如上述的进入和离开都是又封装了port
,这样的目的就是为了便于兼容不同芯片,因为port
函数下面就是使用宏定义封装的最底层的函数,如下
c
portmacro.h层(对应的就是port.c文件)
#define portENTER_CRITICAL() vPortEnterCritical()
#define portEXIT_CRITICAL() vPortExitCritical()
宏定义的拓展,帮助理解源码中无数个宏定义的思路
其实这个地方不仅仅理解为驱动层,个人可以认为是分成两部分(站在整体的代码的角度,FreeRTOS就是一个内核驱动):
一部分是FreeRTOS驱动层
一部分是FreeRTOS接口层,这就是触及到芯片的最深处,部分代码是通过汇编实现的,或者是进行一些寄存器的内置,寄存器的配置必须也只能是在这个里面实现,部分也就是一些中断函数例如:
这里涉及到宏定义的一些内容在《C语言关键字_慈悲不渡自绝人的博客-CSDN博客》详细阐述
所以FreeRTOS内部实现的中断函数,都是通过宏定义进行对接到不同芯片的实现的。
c
#define vPortSVCHandler SVC_Handler
#define xPortPendSVHandler PendSV_Handler
#define xPortSysTickHandler SysTick_Handler
在FreeRTOS是中都是通过函数名vPortSVCHandler()
,xPortPendSVHandler()
,xPortSysTickHandler()
,进行编写,然后再通过宏定义进行映射。
而函数vPortSVCHandler()
,xPortPendSVHandler()
,xPortSysTickHandler()
,都是在port.c
文件,并且都是通过宏定义进行与硬件实现链接(例如,复位中断的入口是 Reset_Handler
,非屏蔽中断是 NMI_Handler
,而系统滴答定时器(SysTick)中断的入口就是 SysTick_Handler
。这个名称是芯片内核标准所规定的,是硬件唯一能够识别并跳转执行的入口点。)。



以实际应用代码为例(二)
对于任务级的封装进一步阐述
以进入为例:
1、实际应用
c
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
taskENTER_CRITICAL();
{
xInheritanceOccurred = xTaskPriorityInherit( ( void * ) pxQueue->pxMutexHolder );
}
taskEXIT_CRITICAL();
}
2、FreeRTOS任务调用→ → →接口
c
#define taskENTER_CRITICAL() portENTER_CRITICAL()
#define taskEXIT_CRITICAL() portEXIT_CRITICAL()
3、接口→ → →具体的函数实现
c
#define portENTER_CRITICAL() vPortEnterCritical()
#define portEXIT_CRITICAL() vPortExitCritical()
4、具体实现
c
void vPortEnterCritical( void )
{
portDISABLE_INTERRUPTS();
uxCriticalNesting++;
/* This is not the interrupt safe version of the enter critical function so
assert() if it is being called from an interrupt context. Only API
functions that end in "FromISR" can be used in an interrupt. Only assert if
the critical nesting count is 1 to protect against recursive calls if the
assert function also uses a critical section. */
if( uxCriticalNesting == 1 )
{
configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
}
}
void vPortExitCritical( void )
{
configASSERT( uxCriticalNesting );
uxCriticalNesting--;
if( uxCriticalNesting == 0 )
{
portENABLE_INTERRUPTS();
}
}
5、任务级实现也是通过BASEMASK寄存器,所以最终还是需要调用寄存器的设置接口函数
c
#define configASSERT( x ) if( ( x ) == 0 ) { taskDISABLE_INTERRUPTS(); for( ;; ); }
#define taskDISABLE_INTERRUPTS() portDISABLE_INTERRUPTS()
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )

portENTER_CRITICAL()
和 taskENTER_CRITICAL()
是 操作系统层提供的、用户应使用的API,旨在保证代码在不同平台间的可移植性。
只不过在任务中一般使用的都是 taskENTER_CRITICAL()
而接口就是: vPortEnterCritical()
,但是接口里面需要需要关中断,也正是我们的目的,就是关闭一些中断,然后让我们在调用的地方形成一个原子操作,确保不会被打断。这不就是我们现在记笔记的目的就是这句话。

而对于关中断操作,为了移植性,FreeRTOS又进行了封装。
c
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )
c
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
}
}
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
}
}
并且在task.h里面也实现了关中断的操作,不过这些不是用于临界区,而是单纯的关中断。
c
/**
* task. h
*
* Macro to disable all maskable interrupts.
*
* \defgroup taskDISABLE_INTERRUPTS taskDISABLE_INTERRUPTS
* \ingroup SchedulerControl
*/
#define taskDISABLE_INTERRUPTS() portDISABLE_INTERRUPTS()
/**
* task. h
*
* Macro to enable microcontroller interrupts.
*
* \defgroup taskENABLE_INTERRUPTS taskENABLE_INTERRUPTS
* \ingroup SchedulerControl
*/
#define taskENABLE_INTERRUPTS() portENABLE_INTERRUPTS()
是通过调用configASSERT
实现。使用来断言检查
c
#define configASSERT( x ) if ((x) == 0) {taskDISABLE_INTERRUPTS(); for( ;; );}

至此封装层面,以及调用关系理清楚了。
断言检查理解
是通过调用configASSERT
实现。使用来断言检查
c
#define configASSERT( x ) if ((x) == 0) {taskDISABLE_INTERRUPTS(); for( ;; );}

c
#define configASSERT( x ) if ((x) == 0) {taskDISABLE_INTERRUPTS(); for( ;; );}
断言的作用:
用于调试和确保代码正确性 的重要机制,特别是用于防止在中断服务程序(ISR)中错误调用非ISR版本的临界区保护函数。
c
configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
#define configASSERT( x ) if ((x) == 0) {taskDISABLE_INTERRUPTS(); for( ;; );}
这行代码使用 configASSERT
宏来断言(assert)一个条件。如果这个条件不成立(即表达式为假),则会触发断言失败,通常会导致程序停止运行以便开发者调试。
-
portNVIC_INT_CTRL_REG
: 这是 Cortex-M 内核中中断控制状态寄存器**(例如 SCB->ICSR)的宏定义,用于获取当前中断的状态。 -
portVECTACTIVE_MASK
: 这是一个掩码,用于从portNVIC_INT_CTRL_REG
中提取出当前正在活动的中断号**(VECTACTIVE 字段)。 -
( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0
: 这个表达式检查当前活动的中断号是否为 0。在 Cortex-M 内核中,中断号为 0 表示当前没有中断正在执行**(即处于任务上下文)。
因此,这行断言的意思是:确保当前没有中断正在执行(即不在中断上下文)。 如果检测到当前正在处理中断(即表达式不为0),断言就会触发。
这定义了 configASSERT
宏的具体行为。它告诉编译器,当传入的参数 x
为 0(即条件为假)时该怎么做。
-
if ((x) == 0)
**: 判断断言的条件是否不成立(为假)。 -
taskDISABLE_INTERRUPTS();
: 如果断言失败,立即禁用中断**。这是为了防止断言失败后系统进入一种更不可预测的状态,或者中断例程继续访问可能已被破坏的数据。 -
for( ;; );
: 这是一个 无限空循环**,作用是让程序彻底停止在这里(或称"挂起"、"死机")。这保证了在调试时,开发者能立刻发现这个错误,并通过调试器检查当时的程序状态(如调用栈、变量值等),从而定位问题根源。
因此,这整个宏的意思是:如果断言条件 x
不成立,就关中断并死循环。
-
目的 :FreeRTOS 区分了任务级 临界区函数(如
taskENTER_CRITICAL()
)和中断级 临界区函数(如taskENTER_CRITICAL_FROM_ISR()
)。任务级的函数绝对不能在中断服务程序中被调用。 -
检查 :
configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
这行代码就是在执行任务级临界区保护时,检查当前是否正处于中断上下文中。 -
触发 :如果某个中断服务程序错误地 调用了任务级的
taskENTER_CRITICAL()
,这个断言就会检测到当前正在处理中断((portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK)
不等于0),条件为假 ,从而触发configASSERT
中定义的错误处理行为------关闭所有中断并进入死循环。
也就是这个地方的目的就是为了判断在第一次启用任务级临界区保护的时候,需要判断一下,是否是中断在运行,如果是中断在运行,则说明这是错误的。
也就是说我在写的时候不小心将只能在任务中使用的临界区保护函数taskENTER_CRITICAL();
在中断中使用了,那么断言就会检查出当前活跃中断号并不是0,那么就相当于是断言成立,就是此时是中断在调用,那么就意味着调用错误,因为在中断中是不能调用这个函数的,所以就会报错。
在中断服务程序(ISR)中错误地使用了只能在任务中使用的 taskENTER_CRITICAL()
,断言 configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 )
会检测到当前正处于中断上下文中(因为 portVECTACTIVE_MASK
提取出的活跃中断号不为0),于是断言条件不成立 ((x) == 0
),从而触发断言失败,导致程序关中断并死循环。这正好帮你捕捉到了一个严重的编程错误。
在任务级临界区实际管理思想
特性 | 任务级临界区 (taskENTER_CRITICAL() ) |
---|---|
适用场景 | 在任务中保护临界区 |
API 函数 | taskENTER_CRITICAL() / taskEXIT_CRITICAL() |
实现机制 | 通过操作 BASEPRI 寄存器屏蔽低于特定优先级的中断,并通过全局嵌套计数器 (uxCriticalNesting ) 管理嵌套 |
嵌套管理 | 自动管理。全局计数器递增/递减,仅在首次进入时设置BASEPRI,末次退出时恢复 |
安全性 | 禁止在ISR中调用 |
设计初衷 | 简化任务中的临界区编写,开发者无需关心嵌套细节 |
任务级临界区 (taskENTER_CRITICAL()
):
c
void vPortEnterCritical( void )
{
portDISABLE_INTERRUPTS();
uxCriticalNesting++;
/* This is not the interrupt safe version of the enter critical function so
assert() if it is being called from an interrupt context. Only API
functions that end in "FromISR" can be used in an interrupt. Only assert if
the critical nesting count is 1 to protect against recursive calls if the
assert function also uses a critical section. */
if( uxCriticalNesting == 1 )
{
configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
}
}
嵌套计数器是关键 。FreeRTOS 使用一个全局变量 uxCriticalNesting
来记录临界区的嵌套深度。
首次进入 时(uxCriticalNesting
从0变为1),才会真正执行 vPortRaiseBASEPRI()
函数来设置 BASEPRI 寄存器,屏蔽中断。
嵌套进入 时(uxCriticalNesting > 0
),仅仅将 uxCriticalNesting
加一,而不会重复设置 BASEPRI,减少了开销。
退出 时,每次调用 taskEXIT_CRITICAL()
都会将 uxCriticalNesting
减一。只有当嵌套深度归零 (最后一次退出)时,才会调用 vPortSetBASEPRI(0)
将 BASEPRI 清零,全面恢复中断。
仅在首次进入时检查中断上下文 :if( uxCriticalNesting == 1 )
这里的断言 (configASSERT
) 是一个安全检测。它确保这个非中断安全版本 的临界区函数 (vPortEnterCritical
) 不是在中断服务程序 (ISR) 中被调用的(因为 ISR 中应使用 taskENTER_CRITICAL_FROM_ISR()
)。这个检查只在最外层临界区(第一次进入)时做一次,避免了不必要的性能开销。
这是因为如果第一次的时候就是在中断里面,那直接就得报错,如果第一次不是,那么后续很大可能也不会,因为第一次直接就关闭中断了,这样设计就降低了开销。
嵌套计数器的关键角色
嵌套计数器 uxCriticalNesting
的真正重要性体现在退出临界区 的函数 vPortExitCritical()
中:
c
void vPortExitCritical( void )
{
configASSERT( uxCriticalNesting ); // 确保当前嵌套计数大于0
uxCriticalNesting--; // 嵌套深度减1
if( uxCriticalNesting == 0 ) // 只有当退出到最外层时...
{
portENABLE_INTERRUPTS(); // ...才真正重新开启中断
}
}
可以看到,并不是每次调用 taskEXIT_CRITICAL()
都会打开中断 。每次退出只是将嵌套计数器减 1,只有当计数器减到 0,意味着所有层次的临界区都已退出,系统才会安全地恢复中断。
这种"每次进入都关中断,但仅在最外层退出时开中断"的设计,主要是为了确保临界区保护的绝对可靠性:
-
安全性第一:临界区的核心要求是代码执行不可被打断。只要有一层临界区未退出,中断就必须保持关闭。如果只在第一次进入时关中断,内层嵌套退出时就贸然开启中断,会破坏外层临界区的保护,导致数据竞争。
-
简化逻辑 :逻辑清晰且一致。无论当前嵌套深度如何,
taskENTER_CRITICAL()
的行为都是"确保中断是关闭的",而taskEXIT_CRITICAL()
的行为都是"我这一层退出了,如果这是最外层,就开中断"。这种对称性使得代码非常健壮。 -
支持任意嵌套:你可以在函数中任意嵌套使用临界区,而无需担心如何管理中断状态,全部由计数器自动处理。
那是不是说只需要在第一次进入的时候关闭中断就行,后续在进行嵌套不需要关闭,反正已经关闭了。而只有在最后一次退出再打开,是不是也能实现想要的结果?
FreeRTOS选择当前这种"每次进入都关中断,只在最外层退出时开中断"的设计,是经过深思熟虑的,主要是为了绝对可靠和安全。
特性 | 你的设想 (仅第一次关中断) | FreeRTOS 实际实现 (每次进入都关中断) |
---|---|---|
核心逻辑 | 仅在最外层关闭中断,内层假设中断已关 | 每次进入都确保中断处于关闭状态 |
安全性 | 存在风险。假设内层临界区执行时中断已被关闭,但无法保证(例如,可能在其他地方被意外开启) | 绝对安全。不依赖任何外部状态,每次进入都强制执行关中断操作 |
一致性 | 行为依赖于嵌套的层级,不够一致和 predictable | 行为一致且 predictable,无论嵌套多少层,ENTER 就是关中断,EXIT 就是减计数 |
嵌套管理 | 仍需一个计数器来跟踪嵌套深度,以决定何时打开中断 | 使用 uxCriticalNesting 计数器完美管理嵌套深度 |
潜在问题 | 如果在临界区嵌套过程中,一个不受控的外部操作 或错误代码打开了中断,会导致内层临界区保护失效,数据可能被破坏。这种Bug极难追踪。 | 从根本上杜绝了上述可能性。即使中断在进入临界区前因意外已处于打开状态,ENTER 也能立刻将其关闭,确保保护立即生效。 |
-
确保保护的绝对可靠性(最重要)
临界区的核心要求是独占访问 。当前设计保证只要有一层临界区未退出,中断就一定处于关闭状态 。它不依赖于任何先决条件(比如"我相信在我进入之前中断肯定是关的"),而是主动确保所需状态。这是一种非常健壮和防御性的编程思想。
-
行为一致,简单清晰
无论当前嵌套深度如何,
taskENTER_CRITICAL()
的行为都是"确保中断是关闭的,并增加嵌套计数",而taskEXIT_CRITICAL()
的行为都是"减少嵌套计数,如果这是最外层,就开中断"。这种对称和一致的行为使得代码逻辑非常清晰,易于理解和维护。 -
简化嵌套逻辑
你无需关心当前是在第几层嵌套里,也无需担心内层临界区是否会错误地改变外层的中断状态。你只需要严格遵守"成对使用"的原则,嵌套管理完全由
uxCriticalNesting
计数器自动、可靠地处理。 -
便于调试和断言
正如我们之前讨论的,断言
configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 )
只在uxCriticalNesting == 1
(即第一次进入)时检查。如果在第一次进入时发现中断上下文,就能立即报错。如果只在第一次关中断,那么这个断言检查的时机和意义就会变得复杂。
FreeRTOS选择了性能上可能略有冗余(但现代处理器中开关中断指令通常非常快),但在稳定性和可靠性上万无一失的方案。
FreeRTOS的设计是为了应对"复杂且可能出错的现实情况"。每次都强制关闭中断是保证临界区机制在任何嵌套情况下都绝对可靠的基石。
实用建议:
-
严格成对使用 :确保每一个
taskENTER_CRITICAL()
都对应一个taskEXIT_CRITICAL()
。 -
保持临界区短小精悍:由于它会关闭中断,所以里面的代码执行时间应尽可能短,以免影响系统的实时响应能力。
-
在中断中使用正确版本 :切记在中断服务程序(ISR)中使用
taskENTER_CRITICAL_FROM_ISR()
和taskEXIT_CRITICAL_FROM_ISR()
。
2.1.2、中断级
特性 | 任务级临界区 (taskENTER_CRITICAL() ) |
中断级临界区 (taskENTER_CRITICAL_FROM_ISR() ) |
---|---|---|
适用场景 | 在任务中保护临界区 | 在中断服务程序(ISR)中保护临界区 |
嵌套管理 | 自动管理 。通过全局计数器 (uxCriticalNesting ) 记录嵌套深度,只有最后一次退出才恢复中断 |
手动保存状态。需用户提供变量保存进入前的状态,并在退出时传入以精确恢复 |
安全性 | 禁止在ISR中调用 | 专为ISR设计,可在允许嵌套中断的端口上安全使用 |
设计初衷 | 简化任务中的临界区编写,开发者无需关心嵌套细节 | 为ISR提供安全且可嵌套的临界区保护机制 |
中断临界区保护什么情况下使用
在 FreeRTOS 中,中断临界区保护 主要用于需要防止中断服务程序(ISR)打断当前代码执行的场景,其核心目的是保护那些会被任务和中断服务程序共享的资源,确保操作的原子性(即不可分割性)。
场景分类 | 具体情形 | 保护目标 | 常用API |
---|---|---|---|
任务与ISR共享资源 | 任务和中断服务程序共同读写一个全局变量、缓冲区或数据结构 | 防止数据竞争,确保数据一致性 | taskENTER_CRITICAL_FROM_ISR() / taskEXIT_CRITICAL_FROM_ISR() |
访问硬件外设 | 操作特定的硬件寄存器(如串口数据寄存器)、读取传感器数据等 | 确保硬件访问的时序完整,防止配置被意外修改 | taskENTER_CRITICAL_FROM_ISR() / taskEXIT_CRITICAL_FROM_ISR() |
执行原子操作 | 执行必须一次性完成的操作,如更新状态标志、读写非对齐数据 | 确保操作过程不被中断,避免中间状态被破坏 | taskENTER_CRITICAL_FROM_ISR() / taskEXIT_CRITICAL_FROM_ISR() |
仅在ISR内部 | 在中断服务程序内部需要保护共享资源或执行原子操作时 | 防止被更高优先级的中断打断,保护ISR内部的执行流 | taskENTER_CRITICAL_FROM_ISR() / taskEXIT_CRITICAL_FROM_ISR() |
-
中断级临界区 (
FROM_ISR
版本) :用在中断服务程序(ISR) 内部。它通过操作BASEPRI
等寄存器屏蔽特定优先级及以下的中断 ,但不影响任务调度 。它不支持嵌套 ,并且必须使用保存和恢复状态值的方式成对使用。 -
任务级临界区 :用在任务 中。它主要禁止任务调度 (防止任务被抢占),但通常不会屏蔽所有中断 (高优先级中断仍可响应)。它支持嵌套,内部通过嵌套计数器管理。
当你需要在 中断服务程序内部 保护与任务共享的资源或硬件寄存器,并确保操作不被其他中断打断时,就应该使用中断临界区保护。
当你处于一个低优先级中断(例如优先级6)的临界区内时,一个高优先级中断(例如优先级3)发生了:
-
硬件自动处理现场 :CPU 会自动保存当前中断(优先级6)的上下文(如寄存器值),然后去执行高优先级中断(优先级3)的服务程序。这是硬件机制,无需软件干预。
-
高优先级ISR执行 :高优先级 ISR 会完成它自己的任务。正如表格所述,它不能调用任何FreeRTOS API ,并且需要尽快执行完毕。
-
硬件自动恢复现场 :当高优先级 ISR 执行完毕退出后,CPU 会自动恢复 之前保存的上下文,程序流程返回到之前被打断的低优先级中断的临界区精确继续执行,就像什么都没发生过一样。
关键在于 :虽然高优先级中断可以打断 低优先级中断的临界区执行流,但只要高优先级中断不访问被打断临界区所保护的共享资源,就不会造成数据混乱。FreeRTOS 通过固定的优先级边界和编程规范(强制高优先级中断不调用RTOS API)来确保这一点。
特性 | 中断级临界区的行为 |
---|---|
嵌套原则 | 允许硬件优先级嵌套:更高优先级的中断(通常是 0-4)可以打断处于临界区的低优先级中断(5-15) |
保护机制 | 基于优先级屏蔽 :通过设置 BASEPRI 寄存器,仅屏蔽优先级数值 ≥ configMAX_SYSCALL_INTERRUPT_PRIORITY (例如5)的中断。更高优先级的中断(0-4)不受影响,可以正常打断。 |
状态管理 | 现场自动保存与恢复 :当更高优先级中断发生时,CPU硬件会自动保存当前中断的上下文(包括临界区状态);高优先级中断执行完毕后,硬件会自动恢复现场,让被中断的临界区代码无缝地继续执行,就像从未被打断过一样。 |
数据安全 | 关键数据不受影响 :只要高优先级中断不访问被低优先级临界区保护的共享资源,就不会引发数据竞争或不一致问题。 |
设计精髓 | 在安全与实时间取得平衡 :此机制既保证了临界区内共享资源的安全(通过屏蔽同级及更低优先级中断),又确保了系统对最紧急事件(高优先级中断)的实时响应能力。 |
为什么要这么写以及为什么要这么恢复?
c
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;
}
-
中断屏蔽的粒度 :FreeRTOS 通过操作 Cortex-M 处理器的 BASEPRI 寄存器 来屏蔽中断。它允许设置一个优先级阈值 (由
configMAX_SYSCALL_INTERRUPT_PRIORITY
定义)。所有优先级数值大于等于(即逻辑优先级低于或等于)该阈值的中断会被屏蔽。这意味着非常高优先级的中断(如硬件故障,优先级数值通常很低)仍然能被响应,保证了系统的实时性。 -
状态保存的必要性 :ISR 可能在任何上下文中被调用,并且中断本身也可能嵌套。直接开关中断而不考虑当前状态,极易错误地提前打开中断或破坏其他部分的中断屏蔽状态。保存和恢复机制确保了临界区操作是可重入 且安全的。
-
中断级临界区不支持嵌套 :这一点与任务级临界区不同。确保在同一个 ISR 中
taskENTER_CRITICAL_FROM_ISR()
和taskEXIT_CRITICAL_FROM_ISR()
严格成对使用。
虽然中断级临界区不支持嵌套 (即不可在内部再次调用),但它通过 ulPortRaiseBASEPRI()
函数保存进入前的状态 ,并在退出时通过 taskEXIT_CRITICAL_FROM_ISR(uxSavedInterruptStatus)
精确恢复 。这确保了中断屏蔽状态不会被错误地提前恢复,避免了手动管理状态的复杂性。

C语言中中断的嵌套并不直接依赖BASEPRI
寄存器 来实现其硬件机制,但 BASEPRI
是实现可控、可预测的中断嵌套 和保护临界区的核心工具。它允许高优先级中断嵌套低优先级中断,同时又能临时屏蔽那些不应该打扰关键代码的低优先级中断。
既然屏蔽了FreeRTOS 管理范围内的中断(优先级 5-15),而更高优先级中断的硬件嵌套(优先级数值 0-4)又不会调用FreeRTOSAPI,那为啥还要恢复原本的BASEPRI之前保存当前值?
尽管高优先级中断(IRQ-H)不会使用 FreeRTOS API,但 taskENTER_CRITICAL_FROM_ISR()
的职责并非预测中断的行为,而是无条件地保证执行环境的原子性和状态完整性。其核心逻辑是:
-
职责单一性原则 :
taskENTER_CRITICAL_FROM_ISR()
的核心职责是"进入临界区并返回之前的状态 "。它不关心也不会预测调用它的中断是否会被更高优先级中断嵌套,或者嵌套者会做什么。它的任务就是精确地记录进入前的状态 ,并相信配对的taskEXIT_CRITICAL_FROM_ISR()
会利用这个状态进行精确恢复。这是一种确保行为一致的契约式设计。 -
防范未知与错误 :虽然高优先级中断的 ISR 不应该 调用 FreeRTOS API,但软件可能存在未知的 Bug。如果某个高优先级 ISR 错误地调用了 FreeRTOS API,而没有保存/恢复机制,它会直接破坏底层中断环境。保存和恢复机制为这种潜在的错误提供了最后一道保护屏障,确保了最坏情况下也不会破坏外层中断的状态。
-
统一的设计模式 :FreeRTOS 为中断临界区提供了一套统一的、可靠的编程模型。无论你在何种优先级的中断中使用
FROM_ISR
函数,其行为都是一致的:保存状态 -> 屏蔽中断 -> 执行代码 -> 恢复状态。这种一致性简化了程序员的理解和使用,无需为不同场景记忆不同的规则。
-
核心目的 :是为了保证中断级临界区操作的原子性和状态完整性,确保任何时候都能安全、正确地恢复现场。
-
设计哲学 :这是一种防御性编程 和契约式设计。它建立了一个坚固的基座,确保无论外部(更高优先级中断)如何变化,内核自身的状态管理都是稳健和可靠的。
防御性编程 和契约式设计
一些发散思路
1、在执行该任务的时候,第一次进行了临界保护区,但是在执行到某一行的时候又触发了临界保护区,这样就相当于是进行了两次临界保护区。但是中断的嵌套,是因为中断,而我屏蔽了一些中断,只能依靠那些不会调用FreeRTOS的中断进行嵌套。
特性 | 任务内临界区嵌套 | 中断嵌套 |
---|---|---|
触发机制 | 软件主动调用 :由任务代码中多次调用 taskENTER_CRITICAL() 引起 |
硬件事件触发:由更高优先级的中断源发生引起 |
核心驱动 | 嵌套计数器 (uxCriticalNesting ) |
中断优先级 (NVIC硬件管理) |
嵌套条件 | 任何情况下,任务内可多次进入临界区 | 必须是更高优先级(数值更小)的中断才能嵌套 |
行为表现 | 维持中断关闭状态,仅递增递减计数器,在最外层退出时才打开中断 | 高优先级ISR立即执行,硬件自动保存和恢复现场 |
-
任务内的临界区嵌套(软件行为):
你在任务中第一次调用
taskENTER_CRITICAL()
,内核会关闭中断(如设置BASEPRI)并将嵌套计数器uxCriticalNesting
从0加到1。在临界区内再次调用
taskENTER_CRITICAL()
,并不会再次执行关闭中断的操作 (因为中断已经关了),仅仅是将嵌套计数器uxCriticalNesting
从1增加到2 。这保证了无论嵌套多少层,只在最外层(计数器从1减到0时)才真正打开中断。 -
中断嵌套(硬件行为):
中断嵌套的发生完全不依赖于任务是否处于临界区。
它的唯一条件是:发生的中断优先级高于当前CPU执行流程的优先级(无论是任务还是中断服务例程)。
由于你在任务临界区中通过设置BASEPRI屏蔽了优先级数值高于某一阈值(如5)的中断 ,所以此时只有那些优先级更高(数值低于或等于4)、不受FreeRTOS管理的中断 才能打断当前任务(包括其临界区代码)。这些中断服务程序(ISR)不能调用任何FreeRTOS API。
任务的临界区嵌套 是一个软件逻辑 概念,通过计数器管理,保证了开关中断的成对性,它决定了中断何时被打开。
中断嵌套 是一个硬件特性 ,由中断优先级决定,它决定了当前执行的代码能否被以及被谁打断。
如果觉得我的内容对您有帮助,希望不要吝啬您的赞和关注,您的赞和关注是我更新优质内容的最大动力。
专栏介绍
《嵌入式通信协议解析专栏》
《PID算法专栏》
《C语言指针专栏》
《单片机嵌入式软件相关知识》
《FreeRTOS源码理解专栏》
《嵌入式软件分层架构的设计原理与实践验证》
文章源码获取方式:
如果您对本文的源码感兴趣,欢迎在评论区留下您的邮箱地址。我会在空闲时间整理相关代码,并通过邮件发送给您。由于个人时间有限,发送可能会有一定延迟,请您耐心等待。同时,建议您在评论时注明具体的需求或问题,以便我更好地为您提供针对性的帮助。
【版权声明】
本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议。这意味着您可以自由地共享(复制、分发)和改编(修改、转换)本文内容,但必须遵守以下条件:
署名:您必须注明原作者(即本文博主)的姓名,并提供指向原文的链接。
相同方式共享:如果您基于本文创作了新的内容,必须使用相同的 CC 4.0 BY-SA 协议进行发布。
感谢您的理解与支持!如果您有任何疑问或需要进一步协助,请随时在评论区留言,笔者一定知无不言,言无不尽。