内存属性的来源
在 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()(确保状态之后的数据有效)。
-