找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

358

积分

0

好友

44

主题
发表于 2025-12-27 09:38:26 | 查看: 31| 回复: 0

在实时操作系统中,临界区保护是确保多线程/任务安全访问共享资源的关键机制。本文深入探讨了RT-Thread如何通过禁止调度和屏蔽中断来保护临界区,并通过源码对比分析了其与FreeRTOS在实现上的核心差异。

一、理解临界区及其重要性

1.1 临界区与临界资源

临界区是指每个线程中访问临界资源的那段代码。而临界资源则是一次仅允许一个线程使用的共享资源,例如全局变量、硬件外设等。
RT-Thread与FreeRTOS临界区保护机制对比及源码分析 - 图片 - 1

在多线程的RTOS环境中,若不加以保护,多个线程或中断服务例程对同一临界资源的并发访问将导致数据不一致或程序逻辑错误,理解并发访问的安全隐患是嵌入式开发的基础。

1.2 RTOS中的临界区场景

场景一:线程与中断的冲突
最常见的情况是线程在执行过程中被中断打断,而中断服务函数也访问了相同的全局变量。
RT-Thread与FreeRTOS临界区保护机制对比及源码分析 - 图片 - 2
如上图所示,若在线程对变量a操作中途发生中断,且中断也修改了a,则线程恢复后使用的a值可能并非其预期值,从而导致逻辑错误。

场景二:线程与线程间的冲突
即使没有中断,RTOS自身的调度器也可能在某个线程操作临界资源未完成时,切换到另一个也要访问该资源的线程。
RT-Thread与FreeRTOS临界区保护机制对比及源码分析 - 图片 - 3
此场景深刻关联RTOS的调度原理。除了外部中断,系统滴答定时器(Systick)和用于上下文切换的PendSV异常都可能打断当前线程。PendSV被设置为最低优先级异常,专门由操作系统触发以进行线程切换。这就要求开发者在访问临界资源时,必须考虑所有可能的并发路径并实施保护。

为避免上述问题,RTOS通常提供多种保护机制,如关闭调度、屏蔽中断、使用信号量/互斥量等。下文将重点解析RT-Thread的前两种机制及其源码实现。

二、RT-Thread的临界区保护机制

2.1 禁止调度(调度器上锁)

RT-Thread提供了调度器上锁与解锁的函数对,用于防止线程切换。

void rt_enter_critical(void); //调度器上锁,进入临界区
void rt_exit_critical(void);  //调度器解锁,退出临界区

使用示例
进入临界区前调用 rt_enter_critical(),操作完成后调用 rt_exit_critical()。需注意,此方法仅能防止线程切换,无法阻止中断。若中断服务例程内也需访问同一临界资源,此方法无效。
RT-Thread与FreeRTOS临界区保护机制对比及源码分析 - 图片 - 4

源码简析
rt_enter_critical()rt_exit_critical() 的核心是操作一个全局计数器 rt_scheduler_lock_nest

  1. 上锁rt_enter_critical() 仅将 rt_scheduler_lock_nest 加1。
    RT-Thread与FreeRTOS临界区保护机制对比及源码分析 - 图片 - 5
  2. 影响调度:调度器在决定是否进行线程切换时(例如在 rt_schedule() 函数中),会检查此计数器。若其值大于0,则不会切换线程。
    RT-Thread与FreeRTOS临界区保护机制对比及源码分析 - 图片 - 6
  3. 解锁rt_exit_critical()rt_scheduler_lock_nest 减1。只有当计数器减为0时,调度才会恢复正常。
    RT-Thread与FreeRTOS临界区保护机制对比及源码分析 - 图片 - 7

从源码可知,RT-Thread的调度锁支持嵌套。上锁几次,就必须对应解锁几次。

2.2 屏蔽中断(中断锁)

中断锁通过直接屏蔽CPU中断来保护临界区,这是最彻底的保护方式,因为它同时阻止了线程切换和中断响应。

rt_base_t rt_hw_interrupt_disable(void); //屏蔽中断,返回原中断状态
void rt_hw_interrupt_enable(rt_base_t level); //恢复中断,参数为原状态

使用示例
通过 rt_hw_interrupt_disable() 获取当前中断状态并关中断,操作完成后,将获取到的状态值传入 rt_hw_interrupt_enable() 以恢复之前的中断环境。
RT-Thread与FreeRTOS临界区保护机制对比及源码分析 - 图片 - 8

注意:中断锁会增大中断响应延迟,在实时性要求极端苛刻的场景需谨慎使用。

源码简析(基于Cortex-M)
上述函数在RT-Thread中由汇编语言实现,直接操作ARM Cortex-M内核的 PRIMASK 寄存器。

.global rt_hw_interrupt_disable
.type rt_hw_interrupt_disable, %function
rt_hw_interrupt_disable:
    MRS     R0, PRIMASK   // 读取PRIMASK原值到R0(函数返回值)
    CPSID   I             // 执行“关中断”指令
    BX      LR            // 返回

