当前位置: 首页 > web >正文

ARM杂谈——临界段保护恢复的中断状态可靠吗

0 前言

在MCU中,临界段保护是指在多任务或多线程环境中,确保某段代码在执行时不会被其他任务或中断打断,从而避免数据竞争或不一致的问题。临界段保护通常用于共享资源的访问,如全局变量、硬件寄存器等。

我们有一些常用的临界段保护方法,但他们在任何场景下都可靠吗?

1 常用的临界段保护方法

PRIMASK是1bit的中断屏蔽寄存器,在置位时会阻止除不可屏蔽中断和HardFault异常外的所有中断。

在CMSIS库中,可以直接用__disable_irq()__enable_irq()来操作PRIMASK寄存器。
使用汇编指令MSRMRS可以访问该寄存器。

  • 直接关掉中断
void main()
{/* 代码段1 */__disable_irq();	// 关闭全局中断/* 需要临界段保护的代码 */__enable_irq();		// 打开全局中断/* 代码段2 */
}

在执行临界段前后,中断使能状态应该保持一致。
而这段代码如果在__disable_irq()的时候全局中断是关闭的,那么在后面调用__enable_irq()的时候就会打开全局中断,改变了系统的中断屏蔽状态。

  • 临界段后,恢复中断状态
void main()
{/* 代码段1 */uint32_t intFlag = __get_PRIMASK();	// 获取当前中断状态__disable_irq();					// 关闭全局中断/* 需要临界段保护的代码 */__set_PRIMASK( intFlag );			// 恢复之前的中断状态/* 代码段2 */
}

这段代码先保存了当前的中断屏蔽状态,在执行临界段后恢复了之前的中断屏蔽状态,保证了执行临界段前后中断屏蔽状态的一致性。

但这样是否会也会存在一些问题呢?

2 问题:上面方法恢复的中断状态可靠吗?

在上面的代码中:
(1)使用了一个变量intFlag保存当前的中断屏蔽状态
(2)使用了__disable_irq()关闭全局中断
(3)执行临界段
(4)使用__set_PRIMASK( intFlag )恢复中断。

如果在步骤(1)和步骤(2)之间,一个中断被响应,并且在该中断中关闭了全局中断,那么在步骤(4)恢复中断的时候,就会错误的将中断开启。

这么做当然很蠢,因为如果一个中断希望获取临界段保护,那么在它关闭中断后应该恢复之前的状态。
但我想知道的是:如果有一个中断,他的目标就是触发的时候禁止全局中断,这样会导致临界段保护恢复一个错误的中断状态吗?

上述情况的前提:
1.在临界段之前全局中断是使能的
——否则也不会响应中断,更不会关掉全局中断了
.
2.Cortex-M内核
在处理器接受了一个异常后,寄存器组中的一些寄存器(R0R1R2R3R12R14)、返回地址(PC)以及程序状态寄存器(xPSR)会被自动压入当前栈空间里,以便在退出中断后恢复运行环境,使被中断的程序正确执行。
.
而保存中断屏蔽状态的PRIMASK寄存器中是没有被压栈的。因此,若在中断处理期间修改了PRIMASK寄存器,从而改变了中断屏蔽状态,那么在退出中断后,中断屏蔽状态将保持与中断处理期间修改后的状态一致。
.
而在ARM7TDMI内核中,中断屏蔽状态保存在其CPSR寄存器中,而该寄存器在处理器接受异常后会被压栈,因此在退出异常后CPSR寄存器会被恢复为中断处理前的状态。

由此可见,在Cortex-M处理器中,若某个中断的意图是关闭全局中断,则可能导致临界段保护机制恢复至错误的中断状态。

为了进一步分析,我想看一下成熟的代码是怎么处理该问题的。

3 FreeRTOS的临界段保护操作

注:这里用的是FreeRTOS在Cortex M0/M0+上的接口文件,Cortex M3及以上内核的有不同的实现方法。

3.1 不可在中断中使用的临界段保护

  • taskENTER_CRITIAL() 进入临界段保护
void vPortEnterCritial( void )
{portDISABLE_INTERRUPTS();			// __disable_irq(),即关闭全局中断uxCriticalNesting++;				// 支持嵌套使用,调用一次计数变量+1__dsb( portSY_FULL_READ_WRITE );	// portSY_FULL_READ_WRITE  = 15__isb( portSY_FULL_READ_WRITE );	// portSY_FULL_READ_WRITE  = 15
}

DSB:数据同步屏障。确保下一条指令执行前,所有的存储器访问都已完成。
例如对存储器映射切换寄存器变成后,为了确保写操作完成并且存储器配置已经得到更新,在进行下一步以前,应该使用DSB命令。

ISB:清除流水线。确保在执行新指令前,之前的所有指令都已完成。
例如在使用MSR指令改变CONTROL寄存器的值之后,应该使用ISB,以确保接下来的操作使用已经更新的设置。

  • taskEXIT_CRITIAL() 退出临界段保护
