PAE Paging与32-Bit Paging的区别
PAE Paging与32-Bit Paging的不同点在于PAE Paging可将32-Bit的线性地址转换成最高52位的物理地址,不过受到线性地址宽度的限制,最高可访问的线性地址空间还是4G。还有就是PAE Paging使用4个PDPTE寄存器进行地址转换,CR3寄存器保存这4个PDPTE寄存器的物理地址。这样在地址转换时可以使用更多的地址转换结构,而不是像32-Bit Paging那样都是从CR3开始的一组地址转换结构。
开启PAE Paging的条件
首先要通过CPUID指令来查询CPU是否支持PAE Paging,如果CPUID.01H:EDX.PAE [bit 6] = 1表示CPU支持PAE Paging,否则不支持。其次是CPUID.80000008H:EAX[7:0]表示最高的物理地址宽度,这里MAXPHYADDR最高可达52位,如果MAXPHYADDR<52那么,转换后的物理地址中52:MAXPHYADDR都是0。
当CR0.PG = 1, CR4.PAE = 1, and IA32_EFER.LME = 0时开启PAE Paging。同样这个设置顺序是有要求的,不能够随意设置,否则会触发#GP异常。
PDPTE寄存器
开启PAE Paging之后,地址转换的顶层结构来源于CPU内部使用4个寄存器,这4个寄存器分别是PDPTE0、PDPTE1、PDPTE2和PDPTE3,每一个寄存器对应1G的地址空间。
开启PAE Paging之后,CR3中保存这叫做“page-directory-pointer table”的物理地址,page-directory-pointer table一共有4项,每一项是一个64位的单元,叫做PDPTE。在特定的时候,CPU会从CR3指定的物理地址中将page-directory-pointer table的4项加载到4个PDPTE寄存器中。
下图是开启PAE Paging之后CR3的结构:
通过CR3来计算page-directory-pointer table的地址:
- 31:5位来源于CR3的31:5位
- 4:0位是0
CPU中的PDPTE寄存器内容不是一成不变的,如下的时候CPU把CR3保存的物理地址中的内容加载到PDPTE寄存器:
- 开启PAE Paging后,CR0和CR4中与分页有关的位被修改的时候。
- CR3改变的时候。
- 任务切换改变CR3的时候。
下图是PDPTE的结构:
加载PDPTE的过程中如果发现PDPTE.P = 0或者任何保留位被设置了就会触发#GP异常,并且不会加载PDPTE。
地址转换
PAE Paging可以将32位的线性地址转换成4-KByte或者2-MByte的页,这主要取决于PDE.PS,如果PDE.PS=1则使用2-MByte的页,否则使用4-KByte的页。PAE Paging与32-Bit Paging不同的是地址转换结构,如PDE,PTE等都是64位的。
2-MByte Page
下图是Intel手册中使用PAE将线性地址转换成2-MByte页的过程:
PDE的结构:
2-MByte Page的情况下从线性地址转换到物理地址的过程:
- 通过线性地址的31:30位来选择PDPTE寄存器:
- 通过PDPTE寄存器来计算PDE的地址:
- PDE的51:12位来自于PDPTE的51:12位
- 11:3位来自于线性地址的29:21位
- 2:0位是0
- 通过PDE计算实际的物理地址:
- 物理地址的51:21位来自于PDE的51:21位
- 20:0位来自于线性地址的20:0位
4-KByte Page
下图是Intel手册中使用PAE将线性地址转换成4-KByte的转换过程:
采用4-KByte分页的时候,PDE与2-MByte分页时候结构是不同的,下图是Intel手册中采用4-KByte时PDE和PTE的结构:
4-KByte Page情况下从线性地址转换成物理地址的过程:
- 通过线性地址的31:30位来选择PDPTE寄存器:
- 通过PDPTE寄存器来计算PDE的地址:
- PDE的51:12位来自于PDPTE的51:12位
- 11:3位来自于线性地址的29:21位
- 2:0位是0
- 通过PDE来计算PTE的地址:
- PTE的51:12位来自于PDE的51:12位
- 11:3位来自于线性地址的20:12位
- 2:0位是0
- 通过PTE计算物理地址:
- 物理地址的51:12位来自于PTE的51:12位
- 11:0位来自于线性地址的11:0位
例子
这里的例子是根据《x86/x64体系结构探索及编程》配套的代码编写的,首先调用pae_enable来开启PAE:
pae_enable:
movl $1, %eax
cpuid
btl $6, %edx
jnc pae_enable_done
movl %cr4, %eax
bts $PAE_BIT, %eax
movl %eax, %cr4
pae_enable_done:
ret
然后调用init_pae32_paging来预先设置地址转换结构:
init_pae32_paging:
movl $PDPT_BASE, %esi
call clear_4k_page
movl $0x201000, %esi
call clear_4k_page
movl $0x202000, %esi
call clear_4k_page
# PDPTE[0]
movl $PDPT_BASE, %esi
movl $0x201001, (%esi)
movl $0x00, 4(%esi)
# PDE[0] 0x00 ~ 0x1fffff (2M)
# PDE[1] 0x200000 ~ 0x3fffff (2M)
# PDE[2] 0x400000 ~ 0x400fff (4K)
movl $0x201000, %esi
movl $0x00, %eax
movl $0x00000087, (%esi, %eax, 8)
movl $0x00, 4(%esi, %eax, 8)
inc %eax
movl $0x00200087, (%esi, %eax, 8)
movl $0x00, 4(%esi, %eax, 8)
inc %eax
movl $0x00202007, (%esi, %eax, 8)
movl $0x00, 4(%esi, %eax, 8)
# PTE[0] 0x400000 ~ 0x400fff (4K)
movl $0x202000, %esi
movl $0x00400001, (%esi)
movl xd_bit, %eax
movl %eax, 4(%esi)
ret
这里设置的分页结构很简单:
开启分页之后使用dump_pae_paging将几个线性地址转换成物理地址的过程打印出来:
# enable sse
movl %cr4, %eax
btsl $9, %eax
movl %eax, %cr4
call pae_enable
call xd_enable
call init_pae32_paging
movl $PDPT_BASE, %eax
movl %eax, %cr3
movl %cr0, %eax
bts $31, %eax
movl %eax, %cr0
movl $msg1, %esi
call puts
call println
call println
movl $msg2, %esi
call puts
call println
movl $0x200000, %esi
call dump_pae_page
call println
movl $msg3, %esi
call puts
call println
movl $0x400000, %esi
call dump_pae_page
call println
movl $msg4, %esi
call puts
call println
movl $0x401000, %esi
call dump_pae_page
call println
movl $msg5, %esi
call puts
call println
movl $0x600000, %esi
call dump_pae_page
call println
movl $msg6, %esi
call puts
call println
movl $0x40000000, %esi
call dump_pae_page
jmp .
代码执行的结果:
参考
《Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 3 (3A, 3B & 3C): System Programming Guide》
《x86/x64体系探索及编程》