目录


EPT 原理


EPT 概括

EPT 全称 “The Extended page-table machanism”, 它在内存虚拟化中将 Guest OS 物理地址转换成 Host OS 物理地址,以便 Guest OS 访问物理内存. EPT 是 Intel 架构支持的硬件功能. 在支持 EPT 的系统中,Guest OS 物理地址转换成 Host OS 的物理地址通过一系列的 EPT 页表结构。EPT 页表结构类似于 IA-32e 模式下线性地址转换时的页表结构。当系统支持 EPT 功能之外,EPT 页表的行为还与多个控制位有关,Intel 手册中这么描述:

CR0 寄存器的 PG 控制位用于控制系统的分页功能。在 Guest OS 内部,当 CR0.PG 置位的时候,Guest OS 的线性地址就是虚拟地址,虚拟地址需要 CR3 寄存器指向的页表进行转换才能访问 Guest OS 的内存,并且当 EPT 功能使能的情况下,这些页表称为 “Guest paging structures”; 反之当 CR0.PG 清零时,Guest OS 的线性地址就是物理地址,此时线性地址不需要页表的转换,Guest OS 直接使用线性地址就能访问 Guest OS 的内存。因此在 Guest OS 内部虚拟地址转换成物理地址的逻辑如下图:

CR0 寄存器

当 CR0.PG 置位,Guest OS 的线性地址转换成物理地址需要多次的 EPT 页表进行转换才能获得对应的 Host OS 的物理地址。假设 CR4.PAE 和 CR4.PSE 都清零,那么在 EPT 使能的情况下,一个 32 位的 Guest OS 线性地址的转换如下:

当一个 Guest OS 的虚拟地址转(GVA) 换成 Guest OS 物理地址(GPA)时,Guest OS 首先从 gCR3 寄存器中获得指向 Guest OS 页表的页目录基地址,该值是一个 Guest OS 的物理地址(GPA), 存在 gCR3 寄存器里,当 Guest OS 引用 gCR3 寄存器里面的物理地址(GPA)时,会触发以此 EPT 页表转换,系统将 Guest OS 的物理地址地址(GPA)转换成 Host OS 的物理地址(HPA),这样可以读取到内存真实存储页目录页表的内容,页目录页表的内容里全部都是 Guest OS 的物理地址 (GPA), 此时将 Guest OS 虚拟地址(GVA)的 31:22 位作为索引,在页目录页表中找到一个入口,这个入口称为 PDE, PDE 里面存储的同样是一个 Guest OS 的物理地址 (GPA), 并指向 PTE 页表的入口。当 Guest OS 引用这个 PDE 入口时会触发 EPT 将这个 Guest OS 的物理地址(GPA) 转换成 Host OS 的物理地址(HPA), 此时 HOST OS 物理地址对应的内存中存储着 PTE 页表的内容,此时系统将 Guest OS 虚拟地址(GVA) 的 21:12 位域作为索引找到一个 PTE, 这个 PTE 里面同样存储着一个 Guest OS 的物理地址(GPA), 该地址指向一个 Guest OS 的物理页。此时对这个 PTE 引用将会触发 EPT 将这个Guest OS 物理地址(GPA) 转换成一个 HOST OS 的物理地址(HPA). Guest OS 继续将 Guest OS 虚拟地址的 11:0 位域作为索引获得一个最后的 Guest OS 物理地址(GPA), 至此,一个 Guest OS 的虚拟地址(GVA)就完成了转换获得了一个 Guest OS 的物理地址(GPA). 如果接下来 Guest OS 对这个物理地址进行引用,那么会触发 EPT 页表将这个 Guest OS 物理地址(GPA) 转换成 Host OS 的物理地址(HPA), 最终访问真实的内存.

CR4 寄存器

另外,一个处理器只有在在 Guest OS 访问一段 Guet OS 的物理内存时候才会触发 EPT 页表将一个 Guest OS 物理地址转换成一个 HOST OS 物理地址.

虽然 Guest OS 访问内存会触发 EPT 转换,但在有的情况下,同样的指令操作不一定触发内存的访问,例如当执行 “MOV to CR3” 指令,该指令会将一个 Guest OS 的物理地址(GPA) 加载到 CR3 寄存器中,此时访问 CR3 寄存器的值时是否会触发 EPT 转换,这得依赖与 PAE 分页的使用,从之前的分析可以知道 CR4.PAE 用于使能物理地址拓展功能,如果 PAE 启用,那么物理地址的位数将超过 32bit。因此当 PAE 没有使用的情况下,”MOV to CR3” 不会触发 Guest OS 访问内存,因此不会触发 EPT 页表的转换; 反之如果 PAE 启用,那 CR3 寄存器中存储的 PDPTEs,即页目录指针页表的入口,因此会涉及内存的访问,因此 “MOV to CR3” 会触发 EPT 页表的转换. “MOV to CR0” 和 “MOV to CR3” 会触发 CR3 加载一个 PDPTEs 的操作,并访问内容,那么该操作会触发 EPT 页表转换. 如果将一个 PDPTEs 里面存储这个一个 Guest OS 物理地址(GPA), 并加载到 CR3 寄存器,但没有触发内存的访问,那么也不会触发 EPT 页表的转换。

CR0 寄存器

CR3 寄存器

CR4 寄存器


EPT EPTP

在每个 VCPU 的 VMCS 上下文的 VM-execution 控制域中存在一个名为 “The extended-page-table Point(EPTP)” 的控制域,该控制域包含了即 EPT PML4 页表的基地址,也包含了一些 EPT 的配置信息,EPTP 的格式如表.


bit N-1:12

在 EPTP 中,”N-1:12” 字段用于指向一张 4KiB 的 EPT PM4L 页表的物理地址,这里的物理地址是一个 HOST OS 物理地址(HPA), 正如上图所示的,EPT 在遍历页表的时候,首先从 EPTP 中获得 EPT PM4L 页表的基地址, 然后开始遍历 EPT 页表。这里的 N 代表 Guest OS 的物理地址宽度,比如 Guest OS 是 32 位,那么 N 就是 32; 反之 Guest OS 是 64 位,那么 N 就是 64.

