ARM内存屏障/编译屏障API(__DMB、__DSB、__ISB)用法及举例

2024-08-31 14:44

本文主要是介绍ARM内存屏障/编译屏障API(__DMB、__DSB、__ISB)用法及举例,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

0 参考资料

STM32F7 Series and STM32H7 Series Cortex®-M7 processor.pdf
ARM Cortex™-M Programming Guide to Memory Barrier Instructions.pdf

1 ARM内存屏障/编译屏障指令(__DMB、__DSB、__ISB)说明

内存屏障和编译屏蔽其实是2个东西,一个是处理器运行时避免乱序引入的指令,一个是编译时避免编译器编译后的代码乱序引入的指令。它们二者具有什么样的联系呢,为什么在嵌入式开发中常见的__DMB、__DSB、__ISB API将这二者都结合在一起了呢?下面一一道来。
在进行嵌入式开发中,经常会看到DMB、DSB、ISB这三个指令。在具有超标量流水线的MCU(如Cortex-M7内核的stm32H743)/SoC更是经常会使用到,相对于只支持三级流水线的Cortex-M4在每个时钟周期只能执行一条指令,Cortex-M7拥有六级流水线和双发射超标量架构,拥有在一个时钟周期内执行两条指令的能力。以五级指令流水线为例,其指令执行步骤如下:

取指->译码->发射->执行->写回

其中发射和执行都是乱序的。
超标量流水线的设计产生了乱序指为嵌入式开发带来新的问题,同时固有的编译器优化也会导致内存乱序访问。总结来说会带来以下2个问题:
(1)编译时,编译器优化导致指令执行步骤和实际书写的顺序不一致,进而产生内存乱序访问。为了解决这一问题,可以使用"memory"这个伪指令,告诉编译器不要将该指令前后代码顺序打乱。
(2)运行时,由于处理器乱序执行导致内存乱序访问
DMB、DSB、ISB就是为了解决这一问题而引入的指令。

为了解决上述的2个问题,可以直接将内存屏障和编译屏障结合在一起使用。以基于Cortex-A7的stm32MP135为例,其__DMB、__DSB、__ISB的API如下:

/**\brief   Instruction Synchronization Barrier\details Instruction Synchronization Barrier flushes the pipeline in the processor,so that all instructions following the ISB are fetched from cache or memory,after the instruction has been completed.*/
__STATIC_FORCEINLINE  void __ISB(void)
{__ASM volatile ("isb 0xF":::"memory");
}/**\brief   Data Synchronization Barrier\details Acts as a special kind of Data Memory Barrier.It completes when all explicit memory accesses before this instruction complete.*/
__STATIC_FORCEINLINE  void __DSB(void)
{__ASM volatile ("dsb 0xF":::"memory");
}/**\brief   Data Memory Barrier\details Ensures the apparent order of the explicit memory operations beforeand after the instruction, without ensuring their completion.*/
__STATIC_FORCEINLINE  void __DMB(void)
{__ASM volatile ("dmb 0xF":::"memory");
}

可以看到每个指令既包含和自己相关的指令,也包含"memory"这个伪指令,这是ARM平台的编译屏障指令。

1.1 DMB(Data Memory Barrier,数据内存屏障)指令

DMB主要用于多核处理器系统中,不同的处理器可能同时执行数据内存传输指令。DMB指令确保在DMB之前的所有显式数据内存传输指令都已经在内存中读取或写入完成,同时确保任何后续的数据内存传输指令都将在DMB执行之后开始执行,否则有些数据传输指令可能会提前执行。
它保证的是DMB之前的内存访问指令与DMB之后的内存访问指令的执行顺序,DMB不保证内存访问的完成顺序(保执行,不保完成)。也就是说,DMB指令之后的内存访问指令不会被处理器重排到DMB指令的前面。DMB指令不会保证内存访问指令在内存屏障指令之前完成,它仅仅保证内存屏障指令前后的内存访问的执行顺序。DMB指令只影响内存访问指令、数据cache指令以及cache管理指令等,并不会影响其他指令(例如算术运算指令)的执行顺序。
注意:这里所说的多核不特指具有多个核心的CPU,如对多个不同区域的内存空间进行操作,如对DDR和系统寄存器。

1.2 DSB(Data Synchronization Barrier,数据同步屏障)指令

