RTOS中临界区嵌套保护的实现原理(基于RT-Thread)

0 前言

什么是临界区(临界段)?
裸机编程中由于不涉及线程和线程切换,因此没有临界区这一个概念。在RTOS中由于存在线程切换等场景,便有了临界区这个概念。简单来说,临界区就是不允许被中断的代码区域。什么时候代码执行的过程会被打断:一个是线程切换,一个是异常事件。对于RT-Thread而言实际上都是异常事件导致代码执行过程被中断,因为RT-Thread是在PendSV事件内进行线程切换的。
什么样的代码区域应该作为临界区保护起来?
假如我们有2个线程A和B,它们都有可能使用同一个串口向外打印数据,而我们希望线程A和B在执行printf这个操作时是不会被打断的,否则我们看到的串口打印数据将杂乱无章。下面是一个例子:

void thread_a(void *arg)
{
    for (;;)
    {
        printf("A thread!\r\n");
    }
}

void thread_b(void *arg)
{
    for (;;)
    {
        printf("B thread!\r\n");
    }
}

假设线程A和线程B是按照时间片轮转进行调度的,那么就有可能出现线程A正在打印"A thread!\r\n"时被线程B获得CPU使用权进而打印线程B的"B thread!\r\n",最后呈现在串口上的数据杂乱无章。这不是我们希望看到的结果,因此我们需要将printf作为临界区保护起来,以RT-Thread临界区保护API为例,修改后的代码如下:

void thread_a(void *arg)
{
    for (;;)
    {
        rt_enter_critical(); // 进入临界区
        printf("A thread!\r\n");
        rt_exit_critical(); // 退出临界区
    }
}

void thread_b(void *arg)
{
    for (;;)
    {
        rt_enter_critical(); // 进入临界区
        printf("B thread!\r\n");
        rt_exit_critical(); // 退出临界区
    }
}

1 RTOS中临界区保护的实现原理(基于RT-Thread)

前面我们已经知道导致代码段被中断的原因就是发生了异常事件,为了实现对临界区的保护可以选择最简单直接的方法:关闭全部中断(NMI fault和硬fault除外),为了避免直接关闭中断带来的不良影响,RT-Thread为我们提供了2种方案:
(1)开关全部中断
(2)使能/失能线程调度

1.1 开关全部中断

ARM的M3/M4/M7内核为了快速开关中断,专门设置了一条CPS指令,共有以下4种用法:

CPSID I ;PRIMASK=1;关中断
CPSIE I ;PRIMASK=0;开中断
CPSID F ;FAULTMASK=1, ;关异常
CPSIE F ;FAULTMASK=0 ;开异常

有关上述寄存器的定义如下:
在这里插入图片描述
这里我们并不想关闭硬件fault,因此只会使用CPSID I和CPSIE I这2条指令。对于没有出现临界区嵌套的情况,直接在进入临界区前关中断,退出临界区后关中断即可实现对临界区的保护。但如果临界区出现嵌套的话,只是单纯使用开关中断显然无法满足我们要求,看看RT-Thread如何解决这个问题的:

// 关全部中断
rt_base_t rt_hw_interrupt_disable(void);
;/*
; * rt_base_t rt_hw_interrupt_disable();
; */
rt_hw_interrupt_disable    PROC
    EXPORT  rt_hw_interrupt_disable
    MRS     r0, PRIMASK
    CPSID   I
    BX      LR
    ENDP
// 开全部中断
void rt_hw_interrupt_enable(rt_base_t level);
;/*
; * void rt_hw_interrupt_enable(rt_base_t level);
; */
rt_hw_interrupt_enable    PROC
    EXPORT  rt_hw_interrupt_enable
    MSR     PRIMASK, r0
    BX      LR
    ENDP

可以看到,我们使能中断时是需要传入一个rt_base_t类型参数,这个就是RT-Thread实现临界区嵌套保护的关键。我们使用时只需要按照如下格式进行嵌套保护即可,下面是一个一重临界区嵌套保护的示例:

