临界区保护

【操作系统基础】临界区问题 和 和原子操作的理解 和 互斥锁的实现和理解 临界区问题 每个并发的进程都有一个代码段,被叫做临界区,这个代码段也是临界区可能会改变并发进程的公告资源数据,更新数据,写入文件等操作; 在并发的进程中,当有一个进程正在执行临界区的代码时候,其他并发的进程是不可以被允许去执行临界区的代码的,换句话说:两个进程是不可以不可能同时执行临界区的内容; 总的来说:临界区问题就是设计一种协议:这种协议它可以保证进程之间相互协作; 进程进入临界区协议 当我们一个进程需要进入临界区时候,需要获取进入临界区的许可,当我们离开临界区时候,需要归还临界区的许可; 阅读详情

1 临界区保护

1.1 问题引入

首先看一下如下问题:
在这里插入图片描述
原因分析:

  • 根本原因在于读-改-写过程中随时会被打断,再恢复运行时写,导致打断过程中其它写的效果被覆盖。在这里插入图片描述

1.2 临界区概念

临界区的概念如下:

  • 临界区指的是访问多个任务共享资源的一段代码。当有任务进入临界区时,其它任务必须等待直至该任务离开临界区,以确定共享资源的访问不会冲突。
    在这里插入图片描述
    由于共享资源的访问存在于任务与任务之间、任务与中断ISR之间;那么,只需要防止任务在访问共享资源时,切换至其它任务或防止中断发生即可。
    在这里插入图片描述

1.3 使用关中断保护临界区

我们可以使用关中断来保护临界区,如下:
在这里插入图片描述
但是当中断发生嵌套的时候就会出现问题:
在这里插入图片描述
所以我们需要采用如下的解决方案:
在这里插入图片描述

1.4 设计实现

中断控制寄存器PRIMASK:
在这里插入图片描述
进入临界区:
在这里插入图片描述
退出临界区:
在这里插入图片描述
这里只贴一下main.c文件中的内容:

/*************************************** Copyright (c)******************************************************
** File name            :   main.c
** Latest modified Date :   2016-06-01
** Latest Version       :   0.1
** Descriptions         :   主文件,包含应用代码
**
**--------------------------------------------------------------------------------------------------------
** Created by           :   01课堂 lishutong
** Created date         :   2016-06-01
** Version              :   1.0
** Descriptions         :   The original version
**
**--------------------------------------------------------------------------------------------------------
** Copyright            :   版权所有,禁止用于商业用途
** Author Blog          :   http://ilishutong.com
**********************************************************************************************************/
#include "tinyOS.h"
#include "ARMCM3.h"

// 当前任务:记录当前是哪个任务正在运行
tTask * currentTask;

// 下一个将即运行的任务:在进行任务切换前,先设置好该值,然后任务切换过程中会从中读取下一任务信息
tTask * nextTask;

// 空闲任务
tTask * idleTask;

// 所有任务的指针数组:简单起见,只使用两个任务
tTask * taskTable[2];

// 用于临界区测试的计数器
uint32_t tickCounter;

