目录
Huge-PageFault 机制
Huge-PageFault 场景
Huge-PageFault: 可读可写透明大页场景
Huge-PageFault: 只读透明大页场景
Huge-PageFault: 写保护透明大页场景
Huge-PageFault: COW 透明大页场景
Huge-PageFault: NUMA Balancing 透明大页场景
Huge-PageFault: EXT4 DAX 文件透明大页场景
Huge-PageFault: XFS DAX 文件透明大页场景
Huge-PageFault: PUD HUGE FAULT 场景
Huge-PageFault 机制
在 Linux 里,当进程使用 LazyAlloc 方式分配虚拟内存时,进程一旦访问虚拟内存时,MMU 检查到物理内存不存在而触发缺页异常,缺页异常处理函数为虚拟内存分配物理内存并建立页表,那么进程可以正常访问虚拟内存。在这个过程中,如果虚拟内存和物理内存粒度都是 4KiB,那么这里称为标准的 PageFault 场景. 如果虚拟内存和物理内存的粒度都是 2MiB,并且都按 2MiB 粒度对齐,那么这里称为 Huge-PageFault 场景. 在某些情况下 Huge-PageFault 也支持 1Gig 粒度的缺页场景.
目前支持 Huge-PageFault 的内存类型包括: 匿名透明大页、采用 DAX 的文件透明大页、RSVD PMDMAPPED 和 RSVD PUDMAPPED. 这些内存类型在发生缺页时都采用 Huge-PageFault 流程进行处理,与普通 4KiB 粒度内存缺页不相同.
Huge-PageFault 虚拟内存发生缺页场景的场景是: 当 CPU 访问了未建立页表映射内存,MMU 检查到物理内存不存在即触发缺页异常. 但在 Linux 里还有很多场景会导致 Huge-PageFault 缺页,总结包括如下场景:
- 可读可写匿名透明大页场景: 当进程分配一个匿名透明大页虚拟内存,当进程访问这段虚拟内存时,MMU 检查到物理内存不存在直接触发缺页异常,在缺页异常处理函数里检查到内存为匿名透明大页内存,并且此时发现 PMD Entry 为空,另外虚拟内存也符合 Huge-PageFault 的条件,于是分配物理大页并建立 PMD 页表映射,接下来进程可以对虚拟内存进行读写操作.
- 只读匿名透明大页场景: 当进程分配一个只读匿名透明大页虚拟内存,当进程对这段虚拟内存首次发起读操作,MMU 检查到物理内存不存在直接触发缺页异常,在缺页异常处理函数里检查到为匿名透明大页,并且此时发现 PMD Entry 为空,另外虚拟内存也符合 Huge-PageFault 的条件,另外如果系统支持 HUGE ZERO PAGE,那么缺页异常处理函数将虚拟内存映射到 HUGE ZERO PAGE,否则就映射到普通大页上,最后建立写保护的页表。进程接下来可以读虚拟内存进行读操作,一旦发生写操作将再次触发缺页并直接 SegmentFault.
- 写保护的匿名透明大页场景: 当进程一块写保护的匿名透明大页虚拟内存,当进程对这段虚拟内存进行读操作时,不会触发缺页可以正常读取. 一旦进程对虚拟内存发起写操作时,MMU 发现权限不对而触发缺页,缺页流程里检查到是匿名透明大页,那么会将匿名透明大页拆分(Split)成小页,然后将这些小页页表设置为写保护,作为这些之后缺页异常处理函数将这次缺页视为对 4KiB 粒度写保护写操作缺页来处理,处理完之后会发现只有缺页的 4KiB 小页变成可读可写匿名页,原先匿名大页内存的其他小页变成写保护匿名页. 接下来进程可以对 4KiB 虚拟内存进行读写操作.
- COW 的匿名透明大页场景: 当进程分配一块匿名透明大页虚拟内存之后,发生了 FORK 操作,匿名透明大页变成写保护。当父子进程对这段虚拟内存进行读操作时,不会触发缺页可以正常读取. 一旦父子其一进程对虚拟内存发起写操作时,MMU 发现权限不对而触发缺页,缺页流程里检查到是匿名透明大页,那么会将匿名透明大页拆分(Split)成小页,然后将这些小页页表设置为写保护,作为这些之后缺页异常处理函数将这次缺页视为对 4KiB 粒度写保护写操作缺页来处理,处理完之后会发现只有缺页的 4KiB 小页变成可读可写匿名页,原先匿名大页内存的其他小页变成写保护匿名页. 接下来进程可以对 4KiB 虚拟内存进行读写操作. 那么当另外一个进程对该内存写操作时,也会发生缺页,缺页的过程和前一个进程一致.
- NUMA Balancing 匿名透明大页场景: 当在支持多 NUMA NODE 的系统里,进程跨 NUMA 分配了一个匿名透明大页. 系统支持 NUMA Balancing 机制,并定期将跨 NUMA 的内存迁移会本地 NUMA NODE. 在这个场景下,进程开始可以正常访问匿名透明大页,但随着 NUMA Balancing 的运行,发现匿名透明大页跨 NUMA,于是修改其页表为 PROT_NONE,那么当进程再次访问匿名透明大页之后,MMU 检查物理内存不存在并触发缺页异常,在缺页异常处理函数里,匿名透明大页的页表是 PROT_NONE 的,于是从页表中获得跨 NUMA 的物理地址,于是将匿名透明大页迁移到 LOCAL NUMA NODE. 待缺页异常处理函数处理完毕之后,进程可以正常访问匿名透明大页内存.
- EXT4/XFS HUGE-DAX 文件透明大页场景: 在支持 DAX 的 EXT4/XFS 文件系统里,进程可以将虚拟内存直接映射到 PMEM 上,并且可以支持 HUGE-DAX 映射,也就是将 2MiB 的虚拟内存映射到 2MiB 的 PMEM 区域上。进程将文件映射到进程地址空间,并对该段虚拟内存进行访问,MMU 发现物理内存不存在并触发缺页,缺页异常处理发现文件对应的文件系统提供了 huge_fault 接口,并调用该接口将虚拟内存映射到 PMEM 上. 待缺页中断访问之后,进程可以正常访问这段内存.
- PMDMAPPED 1G 内存场景: 进程分配 1G 虚拟内存,准备映射到系统预留物理内存上,当进程访问这段虚拟内存时,MMU 发现物理内存不存在并触发缺页异常,缺页异常处理函数发现其属于文件映射,并且支持 huge_fault 接口,另外虚拟地址也是按 1G 对齐,那么调用 huge_fault 接口将 1G 的虚拟内存直接映射到 1G RSVD 物理内存上,待缺页异常处理函数返回之后进程可以正常访问虚拟内存.