痞子衡嵌入式:嵌入式Cortex-M裸机环境下临界区保护的三种实现
大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家分享的是Cortex-M裸机环境下临界区保护的三种实现。
搞嵌入式玩过 RTOS 的朋友想必都对 OS_ENTER_CRITICAL()、OS_EXIT_CRITICAL() 这个功能代码对特别眼熟,在 RTOS 里常常会有多任务(进程)处理,有些情况下一些特殊操作(比如 XIP 下 Flash 擦写、低功耗模式切换)不能被随意打断,或者一些共享数据区不能被无序访问(A 任务正在读,B 任务却要写),这时候就要用到临界区保护策略了。
所谓临界区保护策略,简单说就是系统中硬件临界资源或者软件临界资源,多个任务必须互斥地对它们进行访问。RTOS 环境下有现成的临界区保护接口函数,而裸机系统里其实也有这种需求。在裸机系统里,临界区保护主要就是跟系统全局中断控制有关。痞子衡之前写过一篇 《嵌入式MCU中通用的三重中断控制设计》,文中介绍的第三重也是最顶层的中断控制是系统全局中断控制,今天痞子衡就从这个系统全局中断控制使用入手给大家介绍三种临界区保护做法:
一、临界区保护测试场景
关于临界区保护的测试场景无非两种。第一种场景是受保护的多个任务间并无关联,也不会互相嵌套,如下面的代码所示,task1 和 task2 是按序被保护的,因此 enter_critical() 和 exit_critical() 这两个临界区保护函数总是严格地成对执行:
void critical_section_test(void)
{
// 进入临界区
enter_critical();
// 做受保护的任务1
do_task1();
// 退出临界区
exit_critical();
// 进入临界区
enter_critical();
// 做受保护的任务2,与任务1无关联
do_task2();
// 退出临界区
exit_critical();
}
第二种场景就是多个任务间可能有关联,会存在嵌套情况,如下面的代码所示,task2 是 task1 的一个子任务,这种情况下,你会发现实际上是先执行两次 enter_critical(),然后再执行两次 exit_critical()。需要注意的是 task1 里面的子任务 task3 虽然没有像子任务 task2 那样被主动加一层保护,但由于主任务 task1 整体是受保护的,因此子任务 task3 也应该是受保护的。
void do_task1(void)
{
// 进入临界区
enter_critical();
// 做受保护的任务2,是任务1中的子任务
do_task2();
// 退出临界区
exit_critical();
// 做任务3
do_task3();
}
void critical_section_test(void)
{
// 进入临界区
enter_critical();
// 做受保护的任务1
do_task1();
// 退出临界区
exit_critical();
}
二、临界区保护三种实现
上面的临界区保护测试场景很清楚了,现在到 enter_critical()、exit_critical() 这对临界区保护函数的实现环节了:
2.1 入门做法
首先是非常入门的做法,直接就是对系统全局中断控制函数 __disable_irq()、__enable_irq() 的封装。回到上一节的测试场景里,这种实现可以很好地应对非嵌套型任务的保护,但是对于互相嵌套的任务保护就失效了。上一节测试代码里,task3 应该也要受到保护的,但实际上并没有被保护,因为紧接着 task2 后面的 exit_critical() 直接就打开了系统全局中断。
void enter_critical(void)
{
// 关闭系统全局中断
__disable_irq();
}
void exit_critical(void)
{
// 打开系统全局中断
__enable_irq();
}
2.2 改进做法
针对入门做法,可不可以改进呢?当然可以,我们只需要加一个全局变量 s_lockObject 来实时记录当前已进入的临界区保护的次数,即如下代码所示。每调用一次 enter_critical() 都会直接关闭系统全局中断(保证临界区一定是受保护的),并记录次数,而调用 exit_critical() 时仅当当前次数是 1 时(即当前不是临界区保护嵌套情况),才会打开系统全局中断,否则只是抵消一次进入临界区次数而已。改进后的实现显然可以保护上一节测试代码里的 task3 了。
static uint32_t s_lockObject;
void init_critical(void)
{
__disable_irq();
// 清零计数器
s_lockObject = 0;
__enable_irq();
}
void enter_critical(void)
{
// 关闭系统全局中断
__disable_irq();
// 计数器加 1
++s_lockObject;
}
void exit_critical(void)
{
if (s_lockObject <= 1)
{
// 仅当计数器不大于 1 时,才打开系统全局中断,并清零计数器
s_lockObject = 0;
__enable_irq();
}
else
{
// 当计数器大于 1 时,直接计数器减 1 即可
--s_lockObject;
}
}
2.3 终极做法
上面的改进做法虽然解决了临界区任务嵌套保护的问题,但是增加了一个全局变量和一个初始化函数,实现不够优雅,并且嵌入式系统里全局变量极容易被篡改,存在一定风险,有没有更好的实现呢?当然有,这要借助 Cortex-M 处理器内核的特殊屏蔽寄存器 PRIMASK,下面是 PRIMASK 寄存器位定义(取自 ARMv7-M 手册),其仅有最低位 PM 是有效的,当 PRIMASK[PM] 为 1 时,系统全局中断是关闭的(将执行优先级提高到 0x0/0x80);当 PRIMASK[PM] 为 0 时,系统全局中断是打开的(对执行优先级无影响)。
看到这,你应该明白了 __disable_irq()、__enable_irq() 功能其实就是操作 PRIMASK 寄存器实现的。既然 PRIMASK 寄存器控制也保存了系统全局中断的开关状态,我们可以通过获取 PRIMASK 值来替代上面改进做法里的全局变量 s_lockObject 的功能,代码实现如下:
uint32_t enter_critical(void)
{
// 保存当前 PRIMASK 值
uint32_t regPrimask = __get_PRIMASK();
// 关闭系统全局中断(其实就是将 PRIMASK 设为 1)
__disable_irq();
return regPrimask;
}
void exit_critical(uint32_t primask)
{
// 恢复 PRIMASK
__set_PRIMASK(primask);
}
因为 enter_critical()、exit_critical() 函数原型有所变化,因此使用上也要相应改变下:
void critical_section_test(void)
{
// 进入临界区
uint32_t primask = enter_critical();
// 做受保护的任务
do_task();
// 退出临界区
exit_critical(primask);
// ...
}
附录、PRIMASK寄存器设置函数在各 IDE 下实现
//////////////////////////////////////////////////////
// IAR 环境下实现(见 cmsis_iccarm.h 文件)
#define __set_PRIMASK(VALUE) (__arm_wsr("PRIMASK", (VALUE)))
#define __get_PRIMASK() (__arm_rsr("PRIMASK"))
//////////////////////////////////////////////////////
// Keil 环境下实现(见 cmsis_armclang.h 文件)
__STATIC_FORCEINLINE void __set_PRIMASK(uint32_t priMask)
{
__ASM volatile ("MSR primask, %0" : : "r" (priMask) : "memory");
}
__STATIC_FORCEINLINE uint32_t __get_PRIMASK(void)
{
uint32_t result;
__ASM volatile ("MRS %0, primask" : "=r" (result) );
return(result);
}
至此,Cortex-M裸机环境下临界区保护的三种实现痞子衡便介绍完毕了,掌声在哪里~~~
欢迎订阅
文章会同时发布到我的 博客园主页、CSDN主页、知乎主页、微信公众号 平台上。
微信搜索"痞子衡嵌入式"或者扫描下面二维码,就可以在手机上第一时间看了哦。