/**********************************************************************************************************
** Function name        :   tTaskInit
** Descriptions         :   初始化任务结构
** parameters           :   task        要初始化的任务结构
** parameters           :   entry       任务的入口函数
** parameters           :   param       传递给任务的运行参数
** Returned value       :   无
***********************************************************************************************************/
void tTaskInit (tTask * task, void (*entry)(void *), void *param, uint32_t * stack)
{
    // 为了简化代码,tinyOS无论是在启动时切换至第一个任务,还是在运行过程中在不同间任务切换
    // 所执行的操作都是先保存当前任务的运行环境参数(CPU寄存器值)的堆栈中(如果已经运行运行起来的话),然后再
    // 取出从下一个任务的堆栈中取出之前的运行环境参数,然后恢复到CPU寄存器
    // 对于切换至之前从没有运行过的任务,我们为它配置一个“虚假的”保存现场,然后使用该现场恢复。

    // 注意以下两点:
    // 1、不需要用到的寄存器,直接填了寄存器号,方便在IDE调试时查看效果;
    // 2、顺序不能变,要结合PendSV_Handler以及CPU对异常的处理流程来理解
    *(--stack) = (unsigned long)(1<<24);                // XPSR, 设置了Thumb模式,恢复到Thumb状态而非ARM状态运行
    *(--stack) = (unsigned long)entry;                  // 程序的入口地址
    *(--stack) = (unsigned long)0x14;                   // R14(LR), 任务不会通过return xxx结束自己,所以未用
    *(--stack) = (unsigned long)0x12;                   // R12, 未用
    *(--stack) = (unsigned long)0x3;                    // R3, 未用
    *(--stack) = (unsigned long)0x2;                    // R2, 未用
    *(--stack) = (unsigned long)0x1;                    // R1, 未用
    *(--stack) = (unsigned long)param;                  // R0 = param, 传给任务的入口函数
    *(--stack) = (unsigned long)0x11;                   // R11, 未用
    *(--stack) = (unsigned long)0x10;                   // R10, 未用
    *(--stack) = (unsigned long)0x9;                    // R9, 未用
    *(--stack) = (unsigned long)0x8;                    // R8, 未用
    *(--stack) = (unsigned long)0x7;                    // R7, 未用
    *(--stack) = (unsigned long)0x6;                    // R6, 未用
    *(--stack) = (unsigned long)0x5;                    // R5, 未用
    *(--stack) = (unsigned long)0x4;                    // R4, 未用

    task->stack = stack;                                // 保存最终的值
    task->delayTicks = 0;
}

/**********************************************************************************************************
** Function name        :   tTaskSched
** Descriptions         :   任务调度接口。tinyOS通过它来选择下一个具体的任务,然后切换至该任务运行。
** parameters           :   无
** Returned value       :   无
***********************************************************************************************************/
void tTaskSched () 
{   
    // 进入临界区,以保护在整个任务调度与切换期间,不会因为发生中断导致currentTask和nextTask可能更改
    uint32_t status = tTaskEnterCritical();

    // 空闲任务只有在所有其它任务都不是延时状态时才执行
    // 所以,我们先检查下当前任务是否是空闲任务
    if (currentTask == idleTask) 
    {
        // 如果是的话,那么去执行task1或者task2中的任意一个
        // 当然,如果某个任务还在延时状态,那么就不应该切换到他。
        // 如果所有任务都在延时,那么就继续运行空闲任务,不进行任何切换了
        if (taskTable[0]->delayTicks == 0) 
        {
            nextTask = taskTable[0];
        }           
        else if (taskTable[1]->delayTicks == 0) 
        {
            nextTask = taskTable[1];
        } 
        else 
        {
            tTaskExitCritical(status);
            return;
        }
    } 
    else 
    {
        // 如果是task1或者task2的话,检查下另外一个任务
        // 如果另外的任务不在延时中,就切换到该任务
        // 否则,判断下当前任务是否应该进入延时状态,如果是的话,就切换到空闲任务。否则就不进行任何切换
        if (currentTask == taskTable[0]) 
        {
            if (taskTable[1]->delayTicks == 0) 
            {
                nextTask = taskTable[1];
            }
            else if (currentTask->delayTicks != 0) 
            {
                nextTask = idleTask;
            } 
            else 
            {
                tTaskExitCritical(status);
                return;
            }
        }
        else if (currentTask == taskTable[1]) 
        {
            if (taskTable[0]->delayTicks == 0) 
            {
                nextTask = taskTable[0];
            }
            else if (currentTask->delayTicks != 0) 
            {
                nextTask = idleTask;
            }
            else 
            {
                tTaskExitCritical(status);
                return;
             }
        }
    }

    tTaskSwitch();   

    // 退出临界区
    tTaskExitCritical(status); 
}

