0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Cortex-M裸机环境下临界区保护的三种实现

Dp1040 来源:痞子衡嵌入式 2023-07-07 09:06 次阅读

今天给大家分享的是Cortex-M裸机环境下,临界区保护的三种实现。

嵌入式玩过 RTOS 的小伙伴,想必都对 OS_ENTER_CRITICAL()、OS_EXIT_CRITICAL() 这个功能代码特别眼熟,在 RTOS 里常常会有多任务(进程)处理,有些情况下一些特殊操作(比如 XIP 下 Flash 擦写、低功耗模式切换)不能被随意打断,或者一些共享数据区不能被无序访问(A 任务正在读,B 任务却要写),这时候就要用到临界区保护策略了。

所谓临界区保护策略,简单说就是系统中硬件临界资源或者软件临界资源,多个任务必须互斥地对它们进行访问。RTOS 环境下有现成的临界区保护接口函数,而裸机系统里其实也有这种需求。在裸机系统里,临界区保护主要就是跟系统全局中断控制有关。

在之前的文章《嵌入式MCU中通用的三重中断控制设计》中,介绍的第三重也是最顶层的中断控制是系统全局中断控制,今天就从这个系统全局中断控制使用入手给大家介绍三种临界区保护做法:

一、临界区保护测试场景

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

voidcritical_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 也应该是受保护的。

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

//做任务3
do_task3();
}

voidcritical_section_test(void)
{
//进入临界区
enter_critical();
//做受保护的任务1
do_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 时,系统全局中断是打开的(对执行优先级无影响)。

398b30f0-1c5e-11ee-962d-dac502259ad0.png

看到这,你应该明白了 __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("MSR primask,%0"::"r"(priMask):"memory");
}

__STATIC_FORCEINLINEuint32_t__get_PRIMASK(void)
{
uint32_tresult;

__ASMvolatile("MRS%0,primask":"=r"(result));
return(result);
}
至此,Cortex-M裸机环境下临界区保护的三种实现便介绍完毕了,感谢大家阅读。





审核编辑:刘清
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • RTOS
    +关注

    关注

    20

    文章

    776

    浏览量

    118796
  • 中断控制器
    +关注

    关注

    0

    文章

    59

    浏览量

    9373
  • Cortex-M
    +关注

    关注

    2

    文章

    224

    浏览量

    29574
  • 裸机
    +关注

    关注

    0

    文章

    37

    浏览量

    6154

原文标题:单片机要这样保护临界区

文章出处:【微信号:玩点嵌入式,微信公众号:玩点嵌入式】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    MDK Eclipse 环境搭建 cortex-M Cortex-A 多核混合调试

    MDK Eclipse 环境搭建 cortex-M Cortex-A 多核混合调试
    发表于 03-15 16:11

    你总得知道你为什么要用Cortex-M

    丰富,种类众多,到底该怎样选择适合自己产品的芯片类型?Cortex系列组合大体上分为三种类别: Cortex-M—面向各类嵌入式应用的微控制器内核Cortex-R—面向实时应用的高性能
    发表于 06-09 16:50

    Cortex-MCortex-A认识ARM处理器

    可通过DesignStart免费获得许可费非常适用于智能传感器和片上混合信号系统(SoC)三种高度优化的低功耗模式2.Cortex-M0 +Cortex-M中面积最小,功耗最低的处理器8位处理器成本
    发表于 08-23 10:04

    介绍一Cortex-M内核中的精确延时方法

    本文介绍一Cortex-M内核中的精确延时方法前言为什么要学习这种延时的方法?很多时候我们跑操作系统,就一般会占用一个硬件定时器——SysTick,而我们一般操作系统的时钟节拍一般是设置
    发表于 08-12 06:11

    cortex-m各种微架构的区别是什么?

    cortex-m单片机在arm产品中的位置是哪里?cortex-m 单片机的类别有哪些?cortex-m各种微架构的区别是什么?
    发表于 11-04 06:00

    ARM Cortex-M堆栈机制介绍

      大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家介绍的是ARM Cortex-M堆栈机制。  今天给大家分享的这篇依旧是2016年之前痞子衡写的技术文档,花了点时间重新编排了一
    发表于 12-16 06:26

    ARM Cortex-M内核的相关资料推荐

      大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家介绍的是ARM Cortex-M功能模块,不过侧重点是款安全特性处理器。  ARM Cortex-M处理器家族发展至今(2020),已有
    发表于 12-27 07:21

    分享的是Cortex-M中断向量表原理及其重定向方法

    大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家分享的是Cortex-M中断向量表原理及其重定向方法。接着前文《嵌入式Cortex-M裸机环境
    发表于 01-25 06:47

    ARM Cortex-M 开发实战指南入门篇(

    Cortex-M下载程序及调试Cortex-M三种启动模式对应的存储介质均是芯片内置的,它们是:1)用户闪存 = 芯片内置的Flash。2)SRAM = 芯片内置的RAM,就是内存
    发表于 04-20 17:35

    介绍Cortex-A和Cortex-M的TrustZone之间的差异

    相信关注安全和嵌入式的开发者对TrustZone都不陌生,最近看到有网友在问Cortex-A和Cortex-M的TrustZone之间的差异,我们来简单介绍。Arm在2003年的Armv6开始
    发表于 07-13 14:45

    Arm Cortex-M处理器—Cortex-M85介绍

    Arm发布了新一代的Cortex-M处理器,Cortex-M85。简单粗暴的打个比方:Cortex-M85 ≈ Cortex-M7TrustZoneHelium(
    发表于 07-15 14:59

    Cortex-M裸机环境临界保护三种实现

    今天给大家分享的是Cortex-M裸机环境临界保护三种
    的头像 发表于 09-08 09:23 2971次阅读
    <b class='flag-5'>Cortex-M</b><b class='flag-5'>裸机</b><b class='flag-5'>环境</b>下<b class='flag-5'>临界</b>区<b class='flag-5'>保护</b>的<b class='flag-5'>三种</b><b class='flag-5'>实现</b>

    Cortex-M中断向量表原理及其重定向方法~

    大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家分享的是Cortex-M中断向量表原理及其重定向方法。接着前文《嵌入式Cortex-M裸机环境
    发表于 12-01 12:21 9次下载
    <b class='flag-5'>Cortex-M</b>中断向量表原理及其重定向方法~

    痞子衡嵌入式:嵌入式Cortex-M中断向量表原理及其重定向方法

      大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家分享的是Cortex-M中断向量表原理及其重定向方法。  接着前文 《嵌入式Cortex-M裸机环境
    发表于 12-01 12:36 8次下载
    痞子衡嵌入式:嵌入式<b class='flag-5'>Cortex-M</b>中断向量表原理及其重定向方法

    分享一下Cortex-M裸机环境临界保护的几种实现方法

    RTOS有临界区,裸机依然有临界区。今天给大家分享一下Cortex-M裸机环境
    发表于 06-13 09:08 317次阅读
    分享一下<b class='flag-5'>Cortex-M</b><b class='flag-5'>裸机</b><b class='flag-5'>环境</b>下<b class='flag-5'>临界</b>区<b class='flag-5'>保护</b>的几种<b class='flag-5'>实现</b>方法