rt_base_t level1;
rt_base_t level2;
void thread_a(void *arg)
{
    for (;;)
    {
        level1 = rt_hw_interrupt_disable(); 
        printf("A thread!\r\n");
        level2 = rt_hw_interrupt_disable();
        printf("A thread!!\r\n");
        rt_hw_interrupt_enable(level2); 
        printf("A thread!!!\r\n");
        rt_hw_interrupt_enable(level1);
    }
}

结合前面的汇编代码,我们很容易可以发现,只有在执行最后一个rt_hw_interrupt_enable操作时才会打开全部中断,真正实现嵌套临界区的保护。
它的实现原理就是使用全局变量保存进入临界区之前的全部中断开关状态,然后退出临界区时根据这个开关状态来进行全部中断开关。根据临界区嵌套保护语句的顺序,显然只有执行了最后一个rt_hw_interrupt_enable才会打开全部中断。

1.2 使能/失能线程调度

相比前面直接开关中断来实现临界区保护,使能/失能线程调度则显得“温柔”很多,它并不会长时间关闭中断对我们的一些中断事件(例如SysTick时间基准)造成影响,如果我们的线程并不会被中断服务函数内的操作影响,推荐使用使能/失能线程调度的方法去实现临界段保护,RT-Thread提供的相关函数如下:

// 失能线程调度
/**
 * This function will lock the thread scheduler.
 */
void rt_enter_critical(void)
{
    register rt_base_t level;

    /* disable interrupt */
    level = rt_hw_interrupt_disable();

    /*
     * the maximal number of nest is RT_UINT16_MAX, which is big
     * enough and does not check here
     */
    rt_scheduler_lock_nest ++;

    /* enable interrupt */
    rt_hw_interrupt_enable(level);
}
;/*
; * rt_base_t rt_hw_interrupt_disable();
; */
rt_hw_interrupt_disable    PROC
    EXPORT  rt_hw_interrupt_disable
    MRS     r0, PRIMASK
    CPSID   I
    BX      LR
    ENDP
// 使能线程调度
/**
 * This function will unlock the thread scheduler.
 */
void rt_exit_critical(void)
{
    register rt_base_t level;

    /* disable interrupt */
    level = rt_hw_interrupt_disable();

    rt_scheduler_lock_nest --;
    if (rt_scheduler_lock_nest <= 0)
    {
        rt_scheduler_lock_nest = 0;
        /* enable interrupt */
        rt_hw_interrupt_enable(level);

        if (rt_current_thread)
        {
            /* if scheduler is started, do a schedule */
            rt_schedule();
        }
    }
    else
    {
        /* enable interrupt */
        rt_hw_interrupt_enable(level);
    }
}
;/*
; * void rt_hw_interrupt_enable(rt_base_t level);
; */
rt_hw_interrupt_enable    PROC
    EXPORT  rt_hw_interrupt_enable
    MSR     PRIMASK, r0
    BX      LR
    ENDP

(1)rt_enter_critical函数
首先关闭全部中断,将rt_scheduler_lock_nest自增1,然后将中断状态还原到rt_enter_critical函数最开始的状态。
(2)rt_exit_critical函数
首先关闭全部中断,将rt_scheduler_lock_nest自减1,如果rt_scheduler_lock_nest<=0表示临界区保护结束需要退出临界区,这时rt_scheduler_lock_nest=0然后将中断状态还原到函数最开始的状态,如果调度已经开始则发起一次调度尽可能保证更高优先级线程能够得到及时运行。如果rt_scheduler_lock_nest>0表示临界区保护还未结束,将中断状态还原到函数最开始的状态。
实现临界区嵌套保护的核心就是rt_scheduler_lock_nest,借助嵌套临界区保护操作成对出现这一特点,使用rt_scheduler_lock_nest调度锁表征当前临界区保护嵌套执行情况,当rt_scheduler_lock_nest<=0时则可以退出临界区保护,因此rt_exit_critical()和rt_exit_critical()无需人为定义全局变量就可以实现临界区嵌套保护,使用起来更加方便。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

从PDF到高清图片:一步步学习如何转换PDF文件为高清图片

引言 PDF文件是一种便携式文档格式&#xff08;Portable Document Format&#xff09;&#xff0c;最初由Adobe Systems开发&#xff0c;用于在不同操作系统和软件之间保持文档格式的一致性。PDF文件通常包含文本、图片、图形等多种元素&#xff0c;并且可以以高度压缩的方式存…