在计算机的体系结构中,处理器在执行指令时通常会利用指令流水线来提高性能。但也会产生一些问题,比如在多线程编程中,两个线程同时对共享的内存进行读写操作,由于读/写操作的重排序,就会导致数据的不一致。
当执行DSB指令时,它确保在DSB之前的所有显式数据内存传输指令都已经在内存中读取或写入完成,同时确保任何后续的指令都将在DSB执行之后开始执行。
DSB比DMB指令严格一些,仅当所有在它前面的内存访问指令都完成后,才会执行在它后面的指令,即任何指令都要等待DSB指令前面的内存访问指令完成。位于此指令前的所有缓存(如分支预测和TLB维护)操作需要全部完成。

注意:设备内存(Device Memory)/强序内存(Strongly Ordered Memory)类型访问时自动添加数据同步屏障DSB,不需要再自行添加

1.3 ISB(Instruction Synchronization Barrier,指令同步屏障)指令

指令的流水线允许处理器同时执行多条指令的不同阶段,然而这样并行执行可能会导致一些问题,特别是涉及到上下文切换(更改上下文操作包括cache、TLB、分支预测等维护操作以及改变系统控制寄存器等操作)的情况,如实时操作系统的任务切换。当上下文切换时,可能指令流水线中的指令还在执行,而此时上下文已经改变,导致指令执行的结果不正确。

通过插入ISB指令,处理器会将流水线中的指令全部刷新,从而确保之前的指令不会影响后续指令的执行,并且后续指令将从正确的上下文开始重新获取。
注:大多数CPU的体系架构在异常的入口和出口都有ISB的语义(自动执行)

2 ARM内存屏障指令(__DMB、__DSB、__ISB)在stm32MP135中的使用举例

下面介绍以基于Cortex-A7的stm32MP135为例,查看官方HAL库在哪些地方使用到了内存屏障指令,加深我们对内存屏障指令的认识。

2.1 __DMB使用举例

(1)

if (heth->RxDescList.RxBuildDescCnt != desccount){/* Set the tail pointer index */tailidx = (descidx + 1U) % ETH_RX_DESC_CNT;/* DMB instruction to avoid race condition */__DMB();/* Set the Tail pointer address */WRITE_REG(heth->Instance->DMAC0RXDTPR, ((uint32_t)(heth->Init.RxDesc + (tailidx))));heth->RxDescList.RxBuildDescIdx = descidx;heth->RxDescList.RxBuildDescCnt = desccount;}

__DMB()前一个操作是设置尾指针索引,后一个操作是写通道0 Rx描述符尾指针寄存器。通过__DMB()指令能够确保设置尾指针索引操作完成后再执行写通道0 Rx描述符尾指针寄存器,避免内存乱序执行带来错误的尾指针索引写入。
如果操作的数据属于同一个内存区域,则不需要使用该API,CPU会保证乱序执行后的效果和正常执行顺序的数据操作效果一致。

2.2 __DSB使用举例

(1)

if (heth->gState == HAL_ETH_STATE_STARTED){/* Config DMA Tx descriptor by Tx Packet info */if (ETH_Prepare_Tx_Descriptors(heth, pTxConfig, 0) != HAL_ETH_ERROR_NONE){/* Set the ETH error code */heth->ErrorCode |= HAL_ETH_ERROR_BUSY;return HAL_ERROR;}/* Ensure completion of descriptor preparation before transmission start */__DSB();dmatxdesc = (ETH_DMADescTypeDef *)(&heth->TxDescList)->TxDesc[heth->TxDescList.CurTxDesc];/* Incr current tx desc index */INCR_TX_DESC_INDEX(heth->TxDescList.CurTxDesc, 1U);/* Start transmission *//* issue a poll command to Tx DMA by writing address of next immediate free descriptor */WRITE_REG(heth->Instance->DMAC0TXDTPR, (uint32_t)(heth->TxDescList.TxDesc[heth->TxDescList.CurTxDesc]));/* Return function status */return HAL_OK;}else{return HAL_ERROR;}

__DSB()用于确保传输开始前完成DMA描述符的准备。
如果操作的数据属于同一个内存区域,则不需要使用该API,CPU会保证乱序执行后的效果和正常执行顺序的数据操作效果一致。

2.3 __ISB使用举例

Cortex-A7建议在以下地方使用__ISB指令:
在这里插入图片描述
整个cache操作(如清空、无效化)、TLB(Translation Lookaside Buffer的简称,可翻译为“地址转换后援缓冲器”,也可简称为“快表”)操作、分支预测器操作、更改系统控制寄存器操作。

(1)

__set_TTBR0(((uint32_t)ttb_addr) | 9U);
__ISB();

__ISB()使用之后可以看到TTBR0寄存器的设置效果。TTBR0寄存器是页表基地址寄存器,和TLB相关。
(2)

