进程抽象由两部分组成,cpu抽象和内存抽象。
本文没有讨论安全模式的地址空间、虚拟化的两级翻译以及兼容的ARMv7-A相关内容。
虚拟内存系统的主要目的是给进程提供单一的运行地址空间的抽象。这样一个进程在运行的时候就不用考虑其它进程的存在。
除了地址翻译,MMU还控制每一个内存区域的访问权限,内存序和cache策略。
使用页表进行地址翻译的方式如下图所示
1. 内核态和用户态虚拟地址空间的划分
页表基址由Translation Table Base Registers即TTBR0_EL1和TTBR1_EL1给出。当VA首几位为0时,使用TTBR0翻译。当VA首几位为1时,使用TTBR1翻译。
处理器使用的数据和指令的虚拟地址均为64位,然而虚拟地址空间实际上只有48位(也就是说实际上不支持完整的64位寻址,高几位仅用于基址寄存器选择)。
从图中可以注意到不论是虚拟地址空间中位于0xFFFFFFFF_FFFFFFFF到0xFFFF0000_00000000的内核空间,还是位于0x0000FFFF_FFFFFFFF到0x00000000_0000000的用户空间实际上占用的物理内存范围都在前48位地址空间。
Translation Control Register(TCR_EL1)定以了需要检查的most significant bits的长度。其内T0SZ[5:0]和T1SZ[5:0]域分别给出TTB0和TTB1,这两个域有规定的最大值和最小值,且随着系统页大小和起始翻译表级别变化。
- Intermediate Physical Address Size(IPS)域控制着最大输出地址大小。000表示32位物理地址,101表示48位物理地址。
- 2位的Translation Granule(TG)域TG1和TG0分别给出了内核或用户空间的页大小,00=4kB,01=16kB,11=64kB。
- 用于第一次翻译的页表的级别能够被配置。第一级查询的页表级别由页大小和TCR_ELn.TxSZ域决定。
2. 虚拟地址到物理地址的翻译
VA[63:n]为全0或全1,否则访问该地址会导致fault。剩余的least significant位则给出选择区域的偏移,以便MMU结合页表项的物理地址和最终的业内偏移构成物理地址。
示例,对于一个一级查询的地址翻译过程,假设粒度为64kB(2^16),地址空间为42位。MMU翻译方式如下:
- 若VA[63:42]=1,TTBR1作为Level2页表的基地址;全0类似
- 页表每项64位,8字节(2^3),共8192(2^13)项,使用VA的[41:29]位,对应的值作为索引页表项查询
- MMU确认页表项的有效性以及是否允许访问。若有效,则允许访问
- 页表项的[47:29]和VA剩余的[28:0]位组合,构成最终的物理地址,这表明页表项为块描述符(block descriptor)
除了上述用法,一级页表项也能指向二级页表基地址。对于一个二级表,一级描述符包含了二级翻译表的基地址。真正的物理地址对应着二级描述符的页表项。下面例子展示了以2级页表开始查询,64kB页粒度,42位虚拟地址空间的翻译过程。一页64kB(2^16),每一个页表项8B(2^3),即每个页表2^13项=8192项。因而VA的每13位用于页表项的索引,13位、13位、16位。
每一个二级表可能和1个或多个一级表项关联。因此,可能存在多个1级表项指向相同的二级表
- 若VA[63:42]=1,TTBR1作为Level2页表(第一个翻译表)的基地址,若全0,则TTBR0作为第一个翻译表的基地址
- 页表每项64位,8字节(2^3),共8192(2^13)项,VA[41:29]位对应的值作为索引查到页表项
- MMU验证查到的页表项的有效性以及是否允许访问。若有效,则允许访问
- 使用查到的地址的[47:16]位作为基地址,VA[28:16]作为偏移量查到level 3翻译表条目。MMU读取
- MMU验证
- level 3条目的[47:16]位给出了最终物理地址的PA[47:16]
- 由于页大小为64KB,加上最后VA[15:0],得到最终的物理地址
3. 多个虚拟地址空间
在任意时刻,仅有1个虚拟地址空间被使用。然而,由于事实上存在三个不同的TTBR,因此存在3个平行的虚拟地址空间(EL0/1,EL2和EL3):
4. Armv8-A的翻译表
Armv8-A AArch64使用长描述符格式(long Descriptor format),总是在AArch64执行状态中被使用。相比于Armv7-A,它引入了level 0表索引。也增加了对至多48位输入、输出的地址的支持。
由于架构不支持完整的64位寻址,地址[63:48]位必须相同,或者使用最顶部8位作为VA tagging。
AArch64支持3种页大小。4kB,16kB和64kB,并且系统能够使用哪个是IMPLEMENTATION DEFINED。Memory Model Feature Register 0 system register (ID_AA64MMFR0_EL1)给出了支持的大小。每一级翻译表的大小能够在TCR_EL1中配置。
4.1. AArch64页表项描述符格式
AArch64描述符格式被用在所有级别的表中,从Level 0到Level 3。Level 0描述符仅能输出Level 1表的地址。Level 3描述符不能指向其它表,仅能输出最终内存页的地址。因而Level 3格式稍有不同。
页表项的位[1:0]用于表示页表项的类型
- 下一级表的地址
- 一个可变大小(指4k,16k或64k)的内存块地址
- 页表项,可能被标记为fault或invalid
:
4.2. 页大小对翻译表的影响
对于4kB(2^12)页,硬件可以使用4级查询。每个页表2^9=512项,需要VA的9位做索引。
9位,9位,9位,9位,12位。如下图
VA的[47:39]索引到L0表的512项。每个表项对应512GB(2^39)范围,并指向L1表。[38:30]位索引L1表,指向1GB内存块或指向L2表。[29:21]位索引L2表,指向2MB块或者L3表。[20:12]索引L3表,指向4kB的块。
16kB页大小,每个页表2^11项=2048项,需要VA的11位提供索引。
1位,11位,11位,11位,14位
level0表仅包含两项,对应128TB范围如下
64kB,每个页表2^13项=8192项,需要VA的13位提供索引。
13位,13位,13位,16位
4.3. 内存属性
- 执行许可:Unprivileged eXucute Never(UXN)和PXN
- AF: access flag
- SH: shareable attributed
- AP: access permission
- NS: security bit, 仅在EL3和Secure EL1使用
- Indx是到MAIR_ELn的索引
描述符格式支持继承,即某一级的属性能够被低级别继承。
5. 翻译表配置
除了在TLB中存储翻译表,MMU也可被配置成在可cache内存中存储翻译表。
5.1. 虚拟地址tagging
TCR_ELn的Top Byte Ignore(TBI)位用于提供tagged addressing support。
若开启了TBI,MMU不会检查64位地址前8位,仅会检查之后的8位。前8位即可用于传递数据。
6. 访问许可
访问许可通过翻译表项控制。访问许可控制着一个区域可读/可写,并可独立为EL0和(EL1,EL2,EL3)配置访问权限。如下
AP | EL0 | EL1/2/3 |
---|---|---|
00 | No access | R/W |
01 | R/W | R/W |
10 | No access | R |
11 | R | R |
另一种权限是是否可执行。Unprivileged Execute Never(UXN)和Privileged Execute Never(PXN)可以被独立配置。
7. 实现相关
7.1. OS和MMU交互
为了使用MMU,OS需要使能MMU,然后提供给他用于翻译的页表。即MMU会提供翻译功能,但是OS需要负责创建和维护页表。
MMU使能后所有处理器指令的取出和内存访问都会通过MMU翻译,因此内核和用户空间均需要提供合适的页表。
每一级的页表数量以及需要映射多少内存取决于操作系统。该设计中用户空间仅会使用1GB虚拟内存。因此对于用户页表,仅会使用1个L2页表,其中包含2项(512MB*2=1024MB=1G)指向不同的L3页表,每一个L3表有8192项,均指向64kB的页。
在这种设计下,用户空间能够使用的虚拟地址为0xFFFF_FFFF_c000_0000以上,因此T1SZ寄存器需要配置。
7.2. 页表项
- 页表项[47:16]位:ADDR域,包含了32位输出地址,若是L2表项,则输出地址会和VA[28:16]位结合指向L3页表。若是L3表项,会指向翻译的物理地址。
- 页表项低10位表示内存属性
- VALID: Identifies whether the descriptor is valid. You should set the entry as valid to use it.
- TYPE: L2 entry can be interpreted as a descriptor pointing a block of memory or a descriptor pointing next level of translation table (L3). Note that our L2 entries needs to point the L3 table, while our L3 entries will point to the memory pages.
- ATTR: Describes memory region attributes. When user page table allocates a new page, its L3 entry should be normal memory. Likewise, when kernel page table sets L3 entries, they should be normal memory. On the other hand, for the memory range from
IO_BASE
toIO_BASE_END
, they should have device-memory entries in L3 page table. - NS: Non-secure bit
- AP: Data access permission bits. Sets data access permission of the entry. The kernel page table should have KERN_RW permission while the user page table should have USER_RW permission.
- SH: Shareability field. Normal memory space should set their entries as inner shareable while device memory should set theirs as outer shareable.
- AF: Access flag. Should be set when first accessed. Make sure you set this bit whenever you make a page table entry; in our implementation, we will assume all pages are being used.
7.3. 操作系统内存空间布局
一种设计如下
虚拟空间中,内核地址从0x0开始,用户空间地址从0xffff_ffff_c000_0000开始。
对于物理空间,在内核启动时创建内核页表,将所有可用物理内存映射到内核虚拟内核虚拟内存。具体地,通过atags获取内存起始和结束位置,然后从物理和虚拟地址0x0
处开始,为每一页插入一个页表项直到结束位置。这样内核能够继续使用物理地址(此时物理地址和虚拟地址一一对应),并且MMU也不会出问题。
对于内存映射IO设备,进行同样的处理。
为内核虚拟地址映射页并不意味着它们被分配了。创建这些映射仅仅能够让内核使用虚拟空间管理物理内存。注意,allocator仍会继续使用memory_map
给出的可用内存起始、结束地址管理内存,因为内核空间物理和虚拟地址相同因此不会有影响。
至于用户需要分配内存的时候,我们仍使用allocator分配,并在用户trapframe内页表基地址对应的页表中插入一个页表项。
一些疑惑的地方
用户空间如何直到使用的虚拟地址是什么??换言之,用户态load的时候若按上述例子,应当load到高地址
- 用户空间如何知道自己load到高地址。强行改linker脚本
- 为什么用户空间能访问高虚拟地址,改页表项的访问权限