N 的值可以通过往 CPUID 的 EAX 寄存器写入 0x80000008H 获得,当向 CPUID 写入该值之后,EAX 的返回值的 7:0 位将表明 Guest OS 的物理地址宽度.


bit 6

EPTP 的 bit 6 如果置位,那么将使能 EPT 的 “Accessed” 和 “Dirty” flags 属性. 但是不是所有的处理器都支持 EPT 的 “accessed” 和 “dirty” flags, 软件可以通过读取 IA32_VMX_EPT_VPID_CAP MSR 寄存器获得相关的信息并设置该位。

从 IA32_VMX_EPT_VPID_CAP MSR 寄存器的 bit21 可以获得相关信息,当改位置位的时候,EPT 支持 “accessed” 和 “dirty” flags 属性. 同理,EPTP 的 2:0 字段指明了 EPT 页表的内存类型,当该字段的值为 0,那么 EPT 页表是 “Uncacheable(UC)” 的,而当该字段的值是 6, 那么 EPT 页表是 “Write-back(WB)” 的,同理软件应该通过读取 IA32_VMX_EPT_VPID_CAP MSR 寄存器来设置该字段。如上图,IA32_VMX_EPT_VPID_CAP MSR 寄存器的 bit8 为 1,那么逻辑处理器允许将 EPT 页表内存类型设置为 “Uncacheable(UC)” 类型,即将 EPTP 的 bit 0:2 设置为 0; 同理如果 IA32_VMX_EPT_VPID_CAP MSR 寄存器的 bit14 为 1,那么软件可以将 EPT 页表的内存类型设置为 “Write-back(WB)”, 即将 EPTP 的 bit 0:2 设置为 6.


bit 5:3

如果 EPTP 的 5:3 字段为 1,那么表明当前 EPT 页表的长度小于 “EPT Page-walk” 的长度,”EPT Page-walk” 的长度指的是 EPT 遍历页表最多级数。在支持 EPT 1Gig 页表映射获得 EPT 2MiB 页表映射的时候,EPT 遍历页表的长度小于 “EPT Page-walk” 的长度,因此 EPTP 的 5:3 字段从侧面描述了 EPT 存在非 4KiB 页表映射.


bit 63:N - 11:7

EPTP 的 63:N 和 11:7 字段均 Reserved。


EPT PML4/PML4E

EPT 页表机制只是用 Guest OS 物理地址(GPA) 的 47:0 字段,EPT 包含了 4 级页表,也就是 Guest OS 的物理地址(GPA) 通过最多 4 级页表就可以获得对应的 Host OS 物理地址(HPA)。VCPU 将这 48 bit 的字段分成多个部分进行页表的遍历。其中 EPT 页表机制的第一级页表称为 PML4 页表。PML4 页表是一个 4KiB 对齐的物理页,其中每个 VCPU 的 VMCS “VM-execution control” 域中的 “The Extended-page-table pointer” (EPTP) 51:12 字段包含了 PML4 页表物理地址的. 因此 EPTP 就执行了 PML4 页表.

正如上图所述,EPTP 中包含了 PML4 页表的物理地址 (HPA), VCPU 可以通过 EPTP 快速获得 PML4 页表的基地址,PML4 页表中包含了 512 个 64bit 的单元,这些单元称为 PML4Es, 每个 PML4Es 中包含了下一级页表的物理地址以及一些 flags 相关的信息。EPT 在页表遍历的时候结合 EPTP 和 Guest OS 的物理地址(GPA) 获得一个 PML4Es, 其处理逻辑是: EPT MMU 首先从 EPTP 中的 51:12 字段获得 PML4 页表的基地址,接着从 Guest OS 物理地址(GPA) 的 47:39 字段作为索引,并将 PML4 页表作为一个 u64 数组进行查找,即可获得一个 PML4Es. 由于 EPT 只使用了 Guest OS 物理地址(GPA) 的 47:39 字段,因此一个 PML4Es 指向了一个 512Gig 的范围,那么一个 PML4 页表获得一个 EPTP 指向的范围是 “512 * 512 Gig”, 即 262144 Gig (256 TiB). 当获得一个 PML4Es 之后,EPT MMU 将解析 PML4Es 中的内容,以此获得下一级页表的信息。PML4Es 中的布局如下表:

每个 PML4Es 中的布局如上表,其分作两端,高于 bit12 的区域用于寻址下一级页表的物理地址,低 12 bits 包含了多个控制信息,用于控制页表的访问控制.


bit 0/1

PML4Es 的 bit0 和 bit1 用于控制 PML4 寻址的 512Gig 范围是否具有读或写权限,bit0 位用于控制读权限,bit1 位用于控制写权限. 如果对应的控制位清零,那么对应的 512Gig 范围将不具有读或写权限.


bit8

PML4Es 的 bit8 用于指明对应的 512Gig 范围是否已经被访问过。如果该位置位,那么对应的 512 Gig 范围已经被访问过,否则 512 Gig 范围没有访问过. 但值得注意的是 PMLEs 的 bit8 要能起作用,那还依赖 EPTP 的 bit6. EPTP 的 bit 6 如果置位,那么将使能 EPT 的 “Accessed” 和 “Dirty” flags 属性. 但是不是所有的处理器都支持 EPT 的 “accessed” 和 “dirty” flags, 软件可以通过读取 IA32_VMX_EPT_VPID_CAP MSR 寄存器获得相关的信息并设置该位。

从 IA32_VMX_EPT_VPID_CAP MSR 寄存器的 bit21 可以获得相关信息,当改位置位的时候,EPT 支持 “accessed” 和 “dirty” flags 属性. 同理,EPTP 的 2:0 字段指明了 EPT 页表的内存类型,当该字段的值为 0,那么 EPT 页表是 “Uncacheable(UC)” 的,而当该字段的值是 6, 那么 EPT 页表是 “Write-back(WB)” 的,同理软件应该通过读取 IA32_VMX_EPT_VPID_CAP MSR 寄存器来设置该字段。如上图,IA32_VMX_EPT_VPID_CAP MSR 寄存器的 bit8 为 1,那么逻辑处理器允许将 EPT 页表内存类型设置为 “Uncacheable(UC)” 类型,即将 EPTP 的 bit 0:2 设置为 0; 同理如果 IA32_VMX_EPT_VPID_CAP MSR 寄存器的 bit14 为 1,那么软件可以将 EPT 页表的内存类型设置为 “Write-back(WB)”, 即将 EPTP 的 bit 0:2 设置为 6.