/**********************************************************************************************************
** Function name        :   tTaskSystemTickHandler
** Descriptions         :   系统时钟节拍处理。
** parameters           :   无
** Returned value       :   无
***********************************************************************************************************/
void tTaskSystemTickHandler () 
{
    // 检查所有任务的delayTicks数,如果不0的话,减1。
    int i;   
    uint32_t status = tTaskEnterCritical();

    for (i = 0; i < 2; i++) 
    {
        if (taskTable[i]->delayTicks > 0)
        {
            taskTable[i]->delayTicks--;
        }
    }
    
    // 在中断中也有访问资源
    tickCounter++;
    tTaskExitCritical(status);

    // 这个过程中可能有任务延时完毕(delayTicks = 0),进行一次调度。
    tTaskSched();
}

/**********************************************************************************************************
** Function name        :   taskDelay
** Descriptions         :   使当前任务进入延时状态。
** parameters           :   delay 延时多少个ticks
** Returned value       :   无
***********************************************************************************************************/
void taskDelay (uint32_t delay) {
    // 配置好当前要延时的ticks数
    uint32_t status = tTaskEnterCritical();
    currentTask->delayTicks = delay;
    tTaskExitCritical(status);

    // 然后进行任务切换,切换至另一个任务,或者空闲任务
    // delayTikcs会在时钟中断中自动减1.当减至0时,会切换回来继续运行。
    tTaskSched();
}

/*********************************************************************************************************
** 系统时钟节拍定时器System Tick配置
** 在我们目前的环境(模拟器)中,系统时钟节拍为12MHz
** 请务必按照本教程推荐配置,否则systemTick的值就会有变化,需要查看数据手册才了解
**********************************************************************************************************/
void tSetSysTickPeriod(uint32_t ms)
{
  SysTick->LOAD  = ms * SystemCoreClock / 1000 - 1;
  NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);
  SysTick->VAL   = 0;
  SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk |
                   SysTick_CTRL_TICKINT_Msk   |
                   SysTick_CTRL_ENABLE_Msk; 
}

/**********************************************************************************************************
** Function name        :   SysTick_Handler
** Descriptions         :   SystemTick的中断处理函数。
** parameters           :   无
** Returned value       :   无
***********************************************************************************************************/
void SysTick_Handler () 
{
    tTaskSystemTickHandler();
}

/**********************************************************************************************************
** 应用示例
** 有两个任务,分别执行task1Entry和task2Entry。功能是分别对相应的变量进行周期性置0置1.
** 每个任务都可以占用一段时间的CPU,一旦用完了,就会被强制暂停,切换到另一个任务中去。
** 本例中有全局共享的变量criticalCounter,同时在taskDelay和tTaskSystemTickHandler中修改。
** 如果不加临界区保护,则在tTaskSystemTickHandler中对criticalCounter的修改将会丢失
**********************************************************************************************************/
int task1Flag;
void task1Entry (void * param) 
{
    tSetSysTickPeriod(10);
    for (;;) 
    {
        task1Flag = 1;
        taskDelay(1);
        task1Flag = 0;
        taskDelay(1);
    }
}

int task2Flag;
void task2Entry (void * param) 
{
    for (;;) 
    {
        // 临界区演示的计数器
        uint32_t i;

        // 由于有临界区的保护,所以即便是这中间中断产生,也不会立即响应中断。
        // criticalCounter的值会被正确修改。等待退出临界区时,才会响应中断,在中断中修改criticalCounter
        // 最终不会导致冲突

        // 进入临界区
        uint32_t status = tTaskEnterCritical();

        uint32_t counter = tickCounter;
        for (i = 0; i < 0xFFFF; i++) {}         // 故意产生长的延时,以便在此期间发生中断
        tickCounter = counter + 1;

        // 退出临界区
        tTaskExitCritical(status);

        task2Flag = 1;
        taskDelay(1);
        task2Flag = 0;
        taskDelay(1);
    }
}

