目录
初识 PageFault
PageFault 导论
缺页数据结构逻辑
Page Fault Exception
PageFault With ERROR Code
PageTable With PageFault
PageFault Event 统计
Page Fault Handlers Reason
PageFault on Architecture
PageFault on Intl X86
PageFault on ARM/ARM64(未来可期)
PageFault on RISCV(未来可期)
PageFault on Kernel
PageFault on Userspace
匿名内存(Anonymous) With PageFault
匿名映射可读可写内存缺页场景
匿名映射只读(Read-Only)内存缺页场景
匿名映射写保护(Write-Protection)内存缺页场景
匿名映射 COW(Copy-On-Write) 内存缺页场景
匿名映射内存交换(SWAP) 缺页场景
匿名映射内存压缩(ZSWAP) 缺页场景
匿名映射内存 KSM 缺页场景
匿名映射内存 NUMA Balancing 缺页场景
匿名映射内存 MCE 缺页场景
匿名映射之堆(Heap) 缺页场景
匿名映射之栈(Stack) 缺页场景
匿名映射内存 Protection Key 缺页场景
匿名映射之 PmdMapped THP 内存缺页场景
共享内存(Shmem) With PageFault
可读可写共享内存(R/W Shmem) 缺页场景
只读共享内存(RO Shmem) 缺页场景
共享内存 Fork 缺页场景
共享内存换入换出(SWAP) 缺页场景
共享内存内存压缩(ZSWAP) 缺页场景
共享内存发生 MCE(UE) 缺页场景
共享内存 SYSV 缺页场景
共享内存 POSIX 缺页场景
共享内存 UNIX-SOCK 缺页场景
共享内存 MEMFD 缺页场景
共享内存 DEV-SHM 缺页场景
共享内存 LOCAL 缺页场景
共享内存 HugeShmem(THP) 缺页场景
共享内存 Protection Key 缺页场景
File-Mapped With PageFault
MINIXFS 文件系统缺页场景
EXT2 文件系统缺页场景
EXT3 文件系统缺页场景
EXT4 文件系统缺页场景
VFAT 文件系统缺页场景
MSDOS 文件系统缺页场景
FAT 文件系统缺页场景
CRAMFS 文件系统缺页场景
BFS 文件系统缺页场景
JFFS2 文件系统缺页场景
UBIFS 文件系统缺页场景
SQUASHFS 文件系统缺页场景
BTRFS 文件系统缺页场景
REISERFS 文件系统缺页场景
JFS 文件系统缺页场景
XFS 文件系统缺页场景
GFS2 文件系统缺页场景
F2FS 文件系统缺页场景
TMPFS 文件系统缺页场景
Huge-Tmpfs 文件系统缺页场景
XFS DAX 缺页场景
PFNMAP With PageFault
PFNMAP 映射 可读写 RSVDMEM 内存场景
PFNMAP 映射 只读(Read-Only) RSVDMEM 内存场景
PFNMAP 映射 写保护(Write-Protection) RSVDMEM 内存场景
PFNMAP 映射 PudMapped 2MiB RSVDMEM 内存场景
PFNMAP 映射 PudMapped 1Gig RSVDMEM 内存场景
HUGE Page Fault
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 场景
HugeTLB Page With PageFault
HugeTLB Memory: R/W 缺页场景
HugeTLB Memory: WP 缺页场景
HugeTLB Memory: COW 缺页场景
HugeTLB Memory: 不同粒度 HugeTLB 缺页场景
HugeTLB Memory: SYSV 缺页场景
HugeTLB Memory: POSIX 缺页场景
HugeTLB Memory: MEMFD 缺页场景
HugeTLB Memory: MCE(UE) 故障缺页场景
HugeTLB Memory: OOM 大页内存不足缺页场景
HugeTLB Memory: Surplus 超发大页缺页场景
PageFault 机制实践
PageFault 机制使用
PageFault 与工程落地
Tools for PageFault
使用 BiscuitOS 观测缺页流程
Kdump/Crash with PageFault
PageFault 源码分析
PageFault 进阶研究
PageFault Exception Reason
PageFault Handlers Reason
SWAP with PageFault
COW with PageFault
SegmentFault/SIG_BUS on PageFault
Migration with PageFault
MCE(UE) with PageFault
NUMA Balancing with PageFault
KSM with PageFault
Memory Compress/Decompress(ZSWAP) with PageFault
COPY-USER with PageFault
HMM 异构内存缺页(技术攻坚中)
IOMMU 缺页
PMEM DAX With PageFault
虚拟化 EPT TDP PageFault
逆向映射/反向映射(Reverse-Mapping)与缺页
STRUCT address_space 与缺页
Protection Key(PKRU) With PageFault
Userfaultfd(用户空间缺页)
Write Protection with PageFault
MMAP_LOTSAMISS
如何区分 ZERO Page 与 COW 的 Write-Protection
PageFault 导论
在 Linux 内存管理里,开启分页之后,CPU 直接访问的虚拟内存,虚拟内存与物理内存之间建立页表,那么 CPU 才能正常访问虚拟内存. 当虚拟内存没有与物理内存建立页表之前,CPU 就访问这段虚拟内存会引起异常(Exception), 在 Linux 里将这个异常称为缺页异常(Page Fault Exception), Linux 为缺页异常设置了缺页异常处理函数(或缺页中断处理函数). 缺页异常处理函数的主要任务是在合理的请求下,为虚拟内存分配物理内存,然后建立虚拟内存到物理内存的页表,并在缺页异常处理函数返回之后重新执行发生异常的指令, 那么 CPU 可以继续访问虚拟内存.
进程分配内存的方式有三种,三种方式虽然不同,但只有建立了虚拟内存到物理内存的页表之后,CPU 才可以访问,三种分配方式的特点如下:
- PreAlloc(预分配): 即进程分配虚拟内存的同时也分配物理内存,并建立虚拟内存到物理内存之间的页表,因此这类型的内存不会发生缺页,例如 malloc() 函数分配的小粒度内存,或者 mmap() 函数添加了 MAP_POPULATE 标志. 由于其分配特点,存在分配内存时会消耗很多时间,另外分配多少虚拟内存就会分配多少虚拟内存,那么可能会造成内存浪费,一旦内存分配好,那么 CPU 初次访问内存时速度会比缺页的快很多,因此这种方式适合对运行时内存访问速度要求高的场景.
- LazyAlloc(惰性分配): 即进程起初只分配虚拟内存,只有当 CPU 访问虚拟内存时,触发缺页异常之后,缺页中断为其分配物理内存并建立页表,在缺页中断返回之后再次执行发送异常的指令,例如使用 mmap() 函数不带 MAP_POPULATE 标志分配内存,或者 malloc 分配大粒度内存. 由于其特点,存在分配内存时速度很快,并只有用到才会去分配内存,因此很节省内存,但是 CPU 初次访问内存会触发缺页相当耗时,因此这种方式适合内存资源紧缺的场景.
- On-Demand(按需分配): 即进程起初只分配虚拟内存,但进程觉得需要为虚拟内存分配物理内存时,那么会为虚拟内存分配物理内存并建立页表,这样 CPU 访问内存时不会发送缺页异常。例如 GUP(Get User Page) 机制 或者 madvise 机制的 MADV_HUGEPAGE 请求。由于其特点,存在分配内存时速度很快,初次访问时无需缺页就可以访问,访问速度很快,但需要进程介入控制分配.
无论是使用何种分配方式,需要借助缺页机制来分配物理内存和建立页表,只是 LazyAlloc 会触发缺页异常完成物理内存分配和页表建立,而 PreAlloc 和 On-Demand 会主动调用缺页机制提供的接口,以此完成物理内存的分配和页表建立. 因此开发者在学习缺页机制时候一定要记住的一个定理: 什么时候分配物理内存,什么时候建立页表.
Page Fault Exception
页面错误异常(Page Fault Exception) 是当 CPU 尝试访问一个当前不在物理内存中的虚拟页时,由内存管理单元(MMU)触发的异常。此异常可能是由于该页尚未加载到 RAM、访问违规,或者对应的页表条目无效所引起的. 缺页异常发送之后,不同架构会给出发生异常的原因. 例如 Intel X86 架构会提供上图引起缺页异常的原因,其他架构与之类似:
- PRSENT: 由于虚拟内存没有映射物理内存引起的页面故障
- READ: 对虚拟区域写操作引起的页面故障
- WRITE: 对写保护的虚拟区域执行写操作引起的页面故障
- User: 缺页故障发生在用户空间
- Super: 缺页故障发生在内核空间
- Instruct: 缺页故障由执行指令引起的页面故障
缺页异常在给出故障原因的同时,也会给出发生缺页的虚拟地址。例如在 Intel X86 架构下,当发生缺页异常时,CR2 寄存器存储了发生缺页的虚拟地址,然后结合当前进程和虚拟地址,缺页异常处理函数可以找到对应的 VMA.