【学习日记】【FreeRTOS】临界段的保护

写在前面

本文主要是对于 FreeRTOS 中临界段的保护的详细解释,代码大部分参考了野火 FreeRTOS 教程配套源码,作了一小部分修改。

一、什么是临界段

临界段就是一段在执行的时候不能被中断的代码段

**临界段(Critical Section)**是指在多任务或多线程环境下,一段代码或一组代码,在执行期间对共享资源进行访问或操作的临界区域。临界段中的代码只能被一个任务或线程单独执行,以确保对共享资源的访问是正确和一致的。

二、为什么要保护临界段

在进入临界段之前,任务或线程需要获得对应的同步机制(如互斥锁、信号量等)的控制权。一旦获得控制权,任务或线程可以安全地访问临界段中的共享资源。在临界段执行完成后,任务或线程释放同步机制的控制权,让其他任务或线程可以进入临界段执行。

三、要保护临界段,我们需要做什么?

1. 了解临界段什么时候会被打断

  1. 系统调度
  2. 外部中断
  • 因此,对临界段的保护的实质——对中断的开和关的控制

2. 了解Cortex-M内核中断指令的开关指令

①一些缩写的解释

  • NMI:Non-Maskable Interrupt(不可屏蔽中断)
  • CPSID:Change Program Status and Interrupt Disable(修改程序状态并禁用中断)
  • CPSID 后面跟着的 I 或者 F 分别表示 Interrupt(普通的可屏蔽中断)和 FAULT Interrupt(异常中断)
  • Primask是一个特殊的寄存器,全称是 “PrIMask”,其含义是 “Priority Mask”,即优先级屏蔽
  • basepri"代表的是"Base Priority",即基本优先级

②中断操作指令

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

③寄存器说明

在这里插入图片描述

3. 可嵌套的中断开启与关闭函数对保护临界区的作用

野火的教程在这方面语焉不详,笔者自行理解了一下:

所谓可嵌套的中断操作函数,指的是该中断函数有返回值,返回该函数操作前的中断屏蔽等级,便于其他函数使用该返回值恢复现场。

对于可嵌套的中断操作函数在保护临界区方面有如下作用:

进入临界区时,关闭中断,此时可嵌套的关闭中断函数可以保存关闭前的屏蔽等级,在退出临界区可以使用该保存的屏蔽等级进行屏蔽等级的恢复设置。

代码例程如下:

// 全局共享资源
volatile int counter = 0;// 中断处理程序
void interrupt_handler()
{// 进入临界区(关闭中断)Interruption_Mask_Level = disable_interrupt();// 访问共享资源counter++;// 退出临界区(打开中断)enable_interrupt(Interruption_Mask_Level);
}// 主程序
int main()
{// 启用中断while (1){// 执行其他任务}return 0;
}

四、临界段相关操作函数详解

主要分为三类函数:

  1. 关中断函数
  2. 开中断函数
  3. 进出临界段函数

其中开关中断函数被进出临界段函数调用。

1. 关中断函数

  • 宏定义
//关中断函数//不带返回值的关中断函数,不能嵌套,不能在中断里面使用
#define portDISABLE_INTERRUPTS()				vPortRaiseBASEPRI()//带返回值的关中断函数,可以嵌套,可以在中断里面使用
#define portSET_INTERRUPT_MASK_FROM_ISR()		ulPortRaiseBASEPRI()//中断屏蔽等级的设置
#define configMAX_SYSCALL_INTERRUPT_PRIORITY 	191   /* 高四位有效,即等于0xb0,或者是11 */
  • 不带返回值的关中断函数,原理就是改写 BASEPRI 寄存器的值屏蔽指定等级的中断
//不带返回值,不能嵌套
static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;__asm{/* Set BASEPRI to the max syscall priority to effect a criticalsection. */msr basepri, ulNewBASEPRI	//宏定义中 basepri = 11,也就是优先级大于11的中断不被响应dsbisb}
}
  • 带返回值的关中断函数,原理就是先记录 BASEPRI 寄存器的值作为函数返回值,然后再改写 BASEPRI 寄存器的值屏蔽指定等级的中断
