思考:
1、cache 的 entry 里都是有什么?
2、TLB 的 entry 里都是有什么?
3、MMU 操作的页表中的 entry 中都是有什么?L1 和 L3 表中的 entry 中分别都是有什么?
本文已有答案,学完之后,你能否知道,看造化了,哈哈…说明:
MMU/TLB/Cache 等知识太过于零碎,各个模块直接又紧密相关,所以在介绍时会串着介绍,本文旨在介绍 MMU 的工作原理,学习 cache 请参考<Arm cache 的学习笔记-一篇就够了>以下来自笨叔叔公众号中的提问:
1、cache 的内部组织架构是怎么样的?能否画出一个 cache 的 layout 图?什么是 set,way?
2、直接映射,全关联和组相联之间有什么区别?优缺点是啥?
3、重名问题是怎么发生的?
4、同名问题是怎么发生的?
5、VIPT 会不会发生重名问题?
6、什么是 inner shareability 和 outer shareability?怎么区分?
7、什么是 PoU?什么是 PoC?
8、什么是 cache 一致性?业界解决 cache 一致性都有哪些方法?
9、MESI 状态转换图,我看不懂。
10、什么 cache 伪共享?怎么发生的,如何避免?
11、DMA 和 cache 为啥会有 cache 一致性问题?
12、网卡通过 DMA 收数据和发数据,应该怎么操作 cache?
13、对于 self-modifying code,怎么保证 data cache 和指令 cache 的一致性问题?
文章目录
1、Memory attribute
2、cache 的一些基本概念
3、Cache 内存访问的模型:
4、MMU 的介绍
5、VMSA 相关术语:
6、address translation system (AT)
(1)、地址翻译的过程
(2)、和 mmu 相关的 System registers
(3)、Enable mmu and endianness 的相关寄存器
(4)、Address size configuration
(5)、granule sizes
(6)、granule size 对地址翻译的影响
(7)、disable mmu
7、Translation table
(1)、TTBR0/TTBR1
(2)、页表的 entry 中包含哪些信息
(3)、granule sizes
(4)、Cache configuration
8、Arm mmu 三级页表查询的过程
9、Translation Lookaside Buffer (TLB)
(1)、TLB entry 里有什么?
(2)、contiguous block entries
(3)、TLB abort
(4)、TLB 一致性
10、VMSAv8 64 translation table format descriptors
1、Memory attribute
Armv8 定义了 device memory 和 normal memory 两种内存,其中 device memory 固定的就是 Outer-Shareable 和 Non-cacheable,而 normal memory 有多种属性可选。
说明一下:在 B2.7.2 章节中有这么一句话“Data accesses to memory locations are coherent for all observers in the system, and correspondingly are treated as being Outer Shareable”, treated as 被看作是(但不是),所以在一些的文章中就认为 device memory 是没有 shareable 属性的。其实也能够理解,一段 memory 设置成了 non-cacheable,然后再去讨论该 memory 的 shareable 属性,好像也没有意义。不管怎么样,我们还是按照下方表格的来理解吧,device memory 固定为 Outer-Shareable 和 Non-cacheable
对于 device memory,又分下面三种特性:
- Gathering 和 non Gathering(G or nG):表示对多个 memory 的访问是否可以合并,如果是 nG,表示处理器必须严格按照代码中内存访问来进行,不能把两次访问合并成一次。例如:代码中有 2 次对同样的一个地址的读访问,那么处理器必须严格进行两次 read transaction
- Reordering(R or nR):表示是否允许处理器对内存访问指令进行重排。nR 表示必须严格执行 program order
- Early Write Acknowledgement(E or nE):PE 访问 memory 是有问有答的(更专业的术语叫做 transaction),对于 write 而言,PE 需要 write ack 操作以便确定完成一个 write transaction。为了加快写的速度,系统的中间环节可能会设定一些 write buffer。nE 表示写操作的 ack 必须来自最终的目的地而不是中间的 write buffer
针对上面的三种特性,给出下面四种配置
- Device-nGnRnE : 处理器必须严格按照代码中内存访问来进行、必须严格执行 program order(无需重排序)、写操作的 ack 必须来自最终的目的地
- Device-nGnRE : 处理器必须严格按照代码中内存访问来进行、必须严格执行 program order(无需重排序)、写操作的 ack 可以来自中间的 write buffer
- Device-nGRE : 处理器必须严格按照代码中内存访问来进行、内存访问指令可以进行重排、写操作的 ack 可以来自中间的 write buffer
- Device-GRE : 处理器对多个 memory 的访问是否可以合并、内存访问指令可以进行重排、写操作的 ack 可以来自中间的 write buffer
PoU/PoC/Inner/Outer 的定义
简而言之,PoU/PoC 定义了指令和命令的所能抵达的缓存或内存,在到达了指定地点后,Inner/Outer Shareable 定义了它们被广播的范围。
例如:在某个 A15 上执行 Clean 清指令缓存,范围指定 PoU。显然,所有四个 A15 的一级指令缓存都会被清掉。那么其他的各个 Master 是不是受影响?那就要用到 Inner/Outer/Non Shareable
有了上面的这些定义,那么我们再来看 inner Shareable 和 outer Shareable
读分配(read allocation)
当 CPU 读数据时,发生 cache 缺失,这种情况下都会分配一个 cache line 缓存从主存读取的数据。默认情况下,cache 都支持读分配。
写分配(write allocation)
当 CPU 写数据发生 cache 缺失时,才会考虑写分配策略。当我们不支持写分配的情况下,写指令只会更新主存数据,然后就结束了。当支持写分配的时候,我们首先从主存中加载数据到 cache line 中(相当于先做个读分配动作),然后会更新 cache line 中的数据。
写直通(write through)
当 CPU 执行 store 指令并在 cache 命中时,我们更新 cache 中的数据并且更新主存中的数据。cache 和主存的数据始终保持一致。
写回(write back)
当 CPU 执行 store 指令并在 cache 命中时,我们只更新 cache 中的数据。并且每个 cache line 中会有一个 bit 位记录数据是否被修改过,称之为 dirty bit(翻翻前面的图片,cache line 旁边有一个 D 就是 dirty bit)。我们会将 dirty bit 置位。主存中的数据只会在 cache line 被替换或者显示的 clean 操作时更新。因此,主存中的数据可能是未修改的数据,而修改的数据躺在 cache 中。cache 和主存的数据可能不一致。
- 在 ArmV8 定义这些属性的寄存器和 linux kernel 或 optee 软件代码中使用的示例,请点击此处
<ArmV8 MMU 内存管理中的 Memory attributes 和 Cache policies>
2、cache 的一些基本概念
cache 是一个高速的内存块,它包含了很多 entries,每一个 entrie 中都包含: memory 地址信息(如 tag)、associated data
cache 的设计考虑了两大原则:
空间域(spatial locality): 访问了一个位置后,可能还会访问相邻区域, 如顺序执行的指令、访问一个结构体数据
时间域(Temporal locality):内存区域的访问很可能在短时间内重复,如软件中执行了一个循环操作.
为了减少 cache 读写的次数,将多个数据放到了同一个 tag 下,这就是我们所说的 cache line
访问缓存中已经存在的信息叫做 cache hit,访问缓存中不存在的数据叫做 cache miss
cache 引入的潜在问题:
内存的访问不一定同编程者预期的一样;
一个数据可以存在多个物理位置处
3、Cache 内存访问的模型:
Memory coherency 的术语定义:
Point of Unification (PoU), Point of Coherency (PoC), Point of Persistence (PoP), and Point of Deep Persistence (PoDP).
4、MMU 的介绍
在 Arm V8-aarch64 体系下,Arm Core 访问内存的硬件结构图如下所示:
其中,MMU 由 TLB 和 Table Walk Unit 组成的.
TLB:Translation Lookaside Buffer (TLB),对应着 TLB 指令
Table Walk Unit,也叫地址翻译,address translation system,对应着 AT 指令
5、VMSA 相关术语:
➨VMSA - Virtual Memory System Architecture
➨VMSAv8
➨VMSAv8-32
➨VMSAv8-64➨Virtual address (VA)
➨Intermediate physical address (IPA)
➨Physical address (PA)Translation stage can support only a single VA range
➨48-bit VA, 0x0000000000000000 to 0x0000FFFFFFFFFFFF
➨Armv8.2-LVA : 64KB granule :52-bit VA,0x0000000000000000 to 0x000FFFFFFFFFFFFFTranslation stage can support two VA ranges
➨48-bit VA: 0x0000000000000000 - 0x0000FFFFFFFFFFFF , 0xFFFF000000000000 to 0xFFFFFFFFFFFFFFFF
➨52-bit VA: 0x0000000000000000 - 0x000FFFFFFFFFFFFF , 0xFFF0000000000000 to 0xFFFFFFFFFFFFFFFF
Address tagging / Memory Tagging Extension / Pointer authentication
6、address translation system (AT)
(1)、地址翻译的过程
MMU 的地址翻译工作是一种自动行为,当填好页表、配置好系统寄存器之后,cpu 发起的虚拟地址读写操作,将会经过 MMU 自动转换成物理地址,然后发送到 AXI 总线上完成真正的内存或 device 的读写操作. 如下列举了 Arm 在不同 exception level 中的地址翻译的模型.
在一般的情况下,我们是不会启用 stage2 的,除非 enable 了 EL2、实现了 hypervisor,那么这时候才会开启 stage2,如下图所示:
(2)、和 mmu 相关的 System registers
在 Armv8-aarch64 体系下,TCR(Translation Control Register)寄存器有
- TCR_EL1
- TCR_EL2
- TCR_EL3
- VTCR_EL2
它们的含义:地址翻译的控制寄存器
- TCR_EL1, Translation Control Register (EL1)
The control register for stage 1 of the EL1&0 translation regime. - TCR_EL3, Translation Control Register (EL3)
The control register for stage 1 of the EL3 translation regime - TCR_EL2, Translation Control Register (EL2)
The control register for stage 1 of the EL2, or EL2&0, translation regime - VTCR_EL2, Virtualization Translation Control Register
The control register for stage 2 of the EL1&0 translation regime
对应的 bit map 为
(3)、Enable mmu and endianness 的相关寄存器
在 ArmV8-aarch64 架构下有三个 sctlr 寄存器
- SCTLR_EL1
- SCTLR_EL2
SCTLR_EL3
以 SCTLR_EL3,该系统寄存器的 SCTLR_EL3.EE(BIT[25])定义了 MMU 访问页表的方式:小端方式读、还是大端方式读
(4)、Address size configuration
- Physical address size – 告诉 cpu,当前系统的物理地址是多少位
- Output address size – 告诉 mmu,你需要给我输出多少位的物理地址
- Input address size – 告诉 mmu,我输入的是多数为的虚拟地址
- Supported IPA size – stage2 页表转换的部分 size,暂不介绍
a. Physical address size
b. output address size
c . Input address size
- TCR_ELx.T0SZ 定义使用 TTBR0_ELx 时,VA 地址的 size
- TCR_ELx.T1SZ 定义使用 TTBR1_ELx 时,VA 地址的 size
注意最大的 size 为:2^(64-x),x 为 TCR_ELx.T0SZ 或 TCR_ELx.T1SZ
d. Supported IPA size
由 VTCR_EL2.SL0 and VSTCR_EL2.SL0 寄存器决定
(5)、granule sizes
a. state 1 granule sizes
b. state 2 granule sizes
(6)、granule size 对地址翻译的影响
granule size 的配置不同,将会影响到页表的建立,不同的 granule size 的页面有着不同的页表结构,例如下表所示的:
(7)、disable mmu
disable mmu 之后,the stage 1 translation,For the EL1&0:
For Normal memory, Non-shareable, Inner Write-Back Read-Allocate Write-Allocate, Outer Write-Back Read-Allocate Write-Allocate memory attributes
7、Translation table
(1)、TTBR0/TTBR1
Arm 文档说:因为应用程序切换时要切换页表,页表经常改变,而 kernel 切换时不需要切换页表,页表几乎不改。所以 Arm 就提供了 a number of features,也就是 TTBR0 和 TTBR1 两个页表基地址. TTBR0 用于 0x00000000_00000000 - 0x0000FFFF_FFFFFFFF 虚拟地址空间的翻译,TTBR1 用于 0xFFFF0000_00000000 - 0xFFFFFFFF_FFFFFFFF 虚拟地址空间的翻译
EL2/EL3 只有 TTBR0,没有 TTBR1,所以 EL2/EL3 的虚拟地址空间是:0x0000FFFF_FFFFFFFF
(2)、页表的 entry 中包含哪些信息
MMU 除了完成地址的翻译,还控制的访问权限、memory ordering、cache policies.
如图所示,列出了三种类型的 entry 信息:
bits[1:0]表示该输出是 block address,还是 next level table address,还是 invalid entry
(3)、granule sizes
有三种 granule sizes 的页表:4kb、16kb、64kb
(4)、Cache configuration
MMU 使用 translation tables 和 translation registers 控制着 cache policy、memory attributes、access permissions、va 到 pa 的转换
8、 Arm mmu 三级页表查询的过程
- (1)、在开启 MMU 后,cpu 发起的读写地址是一个 64bit 的虚拟地址,
- (2)、该虚拟地址的高 16bit 要么是全 0,要么是全 1. 如果是全 0,则选择 TTBR0_ELx 做为 L1 页表的基地址; 如果是全 1,则选择 TTBR1_ELx 做为 L1 页表的基地址;
- (3)、TTBRx_ELn 做为 L1 页表,它指向 L2 页表,在根据 bit[41:29]的 index,查询到 L3 页表的基地址
- (4)(5)、有了 L3 页表的基地址之后,在根据 bit[28:16]的 index,查询到页面的地址
- (6)、最后再根据 bit[15:0]查找到最终的物理地址
9、Translation Lookaside Buffer (TLB)
(1)、TLB entry 里有什么?
TLB 中不仅仅包含物理地址和虚拟地址,它还包含一些属性,例如:memory type、cache policies、access permissions、ASID、VMID
注:ASID - Address Space ID, VMID - Virtual Machine ID
(2)、contiguous block entries
TLB 拥有固定数目的 entries,所以你可以通过减少外部内存地址转换的次数来提升 TLB hit 率.
在 Arm V8 architecture 中有一个 TLB 中的 feature 叫 contiguous block entries,它表示一个 entry 可以对应多个 blocks. 一个 entry 找到多个 blocks,再通过 index 来查找具体是哪个 block。页表的 block entries 中,也有一个 contiguous bit。这个 bit 为 1,则表示开启了 TLB 的 contiguous block entries feature。
contiguous block entries feature 要求 alignment,例如:
• 16 × 4KB adjacent blocks giving a 64KB entry with 4KB granule. 缓存 64kb blocks,只需 16 enties
• 32 × 32MB adjacent blocks giving a 1GB entry for L2 descriptors, 128 × 16KB giving a 2MB entry for L3 descriptors when using a 16KB granule.
• 32 × 64Kb adjacent blocks giving a 2MB entry with a 64KB granule.
如果支持了 contiguous bit,那么:
TLB 查询后的 PA = TLB entry 中的 PA + index。
(3)、TLB abort
如果开启了 contiguous bit,而要转换的 table entries 确不是连续的,或者 entries 的 output 在地址范围之外或没有对齐,那么将会产生 TLB abort
(4)、TLB 一致性
如果 os 修改了页表(entries),那么 os 需要告诉 TLB,invalid 这些 TLB entries,这是需要软件来做的. 指令如下:
TLBI <type><level>{IS} {, <Xt>}
10、VMSAv8-64 translation table format descriptors
这里的 table format descriptors,其实就是本文开头“思考”中提到的 entry,在页表中的 entry, 要么是 invalid,要么是 table entry,要么是 block entry
- An invalid or fault entry.
- A table entry, that points to the next-level translation table.
- A block entry, that defines the memory properties for the access.
- A reserved format
-
如果是 table entry,其 attribute 描述如下:
如果是 block entry,其 attribute 描述如下:
如果是 stage2 的 entry,无论是 table entry 还是 block entry,其 attribute 描述如下:
其实用如下的一张图来描述更清晰:
这里的 bit[1:0]用于定义 entry 的类型(invalid? block ? table? reserved)
bit[4:2]指向 MAIR_ELn 寄存器中的其中的一个字节,用于定义内存(main memory 和 device memory)的类型
MAIR_ELn 寄存器拆分成 8 个 bytes,每个 byte 定义一种内存类型
(MAIR_ELn, Memory Attribute Indirection Register (ELn))
每一个 byte(attrn)的含义如下:
各个 bit 位的具体含义
例如 optee 中的内存属性配置如下:
#define ATTR_DEVICE_INDEX 0x0
#define ATTR_IWBWA_OWBWA_NTR_INDEX 0x1
#define ATTR_INDEX_MASK 0x7
#define ATTR_DEVICE (0x4)
#define ATTR_IWBWA_OWBWA_NTR (0xff)
mair = MAIR_ATTR_SET(ATTR_DEVICE, ATTR_DEVICE_INDEX);
mair |= MAIR_ATTR_SET(ATTR_IWBWA_OWBWA_NTR, ATTR_IWBWA_OWBWA_NTR_INDEX);
write_mair_el1(mair);