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

相关文章

Java内存泄漏问题的排查、优化与最佳实践

《Java内存泄漏问题的排查、优化与最佳实践》在Java开发中,内存泄漏是一个常见且令人头疼的问题,内存泄漏指的是程序在运行过程中,已经不再使用的对象没有被及时释放,从而导致内存占用不断增加,最终... 目录引言1. 什么是内存泄漏?常见的内存泄漏情况2. 如何排查 Java 中的内存泄漏?2.1 使用 J

使用SpringBoot创建一个RESTful API的详细步骤

《使用SpringBoot创建一个RESTfulAPI的详细步骤》使用Java的SpringBoot创建RESTfulAPI可以满足多种开发场景,它提供了快速开发、易于配置、可扩展、可维护的优点,尤... 目录一、创建 Spring Boot 项目二、创建控制器类(Controller Class)三、运行

oracle中exists和not exists用法举例详解

《oracle中exists和notexists用法举例详解》:本文主要介绍oracle中exists和notexists用法的相关资料,EXISTS用于检测子查询是否返回任何行,而NOTE... 目录基本概念:举例语法pub_name总结 exists (sql 返回结果集为真)not exists (s

Java中switch-case结构的使用方法举例详解

《Java中switch-case结构的使用方法举例详解》:本文主要介绍Java中switch-case结构使用的相关资料,switch-case结构是Java中处理多个分支条件的一种有效方式,它... 目录前言一、switch-case结构的基本语法二、使用示例三、注意事项四、总结前言对于Java初学者

关于Java内存访问重排序的研究

《关于Java内存访问重排序的研究》文章主要介绍了重排序现象及其在多线程编程中的影响,包括内存可见性问题和Java内存模型中对重排序的规则... 目录什么是重排序重排序图解重排序实验as-if-serial语义内存访问重排序与内存可见性内存访问重排序与Java内存模型重排序示意表内存屏障内存屏障示意表Int

如何测试计算机的内存是否存在问题? 判断电脑内存故障的多种方法

《如何测试计算机的内存是否存在问题?判断电脑内存故障的多种方法》内存是电脑中非常重要的组件之一,如果内存出现故障,可能会导致电脑出现各种问题,如蓝屏、死机、程序崩溃等,如何判断内存是否出现故障呢?下... 如果你的电脑是崩溃、冻结还是不稳定,那么它的内存可能有问题。要进行检查,你可以使用Windows 11

Springboot中Jackson用法详解

《Springboot中Jackson用法详解》Springboot自带默认json解析Jackson,可以在不引入其他json解析包情况下,解析json字段,下面我们就来聊聊Springboot中J... 目录前言Jackson用法将对象解析为json字符串将json解析为对象将json文件转换为json

NameNode内存生产配置

Hadoop2.x 系列,配置 NameNode 内存 NameNode 内存默认 2000m ,如果服务器内存 4G , NameNode 内存可以配置 3g 。在 hadoop-env.sh 文件中配置如下。 HADOOP_NAMENODE_OPTS=-Xmx3072m Hadoop3.x 系列,配置 Nam

maven 编译构建可以执行的jar包

💝💝💝欢迎莅临我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」👈,「stormsha的知识库」👈持续学习,不断总结,共同进步,为了踏实,做好当下事儿~ 专栏导航 Python系列: Python面试题合集,剑指大厂Git系列: Git操作技巧GO

bytes.split的用法和注意事项

当然,我很乐意详细介绍 bytes.Split 的用法和注意事项。这个函数是 Go 标准库中 bytes 包的一个重要组成部分,用于分割字节切片。 基本用法 bytes.Split 的函数签名如下: func Split(s, sep []byte) [][]byte s 是要分割的字节切片sep 是用作分隔符的字节切片返回值是一个二维字节切片,包含分割后的结果 基本使用示例: pa