// 任务1和任务2的任务结构,以及用于堆栈空间
tTask tTask1;
tTask tTask2;
tTaskStack task1Env[1024];
tTaskStack task2Env[1024];

// 用于空闲任务的任务结构和堆栈空间
tTask tTaskIdle;
tTaskStack idleTaskEnv[1024];

void idleTaskEntry (void * param) {
    for (;;)
    {
        // 空闲任务什么都不做
    }
}

int main () 
{
    // 初始化任务1和任务2结构,传递运行的起始地址,想要给任意参数,以及运行堆栈空间
    tTaskInit(&tTask1, task1Entry, (void *)0x11111111, &task1Env[1024]);
    tTaskInit(&tTask2, task2Entry, (void *)0x22222222, &task2Env[1024]);
    
    // 接着,将任务加入到任务表中
    taskTable[0] = &tTask1;
    taskTable[1] = &tTask2;

    // 创建空闲任务
    tTaskInit(&tTaskIdle, idleTaskEntry, (void *)0, &idleTaskEnv[1024]);
    idleTask = &tTaskIdle;
    
    // 我们期望先运行tTask1, 也就是void task1Entry (void * param) 
    nextTask = taskTable[0];

    // 切换到nextTask, 这个函数永远不会返回
    tTaskRunFirst();
    return 0;
}


参考资料:

  1. 【李述铜】从0到1自己动手写嵌入式操作系统
【操作系统】临界区(含特性、实现方式、应用场景、常见问题) 临界区是并发编程中保障数据一致性的核心机制,需根据场景选择锁、原子操作或硬件级保护。遵循最小化、无嵌套、防死锁的设计原则,可显著提升系统稳定性和性能。在实时嵌入式系统中,还需结合关中断、内存屏障等硬件特性实现高效同步。 阅读详情

相关推荐

FreeRTOS临界资源保护(临界区保护)

临界区未保护出现的异常   有一种问题相信大家都不愿碰上,就是低概率偶现问题,这类问题的根源,根据作者的经验常见于以下四种情况: - 野指针引用 - 数据越界 - 栈溢出 - 临界资源未保护   今天我们说说其中之一的原因:临界资源未保护。   我们先看个例子,假如有一个5个节点单向链表,如下结构: head->1->2->3->4->5->NULL   有一任...

jason_lm的专栏 5291

单片机要这样保护临界区

