今天给大年夜家分享的是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裸机情况下临界区保护的三种实现已经讲述完毕,你学废了吗?
义务编辑:haq
责任编辑: admin
中国河南网所有文字、图片、视频、音频等资料均来自互联网,不代表本站赞同其观点,本站亦不为其版权负责。相关作品的原创性、文中陈述文字以及内容数据庞杂本站无法一一核实,如果您发现本网站上有侵犯您的合法权益的内容,请联系我们,本网站将立即予以删除!