FreeRTOS临界区管理使用中断的思路(一)
文章目录
- 摘要
- 一、临界区与中断(PRIMASK、FAULTMASK、BASEMASK)
- 1.1、相关寄存器
- PRIMASK
- FAULTMASK
- BASEMASK
- 1.2、临界区数据,为什么要用临界区?
- 高优先级中断为何破坏临界区?
- FreeRTOS如何管理中断?
- 1.3、临界区管理的核心
- 通过管理中断实现管理临界区
摘要
一、临界区与中断(PRIMASK、FAULTMASK、BASEMASK)
1.1、相关寄存器
PRIMASK
它的主要作用是快速全局地开启或关闭所有可屏蔽的中断,用于保护对执行时序有严格要求的核心代码段(临界区)。
特性 | 说明 |
---|---|
寄存器类型 | ARM Cortex-M 内核中的特殊功能寄存器 |
位宽与有效位 | 32位寄存器,但仅最低位(bit[0])有效,其他位保留且必须写0 |
功能 | Bit[0] = 1: 屏蔽(关闭)除 NMI(不可屏蔽中断) 和 HardFault(硬故障) 外的所有可屏蔽中断。 Bit[0] = 0: 使能(开放)所有中断(正常操作模式)。 |
默认值 | 复位后默认为 0(中断使能状态) |
访问方式 | 需通过专用的 MSR/MRS 汇编指令或 CPS 指令进行访问 |
PRIMASK 就像一个总开关,可以快速关闭大部分中断,为关键代码提供保护。
-
启用中断(PRIMASK=0):这是处理器的正常状态,所有可配置优先级的中断都可以根据其优先级规则被响应。
-
禁用中断(PRIMASK=1):当设置 PRIMASK 为 1 时,几乎所有通过 IRQ 线申请的中断都会被处理器忽略。只有那些非常关键、无法被屏蔽的异常(如 NMI 和 HardFault)才能被响应。
-
保持临界区短小精悍:在 PRIMASK 置 1 期间,系统无法响应绝大多数外部事件(如串口数据、定时器溢出等)。长时间关中断会导致中断延迟增加,可能丢失中断信号或影响系统实时性。临界区内只做最必要的操作。
-
NMI 和 HardFault 不可屏蔽:务必记住,NMI (不可屏蔽中断) 和 HardFault (硬故障) 不受 PRIMASK 影响。它们用于处理最严重的系统错误(如电源故障、存储器访问错误等),拥有最高的优先级。
-
与 BASEPRI 的区别:Cortex-M 还提供了另一个中断屏蔽寄存器 BASEPRI。BASEPRI 可以屏蔽低于某个特定优先级的中断,而允许高于该阈值的中断继续响应,提供了比 PRIMASK 更精细的中断控制粒度。FreeRTOS 等操作系统常利用 BASEPRI 来保护其内核临界区。
-
故障调试:若在调试时发现系统频繁进入 HardFault,检查是否在临界区内执行了可能引发故障(如访问非法地址)的操作,因为此时大部分中断无法响应,故障来源不易定位。
PRIMASK 是 ARM Cortex-M 处理器中一个用于快速全局禁用中断的特殊寄存器。它通过单一比特控制,是创建临界区、保护关键代码段最直接和彻底的方法。使用时务必快进快出,并理解其无法屏蔽 NMI 和 HardFault 的特性。
FAULTMASK
FAULTMASK 是 ARM Cortex-M 系列处理器中一个用于全面屏蔽异常(包括中断和故障)的特殊功能寄存器。它的核心作用是暂时提升处理器的可执行异常优先级至 -1,从而阻止绝大多数异常响应,为处理严重错误或执行关键任务提供安全环境。
特性 | 说明 |
---|---|
寄存器类型 | ARM Cortex-M 内核中的1位特殊功能寄存器 |
功能 | Bit[0] = 1: 屏蔽(关闭)除 NMI(不可屏蔽中断) 外的所有异常(包括中断和 fault)。 Bit[0] = 0: 正常响应所有异常(默认状态)。 |
默认值 | 复位后默认为 0(异常使能状态) |
访问权限 | 仅在特权级模式下可写 |
重要特性 | 在退出异常处理程序(NMI 除外)时,硬件会自动清零(即退出后自动恢复异常响应)。 |
FAULTMASK 就像一个更强大的“总开关”。当设置为 1 时,除了最高优先级的 NMI (不可屏蔽中断),所有其他异常和中断都会被处理器忽略。这包括所有的外部中断(IRQs)和系统异常(如 HardFault, MemManage, BusFault, UsageFault, SVCall 等)。
FAULTMASK 的使用场景相对特殊,主要由操作系统或深度嵌入式系统在处理严重错误时使用:
-
操作系统中的故障恢复
当一个任务崩溃并触发大量 faults(例如,连续的内存访问错误)时,操作系统可能会使用 FAULTMASK 来暂时禁用 fault 处理机制。这样,系统可以在清理崩溃任务(如删除其资源、进行错误报告)的过程中,避免因持续触发 fault 而无法完成清理工作。
-
极端的临界区保护
在某些对时序要求极其苛刻的场合(例如,访问特定关键硬件寄存器或执行极短的低功耗模式切换序列),如果需要绝对保证不被任何异常(包括 HardFault)打断,可能会短暂使用 FAULTMASK。但这种情况非常罕见,通常会有更优的设计来避免。
-
谨慎使用:FAULTMASK 会阻断几乎所有异常,包括那些用于指示严重硬件错误的 HardFault。滥用它可能导致系统对真正的硬件故障失去响应,从而使调试变得极其困难,甚至导致系统行为异常。
-
自动清零:FAULTMASK 会在退出异常处理程序(NMI 除外)时由硬件自动清零。这意味着在异常服务例程(ISR)中设置 FAULTMASK=1 后,无需在退出 ISR 前手动清除它。
特性 | PRIMASK | FAULTMASK |
---|---|---|
屏蔽范围 | 所有可屏蔽中断(除NMI和HardFault) | 所有异常(除NMI) |
对 HardFault 的影响 | 不影响 HardFault 响应 | 屏蔽 HardFault |
自动清零 | 不会自动清零 | 退出异常时自动清零 |
常见用途 | 保护短小的临界区 | 操作系统进行故障恢复 |
FAULTMASK 是 ARM Cortex-M 处理器中一个功能强大但需谨慎使用的特殊寄存器。它通过将处理器可执行异常优先级提升至 -1,能够屏蔽除 NMI 外的所有异常,包括 HardFault。其主要应用场景是操作系统在处理任务崩溃等严重错误时,用于临时禁用故障处理机制以便顺利清理现场。在绝大多数应用程序开发中,你通常不会直接使用它,但对于理解系统底层机制和进行系统级编程至关重要。
BASEMASK
在 FreeRTOS 中,BASEPRI
是 ARM Cortex-M 内核的一个中断屏蔽寄存器。FreeRTOS 通过操作它来实现一种可配置的、精细化的中断管理机制,这比完全关闭中断(类似使用 PRIMASK)更能保证系统的实时性。它的核心作用是设置一个中断优先级阈值,只有优先级高于(注意:数值低于)某个特定值的中断才不会被打断。
特性 | 说明 |
---|---|
寄存器功能 | ARM Cortex-M 内核的中断屏蔽寄存器,用于设置一个优先级阈值。 |
FreeRTOS 中的操作 | 通过将其设置为 configMAX_SYSCALL_INTERRUPT_PRIORITY 对应的值来屏蔽中断。 |
屏蔽规则 | 所有优先级数值 ≥ BASEPRI 设定值的中断会被屏蔽(即禁用)。优先级数值比设定值更高的中断(即优先级更高,数值更小)不受影响,仍可响应。 |
恢复中断 | 将 BASEPRI 设置为 0,则解除屏蔽(不关闭任何中断)。 |
核心优势 | 选择性屏蔽:仅屏蔽可能调用 FreeRTOS API 的、“不重要”的中断,而让非常紧急的高优先级中断(如电机控制、故障检测)依然能够立即响应,保证了系统的实时性。 |
FreeRTOS 通过配置 configMAX_SYSCALL_INTERRUPT_PRIORITY
这个宏来决定 BASEPRI 的值。
-
中断优先级划分:
-
高于
configMAX...
的中断(优先级数值更小):这些中断不受 FreeRTOS 管理。它们在任何时候都能打断内核代码和临界区,但不能调用任何以FromISR
结尾之外的 FreeRTOS API 函数。 -
低于等于
configMAX...
的中断(优先级数值更大):这些中断受 FreeRTOS 管理。它们可以被 BASEPRI 机制屏蔽,但可以安全地调用FromISR
版本的 FreeRTOS API 函数。
-
-
操作方式:
-
关中断(设置阈值):
vPortRaiseBASEPRI()
函数会将 BASEPRI 设置为configMAX_SYSCALL_INTERRUPT_PRIORITY
的值。例如,若此值设为 5,则所有优先级数值 ≥5 的中断都会被屏蔽。 -
开中断(清除阈值):
vPortSetBASEPRI(0)
函数将 BASEPRI 设为 0,意味着“不屏蔽任何中断”。
-
FreeRTOS 通过封装 BASEPRI 的操作来保护临界区代码(Critical Sections),即那些必须完整执行、不能被打断的代码段(如操作链表、读写全局变量等)。
它主要提供两对宏:
任务级临界区保护(不能在中断服务例程中使用):
这对宏通过一个计数器 uxCriticalNesting
支持嵌套使用,只有在最外层退出时才会真正打开中断。
taskENTER_CRITICAL(); // 内部设置 BASEPRI,屏蔽低优先级中断
// ... 你的临界区代码 ...
taskEXIT_CRITICAL(); // 内部将 BASEPRI 设为 0,恢复中断
中断安全级临界区保护(专用于中断服务例程 ISR):
这对宏的特点是带返回值,用于保存和恢复之前的中断状态,从而支持在中断中的嵌套调用。
UBaseType_t uxSavedInterruptStatus;
uxSavedInterruptStatus = taskENTER_CRITICAL_FROM_ISR(); // 保存当前状态并设置 BASEPRI
// ... 中断中的临界区代码 ...
taskEXIT_CRITICAL_FROM_ISR( uxSavedInterruptStatus ); // 恢复之前的状态
1.2、临界区数据,为什么要用临界区?
临界区保护的共享资源,最主要、最核心的部分就是内核数据。
内核数据指的是维持 FreeRTOS 正常运行所必需的核心数据结构,主要包括:
-
任务调度器状态:例如当前运行的任务指针、就绪任务链表等。
-
任务控制块 (TCB):每个任务的控制块,包含了任务状态、优先级、堆栈指针等关键信息。
-
内核对象:如队列(Queue)、信号量(Semaphore)、互斥量(Mutex)、定时器(Timer)等内部的数据结构。
临界区就是一段访问这些共享内核资源的代码,这段代码的执行必须具有原子性(即不被中断地完整执行)。它的作用是确保一个任务在修改内核数据时,不会被其他任务或可管理的中断打断,从而保证数据一致性。所以如果被高优先级中断破坏了这种保护机制,直接导致了内核数据的损坏。
高优先级中断为何破坏临界区?
FreeRTOS 通过配置 configMAX_SYSCALL_INTERRUPT_PRIORITY
(例如数值 5)来管理中断。其核心机制是设置 ARM Cortex-M 的 BASEPRI
寄存器。
-
对于优先级数值 ≥ 5 的中断:当任务进入临界区时,FreeRTOS 会设置
BASEPRI = 5
。这会屏蔽所有优先级数值 ≥ 5 的中断(逻辑优先级更低的中断)。这些被屏蔽的中断无法触发,因此它们不会在临界区内操作内核资源,从而保证了安全。 -
对于优先级数值 < 5 的中断:这些是更高逻辑优先级的中断。
BASEPRI
寄存器无法屏蔽它们。这意味着,即使任务正处于临界区,正在操作内核数据,这些高优先级中断也能立即抢占当前 CPU 的执行权。
破坏就发生在这个抢占的时刻:
-
任务正在临界区内操作内核数据(如修改任务队列),操作到一半。
-
一个优先级 < 5 的中断发生,立即抢占。
-
该中断的服务程序 (ISR) 错误地调用了一个 FreeRTOS API(如
xQueueSendFromISR
)。 -
该 API 也试图操作同一个内核数据(如同一个任务队列)。
-
此时内核数据结构处于一种中间状态(不一致),而非完整的最终状态。ISR 的访问和修改会破坏这种一致性,导致数据损坏、系统崩溃等未定义行为。
这就像一位会计正在仔细填写一张复杂的表格(操作内核数据),刚填到一半,一位经理(高优先级中断)突然冲进来,抢过笔就在表格上乱画(调用API操作同一数据),表格自然就作废了,甚至可能引发更严重的问题。
特性 | 能使用 FreeRTOS API 的中断 | 不能使用 FreeRTOS API 的中断 |
---|---|---|
中断优先级数值 (NVIC) | 较高 (例如 5-15) | 较低 (例如 0-4) |
逻辑优先级 | 较低 | 较高 |
FreeRTOS 管理 | 可管理 (受 FreeRTOS 控制) | 不可管理 (不受 FreeRTOS 控制) |
API 调用权限 | 允许且必须使用 FromISR 后缀的 API | 禁止使用任何 FreeRTOS API (包括 FromISR 版本) |
临界区是否屏蔽 | 会被 FreeRTOS 的临界区屏蔽 | 不会被 FreeRTOS 的临界区屏蔽 |
典型应用 | 普通外设通信 (如 UART, I2C) | 系统关键操作 (如紧急故障、高速实时采样) |
FreeRTOS如何管理中断?
也就是说有一些中断是能被FreeRTOS管理的,怎么管理呐,那不就是通过BASEMASK寄存器管理?
BASEPRI
是一个中断屏蔽寄存器。它的工作规则是:屏蔽所有优先级数值大于或等于(即逻辑优先级低于或等于)BASEPRI
寄存器所设值的中断。
FreeRTOS 通过 configMAX_SYSCALL_INTERRUPT_PRIORITY
这个宏来计算最终写入 BASEPRI
的值(例如 0x50
,代表优先级阈值 5)。当任务进入临界区(Critical Section)时,FreeRTOS 会调用类似 vPortRaiseBASEPRI()
的函数,将 BASEPRI
设置为这个阈值。之后:
-
所有 优先级数值 ≥ 5 的中断(即可管理中断)会被屏蔽。
-
所有 优先级数值 < 5 的中断(即不可管理中断)不受影响,依然可以立即响应。
退出临界区时,FreeRTOS 会将 BASEPRI
恢复为 0,解除对所有中断的屏蔽。
FreeRTOS 正是通过配置 ARM Cortex-M 内核的 BASEPRI(Base Priority Mask)寄存器,来实现对部分中断的选择性屏蔽,从而达到管理中断的目的。这背后的设计哲学是在系统实时性和内核数据安全性之间取得精妙的平衡。
FreeRTOS管理的精髓在于选择性屏蔽。当内核需要操作临界数据(如任务队列、信号量)时,它会通过类似 vPortRaiseBASEPRI()
的函数设置 BASEPRI
寄存器。例如,若 configMAX_SYSCALL_INTERRUPT_PRIORITY
对应数值5,则设置 BASEPRI = 0x50
,这会屏蔽优先级数值≥5的所有中断。
static portFORCE_INLINE void vPortRaiseBASEPRI( void ){
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;__asm{/* Set BASEPRI to the max syscall priority to effect a criticalsection. */msr basepri, ulNewBASEPRIdsbisb}
}
FreeRTOS 通过 BASEPRI
寄存器设置一个阈值。优先级低于此阈值的中断(即可管理中断)归 FreeRTOS 管,内核可以在需要时屏蔽它们以保护自己;优先级高于此阈值的中断(即不可管理中断)FreeRTOS 不管,它们拥有最高的实时性,但开发者需确保其 ISR 短小且不调用内核 API。
1.3、临界区管理的核心
通过管理中断实现管理临界区
在FreeRTOS中,configMAX_SYSCALL_INTERRUPT_PRIORITY
的配置是中断管理的核心,其作用是 划分可安全调用FreeRTOS API的中断范围,并 控制临界区的中断屏蔽行为。
主要利用的是BASEPRI
寄存器,
#define configPRIO_BITS 4 #define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY: 0000 0101 相当于是左移4个也就是: 0101 0000
其中configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
表示的是FreeRTOS管理的最高优先级。用户应用程序可以配置和使用的任务优先级的最大数值。这个上限值由FreeRTOS内核的配置文件 FreeRTOSConfig.h
中的宏 configMAX_PRIORITIES
定义。
(先认为优先级是015,然后本次屏蔽的是515,但是就算低四位也是属于抢占优先级,也无所谓只需要增加对应数就行了,本质是一样的,就是FreeRTOS针对一部分的高优先级中断是不能管理的。这个是核心,而这一部分只是设置能管理那些,不能管理那些。)
(这一部分是FreeRTOS中任务优先级的内容,属于是乱入。)
特性 | 说明 | 参考配置建议 |
---|---|---|
配置宏 | configMAX_PRIORITIES | 在 FreeRTOSConfig.h 文件中定义 |
可用优先级范围 | 0 到 configMAX_PRIORITIES - 1 | 例如设为5,则可用优先级为0,1,2,3,4 |
优先级数值意义 | 数值越小,优先级越低 | 空闲任务优先级为0 |
数值越大,优先级越高 | 最高可用优先级为 configMAX_PRIORITIES - 1 | |
优化选项的影响 | 若使能 configUSE_PORT_OPTIMISED_TASK_SELECTION (通常为1以优化性能),则 configMAX_PRIORITIES 不能超过32 | 出于RAM使用效率考虑,建议不超过32,并设置为实际需要的最小值 |
若不使用优化(通用C函数方法),则优先级数量无此限制(但仍不宜过大) | ||
调度器行为 | FreeRTOS调度器总是保证当前能够运行的、优先级最高的任务进入运行状态 | 高优先级任务可抢占低优先级任务 |
-
任务优先级:由FreeRTOS内核管理,遵循数值越大,优先级越高的规则,用于调度任务。
-
中断优先级:由微控制器硬件(如STM32的NVIC)管理,遵循数值越小,优先级越高的规则。中断优先级永远高于任何任务的优先级,即中断可以打断任务。
-
合理设置上限:根据你的项目复杂度,在
FreeRTOSConfig.h
中为configMAX_PRIORITIES
设置一个足够用但又不过大的值。通常建议不超过32。 -
规划优先级分配:为不同的任务分配不同的优先级。紧急、关键或实时性要求高的任务应分配较高的优先级(接近
configMAX_PRIORITIES - 1
),而不紧急的任务分配较低的优先级(接近0)。避免随意分配,导致优先级“饥饿”或性能问题。 -
利用同优先级任务:多个任务可以共享相同的优先级,并通过时间片轮转调度共享CPU时间(需配置
configUSE_TIME_SLICING
)。
所以会有FreeRTOS管理的最高的优先级,就是任务的优先级。对于任务优先级数值越大,那么其优先级就越高。这个和中断优先级是相反的。
并且FreeRTOS还是需要管理一部分中断优先级的,至于怎么管理,上面已经讲述的很明白了。
简单而言:
无关紧要的中断,通过FreeRTOS自己利用BASEMASK寄存器进行屏蔽一些中断,而还有一部分中断是不能屏蔽的,那么就要小心。
特性 | 能使用 FreeRTOS API 的中断 | 不能使用 FreeRTOS API 的中断 |
---|---|---|
中断优先级 | 数值较高 (逻辑优先级较低) | 数值较低 (逻辑优先级较高) |
FreeRTOS 管理 | 可管理 (受 FreeRTOS 控制) | 不可管理 (不受 FreeRTOS 控制) |
API 调用权限 | 允许且必须使用 FromISR 后缀的 API | 禁止使用任何 FreeRTOS API (包括 FromISR 版本) |
典型应用 | 普通外设通信 (如 UART, I2C) | 系统关键操作 (如紧急故障、高速实时采样) |
屏蔽特性 | 会被 FreeRTOS 的临界区屏蔽 | 不会被 FreeRTOS 的临界区屏蔽 |
因此就会出现一个为什么有些中断不能调用 API,就是因为一些中断FreeRTOS管不了,如果调用了就会出现数据错乱,也就是前面分析的。
根本原因在于:要确保最高优先级中断的绝对实时性,并防止其破坏 FreeRTOS 内核的临界区(Critical Section)保护机制。
-
临界区保护机制:
FreeRTOS 内核在操作自己的内部数据结构(如任务队列、信号量)时,需要进入临界区。它通过设置
BASEPRI
寄存器来屏蔽所有优先级数值 ≥configMAX_SYSCALL_INTERRUPT_PRIORITY
的中断,从而安全地执行这些操作。-
可管理中断(优先级数值≥5):由于会被屏蔽,它们看到的内核状态总是一致的,因此可以安全调用
FromISR
API。 -
不可管理中断(优先级数值<5):它们永远不会被屏蔽。如果一个这样的中断恰好在 FreeRTOS 处于临界区时触发,并调用了一个试图操作同一内核资源的 API,就会破坏数据的原子性,导致数据损坏、系统崩溃等未定义行为。
-
-
实时性要求:
那些被划分为“不可管理”的中断,通常是系统中最关键、对响应时间要求最苛刻的(如看门狗、电源故障、硬实时数据采集)。它们的唯一任务就是以最快速度响应硬件事件。任何额外的软件调用(即使是
FromISR
API)都会引入不可预测的延迟,违背其设计初衷。 -
系统稳定性:
FromISR
API 设计为“非阻塞”的,它们不会导致任务切换。而很多任务级 API(如vTaskDelay()
)则会导致调用者进入阻塞状态。在中断中调用此类 API 是致命的,因为中断服务程序(ISR)并非任务,没有任务控制块(TCB),会导致内核调度器出现严重错误。
static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;__asm{/* Set BASEPRI to the max syscall priority to effect a criticalsection. */msr basepri, ulNewBASEPRIdsbisb}
}#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
以上是针对中断实现的方式。
如果觉得我的内容对您有帮助,希望不要吝啬您的赞和关注,您的赞和关注是我更新优质内容的最大动力。
专栏介绍
《嵌入式通信协议解析专栏》
《PID算法专栏》
《C语言指针专栏》
《单片机嵌入式软件相关知识》
《FreeRTOS源码理解专栏》
《嵌入式软件分层架构的设计原理与实践验证》
文章源码获取方式:
如果您对本文的源码感兴趣,欢迎在评论区留下您的邮箱地址。我会在空闲时间整理相关代码,并通过邮件发送给您。由于个人时间有限,发送可能会有一定延迟,请您耐心等待。同时,建议您在评论时注明具体的需求或问题,以便我更好地为您提供针对性的帮助。
【版权声明】
本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议。这意味着您可以自由地共享(复制、分发)和改编(修改、转换)本文内容,但必须遵守以下条件:
署名:您必须注明原作者(即本文博主)的姓名,并提供指向原文的链接。
相同方式共享:如果您基于本文创作了新的内容,必须使用相同的 CC 4.0 BY-SA 协议进行发布。
感谢您的理解与支持!如果您有任何疑问或需要进一步协助,请随时在评论区留言,笔者一定知无不言,言无不尽。