static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
{
uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;__asm{/* Set BASEPRI to the max syscall priority to effect a criticalsection. */mrs ulReturn, basepri		//先把basepri中的值保存在返回值中msr basepri, ulNewBASEPRI	//再设置basepri的值dsbisb}return ulReturn;
}

2. 开中断函数

  • 宏定义
    • 不可嵌套的开中断函数就是将 BASEPRI 寄存器的值设置为 0,使任何一个中断都不被屏蔽
    • 可嵌套的开中断函数就是将 BASEPRI 寄存器的值设置为 可嵌套的关中断函数的返回值,使中断屏蔽等级在进出临界区后和之前保持一致
//开中断函数// 不可嵌套
#define portENABLE_INTERRUPTS()					vPortSetBASEPRI( 0 )// 可嵌套
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x)	vPortSetBASEPRI(x)
  • 具体函数实现
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{__asm{/* Barrier instructions are not used as this function is only used tolower the BASEPRI value. */msr basepri, ulBASEPRI}
}

3. 进出临界段函数

进出临界段函数抽象了好几层,如下:
task.h
可以看到,不带中断保护版本多抽象了一层,而带中断保护版本直接对开关中断进行操作

/* ==========进入临界段,不带中断保护版本,不能嵌套=============== */                                
#define taskENTER_CRITICAL() portENTER_CRITICAL()
/* ==========进入临界段,带中断保护版本,可以嵌套=============== */
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()	
/* ==========退出临界段,不带中断保护版本,不能嵌套=============== */
#define taskEXIT_CRITICAL() portEXIT_CRITICAL()
/* ==========退出临界段,带中断保护版本,可以嵌套=============== */
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )

portmacro.h

  • 下面是不带中断保护版本的进出临界段函数的抽象:
//进出临界段
#define portENTER_CRITICAL()					vPortEnterCritical()
#define portEXIT_CRITICAL()						vPortExitCritical()

展示不带中断保护版本的进出临界段函数代码:

  1. vPortEnterCritical(): 这个函数的作用是进入临界区。它首先调用portDISABLE_INTERRUPTS()函数来禁用中断,阻止其他中断干扰。然后,它将uxCriticalNesting计数器加一,表示进入了一个新的临界区。如果uxCriticalNesting计数器的值为1,即当前进入的是第一个临界区,那么可以使用configASSERT()来进行断言检查,确保该函数不是在中断上下文中调用。

  2. vPortExitCritical(): 这个函数的作用是退出临界区。它首先检查uxCriticalNesting计数器的值,确保当前确实在一个临界区中。然后,它将uxCriticalNesting计数器减一,表示退出当前的临界区。如果uxCriticalNesting计数器的值减为0,即没有进入任何临界区了,那么可以调用portENABLE_INTERRUPTS()函数来重新启用中断,允许其他中断恢复执行。

//进入临界段
void vPortEnterCritical( void )
{portDISABLE_INTERRUPTS();uxCriticalNesting++;/* This is not the interrupt safe version of the enter critical function soassert() if it is being called from an interrupt context.  Only APIfunctions that end in "FromISR" can be used in an interrupt.  Only assert ifthe critical nesting count is 1 to protect against recursive calls if theassert function also uses a critical section. */if( uxCriticalNesting == 1 ){//configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );}
}//退出临界段
void vPortExitCritical( void )
{//configASSERT( uxCriticalNesting );uxCriticalNesting--;if( uxCriticalNesting == 0 ){portENABLE_INTERRUPTS();}
}
  • 而带中断保护版本的就是可嵌套开关中断函数:
//进出临界段
//带返回值的关中断函数,可以嵌套,可以在中断里面使用
#define portSET_INTERRUPT_MASK_FROM_ISR()		ulPortRaiseBASEPRI()
//可嵌套开中断函数
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x)	vPortSetBASEPRI(x)

五、临界段操作函数如何使用

1. 中断场合

在这里插入图片描述

2. 非中断场合

在这里插入图片描述

后记

如果您觉得本文写得不错,可以点个赞激励一下作者!
如果您发现本文的问题,欢迎在评论区或者私信共同探讨!
共勉!

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