/** \brief  Invalidate the whole instruction cache
*/
__STATIC_FORCEINLINE void L1C_InvalidateICacheAll(void) {__set_ICIALLU(0);__DSB();     //ensure completion of the invalidation__ISB();     //ensure instruction fetch path sees new I cache state
}

__ISB()之前将整个Cache无效化。ARM推荐的整个cache无效化操作使用ISB指令。
简而言之,__ISB()之后能够看到指令执行以后的效果。例如将整个cache无效化是需要等待时间的,这个__ISB()就相当于等待cache无效化完成。

这篇关于ARM内存屏障/编译屏障API(__DMB、__DSB、__ISB)用法及举例的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1124203

相关文章

C++高效内存池实现减少动态分配开销的解决方案

《C++高效内存池实现减少动态分配开销的解决方案》C++动态内存分配存在系统调用开销、碎片化和锁竞争等性能问题,内存池通过预分配、分块管理和缓存复用解决这些问题,下面就来了解一下... 目录一、C++内存分配的性能挑战二、内存池技术的核心原理三、主流内存池实现:TCMalloc与Jemalloc1. TCM

mapstruct中的@Mapper注解的基本用法

《mapstruct中的@Mapper注解的基本用法》在MapStruct中,@Mapper注解是核心注解之一,用于标记一个接口或抽象类为MapStruct的映射器(Mapper),本文给大家介绍ma... 目录1. 基本用法2. 常用属性3. 高级用法4. 注意事项5. 总结6. 编译异常处理在MapSt

Python包管理工具核心指令uvx举例详细解析

《Python包管理工具核心指令uvx举例详细解析》:本文主要介绍Python包管理工具核心指令uvx的相关资料,uvx是uv工具链中用于临时运行Python命令行工具的高效执行器,依托Rust实... 目录一、uvx 的定位与核心功能二、uvx 的典型应用场景三、uvx 与传统工具对比四、uvx 的技术实

Python中使用uv创建环境及原理举例详解

《Python中使用uv创建环境及原理举例详解》uv是Astral团队开发的高性能Python工具,整合包管理、虚拟环境、Python版本控制等功能,:本文主要介绍Python中使用uv创建环境及... 目录一、uv工具简介核心特点:二、安装uv1. 通过pip安装2. 通过脚本安装验证安装:配置镜像源(可

java中long的一些常见用法

《java中long的一些常见用法》在Java中,long是一种基本数据类型,用于表示长整型数值,接下来通过本文给大家介绍java中long的一些常见用法,感兴趣的朋友一起看看吧... 在Java中,long是一种基本数据类型,用于表示长整型数值。它的取值范围比int更大,从-922337203685477

MyBatis ResultMap 的基本用法示例详解

《MyBatisResultMap的基本用法示例详解》在MyBatis中,resultMap用于定义数据库查询结果到Java对象属性的映射关系,本文给大家介绍MyBatisResultMap的基本... 目录MyBATis 中的 resultMap1. resultMap 的基本语法2. 简单的 resul

Python主动抛出异常的各种用法和场景分析

《Python主动抛出异常的各种用法和场景分析》在Python中,我们不仅可以捕获和处理异常,还可以主动抛出异常,也就是以类的方式自定义错误的类型和提示信息,这在编程中非常有用,下面我将详细解释主动抛... 目录一、为什么要主动抛出异常?二、基本语法:raise关键字基本示例三、raise的多种用法1. 抛

Redis过期删除机制与内存淘汰策略的解析指南

《Redis过期删除机制与内存淘汰策略的解析指南》在使用Redis构建缓存系统时,很多开发者只设置了EXPIRE但却忽略了背后Redis的过期删除机制与内存淘汰策略,下面小编就来和大家详细介绍一下... 目录1、简述2、Redis http://www.chinasem.cn的过期删除策略(Key Expir

Java中常见队列举例详解(非线程安全)

《Java中常见队列举例详解(非线程安全)》队列用于模拟队列这种数据结构,队列通常是指先进先出的容器,:本文主要介绍Java中常见队列(非线程安全)的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录一.队列定义 二.常见接口 三.常见实现类3.1 ArrayDeque3.1.1 实现原理3.1.2

java中Optional的核心用法和最佳实践

《java中Optional的核心用法和最佳实践》Java8中Optional用于处理可能为null的值,减少空指针异常,:本文主要介绍java中Optional核心用法和最佳实践的相关资料,文中... 目录前言1. 创建 Optional 对象1.1 常规创建方式2. 访问 Optional 中的值2.1