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()(确保状态之后的数据有效)。