当前位置: 首页 > news >正文

FreeRTOS实战指南 — 6 临界段保护

目录

6.1 中断与临界段

6.2 Cortex-M内核快速关中断指令

6.3 关中断与开中断

6.3.1 关中断

6.3.2 开中断

6.4 进入/退出临界段

6.4.1 进入临界段

6.4.2 退出临界段


6.1 中断与临界段

中断(Interrupt) 和临界段(Critical Section) 是两个密切相关但本质不同的概念,中断是硬件触发的异步事件机制,临界段是软件层面的同步机制。以下从定义、作用、实现方式等方面详细说明两者的区别:

  • 中断:中断是硬件触发的异步事件,由外部设备(如定时器、传感器、通信模块等)或内部异常(如错误、陷阱)触发,用于打断当前 CPU 执行的任务,转而执行对应的中断服务程序(ISR)。
  • 临界段:临界段是一段需要独占 CPU 资源的代码(如操作共享变量、访问硬件寄存器等),在执行期间不允许被其他任务或中断干扰,否则可能导致数据错误或逻辑混乱退出临界段。

在FreeRTOS中,进入临界段通常使用taskENTER_CRITICAL()宏,它关闭中断以防止任务切换,确保代码的原子性。退出临界段则使用taskEXIT_CRITICAL()宏,它重新开启中断。为了支持中断内的临界段操作,FreeRTOS还提供了taskENTER_CRITICAL_FROM_ISR()和taskEXIT_CRITICAL_FROM_ISR()宏。这些宏利用Cortex-M内核的BASEPRI寄存器来控制中断的屏蔽,而不是完全关闭所有中断,从而允许高优先级的中断仍然可以执行。

临界段的代码必须尽可能短,以避免影响系统的实时性。如果临界段处理时间过长,可能会导致高优先级任务或中断的响应延迟。因此,在使用临界段时,应该尽量减少在临界段内执行的操作,并且只在必要时才使用临界段保护。可以考虑使用信号量、互斥量等同步机制来替代临界段,因为这些同步机制可以提供更高级的资源管理和错误处理能力。

6.2 Cortex-M内核快速关中断指令

Cortex-M内核中的CPS指令专门用于控制中断和异常的屏蔽状态,通过改变特殊寄存器的值快速开启和关闭中断和异常,代码如下。

CPSID I ;PRIMASK=1 ;关中断
CPSIE I ;PRIMASK=0 ;开中断
CPSID F ;FAULTMASK=1 ;关异常
CPSIE F ;FAULTMASK=0 ;开异常
  • PRIMASK:32位的寄存器,用于控制所有中断的使能状态。当PRIMASK位被设置为1时,所有可屏蔽中断将被禁用;当它被清零时,可屏蔽中断将被使能。
  • FAULTMASK:32位的寄存器,用于控制所有异常的使能状态,包括中断和错误异常(如硬错误、内存管理错误等)。当FAULTMASK位被设置为1时,除了NMI(非屏蔽中断)以外的所有异常将被禁用;当它被清零时,所有异常将被使能。
  • BASEPRI:32位的寄存器,用于设置中断优先级的阈值。当一个中断的优先级高于或等于BASEPRI的值时,该中断将被禁用;当一个中断的优先级低于BASEPRI的值时,该中断将被使能。BASEPRI允许更细粒度的控制,可以允许某些高优先级的中断打断当前执行的代码。

在实际应用中,以确保系统的实时性和稳定性,这些寄存器的设置需要谨慎。临界区可以通过BASEPRI来实现,而不是使用PRIMASK,因为BASEPRI允许高优先级的中断继续运行,利于保持系统的实时性。

6.3 关中断与开中断

6.3.1 关中断

FreeRTOS提供了两种关闭中断的方法:不带返回值和带返回值,两种方法的主要区别在于是否保存和返回当前的BASEPRI值。这两种方法都通过修改Cortex-M处理器的BASEPRI寄存器来实现中断的禁用,以保护临界区代码不被中断打断。

