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

相关文章

JDK21对虚拟线程的几种用法实践指南

《JDK21对虚拟线程的几种用法实践指南》虚拟线程是Java中的一种轻量级线程,由JVM管理,特别适合于I/O密集型任务,:本文主要介绍JDK21对虚拟线程的几种用法,文中通过代码介绍的非常详细,... 目录一、参考官方文档二、什么是虚拟线程三、几种用法1、Thread.ofVirtual().start(

Python函数作用域与闭包举例深度解析

《Python函数作用域与闭包举例深度解析》Python函数的作用域规则和闭包是编程中的关键概念,它们决定了变量的访问和生命周期,:本文主要介绍Python函数作用域与闭包的相关资料,文中通过代码... 目录1. 基础作用域访问示例1:访问全局变量示例2:访问外层函数变量2. 闭包基础示例3:简单闭包示例4

Java中的.close()举例详解

《Java中的.close()举例详解》.close()方法只适用于通过window.open()打开的弹出窗口,对于浏览器的主窗口,如果没有得到用户允许是不能关闭的,:本文主要介绍Java中的.... 目录当你遇到以下三种情况时,一定要记得使用 .close():用法作用举例如何判断代码中的 input

SpringBoot中ResponseEntity的使用方法举例详解

《SpringBoot中ResponseEntity的使用方法举例详解》ResponseEntity是Spring的一个用于表示HTTP响应的全功能对象,它可以包含响应的状态码、头信息及响应体内容,下... 目录一、ResponseEntity概述基本特点:二、ResponseEntity的基本用法1. 创

Java8 Collectors.toMap() 的两种用法

《Java8Collectors.toMap()的两种用法》Collectors.toMap():JDK8中提供,用于将Stream流转换为Map,本文给大家介绍Java8Collector... 目录一、简单介绍用法1:根据某一属性,对对象的实例或属性做映射用法2:根据某一属性,对对象集合进行去重二、Du

Python中isinstance()函数原理解释及详细用法示例

《Python中isinstance()函数原理解释及详细用法示例》isinstance()是Python内置的一个非常有用的函数,用于检查一个对象是否属于指定的类型或类型元组中的某一个类型,它是Py... 目录python中isinstance()函数原理解释及详细用法指南一、isinstance()函数

Python内存管理机制之垃圾回收与引用计数操作全过程

《Python内存管理机制之垃圾回收与引用计数操作全过程》SQLAlchemy是Python中最流行的ORM(对象关系映射)框架之一,它提供了高效且灵活的数据库操作方式,本文将介绍如何使用SQLAlc... 目录安装核心概念连接数据库定义数据模型创建数据库表基本CRUD操作创建数据读取数据更新数据删除数据查

Python中的sort方法、sorted函数与lambda表达式及用法详解

《Python中的sort方法、sorted函数与lambda表达式及用法详解》文章对比了Python中list.sort()与sorted()函数的区别,指出sort()原地排序返回None,sor... 目录1. sort()方法1.1 sort()方法1.2 基本语法和参数A. reverse参数B.

vue监听属性watch的用法及使用场景详解

《vue监听属性watch的用法及使用场景详解》watch是vue中常用的监听器,它主要用于侦听数据的变化,在数据发生变化的时候执行一些操作,:本文主要介绍vue监听属性watch的用法及使用场景... 目录1. 监听属性 watch2. 常规用法3. 监听对象和route变化4. 使用场景附Watch 的

Java Instrumentation从概念到基本用法详解

《JavaInstrumentation从概念到基本用法详解》JavaInstrumentation是java.lang.instrument包提供的API,允许开发者在类被JVM加载时对其进行修改... 目录一、什么是 Java Instrumentation主要用途二、核心概念1. Java Agent