今天给大家分享的是Cortex-M裸机环境下,临界区保护的三种实现。搞嵌入式玩过 RTOS 的小伙伴,想必都对 OS_ENTER_CRITICAL()、OS_EXIT_CRITICAL() 这个功能代码特别眼熟,在 RTOS 里常常会有多任务(进程)处理,有些情况下一些特殊操作(比如 XIP 下 Flash 擦写、低功耗模式切换)不能被随意打断,或者一些共享数据区不能被无序访问(A 任务正在读,B...

嵌入式Linux 529

7.临界段代码保护及调度器挂起与恢复

临界段代码保护及调度器挂起与恢复

扬帆起航 1249

RTOS必备操作---临界区保护

1. 临界区的保护实际应用中可能需要的场合: 调用公共函数的代码(不可重入函数) 读取或者修改变量(全局变量)

TrueLink的博客 1466

SAP-ABAP:SAP客户端管理核心事务码SCC4深度解析

SAP客户端管理核心事务码SCC4深度解析

baidu_35680696的博客 1568

autodl云服务器使用中的conda源报错

通过find path -name filename找到.condarc文件,在autodl服务器中,一般在/root/.condarc。为了利用jypyter打开conda虚拟环境运行,需要下载ipykenel。这个报错是源的问题,在通过源获取包元数据时出现了HTTP 429错误。通过conda换源解决。修改.condarc文件,命令: vim .condarc。但这样还不能解决问题,得把报错中有问题的源注释掉。通过以下命令添加清华镜像源。把报错的链接源注释如下。

Ty_kilig的博客 1584

FreeRTOS学习(临界区的保护)

临界区是因为

weixin_52589726的博客 1306

用宝塔面板+CentOS 7.6,30分钟搞定DNF手游私服搭建(附全套资源下载)

本文详细介绍了如何使用宝塔面板和CentOS 7.6在30分钟内快速搭建DNF手游私服。通过图形化界面操作,无需复杂命令,即可完成服务端部署、MySQL数据库配置及客户端适配,适合Linux新手快速上手。

weixin_33737774的博客 601

RTOS临界区的保护机制详解

本文主要介绍RTOS临界区的保护机制,临界区指访问共享资源(如全局变量、外设寄存器)的代码段,需保证其执行的原子性,避免数据竞争。文章介绍了在RTOS中保护临界区的各种方法。

mftang的博客 1198

APO的全面进化之路(2):走向IBP、建设端到端供应链计划体系

前一篇记录阐述了在供应链计划(Supply Chain Planning)领域里SAP的关键产品APO的不断进化之路,即部分产品从APO进化到S/4HANA,本篇将继续阐述进化之路上的另一大中坚产品力量SAP-IBP(Integrated Business Planning)。作为SAP数字化供应链(Digital Supply Chain)中供应链计划SCP的左膀右臂,S/4HANA与IBP共同完善了计划域的完整闭环。本篇作为收尾,在先谈对IBP产品的个人认知之前,特别是在当今这个IT产品迭代多、技术革新

ChampaignWolf的专栏 5230

出现 Transaction rolled back because it has been marked as rollback-only 解决方法

出现 Transaction rolled back because it has been marked as rollback-only 解决方法

码农研究僧的博客 1万+

单片机裸机环境下临界区保护

在嵌入式系统开发中,临界区保护是确保系统可靠性的关键技术。本文以ARM Cortex-M架构为例,系统论述裸机环境下临界区保护的实现方法、技术原理及工程实践。

不脱发的程序猿 1604

UNET改进77:全网首发-添加CFBlock |

本文介绍了在UNet模型中集成CFBlock注意力机制的实现方法。主要分为三个步骤:1) 创建CFBlock模块,包含卷积注意力机制和MLP;2) 在UNet连接处添加注意力模块;3) 在卷积层中嵌入注意力机制。具体实现包括:定义Conv、MLP和ConvolutionalAttention等基础模块,构建CFBlock核心结构;在UNet跳跃连接处插入注意力模块;将注意力机制整合到DoubleConv和Down模块中。该方法通过在不同位置引入注意力机制,可有效提升模型的特征提取能力。代码实现使用PyTor

AIcurator的博客 388

FreeRTOS_临界区代码保护

传送门:博客汇总帖 内容来源于(正点原子的FreeRTOS开发手册、FreeRTOS实时内核使用指南-中文、FreeRTOS数据手册等) 文章目录1、临界区代码保护2、taskENTER_CRITICAL()与taskEXIT_CRITICAL() 1、临界区代码保护 多任务系统中存在一种潜在的风险。当一个任务在使用某个资源的过程中,即还没 有完全结束对资源的访问时,便被切出运行态,使得资源处于非一致,不完整的状态。 如果这个时候有另一个任务或者中断来访问这个资源,则会导致数据损坏或是其它相似..

Fall Down的博客 1644

Unity之Timeline教程

Unity Timeline是Unity的一种时间轴编辑器工具,用于制作和管理游戏中的动画、剧情以及事件触发。它提供了直观的界面,使得开发者可以通过拖放操作轻松创建和编辑时间轴。Activation Track:用于控制物体的激活状态。可以在轨道上添加关键帧,控制物体的激活和非激活状态。Animation Track:用于处理物体的动画。可以在此轨道上添加关键帧,编辑物体的动画状态。Audio Track:用于处理场景中的音频。可以管理音频的播放、暂停、淡入淡出等操作。