事务和事务的隔离级别

1.4.事务和事务的隔离级别 1.4.1.为什么需要事务 事务是数据库管理系统(DBMS)执行过程中的一个逻辑单位(不可再进行分割),由一个有限的数据库操作序列构成(多个DML语句,select语句不包含事务&…

【ASP.NET MVC】使用动软(二)(10)

一、添加动软生成工程 按前文添加动态到工程 双击动软 完成新建数据库服务器后 ,需要关闭重新打开 选择简单三层,注意保存位置 注意切换数据库: 生成后拷贝五个文件夹到工程目录 注意目录结构: 添加四个项目到原来的工程&…

基于云平台的智慧养殖远程监控系统

一、项目背景 冬春季节每天的温度和昼夜温差变化很大,为保证养殖动物有一个温暖舒适的生存环境,使动物的生产性能得到较好的发挥,须注意做好温度、湿度、通风等方面的控制。 智慧养殖智能监控系统可以实现对如温度、湿度、气体浓度、光照度…

关于接口自动化,你不能不知道的高级技巧——接口自动化神器apin进阶操作

一、变量提取和引用 变量提取和引用主要是为了解决接口之间的参数依赖问题。 使用场景:接口 A 的参数中需要使用接口 B 返回的某个数据,那么就要在请求 B 接口之后,提取数据保存,给请求 A 接口时使用。 1、变量提取 在用例集或…

绝了!学编程的还有不知道的吗?这个Java开发工具免费了!

智能开发正在迅速走红! 随着ChatGPT的广泛应用,智能开发越来越受到关注。然而,实际上,在数年前开始尝试智能开发的探索。 自从2014年ForresterResearch提出"低代码"的概念以来,低代码平台的发展非常迅速。…

非计算机科班如何丝滑转码?

近年来,很多人想要从其他行业跳槽转入计算机领域。非计算机科班如何丝滑转码? 如何规划才能实现转码? 对于非计算机科班的人来说,想要顺利转码成为计算机相关岗位的从业者,需要经过以下几个步骤: 规划转码…

[JAVAee]网络编程-套接字Socket

目录 基本概念 发送端与接收端 请求与响应 ​编辑客户端与服务器 Socket套接字 分类 数据报套接字 流套接字传输模型 UDP数据报套接字编程 DatagramSocket API DatagramPacket API InetSocketAddress API 示例一: 示例二: TCP流数据报套接字编程 ServerSock…

Grafana技术文档--基本安装-docker安装并挂载数据卷-《十分钟搭建》-附带监控服务器

阿丹: Prometheus技术文档--基本安装-docker安装并挂载数据卷-《十分钟搭建》_一单成的博客-CSDN博客 在正确安装了Prometheus之后开始使用并安装Grafana作为Prometheus的仪表盘。 一、拉取镜像 搜索可拉取版本 docker search Grafana拉取镜像 docker pull gra…

推出 Elasticsearch 查询语言 (ES|QL)

作者:Costin Leau 我很高兴地宣布,经过大约一年的开发,Elasticsearch 查询语言 (ES|QL) 已准备好与世界共享,并已登陆 Elasticsearch 存储库。 ES|QL 是 Elasticsearch 原生的强大声明性语言,专为可组合性、表现力和速…

Vue中使用uuid生成唯一ID(脚手架创建自带的)

1.utils 说明:一般封装工具函数。 // 单例模式 import { v4 as uuidv4 } from uuid; // 要生成一个随机的字符串,且每次执行不能发生变化 // 游客身份还要持久存储 function getUUID(){// 先从本地获取uuid,本地存储里面是否有let uuid_tok…

逆向破解学习-单机斗地主

试玩 破解思路 9000 是成功的代码 Hook代码 import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.callbacks.XC_LoadPackage; public class HookComJuneGameDouDiZhu extends HookImpl{ Override p…

分布式异步任务处理组件(八)

分布式异步任务组件网络通信线程模型设计-- 大概说一下功能场景: 从节点和主节点建立连接,负责和主节点的网络IO通信,通信动作包括投票,心跳,举证等,步骤为读取主节点的信息,写入IO队列中&…