Linux memory读写约束readl、readl_relaxed、writel、writel_relaxed区别

发布时间 2023-07-01 16:07:10作者: zephyr~

内存类型和属性 | ARM Memory types and attributes

flowchart LR 1(Arm Memory Type) --> 2(Normal Memory) 1 --> 3(Device Memory) 2 --> 4(Shareable) 2 --> 6(Cacheability attributes) 4 --> 7(Inner Shareable) 4 --> 8(Outer Shareable) 4 --> 9(Non-shareable) 6 --> 10(Write-Through Cacheable) 6 --> 11(Write-Back Cacheable) 6 --> 12(Non-cacheable) 3 --> 13(Gathering) 13 --> 14(G) 13 --> 15(nG) 3 --> 16(Reordering) 16 --> 17(R) 16 --> 18(nR) 3 --> 19(Early Write Acknowledgement) 19 --> 20(E) 19 --> 21(nE)

Normal memory

normal memory类型属性适用于系统中的大多数内存。 它表示架构允许硬件对这些位置执行预测读取访问Speculative acess,无论这些位置的访问权限(只读、可读可写等)如何。

normal memory有以下特性:

  1. 一笔写数据到normal memory会在有限时间内完成
  2. 写数据到normal memory类型的内存,不管inner或者outer的cache属性如何配置,都要在有限时间达到末端内存(一般指DDR)
  3. 如果系统支持的话,可以发起非对齐内存访问
  4. Normal memory 内存属性适用于具有幂等性(多次读取)的内存位置,这意味着它具备以下所有属性:
    a. 可以重复进行读取操作而没有副作用。
    b. 重复的读取操作会返回最后写入到被读取资源的值。
    c. 读取操作可以获取额外的内存位置而没有副作用。
    d. 如果在重复写入操作之间或由于异常情况而未更改所访问位置的内容,则可以无副作用地重复执行写入操作
    e. 支持非对齐访问。
    d. 在访问目标内存系统之前可以合并访问操作。
  5. normal memory允许预测访问,而且会受到中间cache(buffer)的行为影响。如果非幂等内存位置映射为普通内存,可能会发生以下情况:
    a. 内存访问返回未知值。
    b. 对内存映射外设产生不可预测的影响。
  6. 生成访问序列的指令可能会被放弃,由于在访问序列期间发生异常。 在从异常返回,指令重新启动,因此,一个或多个内存位置可能会被多次访问。 这可能导致对在写访问之间已经改变的位置进行重复的写访问。
  7. 对于访问普通内存,需要 DMB 指令来确保所需的顺序

Shareable Normal memory

普通内存分为3种共享属性:

  • Inner Shareable
  • Outer Shareable
  • Non-shareable

可共享性属性定义了硬件必须强制执行数据一致性要求的位置。 它们不影响指令获取的一致性要求。

  • 系统设计者可以使用共享性属性来指定普通内存中必须保持一致性的位置。
  • 该架构假设所有使用相同操作系统或管理程序的 PE 都位于同一个 Inner Shareable 共享域中。
  • 因为对不可缓存位置的所有数据访问对于所有观察者来说都是数据一致的,所以不可缓存位置总是被视为外部可共享的。

Non-shareable Normal memory

对于普通内存位置,不可共享属性标识可能仅由单个 PE 访问的普通内存。

Non-shareable & cahceable Normal memory的对于不同观察者访问可能存在一致性问题。
Non-shareable & Non-cacheable Normal memory没有一致性问题

对于不可共享位置,如果其他观察者共享内存系统,如果缓存的存在可能导致观察者之间通信时出现一致性问题,则软件必须使用缓存维护指令

对于不可共享的普通内存,Load-Exclusive和Store-Exclusive同步原语是否考虑多个观察者访问的可能性是由IMPLEMENTATION定义的。

Cacheability attributes for Normal memory

不管是 inner shareable 还是 outer shareable 普通内存,都有3种cache属性:

  • Write-Through Cacheable.
  • Write-Back Cacheable.
  • Non-cacheable.

Device memory

对设备内存类型属性的内存位置的访问可能会导致副作用,或者加载返回的值可能会根据执行的加载次数而变化。 通常,设备内存属性用于内存映射外设和类似位置。

Armv8 设备内存的属性是:

  • Gathering,标记为 G or nG
  • Reordering,标记为 R or nR
  • Early Write Acknowledgement,标记为 E or nE

如下列表中列出了Device Memory支持的类型,其他类型是不支持的。随着列表的下降,内存类型变得越来越弱; 相反,随着列表的上升,内存类型被描述为变得更强。

  • 正如类型列表所示,这些附加属性是分层的。 例如,允许收集的内存位置也必须允许重新排序和早期写入确认。
  • ARM架构不需要具体实现来区分每种内存类型,并且 Arm 认识到并非所有实现都会这样做。

Device Memory特性

  • 不允许预测访问。
    不允许对具有任何设备内存属性的任何内存位置进行预测数据访问。 这意味着对任何设备内存类型的每次内存访问都必须是由程序的简单顺序执行生成的。
  • 有限时间
    对任何设备内存类型的内存位置的写入都会在有限时间内完成。
  • 不可缓存
    具有任何设备内存属性的内存位置都不能分配到缓存中。
  • 是否支持内存对齐错误
    如果内存位置不能够支持未对齐的内存访问,则对该内存位置的未对齐访问会在转换的第一阶段生成对齐错误
  • 预取执行
    硬件不会阻止从具有任何设备内存属性的内存位置进行推测指令提取,除非该内存位置也被标记为所有异常级别的“永不执行”。