bit (N-1):12

PML4Es 的 “(N-1):12” 字段中包含了 EPT Page-directory-pointer 页表按 4 KiB 对齐的物理地址。这里的 N 代表 Guest OS 的物理地址宽度,比如 Guest OS 是 32 位,那么 N 就是 “32-12-1” 即 19; 反之 Guest OS 是 64 位,那么 N 就是 “64-12-1” 即 51.

N 的值可以通过往 CPUID 的 EAX 寄存器写入 0x80000008H 获得,当向 CPUID 写入该值之后,EAX 的返回值的 7:0 位将表明 Guest OS 的物理地址宽度.


Reserved

bit 7:3、51:N 必须预留为 0,而 bit9、bit11 和 63:52 忽略.



EPT PDP/PDPTE

EPT 的第二级页表称为 “EPT Page-Directory-Pointer Table”, 改页表通过 PML4Es 获得,以其他级 EPT 页表一样,PDP 页表由一个 4 KiB 对齐的物理页构成,物理页被分成 512 个 64bit 的单元,每个单元称为一个 PDPTEs. PML4Es 查找 PDT 的逻辑如下图:

当 EPT MMU 获得 PML4Es 之后,EPT MMU 会从 PML4Es 中读取 51:12 字段,并将该字段作为一个按 4 KiB 对齐物理页的 51:12 字段,那么这个物理页就是一个 PDP 页表。EPT MMU 继续将 Guest OS 物理地址(GPA) 的 38:30 字段作为索引,而 PDP 页表为为一个 u64 数组进行查找,查找到的地址就是一个 PDPTEs. PDPTEs 用于指向下一级页表或者一块连续的 1 Gig 物理内存空间。PDPTE 由两部分组成,一部分是地址索引,另外一部分则是控制域。由于 PTEP 是通过 Guest OS 物理地址(GPA) 的 38:30 字段索引获得,因此一个 PDPTE 可寻址范围就是 1 Gig。EPT 支持 1 Gig 页表映射,也支持 2 MiB 或者 4 KiB 的页表映射,因此 PDPE 页表的控制域可以指明页表的映射范围。PTEP 映射的范围不同,其布局也不相同,这里分开进行讲解.

> PDPTE mapping 1 Gib

> PDPTE to Page Directory Table


PDPTE Mapping 1 Gig

EPT 1 Gig 页表映射指的是 Guest OS 中的物理地址将 1 Gig 的物理页映射到 Host OS 中 1 Gig 的物理页上,要做到 1 Gig 的物理页映射需要确保 Guest OS 对应的物理内存区域的起始地址按 1 Gig 对齐,并且 Host OS 中的物理页是按 1 Gig 将对应的虚拟地址映射到物理地址上。如上图所述,当通过 PML4Es 和 Guest OS 物理地址的 38:30 字段获得一个 PDPTEs 之后,EPT MMU 将不再继续查询页表,而是将下图进行处理:

EPT MMU 首先确认 PDPTE 的 bit7 置位,在置位的情况下,PDPTE 映射 1 Gig 的物理内存,此时将 PDPTEs 的 bits 51:30 和 Guest OS 物理地址(GPA) 的 bits 29:0 组成一个 Host OS 的物理地址(HPA), 该物理地址就是最终的物理地址. 但不是所有的处理器都支持 1 Gig EPT 映射,因此在进行 1 Gig 映射之前需要从 IA32_VMX_EPT_VPID_CAP 寄存器中进行查询,已确认 EPT 页表支持 1 Gig 页表映射。

正如上表所示,如果 IA32_VMX_EPT_VPID_CAP 的 bit17 置位,那么 EPT 页表支持 1 Gib 页表映射; 反之 EPT 不支持 1 Gig 页表映射。当 PDPTEs 映射 1 Gig 物理内存是,PDPTEs 的内部数据布局如下:

bit 7

PDPTE 的 bit 7 必须置位,这样才表明 PDPTE 按 1 Gig 物理页进行映射.


bit 0/1

PDPTE 的 bit 0/1 用于控制 1 Gig 范围的读写权限。当 bit 0 置位的时候,1 Gig 的范围具有读权限; 反之 bit 0 清零的时候,1 Gig 的范围没有读权限. 当 bit 1 置位的时候,1 Gig 的范围具有写权限; 反之 bit 1 清零的时候,1 Gig 的范围没有写权限.


bit 8/9

PDPTE 的 bit 8/9 用于指明 1 Gig 范围的 “accessed” 和 “dirty” 标志。当 bit 8 置位,那么表明软件已经访问过这段 1 Gig 的物理内存; 反之 bit 8 清零,那么表明软件未访问过这段 1 Gig 的物理内存。当 bit 9 置位,那么表明软件已经向 1 Gig 的物理内存空间写入过数据; 反之当 bit 9 清零,那么表明软件未向这段 1 Gig 物理内存写入过数据。bit 8/9 的启用与 EPTP 的 bit 6 有关. EPTP 的 bit 6 如果置位,那么将使能 EPT 的 “Accessed” 和 “Dirty” flags 属性. 但是不是所有的处理器都支持 EPT 的 “accessed” 和 “dirty” flags, 软件可以通过读取 IA32_VMX_EPT_VPID_CAP MSR 寄存器获得相关的信息并设置该位。

从 IA32_VMX_EPT_VPID_CAP MSR 寄存器的 bit21 可以获得相关信息,当改位置位的时候, EPT 支持 “accessed” 和 “dirty” flags 属性. 同理,EPTP 的 2:0 字段指明了 EPT 页表的内存类型,当该字段的值为 0,那么 EPT 页表是 “Uncacheable(UC)” 的,而当该字段的值是 6, 那么 EPT 页表是 “Write-back(WB)” 的,同理软件应该通过读取 IA32_VMX_EPT_VPID_CAP MSR 寄存器来设置该字段。如上图,IA32_VMX_EPT_VPID_CAP MSR 寄存器的 bit8 为 1,那么逻辑处理器允许将 EPT 页表内存类型设置为 “Uncacheable(UC)” 类型,即将 EPTP 的 bit 0:2 设置为 0; 同理如果 IA32_VMX_EPT_VPID_CAP MSR 寄存器的 bit14 为 1,那么软件可以将 EPT 页表的内存类型设置为 “Write-back(WB)”, 即将 EPTP 的 bit 0:2 设置为 6.