TxNet 9442

YOLOv8改进,YOLOv8主干网络替换为FasterNet(全网独发手把手教学,助力涨点)

FasterNet的整体架构由四个分层阶段组成,每个阶段包含一组FasterNet模块,并在前面加一个嵌入或合并层。最后三层用于特征分类。每个FasterNet模块内部,一个PConv层后跟两个PWConv层,为了保持特征多样性并降低延迟,归一化和激活层仅在中间层之后进行,其中,PConv 是一种快速高效的卷积操作,通过仅对部分输入通道应用卷积滤波器,而保持其余通道不变,从而减少了计算量和内存访问。

在职AI算法工程师,擅长计算机视觉,YOLO目标检测、分割等,擅长web、pyqt界面可视化,好内容持续更新中,来这里跟大家一起学习,共同进步 2016

L17 信号量临界区保护

为什么要保护信号量和临界区? 在现实中如何保护信号量和临界区? empty = -1:的含义? 表示有一个进程正在睡眠 为什么要保护? 进程中存在一种调度,使得信号量引发错误。所以要保护! 怎么保护信号量? 要上锁 什么是临界区? 当一个进程在进入一段代码时,另外一个进程不能进入对应的修改信号的代码。所以出现了保护修改信号量的临界区。 临界区代码的保护原则? 临界区怎样设计呢? 方法一 用一个轮...

qq_1316238389的博客 300

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

STM32 HAL库 FreeRTOS 临界区保护的作用以及使用

STM32F407 是意法半导体(ST)推出的一款基于 ARM Cortex - M4 内核的 32 位微控制器。它具有高达 168MHz 的主频,拥有丰富的外设资源,如 GPIO、SPI、I2C、USART 等,适用于工业控制、消费电子、通信等多个领域。STM32F407 还具备浮点运算单元(FPU),能够高效地处理浮点运算,为一些对计算能力要求较高的应用提供了支持。临界区是指程序中访问共享资源的代码段。共享资源可以是全局变量、硬件寄存器、文件等。

m0_75187370的博客 1227

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

RT-Thread学习笔记五——临界区与临界区保护

RT-Thread中利用禁用中断方式保护临界区,实现对全局变量的运算。

Reasally的博客 2153

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

RT-Thread学习记录6 临界区保护

以下为看视频笔记.......... 1.临界区概念 临界资源是指一次仅允许一一个线程访问的共享资源。它可以是一个具体的硬件设备(如打印机等),也可以是一个变量、一个缓冲区。 不论是硬件临界资源,还是软件临界资源,多个线程必须互斥地对它们进行访问 每个线程中的访问(操作)临界资源的那段代码称为临界区(Critiacl Section),我们每一次只允许一个线程进入临界区。 /* 程序的目的先把全局变量value值加到10000,在赋值为500.注意顺序。 演示全局变量value为临界区资源,两

qingfeng_博客 1013

RT_thread 临界区保护

一、临界资源 临界区资源是指一次仅允许一次线程的访问的共享资源。它可以是一个具体的硬件设备,也可以是一个变量、一个缓冲区。 不论是硬件临界资源,还是软件临界资源,多个线程必须互斥地对他们进行访问。 临界区 每个线程中访问(操作)临界区资源的那段代码称为临界区(Critical Section), 我们每次只允许一个线程进入临界区。 二、关闭系统调度 1.禁止调度 禁止调度,既是把调度器锁住,不让其他进行线程切换。这样就能保证当前能保证当前运行的任务不被换出,直到调度器解锁,所以禁止调度是常用的临界区保护

weixin_52694360的博客 1324

【FreeRTOS】临界区保护

基于STM32的标准库FreeRTOS的临界区保护

qq978114的博客 293
上一篇: Qt中的文件操作
下一篇: 调度锁保护
SlowIsFastLemon
博客等级 码龄8年 265粉丝 790原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值