void vPortExitCritial( void )
{configASSERT( uxCriticalNesting );uxCriticalNesting--;				// 支持嵌套使用,调用一次计数变量-1if( uxCriticalNesting == 0 )		// 支持嵌套使用,在计数变量为0时打开终端{portENABLE_INTERRUPTS();		// __enable_irq(),即打开全局中断}
}

3.2 可在中断中使用的临界段保护

  • taskENTER_CRITIAL_FROM_ISR()进入临界段保护(可在中断中使用)
__asm uint32_t ulSetInterruptMaskFromISR( void )
{mrs r0, PRIMASK		// 将PRIMASK寄存器的值保存到r0寄存器中(即返回值)cpsid i				// 关闭中断bx lr				// 将LR中的返回地址装入PC,即返回到调用程序
}

注:C语言在ARM内核上函数参数的传递通常遵循ARM Architecture Procedure Call Standard (AAPCS),该标准要求第一个参数通过R0 寄存器传递。

  • taskEXIT_CRITIAL_FROM_ISR()进入临界段保护(可在中断中使用)
__asm uint32_t vClearInterruptMaskFromISR( uint32_t ulMask )
{msr PRIMASK, r0		// 将r0寄存器的值(即ulMask)赋给PRIMASK寄存器bx lr				// 将LR中的返回地址装入PC,即返回到调用程序
}

注:AAPCS标准要求返回值通过R0 寄存器传递。

MRS:将特殊寄存器送到寄存器
MSR:将寄存器送到特殊寄存器

看来FreeRTOS也是用的这种方式来实现临界段保护的,即先保存当前状态,再禁止中断,再恢复状态。
FreeRTOS不可能没考虑到这个问题,因此看看ARM对MSR这个指令是怎么说的,有没有可能是从内核上保证了这个问题呢?

4 ARM官方对MRS指令的描述

developer.arm.com / M33 User Guide - MRS
在这里插入图片描述

翻译过来就是:
结合使用 MRSMSR 作为读-修改-写序列的一部分,用于更新 PSR,例如清除 Q 标志。
在进程交换代码中,被换出进程的程序员模型状态必须被保存,包括相关的 PSR 内容,同样,被换入进程的状态也必须被恢复。 这些操作在状态保存指令序列中使用 MRS,在状态恢复指令序列中使用 MSR

显然,ARM要求MRSMSR必须成对使用。

因此我认为前文所说的 “在中断中屏蔽全局中断,而不恢复调用中断前的屏蔽状态就退出中断” 的使用方式是不被ARM允许的,原因之一就是会破坏临界段保护。

5 参考资料

  • 《ARM Cortex-M0/M0+ 权威指南 第二版》
  • Arm Developer
http://www.xdnf.cn/news/6088.html

相关文章:

  • Xcode报错:“Set `maskView` to `nil` before adding it as a subview of ZFMaskView
  • 计算机图形学之几何(Geometry)
  • Maven 下载安装与配置教程
  • 查看字节真实二进制形式示例解析1
  • NAT/代理服务器/内网穿透
  • 容器编排的革命:Kubernetes如何引领IT的云原生时代
  • 高并发内存池(四):Page Cache结构设计
  • How Sam‘s Club nudge customers into buying more
  • PTA编程题: 异常处理(python)
  • AI-02a5a5.神经网络-与学习相关的技巧-权重初始值
  • 联邦+反射器 基础实验
  • TTL、RS-232、RS-485电平转换详解
  • 系统漏洞扫描服务:维护网络安全的关键与服务原理?
  • 【Linux】Linux 的管道与重定向的理解
  • Python中列表(list)知识详解(2)和注意事项以及应用示例
  • 【Linux C/C++开发】轻量级关系型数据库SQLite开发(包含性能测试代码)
  • 音频生成技术的前沿探索:从语音合成到智能Podcast
  • 【VS】VS2019中使用rdlc报表,生成之前修改XML
  • 基于地图的数据可视化:解锁地理数据的真正价值
  • 第五部分:第二节 - Node.js 核心模块:厨房里的基本工具
  • Android架构之自定义native进程
  • 需求实现与测试验证脱节,如何确保产品质量
  • 【大模型面试每日一题】Day 17:解释MoE(Mixture of Experts)架构如何实现模型稀疏性,并分析其训练难点
  • Cadence软件浮点许可优化管理方案助力企业降本增效
  • 通过SSRF击穿内网!kali-ssrf靶场实战!
  • Maven插件学习(七)—— Toolchains设置项目单独构建JDK(或其他工具)
  • CAN(控制器局域网络)协议详解
  • 反射(Reflection)详解
  • leetcode0295. 数据流的中位数-hard
  • python数据分析常用的10个核心库