bit (N-1):30

PDPTE 的 (N-1):30 字段用于指向一个按 1 Gig 对齐的物理地址,这块物理内存即是 Guest OS 物理地址所映射的的物理内存,接下来 EPT MMU 将这个物理地址与 Guest OS 的物理地址 29:0 字段一共组成一个新的物理地址,这个物理地址即是 Guest OS 的物理地址映射的 Host OS 的物理地址.

PDPTE to Page Directory Table

当 EPT PDPTE 没有映射 1 Gig 的物理内存,那么 EPT MMU 将从 PDPTE 中获得下一级页表 Page Directory Table 的物理页的起始地址. PDPTE 要使用下一级页表,那么需要满足一下几个条件,Guest OS 物理地址对应的内存区域不按 1 Gig 物理地址对齐,并且 Host OS 端的物理地址也不是按 1 Gig 物理地址对齐,在满足以上任一条件之后,那么 PDPTE 将指向 EPT Page Directory Table. 在这种情况下,PDPTE 页表的遍历逻辑是: EPT MMU 从 PML4Es 中获取 51:12 字段作为 PDP 页表的 51:12 字段值,然后将 Guest OS 物理地址(GPA) 的 38:30 字段作为索引,并将 PDP 页表作为一个 u64 数组进行查找,查找到的结果即是一个 PDPTE。此时 PDPTEs 的布局如下:

bit 7

PDPTE 的 bit 7 必须清零,这样才表明 PDPTE 不是按 1 Gig 物理页进行映射.


bit 0/1

PDPTE 的 bit 0/1 用于控制 1 Gig 范围的读写权限。当 bit 0 置位的时候,1 Gig 的范>围具有读权限; 反之 bit 0 清零的时候,1 Gig 的范围没有读权限. 当 bit 1 置位的时候 ,1 Gig 的范围具有写权限; 反之 bit 1 清零的时候,1 Gig 的范围没有写权限.


bit 8

PDPTE 的 bit 8 用于指明 1 Gig 范围的 “accessed” 标志。当 bit 8 置位,那么表明软件已经访问过这段 1 Gig 的物理内存; 反之 bit 8 清零,那么表明软件未访问过这段 1 Gig 的物理内存。bit 8 的启用与 EPTP 的 bit 6 有关. EPTP 的 bit 6 如果置位,那么将使能 EPT 的 “Accessed” 和 “Dirty” flags 属性. 但是不是所有的处理器都支持 EPT 的 “accessed” 和 “dirty” flags, 软件可以通过读取 IA32_VMX_EPT_VPID_CAP MSR 寄存器获得相关的信息>并设置该位。

从 IA32_VMX_EPT_VPID_CAP MSR 寄存器的 bit21 可以获得相关信息,当改位置位的时候, EPT 支持 “accessed” 和 “dirty” flags 属性. 同理,EPTP 的 2:0 字段指明了 EPT 页表 的内存类型,当该字段的值为 0,那么 EPT 页表是 “Uncacheable(UC)” 的,而当该字段的 值是 6, 那么 EPT 页表是 “Write-back(WB)” 的,同理软件应该通过读取 IA32_VMX_EPT_VPID_CAP MSR 寄存器来设置该字段。如上图,IA32_VMX_EPT_VPID_CAP MSR 寄存器的 bit8 为 1,那么逻辑处理器允许将 EPT 页表内存类型设置为 “Uncacheable(UC)” 类型,即将 EPTP 的 bit 0:2 设置为 0; 同理如果 IA32_VMX_EPT_VPID_CAP MSR 寄存器的 bit14 为 1>,那么软件可以将 EPT 页表的内存类型设置为 “Write-back(WB)”, 即将 EPTP 的 bit 0:2 设置为 6.


bit (N-1):12

PDPTE 的 (N-1):12 字段用于指向下一级页表 EPT Page Directory Table 页表按 4 Kib 对齐的物理地址. 这里的 N 代表 Guest OS 的物理地址宽度,比如 Guest OS 是 32 位,那么 N 就是 “32-12-1” 即 19; 反之 Guest OS 是 64 位,那么 N 就是 “64-12-1” 即 51.

N 的值可以通过往 CPUID 的 EAX 寄存器写入 0x80000008H 获得,当向 CPUID 写入该值之后,EAX 的返回值的 7:0 位将表明 Guest OS 的物理地址宽度


PD/PDE

EPT 的第三级页表称为 “EPT Page-Directory Table”, 该页表通过 PDPTEs 获得,与其他几级的 EPT 页表一样,Page-Directory 页表由一个 4 KiB 对齐的物理页构成,物理页被分成 512 个 64bit 的单元,每个单元称为一个 PDEs。PDPTE 查找 PD 的逻辑如下图:

当 EPT MMU 获得 PDPTE 之后,检测 PDPTE 的 bit7 清零,那么 PDPTE 将指向 EPT PD 页表。EPT MMU 会从 PDPTE 中读取 51:12 字段,并将该字段作为一个按 4 KiB 对齐物理页的 51:12 字段,那么这个物理页就是一个 PD 页表。EPT MMU 继续将 Guest OS 物理地址(GPA) 的 29:21 字段作为索引,而 PD 页表为为一个 u64 数组进行查找,查找到的地址就是一个 PDEs. PDEs 用于指向下一级页表或者一块连续的 2 MiB 物理内存空间。PDE 由两部分组成,一部分是物理基地址,另外一部分则是控制域。由于 PDE 是通过 Guest OS 物理地址(GPA) 的 29:12 字段索引获得,因此一个 PDEs 可寻址范围就是 2 MiB。EPT 支持 2 MiB 页表映射,也支持 4 KiB 的页表映射,因此 PDE 页表的控制域可以指明页表的映射范围。PTEP 映射的范围不同,其布局也不相同,这里分开进行讲解.

