本文基于kernel 5.8.0, 平台是arm64
关于内存管理的地址,有线性地址,逻辑地址,物理地址和虚拟地址这几个概念, 咋一看很容易混淆,让人云里雾里。
线性地址和逻辑地址都是x86的概念,主要用于内存分段机制,而在arm平台上,没有分段机制,线性地址/逻辑地址和虚拟地址都是同一个概念,都统称为虚拟地址。
① 物理地址:Physical addresses are those used by the actual hardware system。是硬件真实使用的地址。
② 虚拟地址: Virtual addresses are those used by you, and the compiler and linker, when placing code in
memory。 是我们程序使用的内存地址。
为什么要有一个虚拟地址的概念呢?
假设我们的CPU直接操作的是物理地址,那就无法同时运行两个程序了,程序A改写了某个地址,会影响到使用该地址的程序B,这是行不通的。
操作系统解决这个问题的办法就是有一个虚拟内存的概念,每一个进程都有一个独立的地址空间,但这就要求进程不能去访问物理地址。
访问硬件内存需要的是物理地址,操作系统需要有一种机制,将进程持有的虚拟地址转换成物理地址。
这就是CPU中的MMU的作用。
MMU全称是memory management unit。 在ARM的体系结构中,MMU可以使用内存中保存的页表来进行虚拟地址到物理地址的转换,此外MMU还可以控制cache的策略,内存的属性以及访问权限的设置。
此外MMU中还有TLB和table walk unit的概念。
TLB(Translation Lookaside Buffer)是一块高速缓存,缓存最近查找过的VA对应的页表项。如果TLB中有需要查表的VA,就不用Translation Table Walk了,Translation Table Walk较慢,要从内存上读表。
如上图所示,当一个虚拟地址来临的时候,MMU先是查找TLBs,看是否有cached translation,如果没有,那么table walk unit就去memory或cache中读取相应的translation tables.
上文说过,linux虚拟地址空间分为用户地址空间和内核地址空间。页表基地址(base translation address)也被分为2个部分: ttbr0 和 ttbr1.
Kernel Space的页表基地址存放在TTBR1_EL1寄存器中,User Space页表基地址存放在TTBR0_EL0寄存器中。
将虚拟地址(VA)的高位都设置为0时,选择TTBR0指向的转换表。当VA的高位都设置为1时,选择TTBR1。
64bit的虚拟地址并不是所有bit都被用上的。目前有效的VA_BITS的配置是:36, 39, 42, 47
假设我现在使用64K的页和42bit的虚拟地址空间, 使用三级页表。
描述完地址转换的过程, 再详细描述下Table Descriptor, 也就是页表中存放的内容
一个entry是64bit, 以一个4K page size的页来说, 那么就有512个entry。 每一个entry对应一个descriptor.
类型有四种,bit[1:0]来决定,其中bit[0]指示该descriptor是否有效,如果为0,表示是一个invalid entry;
bit[1]表示该descriptor的类型:
0 | Block entry | defines the memory properties for the access. 定义了一个连续物理内存块的基地址和属性。 |
1 | Table entry | points to the next-level translation table,定义了下一级 translation table 的基地址 |
其中Level 0中的Table Descriptor只能输出Level 1页表的地址,Level 3中的Table Descriptor只能输出block addresses.
如上图所示,level1/level2 table中有block和table entry, level3中的table entry指向具体的memory page.
有三种不同的粒度: 4KB / 16KB / 64KB, 三种不同的粒度会影响所需translation tables的数量和大小.
以4KB grandule size为例, 硬件可以使用四级查表。每一级的地址转换有9bit(即512个entry), VA[47:39]是level0页表的索引,每一个entry对应一个512GB的range, 并指向L1 table; L1 table中也有512个entry, 每一个entry指向L1 table或1GB block; l2 table的entry指向L3的table 或2M的block; L3 table的entry指向4KB的block的基地址。
Table descriptor和table entry, block entry中都有attribute的概念
table descriptor(only for stage1):
NSTable | NS表示Non-Secure,该table描述符指向的下一级的转换表是否存储在secure memory之中。为0表示存储在Secutre PA中,否则表示存储在Non-secure PA中。当在non-secure状态下进行地址映射时,该标志别忽略 |
APTable | 下一级level lookup的access permission |
UXNTable | Unprivileged Execute Never, 下一级level lookup的是否有内存的执行权限 |
PXNTable | 下一级level lookup的是否有内存的特权执行权限(EL1) |
UXN | Excute-never, 决定了descriptor指向的region是否excuteable |
PXN | privileged excute-never, 决定了descriptor指向的region在EL1是否excuteable |
Contiguous | 该表项是否为连续表项中的一项。即转换表在该表项前后是连续的,没有空洞。这样,这些连续的表项便有可能一次性加载到cache中(比如由一个TLB entry缓存) |
DBM | dirty bit modifier, dirty bit指示内存页有没有被修改 |
nG | not global, 指明当前的entry是global(nG=0,所有process都可以访问)还是non-global(nG=1,only本process允许访问)。如果是global类型,则TLB中不会tag ASID;如果是non-global类型,则TLB会tag上ASID,且MMU在TLB中查询时需要判断这个ASID和当前进程的ASID是否一致,只有一致才证明这条entry当前process有权限访问。 |
AF | access flag,当该标志为0,标明对应的内存区域(一个block或者一个page)是第一次访问。 |
SH | shareable attribute |
AP | access permission, 设置数据访问的权限,可以设置为4中, 只读,读写,非EL0的只读, 非EL0的读写 |
NS | security bit, 当从Secure状态访问内存时,该标志指示转换后的地址在Secure区域还是在Non-Secure区域 |
Indx | index into the MAIR_ELn, 指向内存区域的类型以及可缓存性(见下文) |
arm系统中,memory都被分为各个region,每个region都有自己的privilege level,memory type,cache policy;
这部分的管理是由MMU来实现的,各个region都对应其中的一个或几个block、page。
MAIR_ELx定义了memory region的属性:
有2种memory types: Normal memory和Device memory
Normal memory,对该种类型的内存可以进行常见的读写操作或只读操作,系统中大部分内存都是这种类型;
Device memory,通常都是内存映射外设会采用这种内存类型。
device类型的内存还有三种属性:
(1) G, Gather, Determines whether multiple accesses can be merged into a single bus transaction。多个memory access merge为 合并成一个bus-transaction
(2) R, reorder, Determines whether accesses to the same device can be reordered。device memory的连续的transation可以乱序
(3) E, Early Write Ack,Indicates to the memory system whether a buffer can send acknowledgements。 write不写入device,通过中间buffer之后就 return ack。
对应四种类型:
(1)Device-nGnRnE,不允许gather, reorder, ealry
(2)Device-nGnRE,允许early
(3)Device-nGRE,允许reorder,early
(4)Device-GRE, 允许gather,reorder,early
除了类型外,ARM还定义了其他内存特性
Shareability(可共享性) | 指当前内存页表项的数据是否可以同步到其它CPU上,多核CPU调用带有该属性页表项的数据,一旦某个CPU修改了数据,那么系统将自动更新到其它CPU的数据拷贝,实现内存数据一致性. 对于Normal类型的内存,有3种情况: • Inner Shareable, 该内存位置可以被InnerShareability domain 中的所有处理器访问,并且硬件保证该位置在这些处理器间的数据一致性,InnerShareability domain中的处理器一般被同一个虚拟机监视器或操作系统控制 • Outer Shareable, 该内存位置可以被OuterShareability domain中的所有处理器访问,并且硬件保证该位置在这些处理器间的数据一致性,InnerShareability domain 是OuterShareability domain的一个子集 • Non-shareable, 该内存位置一般只能被唯一处理器访问,如果还有其他处理器能访问该位置,可能需要软件用缓存一致性指令来保证缓存一致性 |
Cacheability(可缓存性) | 指当前内存页表项对于的数据是否可以加载到Cache当中. 对于Normal类型的内存,有3种情况: • Write-Through Cacheable, 同时更新缓存和内存. • Write-Back Cacheable, 先更新缓存,替换时将修改过的块写回内存. • Non-cacheable, 不使用缓存,直接更新内存 |
ARMv8 address translation
DDI0487E ARMv8 Spec
armv8 memory system
【华为云技术分享】ARMv8-A存储模型概述(2)