containerd配置HTTP私仓

文章目录 1. &#x1f6e0;️ 基础环境配置2. &#x1f433; Docker安装3. &#x1f6a2; 部署Harbor&#xff0c;HTTP访问4. &#x1f4e6; 部署ContainerD5. &#x1f504; 修改docker配置文件&#xff0c;向harbor中推入镜像6. 配置containerd6.1. 拉取镜像验证6.2. 推送镜像…

布隆过滤器:基于哈希函数的原理、应用解析

文章目录 一、引言1、布隆过滤器的概念简介2、布隆过滤器是基于哈希函数的强大工具 二、布隆过滤器基础知识1、布隆过滤器的工作原理2、布隆过滤器的空间效率分析3、布隆过滤器的性能特点 三、布隆过滤器的应用场景1、数据库查询优化2、例子 四、布隆过滤器的实现与优化1、常见…

libusb Qt使用记录

1.libusb 下载 &#xff0c;选择编译好的二进制文件&#xff0c;libusb-1.0.26-binaries.7z libusb Activity 2. 解压 3. 在 Qt Widgets Application 或者 Qt Console Application 工程中导入库&#xff0c;Qt 使用的是 minggw 64编译器&#xff0c;所以选择libusb-MinGW-x64。…

用户故事与用例:软件开发的双翼

用户故事与用例&#xff1a;软件开发的双翼 引言 在敏捷软件开发中&#xff0c;理解用户需求是成功交付高质量产品的关键。本文将详细介绍两种收集和定义需求的流行技术&#xff1a;用户故事和用例。我们将探讨它们的理论基础、区别、联系&#xff0c;以及如何在实际工作中应…

提升LLM效果的几种简单方法

其实这个文章想写很久了&#xff0c;最近一直在做大模型相关的产品&#xff0c;经过和团队成员一段时间的摸索&#xff0c;对大模型知识库做一下相关的认知和总结。希望最终形成一个系列。 对于知识库问答&#xff0c;现在有两种方案&#xff0c;一种基于llamaindex&#xff0…

2024年04月在线IDE流行度最新排名

点击查看最新在线IDE流行度最新排名&#xff08;每月更新&#xff09; 2024年04月在线IDE流行度最新排名 TOP 在线IDE排名是通过分析在线ide名称在谷歌上被搜索的频率而创建的 在线IDE被搜索的次数越多&#xff0c;人们就会认为它越受欢迎。原始数据来自谷歌Trends 如果您相…

WebSocket用户验证

在WebSocket中&#xff0c;如何携带用户的验证信息 一、在OnMessage中进行验证 客户端在连接到服务器后&#xff0c;客户端通过发送消息&#xff0c;服务器端在OnMessage方法中&#xff0c;进行信息验证&#xff0c;这种方式需要将用户身份验证及接收用户消息进行混合处理&am…

分布式全闪占比剧增 152%,2023 年企业存储市场报告发布

近日&#xff0c;IDC 发布了 2023 年度的中国存储市场报告。根据该报告&#xff0c;在 2023 年软件定义存储的市场占比进一步扩大&#xff0c;分布式全闪的增长尤其亮眼&#xff0c;其市场份额从 2022 年的 7% 剧增到 2023 年的 17.7%&#xff0c;增长了 152%。 01 中国企业存…

备战蓝桥杯---贪心刷题2

话不多说&#xff0c;直接看题&#xff1a; 首先我们大致分析一下&#xff0c;先排序一下&#xff0c;Kn&#xff0c;那就全部选。 当k<n时&#xff0c;k是偶数&#xff0c;那么结果一定非负&#xff0c;因为假如负数的个数有偶数个&#xff0c;那么我们成对选它&#xff0…

【问题处理】银河麒麟操作系统实例分享,鲲鹏服务器GaussDB测试ping延迟过高问题