> PDE mapping 2 MiB

> PDE to Page Table


PDPTE Mapping 2 MiB

EPT 2 MiB 页表映射指的是 Guest OS 中的物理地址将 2 MiB 的物理页映射到 Host OS 中 2 MiB 的物理页上,要做到 2 MiB 的物理页映射需要确保 Guest OS 对应的物理内存区域的起始地址按 2 MiB 对齐,并且 Host OS 中的物理页是按 2 MiB 将对应的虚拟地址映射到物理地址上。如上图所述,当通过 PDPTEs 和 Guest OS 物理地址的 29:12 字段获得一个 PDEs 之后,EPT MMU 将不再继续查询页表,而是将下图进行处理:

EPT MMU 首先确认 PDEs 的 bit7 置位,在置位的情况下,PDEs 映射 2 MiB 的物理内存,此时将 PDEs 的 bits 51:21 和 Guest OS 物理地址(GPA) 的 bits 20:0 组成一个 Host OS 的物理地址(HPA), 该物理地址就是最终的物理地址. 但不是所有的处理器都支持 2 MiB EPT 映射,因此在进行 2 MiB 映射之前需要从 IA32_VMX_EPT_VPID_CAP 寄存器中进行查询,已确认 EPT 页表支持 2 MiB 页表映射。

正如上表所示,如果 IA32_VMX_EPT_VPID_CAP 的 bit16 置位,那么 EPT 页表支持 2 MiB 页表映射; 反之 EPT 不支持 2 MiB 页表映射。当 PDEs 映射 2 MiB 物理内存是,PDEs 的内部数据布局如下:

bit 7

PDEs 的 bit 7 必须置位,这样才表明 PDEs 是按 2 MiB 物理页进行映射.


bit 0/1

PDEs 的 bit 0/1 用于控制 2 MiB 范围的读写权限。当 bit 0 置位的时候,2 MiB 的范>围具有读权限; 反之 bit 0 清零的时候,2 MiB 的范围没有读权限. 当 bit 1 置位的时候,2 MiB 的范围具有写权限; 反之 bit 1 清零的时候,2 MiB 的范围没有写权限.


bit 8/9

PDEs 的 bit 8/9 用于指明 2 MiB 范围的 “accessed” 和 “dirty” 标志。当 bit 8 置位,那么表明软件已经访问过这段 2 MiB 的物理内存; 反之 bit 8 清零,那么表明软件未访问过这段 2 MiB 的物理内存。当 bit 9 置位,那么表明软件已经向 2 MiB 的物理内存空>间写入过数据; 反之当 bit 9 清零,那么表明软件未向这段 2 MiB 物理内存写入过数据。bit 8/9 的启用与 EPTP 的 bit 6 有关. EPTP 的 bit 6 如果置位,那么将使能 EPT 的 “Accessed” 和 “Dirty” flags 属性. 但是不是所有的处理器都支持 EPT 的 “accessed” 和 “dirty” flags, 软件可以通过读取 IA32_VMX_EPT_VPID_CAP MSR 寄存器获得相关的信息并设置该位。

从 IA32_VMX_EPT_VPID_CAP MSR 寄存器的 bit21 可以获得相关信息,当改位置位的时候,EPT 支持 “accessed” 和 “dirty” flags 属性. 同理,EPTP 的 2:0 字段指明了 EPT 页表的内存类型,当该字段的值为 0,那么 EPT 页表是 “Uncacheable(UC)” 的,而当该字段的值是 6, 那么 EPT 页表是 “Write-back(WB)” 的,同理软件应该通过读取 IA32_VMX_EPT_VPID_CAP MSR 寄存器来设置该字段。如上图,IA32_VMX_EPT_VPID_CAP MSR 寄存器的 bit8 为 1,那么逻辑处理器允许将 EPT 页表内存类型设置为 “Uncacheable(UC)” 类型,即将 EPTP 的 bit 0:2 设置为 0; 同理如果 IA32_VMX_EPT_VPID_CAP MSR 寄存器的 bit14 为 1,那么软件可以将 EPT 页表的内存类型设置为 “Write-back(WB)”, 即将 EPTP 的 bit 0:2 设置为 6.


bit (N-1):21

PDEs 的 (N-1):21 字段用于指向一个按 2 MiB 对齐的物理地址,这块物理内存既是 Guest OS 物理地址所映射的物理地址. 接下来 EPT MMU 将这个物理地址与 Guest OS 的物理地址 20:0 字段一共组成一个新的物理地址,这个物理地址即是 Guest OS 的物理地址映射的 Host OS 的物理地址.这里的 N 代表 Guest OS 的物理地址宽度,比如 Guest OS 是 32 位,那么 N 就是 “32-12-1” 即 19; 反之 Guest OS 是 64 位,那么 N 就是 “64-12-1” 即 51.

N 的值可以通过往 CPUID 的 EAX 寄存器写入 0x80000008H 获得,当向 CPUID 写入该值之后,EAX 的返回值的 7:0 位将表明 Guest OS 的物理地址宽度.


PDE to Page Table

当 EPT PDEs 没有映射 2 MiB 的物理内存,那么 EPT MMU 将从 PDEs 中获得下一级页表 Page Table 的物理页的起始地址. PDPTE 要使用下一级页表,那么需要满足一下几个条件,Guest OS 物理地址对应的内存区域不按 2 MiB 物理地址对齐,或者 Host OS 端的物理地址也不是按 2 MiB 物理地址对齐,在满足以上任一条件之后,那么 PDEs 将指向 EPT Page Table. 在这种情况下,PDEs 页表的遍历逻辑是: EPT MMU 从 PDPTEs 中获取 51:12 字段作为 PD 页表的 51:12 字段值,然后将 Guest OS 物理地址(GPA) 的 29:21 字段作为索引,并将 PD 页表作为一个 u64 数组进行查找,查找到的结果即是一个 PDEs。此时 PDEs 的布局如下:

bit 7

