当前位置:首页 > 公众号精选 > 小麦大叔
[导读]大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家分享的是Cortex-M裸机环境下临界区保护的三种实现。搞嵌入式玩过RTOS的朋友想必都对OS_ENTER_CRITICAL()、OS_EXIT_CRITICAL()这个功能代码对特别眼熟,在RTOS里常常会有多任务(进程)...

大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家分享的是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裸机环境下临界区保护的三种实现痞子衡便介绍完毕了,掌声在哪里~~~

—— The End ——
推荐好文  点击蓝色字体即可跳转☞ 干货分享:CAN总线详解 整车的控制只需要一条线
☞ 推荐一个直接用于项目开发的PID库!很好用,很稳定☞ 精辟,16张图说透Modbus-RTU协议☞ 推荐一款我私藏已久的串口示波神器
欢迎转发、留言、点赞、分享给你的朋友,感谢您的支持!


点击上方即可关注公众号



分享   点赞   在看 ❤️ 

以“三连”行动支持优质内容!

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

近日有网友在职场内容社区发布消息称,据相关员工内部消息,某大厂开始裁员,裁员比例10%到30%。该消息中点名的PCG、CSIG事业群,均为腾讯公司下属事业群。从腾讯多位知情人士了解到,该消息为假消息。

关键字: 腾讯 裁员

业内消息,近日杭州市规划部门称位于浙江的“蚂蚁之江总部”已退地。值得一提的是,蚂蚁二期地块早已退地,如今一期的退地也得到了官方回复。

关键字: 蚂蚁集团 阿里巴巴

近日从国家知识产权局官网获悉,华为技术有限公司公布了一项名为“一种光模块、光通信设备及光通信系统“的专利,公开号CN117767976A。专利摘要显示,本申请涉及光通信技术领域,尤其涉及一种光模块、光通信设备及光通信系统...

关键字: 华为 光通信 专利

业内消息,近日外媒报道国内游戏巨头腾讯内部正在发生一场巨变,一款可爱角色克服障碍的简单易玩游戏已经被放在了优先开发位置,而以往备受青睐的一款高成本、复杂的外国IP手游则被冷落。

关键字: 腾讯 游戏

市场分析机构 Canalys 发布的最新数据显示,2023 年中国大陆 PC 市场(不含平板)出货量前五的厂商中,除华为实现增长(11%)外其余均下跌,其中戴尔的出货量仅剩 314.8 万台,环比下滑 44%,近乎腰斩。

关键字: 裁员 芯片 戴尔

随着科技的飞速发展,计算机已经成为我们日常生活和工作中不可或缺的工具。然而,有时我们可能会遇到一些棘手的问题,如忘记BIOS密码。BIOS(基本输入输出系统)是计算机启动过程中的重要部分,它负责初始化硬件并加载操作系统。...

关键字: bios密码 计算机

随着科技的不断发展,人们对于现实世界的感知与体验方式也在不断地变革。其中,增强现实(AR)和虚拟现实(VR)技术作为近年来备受瞩目的新兴科技,正在改变我们与世界的交互方式。本文将详细探讨AR技术和VR技术的定义、应用以及...

关键字: AR技术 VR技术 虚拟现实

语音识别模块是一种基于嵌入式技术的模块,主要用于将人类语音中的词语转换成计算机可读的格式,实现与主芯片的通讯。该模块通常包括语音识别芯片和其他附属电路,使得开发者能够方便地将其嵌入到各种智能化产品中,实现人机语音交互。

关键字: 语音识别 嵌入式技术

语音识别系统是一种将人类的口头语言转化为可处理的文本或指令的技术,是人机交互、智能设备和现代通信技术中的重要组成部分。其构建涉及多个复杂而精密的模块,每个部分都承担着不同的功能以确保准确无误地理解并转化语音信息。

关键字: 语音识别 声学处理

语音识别技术,作为人工智能领域的重要分支,近年来取得了显著的进步。它能够将人类语音转化为计算机可理解的文本或指令,为人们的生活和工作带来了极大的便利。那么,语音识别技术究竟可以做什么呢?本文将从多个角度探讨其应用与价值。

关键字: 语音识别 智能家居
关闭
关闭