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

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

3天内不再提示

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

strongerHuang ? 来源:痞子衡嵌入式 ? 作者:痞子衡 ? 2021-09-08 09:23 ? 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

今天给大家分享的是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 时,系统全局中断是打开的(对执行优先级无影响)。

345fd67a-1018-11ec-8fb8-12bb97331649.png

看到这,你应该明白了 __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

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

    关注

    6068

    文章

    45040

    浏览量

    652563
  • 嵌入式
    +关注

    关注

    5159

    文章

    19738

    浏览量

    318790
  • RTOS
    +关注

    关注

    24

    文章

    851

    浏览量

    121354

原文标题:单片机非RTOS时,临界区保护的实现办法

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

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    MEMS中的三种测温方式

    在集成MEMS芯片的环境温度测量领域,热阻、热电堆和PN结原理是三种主流技术。热阻是利用热敏电阻,如金属铂或注入硅的温度电阻系数恒定,即电阻随温度线性变化的特性测温,电阻变化直接对应绝对温度,需恒流源供电。
    的头像 发表于 07-16 13:58 ?791次阅读
    MEMS中的<b class='flag-5'>三种</b>测温方式

    介绍三种常见的MySQL高可用方案

    在生产环境中,为了确保数据库系统的连续可用性、降低故障恢复时间以及实现业务的无缝切换,高可用(High Availability, HA)方案至关重要。本文将详细介绍三种常见的 MySQL 高可用
    的头像 发表于 05-28 17:16 ?459次阅读

    redis三种集群方案详解

    在Redis中提供的集群方案总共有三种(一般一个redis节点不超过10G内存)。
    的头像 发表于 03-31 10:46 ?801次阅读
    redis<b class='flag-5'>三种</b>集群方案详解

    DeepSeek企业级部署RakSmart裸机环境准备指南

    RakSmart裸机环境中部署DeepSeek的企业级环境准备指南,内容涵盖关键步骤和注意事项,主机推荐小编为您整理发布DeepSeek企业级RakSmart裸机云部署指南。
    的头像 发表于 03-24 10:07 ?425次阅读

    介绍三种数据保护策略的特点与适用场景

    在企业IT环境中,数据保护是不可忽视的重要环节,而复制(Replication)、快照(Snapshot)和备份(Backup)是三种常见的策略。它们在数据恢复、业务连续性以及灾难恢复中扮演着不同的角色,但很多企业在选择数据
    的头像 发表于 03-21 11:46 ?933次阅读

    瑞萨RA8快速上手指南:Cortex-M85内核瑞萨RA8开发环境搭建 并点亮一个LED

    因为Cortex-M内核,瑞萨RA8系列单片机支持多种市面上常见的开发环境,像Keil MDK、IAR EWARM等,而本文讲述的是瑞萨自家官方的IDE(e2 studio)。
    的头像 发表于 03-17 14:35 ?1085次阅读
    瑞萨RA8快速上手指南:<b class='flag-5'>Cortex-M</b>85内核瑞萨RA8开发<b class='flag-5'>环境</b>搭建 并点亮一个LED

    示波器的三种触发模式

    示波器的触发方式不仅影响波形捕捉的时机,还决定了显示的波形是否稳定。 常见的触发模式有三种: 单次触发 (Single)、 正常触发 (Normal)和 自动触发 (Auto)。下面将对这三种触发
    的头像 发表于 01-07 11:04 ?8275次阅读
    示波器的<b class='flag-5'>三种</b>触发模式

    如何使用Ozone分析Cortex-M异常

    Ozone可以帮助用户快速分析和查找导致CPU故障的软件bug。本文解释如何使用Ozone的调试功能,深入了解Cortex-M架构上的这些错误。
    的头像 发表于 11-29 11:14 ?1812次阅读
    如何使用Ozone分析<b class='flag-5'>Cortex-M</b>异常

    三种封装形式的400G光模块概述

    本文主要就三种封装形式(QSFP-DD、OSFP、QSFP112)的400G光模块做了简单的梳理,从为什么会有400G光模块问世?400G光模块在三种封装形式的各个具体型号(以短距离为主,最远2km),
    的头像 发表于 11-11 11:35 ?1341次阅读
    <b class='flag-5'>三种</b>封装形式<b class='flag-5'>下</b>的400G光模块概述

    I2S有左对齐,右对齐跟标准的I2S三种格式,那么这三种格式各有什么优点呢?

    大家好,关于I2S格式,有两个疑问请教一 我们知道I2S有左对齐,右对齐跟标准的I2S三种格式,那么这三种格式各有什么优点呢? 而且对于标准的I2S格式,32FS传输16bit的数据,48fs传输24bit的数据,最低位会移
    发表于 10-21 08:23

    mosfet的三种工作状态及工作条件是什么

    )的不同,可以工作在三种主要状态:截止状态、线性和饱和。 1. 截止状态 工作状态描述 : 当VGS小于MOSFET的开启电压(VGS(TH))时,MOSF
    的头像 发表于 10-06 16:51 ?6537次阅读

    电流保护装置的接线方式主要有哪三种

    三种主要的电流保护装置接线方式的介绍: 1. 星形接线(Star Connection) 星形接线,也称为Y型接线,是一常见的电流保护装置接线方式。在这种接线方式中,电流
    的头像 发表于 09-13 15:48 ?2192次阅读

    如何利用三种 SOT-563 封装实现共同布局

    电子发烧友网站提供《如何利用三种 SOT-563 封装实现共同布局.pdf》资料免费下载
    发表于 09-10 14:25 ?0次下载
    如何利用<b class='flag-5'>三种</b> SOT-563 封装<b class='flag-5'>实现</b>共同布局

    单片机的三种总线结构

    单片机的三种总线结构包括地址总线(Address Bus, AB)、数据总线(Data Bus, DB)和控制总线(Control Bus, CB)。这三种总线在单片机内部及与外部设备之间的数据传输
    的头像 发表于 09-10 11:32 ?7323次阅读

    vim的三种工作模式有哪些

    Vim是一个功能强大的文本编辑器,它具有三种工作模式:普通模式、插入模式和命令行模式。以下是对这三种模式的介绍: 普通模式(Normal Mode) 普通模式是Vim的默认模式,当启动Vim时,它会
    的头像 发表于 08-30 14:52 ?1653次阅读