1.问题环境 系统环境 物理机 网络环境 私有网络 硬件环境 机型 TaiShan 200 (Model 2280) (VD) 处理器 HUAWEI Kunpeng 920 5250 内存 32GB*16 显卡 无 主板型号 BC82AMDDRE 架构 ARM 固件版本 iBMC固件版本 3.03.00.31 (U82) 单板ID 0x00a9 BIOS版本 1.8…

SpringBoot mybatis-starter解析

mybatis-starter使用指南 自动检测工程中的DataSource创建并注册SqlSessionFactory实例创建并注册SqlSessionTemplate实例自动扫描mappers mybatis-starter原理解析 注解类引入原理 查看对应的autoconfigure包 MybatisLanguageDriverAutoConfiguration 主要是协助使用注解来…

数论与线性代数——整除分块【数论分块】的【运用】【思考】【讲解】【证明(作者自己证的QWQ)】

文章目录 整除分块的思考与运用整除分块的时间复杂度证明 & 分块数量整除分块的公式 & 公式证明公式证明 代码code↓ 整除分块的思考与运用 整除分块是为了解决一个整数求和问题 题目的问题为&#xff1a; ∑ i 1 n ⌊ n i ⌋ \sum_{i1}^{n} \left \lfloor \frac{n}{…

用ChatGPT出题,完全做不完

最近小朋友正在学习加减法&#xff0c;正好利用ChatGPT来生成加减法练习题&#xff0c;小朋友表示够了&#xff0c;够了&#xff0c;完全做不完。本文将给大家介绍如何利用ChatGPT来生成练习题。 尚未获得ChatGPT的用户&#xff0c;请移步&#xff1a;五分钟开通GPT4.0。 角色…

Qt 实现简易的视频播放器,功能选择视频,播放,暂停,前进,后退,进度条拖拉,视频时长显示

1.效果图 2.代码实现 2.1 .pro文件 QT core gui multimedia multimediawidgets 2.2 .h文件 #ifndef VIDEOPLAYING_H #define VIDEOPLAYING_H#include <QWidget> #include<QFileDialog>#include<QMediaPlayer> #include<QMediaRecorder> #in…

【C语言进阶】- 内存函数

内存函数 1.1 内存函数的使用1.2 memcpy函数的使用1.3 memcpy函数的模拟实现2.1 memmove函数的使用2.2 memmove函数的模拟实现2.3 memcmp函数的使用2.4 memset函数的使用 1.1 内存函数的使用 内存函数就是对内存中的数据进行操作的函数 1.2 memcpy函数的使用 void* memcpy ( …

Tomcat调优总结(Tomcat自身优化、Linux内核优化、JVM优化)

Tomcat自身的调优是针对conf/server.xml中的几个参数的调优设置。首先是对这几个参数的含义要有深刻而清楚的理解。以tomcat8.5为例&#xff0c;讲解参数。 同时也得认识到一点&#xff0c;tomcat调优也受制于linux内核。linux内核对tcp连接也有几个参数可以调优。 因此我们可…

每天五分钟深度学习:神经网络和深度学习有什么样的关系?

本文重点 神经网络是一种模拟人脑神经元连接方式的计算模型&#xff0c;通过大量神经元之间的连接和权重调整&#xff0c;实现对输入数据的处理和分析。而深度学习则是神经网络的一种特殊形式&#xff0c;它通过构建深层次的神经网络结构&#xff0c;实现对复杂数据的深度学习…

用Python实现办公自动化(自动化处理PDF文件)

自动化处理 PDF 文件 目录 自动化处理 PDF 文件 谷歌浏览器 Chrome与浏览器驱动ChromeDriver安装 &#xff08;一&#xff09;批量下载 PDF 文件 1.使用Selenium模块爬取多页内容 2.使用Selenium模块下载PDF文件 3.使用urllib模块来进行网页的下载和保存 4.使用urllib…

AI预测福彩3D第23弹【2024年4月1日预测--第5套算法开始计算第5次测试】

今天&#xff0c;咱们继续进行本套算法的测试&#xff0c;本套算法目前也已命中多次。今天为第五次测试&#xff0c;仍旧是采用冷温热趋势结合AI模型进行预测。好了&#xff0c;废话不多说了。直接上结果~ 仍旧是分为两个方案&#xff0c;1大1小。 经过人工神经网络计算并进行权…