.global rt_hw_interrupt_enable
.type rt_hw_interrupt_enable, %function
rt_hw_interrupt_enable:
    MSR     PRIMASK, R0   // 将参数level(R0)写回PRIMASK寄存器
    BX      LR            // 返回

RT-Thread与FreeRTOS临界区保护机制对比及源码分析 - 图片 - 9
RT-Thread与FreeRTOS临界区保护机制对比及源码分析 - 图片 - 10

  • PRIMASK:这是一个1位宽的中断屏蔽寄存器。将其置1(CPSID I)可禁止所有可配置优先级的异常(仅剩NMI和HardFault能响应)。
    RT-Thread与FreeRTOS临界区保护机制对比及源码分析 - 图片 - 11
  • 实现机制rt_hw_interrupt_disable 先将 PRIMASK 的原始值保存到寄存器R0(作为函数返回值),再关中断。恢复时,将之前保存的状态写回 PRIMASK。这种方式巧妙地支持了中断锁的嵌套,因为每一次关中断都保存了当时的状态。
    RT-Thread与FreeRTOS临界区保护机制对比及源码分析 - 图片 - 12

2.3 与FreeRTOS的临界区实现对比

FreeRTOS在保护临界区时,其“中断屏蔽”的实现思路与RT-Thread有显著不同。它主要操作的是另一个内核寄存器——BASEPRI

FreeRTOS中进入临界区的宏最终会调用类似以下的函数(以Cortex-M3为例):
RT-Thread与FreeRTOS临界区保护机制对比及源码分析 - 图片 - 13

屏蔽中断实现

#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
static void vPortRaiseBASEPRI(void) {
    uint32_t ulNewBASEPRI;
    __asm volatile (
        " mov %0, %1                 \n"
        " msr basepri, %0            \n" // 将特定优先级值写入BASEPRI
        " isb                        \n"
        " dsb                        \n"
        :"=r"(ulNewBASEPRI) : "i"(configMAX_SYSCALL_INTERRUPT_PRIORITY) : "memory"
    );
}

使能中断实现

#define portENABLE_INTERRUPTS() vPortSetBASEPRI(0)
static void vPortSetBASEPRI(uint32_t ulNewMaskValue) {
    __asm volatile ( " msr basepri, %0 " :: "r" (ulNewMaskValue) : "memory" );
}

核心差异解析

  • RT-Thread:使用 PRIMASK 寄存器。CPSID I 一键关闭所有可屏蔽中断,简单粗暴。
  • FreeRTOS:使用 BASEPRI 寄存器。通过向 BASEPRI 写入一个优先级阈值(如 configMAX_SYSCALL_INTERRUPT_PRIORITY),可以仅屏蔽优先级低于或等于该阈值的所有中断,而优先级更高的中断(如紧急的硬件故障)依然可以得到响应。
    RT-Thread与FreeRTOS临界区保护机制对比及源码分析 - 图片 - 14
    这种设计提供了更精细的中断管理策略,为对实时性有严格要求的紧急中断留出了响应通道,是FreeRTOS在系统设计上的一个特点。

三、实际应用场景与建议

在嵌入式开发中,以下场合通常需要考虑临界区保护:

  • 访问全局变量或静态变量
  • 调用非可重入的函数
  • 操作共享的硬件资源(如SPI Flash、公共缓冲区)。
  • 执行需要严格时序的代码段(如精确的I2C通信时序,注意期间避免使用依赖系统滴答的延时函数)。

选择建议

  1. 默认选择:对于常见的、仅在线程间共享的资源,使用禁止调度rt_enter_critical)是高效且合适的,因为它对中断响应无影响。
  2. 强保护场景:当临界资源可能被中断服务例程访问,或需要绝对确保一段代码不被任何情况打断时,应使用中断锁rt_hw_interrupt_disable)。
  3. 原则:无论采用哪种方式,临界区内的代码执行时间都应尽可能短,以最小化对系统实时性和中断响应能力的影响。

结语

掌握临界区保护是进行可靠RTOS编程的基石。本文从使用出发,深入RT-Thread与FreeRTOS的源码层面,分析了禁止调度与屏蔽中断两种核心机制的实现原理及其关键区别(PRIMASK vs BASEPRI)。理解这些底层机制,有助于开发者在不同场景下做出更合适的选择,并编写出更健壮、高效的嵌入式多线程代码。通过对源码的剖析,也展示了阅读源码对于深刻理解系统工作原理的重要性。
RT-Thread与FreeRTOS临界区保护机制对比及源码分析 - 图片 - 15
RT-Thread与FreeRTOS临界区保护机制对比及源码分析 - 图片 - 16 RT-Thread与FreeRTOS临界区保护机制对比及源码分析 - 图片 - 17




上一篇:Android DataBinding双向绑定:BaseObservable与ObservableField详解
下一篇:C语言指针全面解析:从内存管理到函数指针与qsort应用
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-12 03:43 , Processed in 0.200299 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表