// 不带返回值的关中断函数
// 特点:不能嵌套使用,不能在中断服务程序中使用
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()void vPortRaiseBASEPRI(void)
{// 设置新的BASEPRI值,禁用优先级低于该值的中断uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;__asm{msr basepri, ulNewBASEPRI  // 将ulNewBASEPRI的值写入BASEPRI 寄存器,实现关中断dsb                        // 数据同步屏障,确保立即更新isb                        // 指令同步屏障,确保指令立即更新}
}// 带返回值的关中断函数
// 特点:支持嵌套使用,可在中断服务程序中使用
#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()uint32_t ulPortRaiseBASEPRI(void)
{uint32_t ulReturn;// 设置新的BASEPRI值,禁用优先级低于该值的中断uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;__asm{mrs ulReturn, basepri      // 读取当前BASEPRI寄存器的值,保存到ulReturnmsr basepri, ulNewBASEPRI  // 修改BASEPRI寄存器的值dsb                        // 数据同步屏障,确保立即更新isb                        // 指令同步屏障,确保指令立即更新}return ulReturn;  // 返回修改前的BASEPRI值,用于后续恢复
}

vPortRaiseBASEPRI()函数:通过设置BASEPRI寄存器的值,从而禁用所有优先级低于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断。这种方法简单直接,但不能嵌套使用,也不能在中断服务程序(ISR)中使用,因为它不保存当前的中断状态。

ulPortRaiseBASEPRI()函数:在关闭中断之前保存当前的BASEPRI值,并在函数结束时返回这个值。这种方法支持嵌套使用,并且可以在中断服务程序中使用,因为它保留了中断的原始状态,允许在之后恢复。

6.3.2 开中断

同样FreeRTOS中开启中断也有两种方法:不带中断保护和带中断保护。区别在于带保护的方法可以在ISR中安全使用,它允许在修改BASEPRI值之前保存当前的中断状态,并在之后恢复,从而避免对高优先级中断的干扰。而不带保护的方法则不考虑当前状态,直接修改BASEPRI值,可能会在ISR中造成不可预见的中断优先级变化。

