armv8 memory order

本文详细解析了ARMv8架构中Observability的相关概念,包括observer、observedwrite、globallyobservedwrite等术语,为理解内存访问指令的行为及内存屏障指令奠定了基础。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转自www.wowotech.net

ARMv8之Observability

作者:linuxer 发布于:2016-5-25 18:22 分类:ARMv8A Arch

一、前言

在ARMv8关于memory order描述章节中,大量使用了observer、observed、completion等术语,本文主要是澄清这些术语,为后续描述memory order和memory barrier相关指令打下基础。另外,在几个星期前,和codingbelief同学讨论DMB指令的时候,他提出了一个尖锐的问题:什么是PE observes memory access,是指 cpu 执行了 memory access 指令么?当时我对这些概念也比较模糊,未能回答他的疑问,现在希望这份文档可以解决这个问题。

二、observer

原文定义:

An observer is a master in the system that is capable of observing memory accesses

从字面的意思看,能够“观察”到内存访问行为的master角色的硬件组件就是observer。这里需要解释两个关键词:一个是master,另外一个是观察(observing)。一个observer首先需要是一个总线master,可以通过总线发出读写操作。而所谓“观察”,实际上也通过read或者write操作来感知其他的内存操作,例如:通过read操作,observer可以感知到内存值的数据变化。当然,observer不仅仅能够读,还可以执行写操作,一个PE中可能的observer包括:

(1)执行load或者store操作的CPU组件(能够对内存发起读或者写操作)

(2)执行取指操作的CPU组件(仅仅能够发起对内存的read操作)

(3)加载页表的CPU组件(仅仅能够发起对内存的read操作)

从上面的描述可知,并非系统中有多少个CPU core(或者称为PE)就有多少个observer,一个PE往往包括多个observer。实际上,系统中的observer的数目是和具体的实现相关,除了PE之外,GPU,DMA controller等都可以发起对memory的read或者write操作,也都是observer。

 

三、什么是Observability?

可以用下面通俗的语言来描述Observability:

I have observed your write when I can read what you wrote and I have observed your read when I can no longer change the value you read

这里的I和you都是指的系统中的observer。上面的这句话是针对两个场景:一个是对写动作的观察,I have observed your write when I can read what you wrote,当A observer通过对X地址的读操作获取了另外B observer对X地址的写入数值的时候,那么,可以认为A observer观察到了B observer的写入动作。另外一个场景是对读动作的观察,I have observed your read when I can no longer change the value you read,当A observer无法通过对地址X的值的写入操作来影响B observer的对X地址的read操作结果的时候,我们认为A observer观察到了B observer的read动作。

 

四、observed write

原文定义:

A write to a location in memory is said to be observed by an observer when: 
— A subsequent read of the location by the same observer returns the value written by the observed write, or written by a write to that location by any observer that is sequenced in the Coherence order of the location after the observed write. 
— A subsequent write of the location by the same observer is sequenced in the Coherence order of the location after the observed write.

什么叫一次写入的内存操作被某个observer观察到(observed)?这事不能从单个写入操作看,还是让我们拉高一些,从一个写入序列来看。我们还是通过一个经典的例子来说明observed write这个概念。假设系统中有四个cpu core,分别执行同样的代码:cpux给一个全局变量A赋值为x,然后不断对A进行观察(即load操作)。在这个例子中A分别被四个CPU设定了1、 2、3、4的值,当然,先赋值的操作结果会被后来赋值操作覆盖,最后那个执行的write操作则决定了A变量最后的赋值。假设一次运行后,cpu 1看到的序列是{1,2},cpu 2看到的序列是{2},cpu 3看到的序列是{3,2},cpu 4看到的序列是{4,2},具体如下图所示:

我们先选定一个观察对象:cpu4的写入操作。怎么观察呢?通过read或者write来观察(英文原文的两段解释就是分别针对read和write的)。我们先看read吧,这是最直观的感受(或者称为观察)cpu4的写入动作的方法。有两种情况都可以认为本observer已经观察到了cpu4的写入操作:

(1)该observer的read操作返回了“4”。例如在cpu4上,read返回了之前write的数值“4”

(2)该observer的read操作没有返回“4”,但是返回了Coherence order 排在4之后的数值。例如,对于cpu1而言,刚开始,其read操作返回“1”(红色条带),大概在300ns的时间左右,read操作返回了“2”,我们知道,对于全局变量A,其coherent order是{3,1,4,2},只要返回了coherent order序列中,4之后的数值,我们就认为该observer已经观察到了cpu4的对全局变量A写入数值4的操作。

通过write来“观察”另外一个write操作有点不是那么符合人类的逻辑思维,但是可以慢慢适应的,毕竟我们的目标是理解multiprocessing environment。对于write而言,只有一种情况认为本observer已经观察到了cpu4的写入操作:即做为观察者的那个observer的写入动作在coherent order序列中排在“4”之后。对于cpu 2而言,当执行写入2的操作的时候,我们认为,cpu 2已经观察到了cpu4的写入操作。

需要强调的是,本节描述的observed write都是以一个特定的observer为视角的,不是全局的概念。

 

五、globally observed write

原文定义:

A write to a location in memory is said to be globally observed for a shareability domain or set of observers when: 
— A subsequent read of the location by any observer in that shareability domain returns the value written by the globally observed write, or written by a write to that location by any observer that is sequenced in the Coherence order of the location after the globally observed write. 
— A subsequent write of the location by any observer in that shareability domain is sequenced in the 
Coherence order of the location after the globally observed write.

理解了上一节的内容,这里几乎不需要过多的解释,只需要强调两点:

(1)globally observed write是限定在一个shareability domain内部,或者指定的一个observer的集合。

(2)当一个shareability domain(observer集合)内所有的observer都观察到了一次write操作,那么就是globally observed write。

 

六、observed read和globally observed read

原文定义:

• A read of a location in memory is said to be observed by an observer when a subsequent write to the location by the same observer has no effect on the value returned by the read. 
• A read of a location in memory is said to be globally observed for a shareability domain when a subsequent write to the location by any observer in that shareability domain has no effect on the value returned by the read.

怎么观察(感知)其他observer的读操作的确是一个技术活,通过read毫无疑问是不可能感知的,唯有通过write来感知。当某个observer无法通过write操作来影响被观察者的read操作的时候,我们就认为该observer已经感知到了该read操作。类似的,globally observed read就是一个shareability domain内所有的observer们都观察到了该read操作。

 

七、内存访问指令的执行完成(completion)

原文定义:

A read or write is complete for a shareability domain when all of the following are true: 
— The read or write is globally observed for that shareability domain. 
— Any translation table walks associated with the read or write are complete for that shareability domain.

内存访问指令被观察到和内存访问指令执行完毕是两个不同的概念,对于内存访问指令执行完毕,需要满足下面的两个条件:

(1)该内存访问操作被特定的shareability domain内的所有的observer globally observed

(2)和该内存访问指令相关的translation table walks(也会引发内存访问操作)必须执行完毕。

这里又牵扯出另外一个问题:什么叫做translation table walks执行完毕?原文定义如下:

A translation table walk is complete for a shareability domain when the memory accesses associated with the translation table walk are globally observed for that shareability domain, and the TLB is updated.

需要满足两个条件:

(1)这个translation table walks而引起的内存访问操作被该shareability domain内的所有的observer globally observed

(2)TLB已经完成更新

 

八、参考文献

1、Computer Architecture, A Quantitative Approach 5th

2、Programmer’s Guide for ARMv8-A

 

原创文章,转发请注明出处。蜗窝科技,www.wowotech.net

标签: ARMv8 observer observed

评论:

Codingbelief 
2016-05-31 02:47
所以我理解的 PE observes memory access,是 PE 执行 memory access。而 observed,则是指执行的 memory access 可以认定为完成。
linuxer 
2016-05-31 11:55
@Codingbelief:“PE observes memory access”这句话,我的理解是observes是一个动词,表示PE正在“感知”来自自己或者其他PE的memory access的操作结果。 

observed是形容词,用来描述一个memory指令的执行结果是否被感知到。指令完成和指令被感知到是不同的概念,如果一条内存访问的指令已经完成,那么: 
1、该memory access被系统中所有的observer感知到(就是globally observed) 
2、和该内存访问指令相关的translation table walks(也会引发内存访问操作)必须执行完毕。 
因此observed和内存指令执行完成也不是同一个概念。
Codingbelief 
2016-05-31 02:40
个人感觉,observer 刚开始接触时,让人费解的一个原因是把它翻译为“观察者”。 

在中文上,观察有着“看”的操作,进而会让人联想到"读",再而让人联想到 observers 之间通过“读”感知到对方的动作,然后后面的一些 observed read write 解释起来就变得很别扭。 

我觉得,在上面 coherent order {3,1,4,2} 的例子中,每一个 observer 实际上是只能够确定自己的 write 和 read 的发生的时间、结束的时间以及执行的结果,并不能具体的”感知"到其他 observer 所进行的操作,observers 之间也没有进行"观察"对方的动作。 

当然,并不是说 observer 翻译成“观察者”是错误的,只是翻译成“观察者”会在前期理解的时候比较费力。 

换个角度,如果把 observer 翻译为“执行者”,可能会省力一些。 

observer 是“执行者”,它拥有“执行”读或者写内存的能力。那么对于 observed write 和 observed read 的可以解释如下: 


observed write 描述了这样一个事实: “执行者” 完成了特定的 write memory 的操作的执行。 

在上面经典例子的图中,我们可以很清楚的看到,在时间坐标轴上 500ns 时,CPU4 完成了 write 4 的操作。然而,在 PE 中,我们并没有这个关键的时间轴,这时候,我们就需要找其他的方法,来肯定"CPU4 完成了 write 4 的操作”这一事实。在 ARMv8 中,确定 write 操作已经完成执行(即 observed write)的方法是通过以下的两个事件: 

- CPU4 对同样的内存地址执行的下一个读操作,读回了刚刚写入的值,或者读回  coherent order 4 之后的值。 
- CPU4 对同样的内存地址执行下一个写操作,该写操作写入的值在 coherent order 4 之后。 

也就是说,当发生了上面的两个事件中的一个时,我们就可以认为 CPU4 的 write 4 操作已经执行完成,即 observed。 


observed read 则描述了这样一个事实: “执行者” 完成了特定的  read memory 的操作的执行。 

同样在上面经典例子的图中,在时间坐标轴上 500ns 后,CPU4 执行了 read 的操作,并且返回了 2。什么时候我们可以说 CPU4 已经完成了 read 2 的执行呢?在 ARMv8 中,如下定义: 

- 如果 CPU4 对同样内存地址执行了下一个 write 操作,并且该 write 操作没有影响 read 操作返回的值。 

上面的条件一旦达成,就意味着 CPU4 读回 2 已经是铁板钉钉的事实了,就可以说 CPU4  read  2 已经完成,即 observed read。 


observed 给我的感觉更多的是一个“边界”的概念,用于定义某一时刻某个执行者是否已经完成了特定的操作。
linuxer 
2016-05-31 12:05
@Codingbelief:看我之前写的文档,我和你一样被“忽悠”了,以为PE都是通过read来“观察”内存访问指令的结果。当然,具体的翻译而言,我觉得执行者更容易误导啊,因为很容易联想成“指令执行者” 