PDEs 的 bit 7 必须清零,这样才表明 PDEs 不是按 2 MiB 物理页进行映射.


bit 0/1

PDEs 的 bit 0/1 用于控制 2 MiB 范围的读写权限。当 bit 0 置位的时候,2 MiB 的范>围具有读权限; 反之 bit 0 清零的时候,2 MiB 的范围没有读权限. 当 bit 1 置位的时候,2 MiB 的范围具有写权限; 反之 bit 1 清零的时候,2 MiB 的范围没有写权限.


bit 8

PDEs 的 bit 8 用于指明 2 MiB 范围的 “accessed” 标志。当 bit 8 置位,那么表明软件已经访问过这段 2 MiB 的物理内存; 反之 bit 8 清零,那么表明软件未访问过这段 2 MiB 的物理内存。bit 8 的启用与 EPTP 的 bit 6 有关. EPTP 的 bit 6 如果置位,那么将使能 EPT 的 “Accessed” 属性. 但是不是所有的处理器都支持 EPT 的 “accessed” flags, 软件可以通过读取 IA32_VMX_EPT_VPID_CAP MSR 寄存器获得相关的信息并设置该位。

从 IA32_VMX_EPT_VPID_CAP MSR 寄存器的 bit21 可以获得相关信息,当改位置位的时候,EPT 支持 “accessed” 和 “dirty” flags 属性. 同理,EPTP 的 2:0 字段指明了 EPT 页表的内存类型,当该字段的值为 0,那么 EPT 页表是 “Uncacheable(UC)” 的,而当该字段的值是 6, 那么 EPT 页表是 “Write-back(WB)” 的,同理软件应该通过读取 IA32_VMX_EPT_VPID_CAP MSR 寄存器来设置该字段。如上图,IA32_VMX_EPT_VPID_CAP MSR 寄存器的 bit8 为 1,那么逻辑处理器允许将 EPT 页表内存类型设置为 “Uncacheable(UC)” 类型,即将 EPTP 的 bit 0:2 设置为 0; 同理如果 IA32_VMX_EPT_VPID_CAP MSR 寄存器的 bit14 为 1,那么软件可以将 EPT 页表的内存类型设置为 “Write-back(WB)”, 即将 EPTP 的 bit 0:2 设置为 6.


bit (N-1):12

PDEs 的 (N-1):23 字段用于指向下一级的 4 KiB EPT PTE页表. 这里的 N 代表 Guest OS 的物理地址宽度,比如 Guest OS 是 32 位,那么 N 就是 “32-12-1” 即 19; 反之 Guest OS 是 64 位,那么 N 就是 “64-12-1” 即 51.

N 的值可以通过往 CPUID 的 EAX 寄存器写入 0x80000008H 获得,当向 CPUID 写入该值之后,EAX 的返回值的 7:0 位将表明 Guest OS 的物理地址宽度.


PTE

EPT 的第四级页表称为 “EPT Page Table”, 该页表通过 PDEs 获得,与其他几级的 EPT 页表一样,Page Table 页表由一个 4 KiB 对齐的物理页构成,物理页被分成 512 个 64bit 的单元,每个单元称为一个 PTEs。PDEs 查找 Page Table 的逻辑如下图:

当 EPT MMU 获得 PDEs 之后,检测 PDEs 的 bit7 清零,那么 PDEs 将指向 EPT Page Table 页表。EPT MMU 会从 PDEs 中读取 51:12 字段,并将该字段作为一个按 4 KiB 对齐物理页的 51:12 字段,那么这个物理页就是一个 Page Table 页表。EPT MMU 继续将 Guest OS 物理地址(GPA) 的 20:12 字段作为索引,而 Page Table 页表为为一个 u64 数组进行查找,查找到的地址就是一个 PTEs. 由于识别 EPTs 已经占用了 Guest OS 物理地址(GPA) 的 47:12 字段,那么 PTEs 用于指向一块连续的 4 KiB 物理内存空间。PTEs 由两部分组成,一部分是物理基地址,另外一部分则是控制域。由于 PTEs 是通过 Guest OS 物理地址(GPA) 的 47:12 字段索引获得,因此一个 PTEs 可寻址范围就是 4 KiB。EPT 支持 4 KiB 页表映。

bit 0/1

PTE 的 bit 0 用于描述 4 KiB 物理内存是否具有读权限,如果该位置位,那么 4 KiB 物理内存具有读权限,反之如果清零,那么 4 KiB 物理内存不具有读权限。PTE 的 bit 1 用于描述 4 KiB 物理内存是否具有写权限,如果该位置位,那么 4 KiB 物理内存具有写权限,反之如果清零,那么 4 KiB 物理内存不具有写权限。


bit 2

EPT PTE 的 bit 2 的功能与 “mode-based execute control for EPT” VM-execution control 有关,该控制域定义在 VMCS 的 “The Secondary Processor-Based VM-Execution Contrl” 控制域里,如下:

从 “Mode-Based execute control for EPT” 控制域可以看到,当该位置位,EPT 的执行权限基于 Guest OS 的线性地址是被 Guest OS 的内核态访问还是 Guest OS 的用户态访问。回到 EPT PTE 的 bit 2,当 “Mode-Based execute control for EPT” 控制域清零,该 bit 用于控制是否可以从 4 KiB 中指令读取,如果此时 bit 清零,那么没有权限从对应的 4 KiB 中读取指令,反之如果该位置位,那么有权限从对应的 4 KiB 中读取指令.

如果 “Mode-Based execute control for EPT” 控制域置位,那么当该位置位的时候,Guest OS 内核态的线性地址有权限从对应的 4 KiB 中读取指令, 反之 Guest OS 内核太的线性地址没有权限从对应的 4 KiB 中读取指令.


bit 5:3/ bit 6

EPT PTE 的 bit 5:3 指明了对应 4 KiB 物理页的内存类型。具体请查考:

EPT Memory Type Used for Accessing EPT Paging Structures


bit 8/9

