单片机要这样保护临界区

news2024/11/18 17:32:13

目录

一、概述

二、临界区保护测试场景

三、临界区保护三种实现

1、入门做法

2、改进做法

3、终极做法

四、附录---PRIMASK寄存器设置函数在各 IDE 下实现


一、概述

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

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

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

二、临界区保护测试场景

关于临界区保护的测试场景无非两种。第一种场景是受保护的多个任务间并无关联,也不会互相嵌套,如下面的代码所示,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() 这对临界区保护函数的实现环节了:

1、入门做法

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

void enter_critical(void)
{
    // 关闭系统全局中断
    __disable_irq();
}

void exit_critical(void)
{
    // 打开系统全局中断
    __enable_irq();
}

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;
    }
}

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裸机环境下临界区保护的三种实现便介绍完毕了,感谢大家阅读。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/726881.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

程序设计-编程题

CISP-PTE-编程题 2014 #include <iostream> using namespace std;double H(int n,double x) {if (n > 1)return 2 * x*H(n - 1, x) - 2 * (n - 1)*H(n - 2, x);if (n 0)return 1;if (n 1)return 2 * x; }int main() {int n;double x,result;cin >> n;cin >…

torch_geometric安装避坑

总流程&#xff1a; 1. 查看cuda, pytorch, python版本&#xff1b; 2. 依次安装torch_scatter, torch_sparse, torch_cluster, torch_spline&#xff1b; 3. 安装torch_geometric 一. 查看cuda, pytorch, python版本 可选方法如下图&#xff0c;其中该样例的cuda, pytorc…

注意力机制[矩阵]

每一个输入的向量( Embedding后的向量)&#xff0c;均有q,k,v,三个东西。其中q由下图所生成 I矩阵有a1,a2,a3,a4组成&#xff0c;Wq为权重矩阵&#xff0c;将I与Wq相乘求得Q(q1,q2,q3,q4)。K和V与I同理均可求得。 将求得出来的K&#xff0c;转置为竖向量与Q相乘&#xff0c;就…

【UE5 Cesium】12-Cesium for Unreal 去除左下角的icon

问题 在视口左下角的icon如何去除&#xff1f; 解决方法 打开“CesiumCreditSystemBP” 将“Credit Widget Class”一项中的“ScreenCredit”替换为“ScreenCreditWidget” 编译之后icon就不显示了。

戴尔游匣G16 7630原厂Win11系统带F12 Support Assist OS Recevory恢复功能

戴尔游匣G16 7630原厂Win11系统带F12 Support Assist OS Recevory恢复功能 各机型预装系统&#xff0c;带所有dell主题壁纸、dell软件驱动、带戴尔SupportAssist OS Recovery恢复功能&#xff0c;一次性恢复成新机状态&#xff0c;并且以后不用重装系统&#xff0c;直接恢复即…

MVC终极版

MVC终极版 1.把上次的代码打包为架包。2.通过basedao优化代码1.前言 3.后台代码servlet的优化。4前台代码展示 再次优化代码&#xff0c;上两次优化&#xff0c;都不是最完善的&#xff0c;这一次是最完善的了。 1.把上次的代码打包为架包。 选中两个文件&#xff0c;右键然后选…

Unreal 5 Lyra初学者游戏包概览笔记

Lyra初学者游戏包相关的官方视频有两个&#xff1a; Lyra初学者游戏包概览 https://www.bilibili.com/video/BV16B4y197Zy/Lyra跨平台UI开发 https://www.bilibili.com/video/BV1mT4y167Fm/ 这个笔记主要记录的是Lyra初学者包概览笔记的相关内容。里面介绍了如何创建关卡、输…

网络部署的思路

网络部署的思路——网络搭建的步骤 1.拓扑设计——IP地址的规划&#xff08;子网划分&#xff0c;子网汇总&#xff09; 2.实施 2.1 搭建拓扑 2.2 底层——给所有需要配置IP地址的网络节点&#xff0c;配置一个合法的IP地址 2.3 路由——全网可达 2.4 优化 策略——安全方面的…

【Linux初阶】基础IO - FILE结构体中的缓冲区

&#x1f31f;hello&#xff0c;各位读者大大们你们好呀&#x1f31f; &#x1f36d;&#x1f36d;系列专栏&#xff1a;【Linux初阶】 ✒️✒️本篇内容&#xff1a;库函数和系统函数的刷新差异&#xff0c;缓冲区的意义、刷新策略、存在位置、文件写入的逻辑&#xff0c;缓冲…