Gathering

Gathering,中文含义是聚合,符合下面任意一种情况就是Gathering操作:

  • 对同一内存位置发起了多笔读取或者多笔写入操作,被合并成了一笔传输
  • 对不同内存位置发起了多笔读取或者多笔写入操作,被合并成了一笔在总线上的传输

Arm架构仅定义程序员可见的行为。 因此,如果程序员无法判断是否发生了聚集,则可以进行聚集。

Reordering

Reordering,中文含义是重排序,对内存发起的多笔访问是否可以重新排序,与代码指令不同

对于具有非重新排序属性的所有内存类型,访问按程序顺序出现。 此顺序适用于所有使用具有非重新排序属性的任何内存类型进行访问。

Early Write Acknowledgement

Write Acknowledgement,中文含义是写完成通知,也就是是否要求内存系统的末端也就是DDR响应PE的写访问。

对于No Early Write Acknowledgement,要求如下条件:

  • 只有写访问的末端才会返回访问的写确认。
  • 内存系统中的较早的buffer(cahce)没有返回写入确认。

kernel IO 访问

通过 I/O 访问外设是深度架构和设备特定的。对于想要在多种体系结构和总线实现之间移植的驱动程序,内核提供了一系列访问器函数,这些函数提供了不同程度的排序保证:

定义

arch/arm64/include/asm/io.h,可以看出

  • readX()相对于readX_relaxed()多了一个__iormb()指令,__iormb()封装了的dsb指令
  • writeX()相对于writeX_relaxed()多了一个__iowmb()指令,__iowmb()封装了dmb指令
/*
 * Relaxed I/O memory access primitives. These follow the Device memory
 * ordering rules but do not guarantee any ordering relative to Normal memory
 * accesses.
 */
#define readb_relaxed(c)	({ u8  __r = __raw_readb(c); __r; })
#define readw_relaxed(c)	({ u16 __r = le16_to_cpu((__force __le16)__raw_readw(c)); __r; })
#define readl_relaxed(c)	({ u32 __r = le32_to_cpu((__force __le32)__raw_readl(c)); __r; })
#define readq_relaxed(c)	({ u64 __r = le64_to_cpu((__force __le64)__raw_readq(c)); __r; })

#define writeb_relaxed(v,c)	((void)__raw_writeb((v),(c)))
#define writew_relaxed(v,c)	((void)__raw_writew((__force u16)cpu_to_le16(v),(c)))
#define writel_relaxed(v,c)	((void)__raw_writel((__force u32)cpu_to_le32(v),(c)))
#define writeq_relaxed(v,c)	((void)__raw_writeq((__force u64)cpu_to_le64(v),(c)))

/*
 * I/O memory access primitives. Reads are ordered relative to any
 * following Normal memory access. Writes are ordered relative to any prior
 * Normal memory access.
 */
#define readb(c)		({ u8  __v = readb_relaxed(c); __iormb(__v); __v; })
#define readw(c)		({ u16 __v = readw_relaxed(c); __iormb(__v); __v; })
#define readl(c)		({ u32 __v = readl_relaxed(c); __iormb(__v); __v; })
#define readq(c)		({ u64 __v = readq_relaxed(c); __iormb(__v); __v; })

#define writeb(v,c)		({ __iowmb(); writeb_relaxed((v),(c)); })
#define writew(v,c)		({ __iowmb(); writew_relaxed((v),(c)); })
#define writel(v,c)		({ __iowmb(); writel_relaxed((v),(c)); })
#define writeq(v,c)		({ __iowmb(); writeq_relaxed((v),(c)); })

如何使用

内核定义了这么多的read和write函数,到底怎么用呢?

readX()/writex()比readX_relaxed()/writeX_relaxed()多了dsb/dmb内存屏障指令,所以内核是想让开发者灵活使用来提高CPU的执行效率,在没必要使用readX()/writex()的时候,使用readX_relaxed()/writeX_relaxed()函数来减少不必要的内存屏障,下面针对不同的内存类型来分析一下:

  • Normal Memory操作
    Normal Memory支持乱序访问和cache。使用readX()/writex()相当于禁止了这些加速功能。readX_relaxed()/writeX_relaxed()没有关闭加速功能。

    • 单core,只访问Normal Memory的场景。使用_relaxed()函数即可,单个core不需要内存屏障来同步
    • 多core,一个core读某个内存、另一个core写相同内存的场景,要使用readX()/writex(),确保所有的操作均完成
  • Device memory操作
    Device Memory不支持乱序访问和不支持cache。使用readX()/writex()和readX_relaxed()/writeX_relaxed()效果都是一样的,都不会有乱序访问和cache功能。readX()/writex()反而多几条内存屏障指令。

    • 典型场景是CPU操作外设寄存器,不需要执行dsb/dmb指令,使用_relaxed()可以减少指令执行
  • Normal Memory & Device Memory混合操作

    • 典型场景是CPU往内存中读写数据使用readX_relaxed()/writeX_relaxed()函数,然后启动dma搬移使用writex()。
    • 读写Normal Memory可以利用乱序访问和cache特性加快执行速度。但是在dma搬移之前一定要保证cache中的数据写到Normal Memory中,因此要使用writex()来保证之前的操作执行成功。

Ref

https://github.com/torvalds/linux/blob/master/Documentation/memory-barriers.txt