痞子衡嵌入式:嵌入式Cortex-M裸机环境下临界区保护的三种实现的更多相关文章
- hadoop搭建杂记:Linux下JDK环境变量的设置(三种配置环境变量的方法)
Linux下JDK环境变量的设置(三种配置环境变量的方法) Linux下JDK环境变量的设置(三种配置环境变量的方法) ①修改/etc/profile文件 如果你的计算机仅仅作为开发使用时推荐使用这种 ...
- Liunx 环境下vsftpd的三种实现方法(超详细参数)
以下文章介绍Liunx 环境下vsftpd的三种实现方法 ftp://vsftpd.beasts.org/users/cevans/vsftpd-2.0.3.tar.gz,目前已经到2.0.3版本.假 ...
- LINUX环境并发服务器的三种实现模型
服务器设计技术有很多,按使用的协议来分有TCP服务器和UDP服务器.按处理方式来分有循环服务器和并发服务器. 1 循环服务器与并发服务器模型 在网络程序里面,一般来说都是许多客户对应一个服务器,为了 ...
- [CentOS] 环境变量设置的三种方法
在CentOS系统中添加环境变量的方法有几种,推荐第三种方法.这里以添加 TexLive 2017 的环境变量为例进行说明. 1. 修改 ~/.bash_profile 文档,在文末添加以下代码: ...
- fluent中UDF环境变量问题的三种解决方法
方法一: 这种方式最简便,首选这种,但是有时会因为不明原因而不好使,我自己电脑刚开始用这种方式是行得通的,但是后来中途装过很多乱七八糟的软件,估计环境变量改乱了,这时候只能用第二种或者第三种方法.先说 ...
- Linux下环境变量设置的三种方法
如想将一个路径加入到$PATH中,可以像下面这样做: 1.控制台中设置,不赞成这种方式,因为他只对当前的shell 起作用,换一个shell设置就无效了:$PATH="$PATH" ...
- 逐步搭建Lamp环境之vim的三种模式以及基本命令
在Linux中vim的三种模式分别为:命令模式.末行模式.编辑模式.以下是三者的关系图: 三种模式的彼此切换: 命令模式是vim中的默认模式. 命令模式切换至末行模式: 使用英文冒号(:). 末行模式 ...
- 痞子衡嵌入式:嵌入式Cortex-M中断向量表原理及其重定向方法
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是Cortex-M中断向量表原理及其重定向方法. 接着前文 <嵌入式Cortex-M裸机环境下临界区保护的三种实现> 继续聊, ...
- 痞子衡嵌入式:语音处理工具Jays-PySPEECH诞生记(1)- 环境搭建(Python2.7.14 + PyAudio0.2.11 + Matplotlib2.2.3 + SpeechRecognition3.8.1 + pyttsx3 2.7)
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是语音处理工具Jays-PySPEECH诞生之环境搭建. 在写Jays-PySPEECH时需要先搭好开发环境,下表列出了开发过程中会用到的 ...
随机推荐
- 利用NVIDIA-NGC中的MATLAB容器加速语义分割
利用NVIDIA-NGC中的MATLAB容器加速语义分割 Speeding Up Semantic Segmentation Using MATLAB Container from NVIDIA NG ...
- 七、AIDE入侵检测
Aide通过检查数据文件的权限.时间.大小.哈希值等,校验数据的完整性 部署AIDE入侵检测系统 [root@proxy ~]# yum -y install aide //安装软件包 ...
- day20200912
连杆通过运动副相对于啮合连杆运动 ! 运动副: 旋转副:仅旋转 滑动副:仅沿直线滑动 柱面副:可旋转可沿直线滑动 其他: 可以设置上限.下限 3D接触 驱动: 简谐驱动 函数驱动 运动函数驱动
- python学习笔记02-简单游戏
一拖又过去快一个月了,哭聊.. 惰性千万不要有.. 今天简单接触一下条件语句 一个简单的文字游戏 1 print('----第一个文字游戏----') 2 temp = input('猜一下现在心里想 ...
- mapboxgl 互联网地图纠偏插件(一)
之前写过一个 leaflet 互联网地图纠偏插件,引用插件后一行代码都不用写,就能解决国内互联网地图瓦片的偏移问题. 最近想对 mapboxgl 也写一个这样的插件. 原因是自己发布的OSM矢量瓦片地 ...
- 屌炸天,像写代码一样写PPT,一个小工具解决
此文已经废,请移步升级版博文: markdown写ppt (史上最全)
- Kafka 的这些原理你懂吗
如果只是为了开发 Kafka 应用程序,或者只是在生产环境使用 Kafka,那么了解 Kafka 的内部工作原理不是必须的.不过,了解 Kafka 的内部工作原理有助于理解 Kafka 的行为,也利用 ...
- 关于Linux服务器部署
服务器信息: 此小节的内容: SecurityCRT:用来连接到Linux服务器命令操作. FTP(FTPRush):本地文件和Linux服务器文件交互的 工具服务器 借助客户端工具来链接到Linux ...
- 第5章:资源编排(YAML)
5.1 编写YAML注意事项 YAML 是一种简洁的非标记语言. 语法格式: 缩进表示层级关系 不支持制表符"tab"缩进,使用空格缩进 通常开头缩进 2 个空格 字符后缩进 1 ...
- Web 前端开发规范手册
一.规范目的 Web 前端开发规范手册 1.1 概述 ......................................................................... ...