PTEs 的 bit 8/9 用于指明 4 KiB 范围的 “accessed” 和 “dirty” 标志。当 bit 8 置位,那么表明软件已经访问过这段 4 KiB 的物理内存; 反之 bit 8 清零,那么表明软件未访问过这段 4 KiB 的物理内存。当 bit 9 置位,那么表明软件已经向 4 KiB 的物理内存空间写入过数据; 反之当 bit 9 清零,那么表明软件未向这段 4 KiB 物理内存写入过数据。bit 8/9 的启用与 EPTP 的 bit 6 有关. EPTP 的 bit 6 如果置位,那么将使能 EPT 的 “Accessed” 和 “Dirty” flags 属性. 但是不是所有的处理器都支持 EPT 的 “accessed” 和 “dirty” flags, 软件可以通过读取 IA32_VMX_EPT_VPID_CAP MSR 寄存器获得相关的信息并设置该位。

从 IA32_VMX_EPT_VPID_CAP MSR 寄存器的 bit21 可以获得相关信息,当改位置位的时候,EPT 支持 “accessed” 和 “dirty” flags 属性. 同理,EPTP 的 2:0 字段指明了 EPT 页表 的内存类型,当该字段的值为 0,那么 EPT 页表是 “Uncacheable(UC)” 的,而当该字段的值是 6, 那么 EPT 页表是 “Write-back(WB)” 的,同理软件应该通过读取 IA32_VMX_EPT_VPID_CAP MSR 寄存器来设置该字段。如上图,IA32_VMX_EPT_VPID_CAP MSR 寄存器的 bit8 为 1,那么逻辑处理器允许将 EPT 页表内存类型设置为 “Uncacheable(UC)” 类型,即将 EPTP 的 bit 0:2 设置为 0; 同理如果 IA32_VMX_EPT_VPID_CAP MSR 寄存器的 bit14 为 1,那么软件可以将 EPT 页表的内存类型设置为 “Write-back(WB)”, 即将 EPTP 的 bit 0:2 设置为 6.


bit 10

EPT PTE 的 bit 10 的功能与 “mode-based execute control for EPT” VM-execution control 有关,该控制域定义在 VMCS 的 “The Secondary Processor-Based VM-Execution Contrl” 控制域里,如下:

从 “Mode-Based execute control for EPT” 控制域可以看到,当该位置位,EPT 的执行权限基于 Guest OS 的线性地址是被 Guest OS 的内核态访问还是 Guest OS 的用户态访问。回到 EPT PTE 的 bit 10,当 “Mode-Based execute control for EPT” 控制域置位,bit 10 用于指明 Guest OS 的用户空间线性地址是否具有指令读取权限,当 bit 10 置位,那么 Guest OS 用户空间的线性地址对对应的 4 KiB 有指令读取权限, 反之当 bit 10 清零,那么 Guest OS 用户空间的线性地址对对应的 4 KiB 没有指令读取权限。

如果 “Mode-Based execute control for EPT” 控制域清零,那么则忽略这个 bit。


bit (N-1):12

EPT PTE 的 (N-1):12 用于指明 4 KiB 物理页按 4 KiB 对齐的物理基地址。通过该字段可以找到最终的物理页,EPT 的查表过程即将结束。这里的 N 代表 Guest OS 的物理地址宽度,比如 Guest OS 是 32 位,那么 N 就是 “32-12-1” 即 19; 反之 Guest OS 是 64 位,那么 N 就是 “64-12-1” >即 51.

N 的值可以通过往 CPUID 的 EAX 寄存器写入 0x80000008H 获得,当向 CPUID 写入该值之 后,EAX 的返回值的 7:0 位将表明 Guest OS 的物理地址宽度.


bit 7/11/62:52

EPT PTE bit 7/11/62:52 忽略不使用.


EPT 功能开启

Guest OS 的物理地址(GPA) 到 Host 物理地址(HPA) 的转换通过一系列的 EPT 页表。EPT 页表结构有点类似与 IA-32e 模式下的线性地址页表转换。系统要使用 EPT 页表首先要确认当前系统支持 EPT 功能。EPT 功能的启用与 VMCS 的 “The Processor-Based VM-Execution Controls” 和 “The Secondary Processor-Based VM-Execution Controls” 控制域有关。当 “The Processor-Based VM-Execution Controls” 控制域的 31 bit 置位,那么当前系统支持 “The Secondary Processor-Based VM-Execution Controls” 控制域。在 “The Secondary Processor-Based VM-Execution Controls” 控制域的 bit1 中包含了 “Enable EPT”,当该位置位,那么系统支持 EPT, 否则不支持 EPT. 具体请参考:

通过以上两个控制域就可以知道 EPT 功能是否开启,但对于两个控制域内的所有 bit 置位和清零的情况,则需要通过相应的 MSR 寄存器获得. 这里还涉及了 VMCS 的概念,对于每个逻辑处理器而言,VMX 都会使用 “Virtual-machine control data struct(VMCSs)” 去控制一个逻辑处理器的 VM-Entry 和 VM-Exit,以及逻辑处理器在 non-root 模式下的一些操作, 可以简单的理解为 VMCS 为逻辑处理器的上下文。VMCS 存储在内存的一块区域上,这块区域称为 “VMCS region”, 软件可以通过使用一个 64 位的 VMCS 指针引用 VMCS。回到 EPT 话题,EPT 的开启与 “The Processor-Based VM-Execution Controls” 控制域和 “The Secondary Processor-Based VM-Execution Controls” 控制域有关,这两个控制域正好是 VMCS 中的一部分, 其中 “The Processor-Based VM-Execution Control” 控制域的内容与以下控制域有关:

  • IA32_VMX_BASIC(index 480H) 寄存器
  • IA32_VMX_PROCBASED_CTLS MSR(index 482H) 寄存器
  • IA32_VMX_TRUE_PROCBASED_CTLS MSR(index 48EH) 寄存器

上面逻辑可以简单理解为当 IA32_VMX_BASIC 的 bit55 决定了系统是使用 IA32_VMX_PROCBASED_CTLS MSR 寄存器还是 IA32_VMX_TRUE_PROCBASED_CTLS MSR 寄存器作为 “The Primary Processor-Based VM-Execution Controls” 域的内容来源。当选定一个寄存器作为域值来源之后, 寄存器的 31:0 指明允许设置为 0-setting 的位,在设置为 0-setting 的位如果当虚拟机 VM-Entry 时发现为 1,那么 VM-Entry 则失败; 同理 63:32 指明允许设置为 1-setting 的位,在设置为 1-setting 的位如果当虚拟机 VM-Entry 时发现为 0,那么 VM-Entry 则失败. 同理对于 “The Secondary Processor-Based VM-Execution Controls” 控制域,其定义如下:

通过上面的介绍可以知道,”The Secondary Processor-Based VM-Execution Controls” 控制域的内容来自 IA32_VMX_PROCBASED_CTLS2 MSR(index 48BH). 寄存器的 31:0 指明允许设置为 0-setting 的位,在设置为 0-setting 的位如果当虚拟机 VM-Entry 时发现为 1,那么 VM-Entry 则失败; 同理 63:32 指明允许设置为 1-setting 的位,在设置为 1-setting 的位如果当虚拟机 VM-Entry 时发现为 0,那么 VM-Entry 则失败. 分析完 Intel 文档对 EPT 启用的介绍,下面来看看内核中对 EPT 启用的处理逻辑,其逻辑如下图:

从上图可以看出,当 KVM 进行 Intel Architecture 相关的 hardware_setup() 函数时,KVM 首先在函数 setup_vmcs_config() 中对 “The Primary Processor-Based VM-Execution Controls” 和 “The Secondary Processor-Based VM-Execution” 控制域做了检测,检测逻辑如下:

函数通过调用 adjust_vmx_controls() 函数对 MSR_IA32_VMX_PROCBASED_CTLS 寄存器进行能力检测,也就是确认 MSR_IA32_VMX_PROCBASED_CTLS 寄存器某些功能必须开启,以及某些功能可以开启,其中与 EPT 相关的功能是 CPU_BASED_ACTIVATE_SECONDARY_CONTROLS,但在上面的函数处理逻辑中,该功能只是作为可选功能进行检测(由于代码兼容影子页表,因此这里对 CPU_BASED_ACTIVATE_SECONDARY_CONTROLS 的功能不能强制打开,只能确认开启)。在检测完毕之后,函数会将 MSR_IA32_VMX_PROCBASED_CTLS 寄存器支持的功能存储在 “_cpu_based_exec_control” 变量里。如果 CPU_BASED_ACTIVATE_SECONDARY_CONTROL 对应的位置位,那么接下来是对 “MSR_IA32_VMX_PROCBASED_CTLS2” 寄存器的功能进行检测,检测逻辑如下:

在上面的检测逻辑中,开发者重点关注 SECONDARY_EXEC_ENABLE_EPT 功能是否启用,函数将检测的结果存储在 “_cpu_based_2nd_exec_control” 变量里,这里也符合文档中对 EPT 功能的检测。以上结果最终存储到了 vmcs_config 结构里,KVM 就可以通过直接读取里面的信息来确认 EPT 是否开启,如下图的函数:

内核在源码中直接通过调用 cpu_has_vmx_ept() 函数检测当前系统是否支持 EPT 页表功能.


CR0

CR0 寄存器包含了系统控制标志和处理器状态标志。这些控制位控制系统模式。

> CR0.PG

> CR0.PE


CR0.PG

CR0.PG 位用于设置处理器支持分页的功能。如果该位清零,那么系统不支持分页,当不支持分页,所有的线性地址都是物理地址。分页功能与保护模式需要一同存在,保护模式通过 CR0.PE 位进行使能,如果 CR0.PE 没有置位,即保护模式没有打开,如果此时将 CR0.PG 置位,那么会引起 “#GP” 异常。CR0.PG 的改变不会影响 CR0.PE 的改变。

Intel SDM(Software Developer’s Manual) Download


CR0.PE

CR0.PE 位用于控制系统保护模式的使能。当 CR0.PE 置位,并且 CR0.PG 置位,那么系统进行保护模式,保护模式下,线性地址称为虚拟地址,虚拟地址需要通过遍历页表才能查找到指定的物理地址,进而访问内存; 当 CR0.PE 清零,且 CR0.PG 清零,那么系统进入实时模式,在实时模式下,地址总线宽度是 16bits,线性地址就是物理地址,可以直接访问内存。

Intel SDM(Software Developer’s Manual) Download


CR3 (PDBR)

CR3 寄存器称为页目录基址寄存器,寄存器从第 12 bit 之后的空间存储了指向页目录所在物理地址,由于页目录所在的物理地址必须按 4-KiB 对齐,因此页目录所在的物理地址的低 12 位为 0,利用这个特性,CR3 寄存器的低 12 bit 可以用来存储其余信息。PCD 和 PWT 位用于控制是否将分页数据结构存储在处理器内存的缓存中,但这两个位不能控制 TLB 缓存页目录相关信息.

Intel SDM(Software Developer’s Manual) Download


CR4 (PDBR)

CR4 寄存器中包含了一组使能多个体系拓展以及指明操作系统或可执行程序支持特殊能力。

> CR4.PAE

> CR4.PSE


CR4.PAE

CR4.PAE 控制位用于控制系统是否打开物理地址拓展功能。当该位置位,那么系统支持分页对应的物理地址超过 32bit; 反之该位清零,那么系统支持的分页对应的物理地址严格按照 32bit。当系统进入 IA-32e 模式之前,CR4.PAE 控制位必须使能.

Intel SDM(Software Developer’s Manual) Download


CR4.PSE

CR4.PSE 控制位用于控制系统在分页的时候是否支持 4-MiB 页。当该位置位,那么 32-bit 的分页支持 4-MiB 页; 反之该位清零,那么 32-bit 的分页严格按照 4-KiB 页.

Intel SDM(Software Developer’s Manual) Download



BiscuitOS Demo

功能介绍

BiscuitOS 配置

在 BiscuitOS 中使用配置如下:

[*] Package  --->
    [*] Assembly  --->
        [*] X86/i386/X64 Assembly  --->
            [*] MOV  --->

具体实践办法请参考:

通用例程


附录

BiscuitOS Home

BiscuitOS Driver

Linux Kernel

Bootlin: Elixir Cross Referencer

Linux Kernel Driver Data Base

捐赠一下吧 🙂

MMU