Unity与iOS交互(1)——需要了解的IOS相关知识

【前言】 以下只是简要介绍&#xff0c;详细的内容需要自己去看链接 【Objective-C基础知识】 .h .m .mm .cpp文件区别 .h是头文件扩展名。头文件包含类&#xff0c;类型&#xff0c;函数和常数的声明&#xff0c;这里的定义一般是Public的 .m是实现文件扩展名。其包含源代…

云环境利用工具-----cf

简介 CF 是一个云环境利用框架&#xff0c;适用于在红队场景中对云上内网进行横向、SRC 场景中对 Access Key 即访问凭证的影响程度进行判定、企业场景中对自己的云上资产进行自检等等。 项目地址&#xff1a;https://github.com/teamssix/cf 使用手册&#xff1a;https://wi…

Java POI excel单元格背景色(填充)、字体颜色(对齐)、边框(颜色)、行高、列宽设置

文章目录 1、Excel Cell单元格背景色颜色名称对照关系2、Excel Cell单元格背景填充样式颜色填充对照关系3、Excel Cell字体样式设置对照图4、Excel 行高、列宽设置5、Excel单元格边框设置边框类型图片对比附一&#xff1a;一些问题1、关于列宽使用磅*20的计算方式2、关于行高使…

常用数据预处理与特征选择方法总结记录

在很多机器学习或者是深度学习建模之前&#xff0c;对于数据的处理尤为重要&#xff0c;选择合适的数据预处理方法和特征构建算法对于后续模型的结果有着很大的影响作用&#xff0c;这里的主要目的就是想记录总结汇总常用到的处理方法&#xff0c;学习备忘&#xff01; 数据清洗…

Docker集群部署-MySQL主从复制

实验目的 利用Docker实现MySQL主从复制架构的部署&#xff0c;实现1主1从集群配置。 实验准备 要求实验主机能够连接外网&#xff0c;已经正确安装Docker&#xff0c;并关闭防火墙和selinux。 【实验步骤】 新建主服务器容器实例3307 # docker run -p 3307:3306 --name my…

mysql —案例复杂查询+索引使用+DBeaver中创建索引

前言 接上章 我们 对一个简单的选课功能进行 设计分析 实际上在工作中 拿到一个需求&#xff0c;也是这样的一个分析过程 一个份 需求文档原型 出来&#xff0c;只要是你负责这个模块&#xff0c;就需要你自己建表建库&#xff0c;设计接口文档&#xff0c;也许现在有的公司…

AIGC|FineTune工程之LoRa高效参数微调

徐辉 | 后端开发工程师 一、引言 随着深度学习和自然语言处理技术的快速发展&#xff0c;大型预训练语言模型&#xff08;如GPT、Vicuna、Alpaca、Llama、ChatGLM等&#xff09;在各种应用场景中取得了显著的成果。然而&#xff0c;从零开始训练这些模型需要大量的计算资源和…

Cristiano Linux学习小结

一、linux基础 1.1 基本概述 1、Linux组成&#xff1a;内核 Shell 文件系统 应用程序 2、内核&#xff08;Kernel&#xff09;&#xff0c;即核心程序。实现操作系统的基本功能(进程管理、内存管理、进程间通信、虚拟文件系统和网络接口)&#xff0c;决定着系统的性能和稳定性。…

递归典型例题:汉诺塔问题

文章目录 1. 什么是汉诺塔2. 汉诺塔的解题步骤3. 代码实现汉诺塔 1. 什么是汉诺塔 1. 汉诺塔的来源 一位法国数学家曾编写过一个印度的古老传说&#xff1a;在世界中心拿勒斯的圣庙里边&#xff0c;有一块黄铜板&#xff0c;上边插着三根宝柱。印度教的主神梵天在创造世界的时…

div绑定键盘点击事件

为箭头绑定绑定键盘方向键 <div class"toggle-nav"><spanv-if"leftToggleSt"click"toggleGoods(1)"keyup.left"toggleGoods(1)"class"toggle-left"><a-icon type"left" class"icon" /&…

Linux系统设置

Linux的系统设置 01 选择“Install CentOS7” 02 选择安装界面的语言 03 选择时区&#xff0c;这里选择上海 04 选择安装类型&#xff0c;选择最小安装即可&#xff0c;不需要图形界面与其他的组件 05 选择安装位置&#xff0c;自定义分区 06 我要配置分区&#xff0c;进行…