Linux + arm 内存属性

内存属性的来源

在 ARM64(AArch64)下,每个内存访问的"属性"由两方面决定:

  • 页表/MAIR_ELx(Memory Attribute Indirection Register):软件定义虚拟地址到物理地址的 memory type;

  • CPU 访存模型:结合 TLB/缓存/一致性域,决定实际的可见性和顺序。


基本分类

ARMv8 把内存分为两大类:

(1) Normal memory

  • 可缓存,面向常规 RAM。

  • 特性:

    • 允许乱序访问、合并、推测、prefetch。

    • 缓存一致性通过互联(CCI/CMN/DSU 等)保证。

  • 用途:一般代码段、数据段、内核堆栈等。

(2) Device memory

  • 用于 MMIO(寄存器/外设)。

  • 特性:

    • 不能合并、推测或重排。

    • CPU 访问顺序严格保留(对同一设备的访问)。

    • 可能 bypass 缓存(uncacheable)。


Normal Memory 属性

Outer/Inner cacheability 决定(通过页表 AttrIndx → MAIR 映射):

  • Write-back cacheable:最常用,正常的缓存策略。

  • Write-through cacheable:写时同时更新 cache + memory。

  • Non-cacheable:直接访问 DRAM,不进 cache。

额外属性:

  • Shareability:Non-shareable / Inner-shareable / Outer-shareable

    • Inner-shareable:在一个 cluster 内核之间一致。

    • Outer-shareable:在多个 cluster 之间一致。

    • Non-shareable:CPU 自己私有(很少用)。


Device Memory 类型(ARMv8 推荐使用 nGnRE/nGnRnE)

常见的几类(AttrIndx 配置):

  • Device-nGnRnE (Non-Gathering, Non-Reordering, No Early-Write-Ack)

    • 最严格,常用于强顺序 IO。
  • Device-nGnRE (Non-Gathering, Non-Reordering, Early-Write-Ack)

    • 允许写入"提前完成"但仍无重排/合并。

    • Linux 常用这个作为默认 MMIO。

  • Device-GRE (Gathering, Reordering, Early-Write-Ack)

    • 宽松一些,允许合并/重排;适合 FIFO 类设备。

小贴士 :Linux 内核里 ioremap() 默认用的是 Device-nGnRE,即 MMIO 安全模式。


内存顺序直觉(Normal vs Device)

  • Normal memory :可能乱序,需要 内存 屏障 (barrier) 保证顺序。

  • Device memory :同一个设备的读写顺序不会乱,但和 Normal memory 之间的顺序 不一定符合直觉 → 所以驱动里常见 wmb(); writel();


Linux 内核里的常用对应

Linux 在 ARM64 下会通过 pgprot_*ioremap_* 宏来配置:

|---------------------------------|-----------------------------------------------------------------|------------------------|
| 内核 API | 内存类型 | 底层属性 |
| 普通内存 (malloc, vmalloc, kmalloc) | Normal, WB cacheable, Inner-shareable | AttrIndx 指向 Write-back |
| ioremap() | Device-nGnRE | 安全 MMIO |
| ioremap_wc() | Normal, Write-combining | 用于帧缓冲/PCIe 显存 |
| ioremap_cache() | Normal, Write-back | 有时映射外设 RAM |
| dma_alloc_coherent() | Normal, WB + shareable(I/O coherent 平台) 或 Device uncached(非一致性) | 看硬件 |


MAIR_ELx 配置(内存属性寄存器)

每个 EL 有一个 MAIR_ELx(Memory Attribute Indirection Register):

  • 8 个 slot(AttrIndx = 0..7)。

  • 每个 slot 8 bit:

    • 高 4 位 = outer 属性

    • 低 4 位 = inner 属性

Linux 启动时会填好:

  • 比如 AttrIndx=0 → Normal WB Cacheable

  • AttrIndx=1 → Device-nGnRE

  • AttrIndx=2 → Normal Non-cacheable

  • ...

页表的 PTE[AttrIndx] 决定使用哪个 slot。


小结

  • Normal memory = 程序代码和数据,缓存友好,但可能乱序,需要 barrier。

  • Device memory = MMIO,强顺序,不缓存,但和 Normal memory 交互时仍要 barrier。

  • Linux 内核 抽象 API(ioremap, dma_alloc_*)帮你选择合适的属性。

  • 驱动编程口诀

    • 写设备寄存器前要 wmb()(确保数据已写到内存)。

    • 读设备寄存器后要 rmb()(确保状态之后的数据有效)。