Html手机浏览网站变形,wordpress非插件oss,sharepoint做门户网站,粘合剂东莞网站建设前言
这部分的内容比较抽象#xff0c;很多内容我无法理解#xff0c;都是直接翻译过来的。虽然难#xff0c;但是不可不看#xff0c;如果遇到无法理解的都直接跳过#xff0c;那后面都无法学习下去了。觉得无法理解是因为目前的知识还很欠缺#xff0c;到后面具备了这…前言
这部分的内容比较抽象很多内容我无法理解都是直接翻译过来的。虽然难但是不可不看如果遇到无法理解的都直接跳过那后面都无法学习下去了。觉得无法理解是因为目前的知识还很欠缺到后面具备了这些方面的认知再理解这些内容就会好很多了这里先走马观花过一遍即可有个大致的印象后面用到可以回过头来重新理解并修订。觉得翻译有问题的可以参考原文。
在比较老的ARM架构实现中指令的执行顺序是严格按照代码编写的顺序来的一条指令执行完成后才会执行下一条指令。比较新的ARM架构在指令执行顺序和访存操作方面进行了一些优化为了减小内核执行指令的速度与外设访问速度差异带来的影响在架构里面引入了缓存Caches和写缓冲区Write Buffers)。它们的引入带来的一个影响是访存的顺序可能会被改变实际对外设的访问顺序可能跟内核执行访存指令的顺序不一致举个例子说明 左边是内核执行指令的顺序右边是指令实际的执行情况。第一条指令使用STR指令将R12寄存器的值写入到内存中它实际上将写内存操作写到了write buffer里面。后面两条指令是读数据指令这两条指令都会先于write buffer将数据写入到内存之前完成。第二条指令从内存中读取数据未命中Cache所以它会到内存中去读取数据。第三条指令也从内存中读取数据但是命中Cache它可以立即得到数据。对于CPU来说它执行指令的顺序是1 2 3但是对内存来说它接收到的访存请求却是2 1。
当然有时候需要让实际的访存顺序按照我们指令的编写顺序去严格执行比如说修改CP15寄存器或者拷贝内存里面的代码这样的一些操作处理器需要等待对应的操作完成。
对于那些超高性能的处理器往往它们支持数据预测访问指令多发射缓存同步协议和乱序执行的特性这些特性更可能引起访存顺序的改变。在单核的CPU上硬件会处理好这些问题它会考虑访问数据之间的关系保证访存操作的结果正确。但是假如CPU有多个核它们通过共享内存交换数据考虑访存顺序就变得特别重要你可能需要特别关心多线程访问共享内存时的同步问题。
ARMv7-A架构的处理器采用的是弱顺序存储模型该模型允许重排序访存操作实际的访存顺序不用严格遵守加载(LDR)和存储(STR)指令的执行顺序。对Normal Memory的读写操作可以由硬件重排序这种重新排序只受数据依赖性和显式内存屏障指令的约束。如果需要更强有力的排序规则需要在该内存块的转换表入口里面设置相应的属性来告知处理器核。为处理器核强制设置排序规则会限制硬件优化的效果导致性能下降功耗增加。
ARM存储顺序模型
Cortex-A系列处理器采用用的是弱顺序存储模型访存操作可以被重新排序。ARM架构定义了3种互斥的存储类型严格顺序Strongly-ordered类型设备Device)类型常规Normal类型所有的存储区域都必须设置为其中一种存储类型。下表列出了两个访存操作A1 A2访问不同存储类型的存储区域时的实际访存序A1先于A2发出 可以看到对于Normal类型的存储区域访存操作可以被重排序。如果都是访问的Device类型或Strongly-Ordered类型的存储区域则严格按照指令顺序访存。
Strongly-Ordered和Device存储类型
对Strongly-Ordered和Device类型存储区域的访问遵循相同的存储序模型规则如下
访问的次数和数据量大小保持原状访问操作是原子的不会被中途打断。读写访问都会对系统产生影响无法理解访问不经过缓存不允许预测访问。不允许非对齐访问。对于Device类型的存储区域访问到达存储区的顺序保证跟访存的指令顺序一致这种保证仅适用于访问同一个外设或同一块存储区域比如访问同一Device类型页的几个地址它们的访问顺序是被保证的。在ARMv7架构里对Normal存储区域和Strongly-Ordered/Device存储区域的访问顺序可以调整。
Strongly-Ordered和Device存储类型的区别仅有以下两点
对于Strongly-Ordered存储区域的写操作只有当写操作真正到达外设或存储设备后才算完成。对于Device存储区域的写操作可以在写操作真正达到外设或存储设备前提前完成。
系统外设绝大多数都被映射为Device类型Device类型的存储区域可以设置Shareable属性。
Normal存储类型
存储系统的绝大部分存储区域都是Normal类型的所有的ROM和RAM都被当作Normal存储类型的设备。Normal存储类型有如下特点
CPU核可以重复读和某些写操作无法理解。在MMU访问权限允许的情况下CPU核可以对额外的存储位置进行预测访问不包括写操作不会引起问题无法理解。允许非对齐访问。多个访存操作可以被内核硬件合并为更少的访存操作但是单次访存的数据量更大。
这段话直接翻译的无法理解 Normal类型的存储区域必须设置缓存属性cacheability attributesARM架构为Normal内存的两个缓存级别(内侧缓存inner cache和外侧缓存outter cache)提供了缓存属性支持。内侧指的是最靠近CPU核的缓存总是包含CPU核的一级缓存实现可能没有外侧缓存或者可以将二级和三级缓存当作外侧缓存为它们指定外侧缓存属性。这些不同级缓存之间的映射以及缓存的物理实现是由实现自定义的。
Normal类型的存储区域需要设置共享属性shareability attribute)要么是可共享的Shareable要么是非共享的Non-Shareable。共享和非共享是针对CPU核来说的非共享指存储区域只能由一个CPU核使用共享则可以由多个CPU核共用但是软件需要处理存储数据的一致性问题比如可以让某些核来负责维护缓存和操作内存屏障。
这段话直接翻译的无法理解 外部共享属性使得系统的定义包含多级一致性控制。比如说一个内部共享域可以由一个Cortex-A15集群和一个Cortex-A7集群组成。在一个集群内部CPU核的数据缓存对所有具有内部共享属性的数据访问是一致的。同时这个集群和一个具有多核的图像处理器可能组成外部共享域。一个外部共享域可以由多个内部共享域组成但是一个内部共享域只能作为某一个外部共享域的一部分。
这段话直接翻译的无法理解 具有可共享属性的区域是可被系统中的其他代理访问的区域。同一可共享域内的其他处理器对该区域内存的访问是一致的。这意味着你不必考虑数据或缓存的影响。如果没有可共享属性在核心之间不维护共享内存区域的一致性的情况下您必须自己显式地管理一致性。
ARMv7架构允许将共享的存储区域指定为内部可共享Inner Shareable或外部可共享Outer Shareable。
存储屏障 Memory barriers
存储屏障要求CPU核限制屏障前后的访存指令的访存顺序可以使用屏障指令来实现。前面已经提到过缓存写缓冲和乱序执行等优化会让实际的访存顺序与代码里的指令顺序不同这种访存顺序的改变对应用开发人员是不可见的应用开发者通常不用关注内存屏障。但是对于驱动开发人员或者需要同步多个数据观察者时就需要考虑访存顺序改变问题。ARM架构提供了存储屏障指令能够强制让CPU核等待访存操作完成它们在ARM和THUMB代码里面在用户模式和特权模式下均可使用。
先来看一下这些屏障指令在单核系统里面的作用。直接访问(explicit access)指的是由LDR和STR指令引起的数据访问操作不包含取指令操作。 Data Synchronization Barrier (DSB)数据同步屏障。 这条指令强制让CPU核等待所有挂起的直接数据访问完成之后才能执行其它的指令阶段但是它不影响指令预取。 Data Memory Barrier (DMB)数据存储屏障。 这条指令保证代码里面出现在屏障之前的所有访存操作会先于屏障之后的任何直接访问操作在系统里面被观察到但是它不影响其它指令执行的顺序也不影响取指令的顺序。 Instruction Synchronization Barrier (ISB)指令同步屏障 这条指令会冲刷流水线和预取缓冲以便屏障后的所有指令都取自缓存或内存这可以确保上下文调整操作的效果。比如说屏障之前的CP15协处理器操作、改变ASID、TLB或者分支预测操作对屏障之后的任何指令都是可见的。这个屏障本身不会引起数据和指令缓存之间的同步但在同步的时候却需要用到。
有几个选项可以搭配DMB和DSB指令一起使用以提供访问类型和它所适用的可共享域如下所示 SY 这是默认值意味着屏障适用于整个系统包括所有CPU核心和外设。 ST 一个仅等待存储完成的屏障。 ISH 仅适用于内侧共享域的屏障。 ISHST 一个结合ST和ISH的屏障。也就是说它只对内部可共享内存进行存储。 NSH 一个(PoU)Point of Unification屏障。 NSHST 一个仅等待存储完成的屏障并且只能读出到PoU。 OSH 外侧共享域的屏障。 OSHST 仅等待存储完成的屏障并且只能用于外侧共享域。
为了理解这些选项的含义你必须使用在多核系统中更为通用的DMB和DSB定义。以下内容中提到的处理器或代理并不一定指代CPU核也可能指DSP、DMA控制器、硬件加速器或任何其它访问共享内存的模块。
DMB指令的作用是在可共享域内强制执行内存访问顺序保证共享域内的所有处理器能够先观察到DMB指令之前的所有显式内存访问然后才能观察到指令之后的任何显式内存访问。
DSB指令与DMB指令具有相同的效果但除此之外它还使内存访问与完整的指令流同步而不仅仅是与其它内存访问同步。这意味着当发出DSB指令时指令执行将暂停直到所有的显式内存访问完成。当所有的内存读取操作都完成并且写缓冲区已排空时将恢复指令的执行。
通过考虑一个示例可能更容易理解内存屏障的作用。举一个四核Cortex-A9集群的例子该集群形成一个单独的内部可共享域。当集群中的单个核心执行DMB指令时该核心将确保屏障之前的所有访存操作按照指令中的顺序完成然后才执行屏障之后的显式内存访问。这样可以确保集群中的所有核心都将以同样的顺序看到该屏障两侧的内存访问操作。如果使用DMB ISH变体则无法保证外部观察者(例如DMA控制器或DSP)也能做到这一点。
接下来看两个内存屏障使用的实例先看第一个假设有两个核心A和B并且内核寄存器中保存了两个Normal存储区域的地址Addr1和Addr2。A和B分别执行如下两条访存指令 A:
STR R0, [Addr1]
LDR R1, [Addr2]B:
STR R2, [Addr2]
LDR R3, [Addr1]这个示例没有对访存顺序做任何要求也没有对访存事务发生的顺序做出任何声明。地址Addr1和Addr2是独立的两个核心都没有必要按程序中的指令顺序来执行加载和存储操作它们也互不关心另一个核心的操作。因此这段代码有四种可能的合法结果A的R1寄存器和B的R3寄存器中的值会有4种不同组合A.R1获得旧数据 B.R3获得旧数据、A.R1获得旧数据 B.R3获得新数据、A.R1获得新数据 B.R3获得旧数据、A.R1获得新数据 B.R3获得新数据。
如果再多加一个核心C也必须注意到没有要求它以其它核心相同的顺序观察任一存储器。A和B都完全可以在Addr1和Addr2中看到旧值而C可以看到新值。
接下来的这个示例基于上一个示例进行改进考虑B核心上的代码需要根据A核心设置的某个标志来读取内存数据的情况比如你正在从A向B传递消息代码片段看起来可能会像下面这样 A:
STR R0, [Msg] 写一些数据到消息邮箱
STR R1, [Flag] 新数据已经就绪可以读取了B:
Poll_loop:LDR R1, [Flag]CMP R1,#0 标志是否已经设置BEQ Poll_loopLDR R0, [Msg] 读取新数据.同样这段代码可能不会按照预期的方式工作。因为没有理由不允许B核心在读取[Flag]之前投机地先读取[Msg]。这是正常的因为对于弱排序的内存核心并不知道[Msg]和[Flag]之间可能存在依赖关系。你必须通过插入内存屏障来显式强制执行依赖关系。在这个例子中您实际上需要两个内存屏障。A核心需要在两个存储操作之间插入一个DMB以确保它们按代码里面指定的顺序发生。B核心需要在LDR R0, [Msg]之前插入一个DMB以确保在标志设置之前不读取消息。
使用内存屏障来避免死锁
在某些情况下不使用内存屏障可能会引起死锁。比如有如下的代码片段
STR R0, [Addr] write a command to a peripheral register
DSB
Poll_loop:
LDR R1, [Flag]
CMP R1,#0 wait for an acknowledge/state flag to be set
BEQ Poll_loop这段代码假设CPU核心通过Addr地址与某个外设进行通信。外设轮询Addr的数据它获取到数据后会设置Flag标志。CPU核心轮询Flag标志是否设置然后往下运行。
ARMv7架构没有多处理扩展(multiprocessing extensions)不严格要求一定要完成数据到[Addr]的存储可能只是将写请求发送到了写缓冲区。CPU核心和外设都可能锁死外设在等CPU核心的数据CPU核心在等待外设返回的标志位。通过在核心的STR之后插入一个DSB可以让CPU核心在读取Flag之前将数据写入到Addr避免发生死锁。
实现了多处理扩展的CPU核心必须在有限的时间内完成访问即它们的写缓冲区必须排空因此不需要使用屏障指令。
WFE与WFI指令与屏障的交互
WFE等待事件和WFI等待中断指令使您能够停止执行并进入低功耗状态。为了确保在执行WFI或WFE之前所有的内存访问都已完成并且对其它核心可见您必须插入一个DSB指令。
另一个需要考虑的问题与在多核系统中使用WFE和SEV发送事件有关。这些指令使您能够减少与等待获取锁自旋锁相关的功耗。尝试获取互斥锁的核心可能会发现其它核心已经拥有该锁。您可以使用WFE指令让核心暂停执行并进入低功耗状态而不是让核心反复轮询锁。
当接收到一个中断或其发生了异步异常事件或者收到另一个核心发送的事件使用SEV指令时核心会被唤醒。拥有锁的核心将在释放锁后使用SEV指令唤醒处于WFE状态的其它核心。内存屏障指令不将事件信号视为明确的内存访问因此我们必须确保在执行SEV指令之前其它处理器可以看到锁被释放这需要使用DSB。DMB不能满足要求因为它只影响内存访问顺序而不将内存访问同步到特定的指令。而DSB将防止SEV执行直到其它核心看到所有DSB屏障之前的内存访问。
内存屏障在Linux里面的使用
内存屏障用于强制内存操作的顺序。通常您不需要理解或显式使用内存屏障。这是因为它们已经包含在内核的锁和调度原语中。然而设备驱动程序编写者或寻求理解操作系统内核的人可能会希望详细了解内存屏障的用法。
编译器和CPU核心架构的优化允许指令和相关内存操作的顺序被改变。然而有时您希望强制让内存的操作按照指定的执行顺序。例如您可以通过内存映射的方式写外设寄存器写寄存器可能对系统的其它部分产生影响。在我们的程序中此操作之前或之后的内存操作可以像它们可以被重新排序一样出现因为它们在不同的位置上操作。然而在某些情况下您希望确保所有操作在完成此外设寄存器写入之前完成。或者您可能希望确保外设寄存器写入完成之前任何其它的内存操作都不会开始。Linux提供了一些函数用于实现这些目的它们如下
使用barrier()函数可以告知编译器不允许对特定的内存操作进行重新排序。这仅控制编译器的代码生成和优化对硬件重新排序没有影响。通过调用ARM处理器指令集里面的内存屏障指令可以强制内存操作的顺序。对于Cortex-A的处理器linux提供如下的一些屏障指令 1.读内存屏障rmb()函数确保在屏障之前的任何读取操作在屏障之后的任何读取操作执行之前完成。 2.写内存屏障wmb()函数确保在屏障之前的任何写入操作在屏障之后的任何写入操作执行之前完成。 3.内存屏障mb()函数确保在屏障之前的任何内存访问在屏障之后的任何内存访问执行之前完成。
这些屏障指令有相应的SMP版本称为smp_mb()、smp_rmb()和smp_wmb()。这些屏障用于强制同一集群内的核心之间的Normal可缓存内存的顺序例如Cortex-A15集群中的每个核心。它们可以与设备一起使用甚至适用于不可缓存的Normal内存。当内核编译未配置CONFIG_SMP时这些函数的调用展开为barrier()函数。
Linux内核提供的所有锁原语都使用了对应的屏障具体可以参考linux内核中使用内存屏障。
缓存一致性影响Cache coherency implications
大部分时候缓存对应用程序员来说是不可见的。然而当系统中的其它地方更改了内存数据或当应用程序对内存的更新必须对系统的其它部分可见时应用就能够感知到缓存的影响。
一个包含外部DMA设备和核心的系统提供了一个可能出现缓存问题的简单示例。如果DMA从内存中读取数据而更新的数据却存储在CPU核的缓存中那么DMA将会读取到旧的数据。同样地如果DMA向内存写入了数据而CPU核缓存中存在过时的数据那么CPU核可能会继续使用这些过期的旧数据。
因此在DMA开始之前必须显式清理CPU数据缓存中的脏数据。同样地如果DMA正在传输数据到内存则必须确保CPU数据缓存中不包含陈旧数据。缓存不会因为DMA写入内存而自动更新这可能需要CPU在启动DMA之前清理缓存或使受影响的缓存区域无效。由于所有ARMv7-A处理器都可以进行推测性内存访问因此在使用DMA后也需要进行缓存无效化操作。 代码拷贝问题 Issues with copying code 引导代码、内核代码或即时编译器JIT compilers可以将程序从一个位置复制到另一个位置或者修改内存中的代码但是并没有硬件机制来维护指令缓存和数据缓存之间的一致性。您必须将受影响的区域无效化来将陈旧的代码数据从指令缓存中清除并确保写入的代码数据实际上已经到达主内存。如果CPU打算跳转到修改后的代码还需要使用包括屏障指令在内的特定代码序列。 编译器重排序优化 内存屏障指令仅适用于硬件对内存访问的重排序理解这一点非常重要。代码中插入内存屏障指令可能并不会对编译器重排序内存访问产生任何直接影响编译器仍然可能进行内存访问重排序优化。在C语言中可以使用volatile限定符告诉编译器该变量可能会被当前正在访问它的代码之外的东西(比如DMA,中断等等)更改。在C语言中这通常用于通过内存映射的方式访问I/O使得这类设备可以通过指向volatile变量的指针安全地访问。 然而C标准没有提供关于在多核系统中使用volatile的规则。因此尽管你可以确定volatile加载和存储将各自按照程序中指定的顺序发生但却没有关于相对于非volatile加载和存储的访问重排序的保证这意味着volatile不能用于实现互斥锁。 由于volatile不提供跨多个处理器核心的同步保证因此在多线程或多核环境中通常需要使用更复杂的同步机制如互斥锁、读写锁、原子操作或内存屏障指令来确保数据的一致性和顺序性。