单片机裸机时临界区保护方法 天天热议

面包芯语 2023-06-13 08:17:46

作者 | 痞子衡

微信公众号|痞子衡嵌入式

一、临界区保护测试场景

关于临界区保护的测试场景无非两种。第一种场景是受保护的多个任务间并无关联,也不会互相嵌套,如下面的代码所示,task1 和 task2 是按序被保护的,因此 enter_critical() 和 exit_critical() 这两个临界区保护函数总是严格地成对执行:


(资料图片仅供参考)

voidcritical_section_test(void){//进入临界区enter_critical();//做受保护的任务1do_task1();//退出临界区exit_critical();//进入临界区enter_critical();//做受保护的任务2,与任务1无关联do_task2();//退出临界区exit_critical();}

第二种场景就是多个任务间可能有关联,会存在嵌套情况,如下面的代码所示,task2 是 task1 的一个子任务,这种情况下,你会发现实际上是先执行两次 enter_critical(),然后再执行两次 exit_critical()。需要注意的是 task1 里面的子任务 task3 虽然没有像子任务 task2 那样被主动加一层保护,但由于主任务 task1 整体是受保护的,因此子任务 task3 也应该是受保护的。

voiddo_task1(void){//进入临界区enter_critical();//做受保护的任务2,是任务1中的子任务do_task2();//退出临界区exit_critical();//做任务3do_task3();}voidcritical_section_test(void){//进入临界区enter_critical();//做受保护的任务1do_task1();//退出临界区exit_critical();}

二、临界区保护三种实现

上面的临界区保护测试场景很清楚了,现在到 enter_critical()、exit_critical() 这对临界区保护函数的实现环节了:

2.1 入门做法

首先是非常入门的做法,直接就是对系统全局中断控制函数 __disable_irq()、__enable_irq() 的封装。回到上一节的测试场景里,这种实现可以很好地应对非嵌套型任务的保护,但是对于互相嵌套的任务保护就失效了。上一节测试代码里,task3 应该也要受到保护的,但实际上并没有被保护,因为紧接着 task2 后面的 exit_critical() 直接就打开了系统全局中断。

voidenter_critical(void){//关闭系统全局中断__disable_irq();}voidexit_critical(void){//打开系统全局中断__enable_irq();}

2.2 改进做法

针对入门做法,可不可以改进呢?当然可以,我们只需要加一个全局变量 s_lockObject 来实时记录当前已进入的临界区保护的次数,即如下代码所示。每调用一次 enter_critical() 都会直接关闭系统全局中断(保证临界区一定是受保护的),并记录次数,而调用 exit_critical() 时仅当当前次数是 1 时(即当前不是临界区保护嵌套情况),才会打开系统全局中断,否则只是抵消一次进入临界区次数而已。改进后的实现显然可以保护上一节测试代码里的 task3 了。

staticuint32_ts_lockObject;voidinit_critical(void){__disable_irq();//清零计数器s_lockObject=0;__enable_irq();}voidenter_critical(void){//关闭系统全局中断__disable_irq();//计数器加1++s_lockObject;}voidexit_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_tenter_critical(void){//保存当前PRIMASK值uint32_tregPrimask=__get_PRIMASK();//关闭系统全局中断(其实就是将PRIMASK设为1)__disable_irq();returnregPrimask;}voidexit_critical(uint32_tprimask){//恢复PRIMASK__set_PRIMASK(primask);}

因为 enter_critical()、exit_critical() 函数原型有所变化,因此使用上也要相应改变下:

voidcritical_section_test(void){//进入临界区uint32_tprimask=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_FORCEINLINEvoid__set_PRIMASK(uint32_tpriMask){__ASMvolatile("MSRprimask,%0"::"r"(priMask):"memory");}__STATIC_FORCEINLINEuint32_t__get_PRIMASK(void){uint32_tresult;__ASMvolatile("MRS%0,primask":"=r"(result));return(result);}

分享完毕,希望对你有所帮助。

x

热门推荐

单片机裸机时临界区保护方法 天天热议

2023-06

龙胜景区介绍_龙胜景区

2023-06

75%酒精可以喷在菜上面吗_75度酒精可以喷在蔬菜上吗

2023-06

全球观天下!升级生活场景、加速文旅融合:文化产业园,各有新看点

2023-06

范内瓦·布什奖_关于范内瓦·布什奖的简介 环球最资讯

2023-06

南方大蟑螂进军北方吓坏网友 博物科普:暂不会骑人脸|世界快播报

2023-06

当前焦点!dnf进去黑屏未响应_为什么我的Cf 进去就是未响应呢 就算进去了 游戏也进不去

2023-06

上交所就修订上市公司信息披露工作评价指引公开征求意见 增加重大负面事项减分情形|全球今日讯

2023-06

火速跟进!又有银行宣布:“降息”_全球新资讯

2023-06

最新快讯!检察机关依法分别对杨业峰、于富华、薛江炤决定逮捕

2023-06

推荐阅读

多家国际邮轮公司实现全面复航 年底恢复至疫情前水平

2022-05

已纳入医保甲类报销范围 “试管婴儿”家庭将获得更多支持

2022-03

内蒙古满洲里市启动第四轮大规模核酸检测

2021-12

微博博主“鹿道森”确认身亡 警方:排除他杀

2021-12

失联摄影师“鹿道森”确认身亡 尸体被打捞上岸

2021-12

黑龙江讷河第二轮全员核检结果皆为阴性

2021-12

2021天象剧场收官:月“会”群星、日全食、流星雨扎堆亮相

2021-12

陕西新增1例境外归国集中隔离期满确诊病例

2021-12

内蒙古新增本土确诊病例91例

2021-12

宁夏银川警方破获2起以“互联网+物流寄递”为模式的特大毒品案件

2021-12