/* * 不带中断保护的开中断函数* 功能:将BASEPRI寄存器设置为0,完全开启中断*/
#define portENABLE_INTERRUPTS() vPortSetBASEPRI(0)/* * 带中断保护的开中断函数* 功能:恢复BASEPRI寄存器到指定值x,用于嵌套中断场景* 参数x:需要恢复的BASEPRI寄存器值(通常是关中断时保存的原值)*/
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)/** 中断优先级寄存器设置函数* 功能:修改ARM Cortex-M内核的BASEPRI寄存器值* 参数ulBASEPRI:要写入BASEPRI寄存器的值*/
void vPortSetBASEPRI(uint32_t ulBASEPRI)
{__asm{msr basepri, ulBASEPRI  // 将参数值写入BASEPRI寄存器}
}

portENABLE_INTERRUPTS():直接将BASEPRI寄存器设置为0,从而允许所有中断被响应,这种方式简单直接,但没有考虑当前的中断状态,因此不适用于中断服务程序(ISR)中,因为它可能会打断高优先级的中断处理。

portCLEAR_INTERRUPT_MASK_FROM_ISR(x)允许根据传入的参数x来设置BASEPRI寄存器的值,这样可以根据需要允许特定优先级的中断被响应。这种方式带有一定的保护机制,从而避免意外地打断高优先级的中断处理。

6.4 进入/退出临界段

6.4.1 进入临界段

代码实现了在FreeRTOS中进入临界段的功能,用于保护关键代码段不被中断打断从而确保数据的完整性和一致性。

// 临界段进入宏定义,逐层映射到底层实现
#define taskENTER_CRITICAL()        portENTER_CRITICAL()
#define portENTER_CRITICAL()        vPortEnterCritical()/** 进入临界段函数* 特性:不带中断保护机制,不支持嵌套调用* 功能:关闭中断并增加临界区嵌套深度计数*/
void vPortEnterCritical(void)
{portDISABLE_INTERRUPTS();      // 关闭中断uxCriticalNesting++;           // 临界区嵌套深度加1// 首次进入临界段时,确保当前无活跃中断if (uxCriticalNesting == 1){// 断言检查:确认当前没有正在执行的中断服务程序configASSERT((portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK) == 0);}
}// 关闭中断宏定义,映射到具体实现函数
#define portDISABLE_INTERRUPTS()    vPortRaiseBASEPRI()/** 内联函数:提高BASEPRI寄存器值以关闭中断* 功能:通过设置BASEPRI寄存器,禁用优先级低于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断* 注:使用内联函数可减少函数调用开销,适合临界区场景*/
static portFORCE_INLINE void vPortRaiseBASEPRI(void)
{// 配置新的BASEPRI值,用于屏蔽低优先级中断uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;__asm{msr basepri, ulNewBASEPRI  // 将新值写入BASEPRI寄存器dsb                        // 数据同步屏障,确保寄存器更新立即生效isb                        // 指令同步屏障,确保后续指令按新配置执行}
}

vPortEnterCritical函数:首先关闭中断,然后增加一个名为uxCriticalNesting的变量,该变量用于跟踪临界区的嵌套深度。如果这是第一次进入临界区(即uxCriticalNesting从0变为1),则使用configASSERT确保没有中断正在执行,这是为了确保系统状态的一致性。

vPortRaiseBASEPRI函数:通过修改BASEPRI寄存器的值来关闭中断。函数设置了一个新的BASEPRI值,该值高于configMAX_SYSCALL_INTERRUPT_PRIORITY,从而禁用了所有低于这个优先级的中断。同时,使用数据同步屏障(DSB)和指令同步屏障(ISB)确保寄存器值的立即更新,以及处理器状态的一致性。下面的程序用于在中断服务程序(ISR)中进入临界段,同时保留了中断的当前状态,使得临界段操作可以嵌套。

/** 进入临界段(带中断保护版本,支持嵌套)* 用于中断服务程序(ISR)中的临界区保护*/
#define taskENTER_CRITICAL_FROM_ISR()    portSET_INTERRUPT_MASK_FROM_ISR()
#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()/** 内联函数:提高BASEPRI值以关闭中断* 功能:通过设置BASEPRI寄存器屏蔽低优先级中断,并返回原始BASEPRI值* 返回值:修改前的BASEPRI寄存器值,用于后续恢复中断状态*/
static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI(void)
{uint32_t ulReturn;                  // 用于保存当前BASEPRI值// 新的BASEPRI值,用于屏蔽优先级低于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;__asm{mrs ulReturn, basepri           // 读取当前BASEPRI寄存器值并保存msr basepri, ulNewBASEPRI       // 设置BASEPRI寄存器新值,屏蔽低优先级中断dsb                             // 数据同步屏障,确保寄存器更新生效isb                             // 指令同步屏障,确保指令按新配置执行}return ulReturn;                    // 返回原始BASEPRI值,供恢复时使用
}

ulPortRaiseBASEPRI函数:内联函数,它通过汇编语言读取当前的BASEPRI值,保存到ulReturn变量中,然后将BASEPRI设置为configMAX_SYSCALL_INTERRUPT_PRIORITY,这个值定义了在临界段中哪些中断将被屏蔽。通过这种方式,函数不仅关闭了中断,还保留了关闭中断之前的BASEPRI值,以便之后可以通过taskEXIT_CRITICAL_FROM_ISR宏来恢复中断状态。

6.4.2 退出临界段

退出临界段也分为不带保护和带保护两种。不带中断保护退出临界段的方法意味着它不能在ISR中使用,也不能嵌套。它适用于任务级别,确保在退出临界段时,如果之前有中断被屏蔽,那么这些中断将被重新使能。

/** 退出临界段(不带中断保护版本,不支持嵌套)* 对应taskENTER_CRITICAL()的退出操作*/
#define taskEXIT_CRITICAL()    portEXIT_CRITICAL()
#define portEXIT_CRITICAL()    vPortExitCritical()/** 退出临界段的具体实现函数* 功能:减少临界区嵌套计数,当计数为0时重新开启中断*/
void vPortExitCritical(void)
{// 断言检查:确保临界区嵌套计数不为0(防止未进入临界段就退出)configASSERT(uxCriticalNesting);uxCriticalNesting--;  // 临界区嵌套深度减1// 当嵌套计数为0时,说明所有临界区都已退出,重新开启中断if (uxCriticalNesting == 0){portENABLE_INTERRUPTS();  // 开启所有中断}
}/** 开启所有中断的宏定义* 功能:通过设置BASEPRI寄存器为0,允许所有优先级的中断响应*/
#define portENABLE_INTERRUPTS()    vPortSetBASEPRI(0)/** 设置BASEPRI寄存器的值(内联函数)* 功能:通过修改BASEPRI寄存器控制中断使能状态* 参数:ulBASEPRI - 要写入BASEPRI寄存器的值*/
static portFORCE_INLINE void vPortSetBASEPRI(uint32_t ulBASEPRI)
{__asm{msr basepri, ulBASEPRI  // 将参数值写入BASEPRI寄存器}
}

vPortExitCritical函数:首先检查uxCriticalNesting变量,这是一个记录临界区嵌套深度的变量。每次进入临界区,这个变量会增加,每次退出临界区,这个变量会减少。当uxCriticalNesting减少到0时,表示没有更多的嵌套临界区,此时会调用portENABLE_INTERRUPTS宏来重新开启中断。

vPortSetBASEPRI函数:通过修改BASEPRI寄存器的值来开启中断,将BASEPRI设置为0,从而允许所有中断被响应。

带中断保护的方法特别是在中断服务程序中,因为中断可能会在任何时候被触发,需要确保在退出临界段时正确地恢复中断状态。

/** 退出临界段(带中断保护版本,支持嵌套)* 用于中断服务程序(ISR)中,与taskENTER_CRITICAL_FROM_ISR()配合使用* 参数x:进入临界段时保存的BASEPRI寄存器值,用于恢复中断状态*/
#define taskEXIT_CRITICAL_FROM_ISR(x)    portCLEAR_INTERRUPT_MASK_FROM_ISR(x)
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)/** 内联函数:设置BASEPRI寄存器的值* 功能:通过修改ARM Cortex-M内核的BASEPRI寄存器,恢复中断优先级屏蔽状态* 参数ulBASEPRI:要写入BASEPRI寄存器的值(通常是进入临界段时保存的原值)*/
static portFORCE_INLINE void vPortSetBASEPRI(uint32_t ulBASEPRI)
{__asm{msr basepri, ulBASEPRI  // 汇编指令:将参数值写入BASEPRI寄存器}
}

vPortSetBASEPRI(x)函数:通过汇编语言直接修改BASEPRI寄存器的值。这个函数接受一个参数ulBASEPRI,这个参数是在进入临界段时保存的原始BASEPRI值。通过将BASEPRI寄存器的值设置回这个原始值,函数恢复了进入临界段之前中断的使能状态。

http://www.dtcms.com/a/393834.html

相关文章:

  • 关于CFS队列pick_next_task_fair选取下一个任务的分析
  • 【算法笔记】链表相关的题目
  • Netty从0到1系列之Recycler对象池技术【3】
  • 网页开发入门:CSS与JS基础及BS/CS架构解析
  • css单位换算及适配
  • Java制作双脑同步 Hemi-Sync 音频
  • webrtc弱网-ProbeBitrateEstimator类源码分析与算法原理
  • 在OpenHarmony上适配图形显示【4】——rk3568_4.0r_mesa3d适配
  • 嵌入式(3)——RTC实时时钟
  • 内核模块组成和裁剪参考表
  • 140-understanding_the_armv8.x_and_armv9.x_extensions_guide
  • 【序列晋升】40 Spring Data R2DBC 轻量异步架构下的数据访问最佳实践
  • TGRS | 视觉语言模型 | 语言感知领域泛化实现高光谱跨场景分类, 代码开源!
  • Oracle / MySQL / MariaDB / SQL Server 常用连接与基础查询(Linux操作系统上)
  • 将 Jupyter Notebook 转换为 PDF
  • torchvision 编译安装 nano
  • 华为昇腾 910 到 950 系列 NPU 深度解析
  • 设计模式---门面模式
  • SQL Server从入门到项目实践(超值版)读书笔记 26
  • Datawhale学习笔记——深度语义匹配模型DSSM详解、实战与FAQ
  • 一文了解瑞萨MCU常用的芯片封装类型
  • LeetCode:44.二叉搜索树中第K小的元素
  • 初学者如何系统性地学习Linux?
  • LeetCode:43.验证二叉搜索树
  • [学习log] OT/ICS工业控制系统渗透测试
  • 六边形箱图 (Hexbin Plot):使用 Matplotlib 处理大规模散点数据
  • LinuxC++项目开发日志——基于正倒排索引的boost搜索引擎(2——Parser解析html模块)
  • 电脑能ping开发板,开发板不能ping电脑的解决方法:
  • git 覆盖:检出特定分支的文件到当前分支
  • CentOS 8.5.2.111部署Zabbix6.0