另外,你上面这段话我不是很认同,基本上,CPU 4上的对某个共享遍历的读或者写操作是否完成不能从CPU 4的视角来判断,应该是从系统的角度来看,即其他的CPU是如何看待CPU4 上的读写操作的。
<think>嗯,用户之前问了关于内核空间能否访问用户空间地址的问题,现在又转向了ARMv8架构中的普通内存和设备内存。我需要先确定用户的具体需求是什么。可能用户在进行底层开发,或者是在学习操作系统、驱动开发相关的知识。他们可能想知道在ARMv8架构下这两种内存类型的区别,以及如何正确使用它们。 首先,我得回忆一下ARMv8的内存类型分类。ARMv8将内存分为普通内存(Normal Memory)和设备内存(Device Memory)。普通内存用于常规的数据和代码存储,而设备内存用于与外设寄存器通信。这两者在访问属性、顺序性、副作用等方面有很大不同。 用户可能想知道为什么要有这样的区分,以及在实际编程中需要注意什么。比如,访问设备内存时可能需要使用特定的内存屏障,或者避免编译器优化,使用volatile关键字。另外,设备内存的访问属性(如nGnRnE、nGnRE、GRE等)对程序行为的影响也是关键点。 接下来要考虑用户的背景。他们可能是嵌入式开发者,或是学习操作系统的学生。如果是开发者,可能需要具体的代码示例和实际应用中的注意事项;如果是学生,可能需要更基础的概念解释。但根据之前的提问,用户似乎对内核和硬件机制有较深兴趣,因此需要详细的技术细节。 用户可能没有直接问到的,但潜在需要了解的是内存类型在缓存策略上的差异。比如,普通内存通常会被缓存,而设备内存通常不被缓存,这会影响性能和一致性。此外,内存屏障的使用在不同内存类型中的必要性也需要解释清楚。 需要确保回答结构清晰,分点说明两者的区别、属性、访问方式及实际应用。同时,要使用中文,避免专业术语过于密集,必要时举例说明。比如,在访问设备内存时,使用volatile指针防止编译器优化,或者使用DSB指令来确保内存操作的顺序性。 另外,用户可能对ARMv8的内存属性寄存器(MAIR)配置感兴趣,但这个问题可能超出当前问题的范围,可以适当提及但不深入。重点是普通内存和设备内存的核心区别和实际影响。 最后,总结部分要强调正确使用这两种内存的重要性,避免因错误配置导致的问题,如数据损坏、设备故障或性能下降。确保用户理解为什么需要区分这两种内存类型,以及如何在开发中正确应用这些知识。</think>### **ARMv8 普通内存与设备内存详解** 在 ARMv8 架构中,内存根据用途和访问特性被划分为 **普通内存(Normal Memory)** 和 **设备内存(Device Memory)**。这一分类直接影响 CPU 对内存的访问行为、缓存策略和内存屏障要求。以下是两者的核心区别和实际应用解析: --- ### **1. 普通内存(Normal Memory)** #### **(1) 定义与用途** - **用途**:存储程序代码、堆栈、动态数据等常规内容。 - **特点**:允许 **推测访问**(Speculative Access)和 **乱序执行**(Out-of-Order Execution),支持硬件缓存。 #### **(2) 关键属性** - **访问顺序性**: 默认允许 CPU 和编译器对内存操作进行重排序(除非通过内存屏障显式限制)。 - **缓存策略**: 通常配置为 **Write-Back Cacheable**(写回缓存),通过 L1/L2 缓存加速访问。 - **副作用**: 访问普通内存不会触发硬件外设行为(如修改寄存器状态)。 #### **(3) 示例场景** - 读写全局变量、分配堆内存、加载可执行代码。 --- ### **2. 设备内存(Device Memory)** #### **(1) 定义与用途** - **用途**:映射硬件外设寄存器(如 GPIO、UART、DMA 控制器等),用于与设备交互。 - **特点**:访问可能伴随 **硬件副作用**(如触发中断、修改设备状态),通常 **禁止缓存**。 #### **(2) 关键属性** - **访问顺序性**: 严格遵循程序顺序(No Reordering),每次访问必须按代码顺序执行。 - **缓存策略**: 强制配置为 **Non-Cacheable**(不可缓存),直接操作物理内存。 - **副作用**: 读写操作可能直接改变外设状态(如向 UART 数据寄存器写入一个字节会触发串口发送)。 #### **(3) 子类型(ARMv8 内存属性)** 设备内存进一步分为三种类型,通过 `MAIR_ELx`(Memory Attribute Indirection Register)配置: 1. **Device-nGnRnE**(严格设备内存) - 禁止重排序(No reordering)、禁止合并(No gathering)、**错误提前报告**(Early Write Acknowledgement)。 - 用于关键外设(如中断控制器),确保每次访问原子性。 2. **Device-nGnRE** - 禁止重排序和合并,但允许错误延迟报告。 - 常见于普通外设寄存器(如 GPIO)。 3. **Device-GRE** - 允许有限重排序(Gather-Reorder- Early Ack),适用于高吞吐设备(如 DMA 缓冲区)。 --- ### **3. 核心区别对比** | **特性** | **普通内存** | **设备内存** | |-----------------------|---------------------------|---------------------------| | **访问顺序性** | 允许乱序/推测执行 | 严格按程序顺序执行 | | **缓存策略** | 通常可缓存(Write-Back) | 不可缓存 | | **硬件副作用** | 无 | 可能触发设备行为 | | **内存屏障要求** | 需显式屏障控制多核一致性 | 每次访问隐式保序 | | **典型应用** | 代码、数据 | 外设寄存器、MMIO 区域 | --- ### **4. 编程实践与注意事项** #### **(1) 访问设备内存的代码规范** - **使用 `volatile` 关键字**:防止编译器优化(如消除“冗余”读写): ```c volatile uint32_t *reg = (volatile uint32_t *)0x1C010000; *reg = 0x55; // 确保写入实际发生 ``` - **避免缓存干扰**:通过 MMU 页表配置设备内存为 `Non-Cacheable`。 - **显式内存屏障**:即使设备内存隐式保序,跨普通/设备内存混合访问时仍需屏障: ```c // 写入设备寄存器后,确保后续普通内存操作不重排到前面 *device_reg = cmd; DSB(); // 数据同步屏障 ``` #### **(2) 配置示例(ARMv8 MMU 页表)** - **普通内存属性**: ```c // MAIR_EL1 索引 0:Normal Write-Back Cacheable mair = MAIR_ATTR(0, MT_NORMAL_WB); ``` - **设备内存属性**: ```c // MAIR_EL1 索引 1:Device-nGnRE mair |= MAIR_ATTR(1, MT_DEVICE_nGnRE); ``` - **页表项设置**: ```c // 映射外设寄存器区域(0x1C000000~0x1C0FFFFF)为设备内存 set_pte(addr, attr_index=1, AP=RW, S2AP=Unprivileged); ``` --- ### **5. 常见问题与调试** #### **(1) 错误配置的后果** - **普通内存误设为设备内存**: 导致缓存不一致(如 DMA 直接修改内存后,CPU 读取缓存旧值)。 - **设备内存误设为普通内存**: 缓存导致外设寄存器读写丢失(如写入被缓存,未实际到达设备)。 #### **(2) 调试技巧** - **检查页表属性**:通过 `MMU 转储工具` 确认内存类型和缓存配置。 - **使用逻辑分析仪**:捕获总线信号,验证设备寄存器的实际读写时序。 - **内核日志**:关注 `Alignment Fault` 或 `Permission Fault` 异常(可能因属性不匹配触发)。 --- ### **6. 总结** - **普通内存**:用于常规数据,支持缓存和乱序访问,依赖显式屏障保证一致性。 - **设备内存**:用于外设交互,不可缓存且严格保序,需通过 `volatile` 和屏障确保可靠性。 - **设计原则**:通过 MMU 正确配置内存类型,是避免硬件异常、数据竞争的关键。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值