VMA(Virtual Memory Area)
每个进程都有自己的虚拟地址空间(Userspace), 并且被换分成不同的区域,Linux 内核将属性相同的虚拟内存区域统一使用 struct vm_area_struct 数据结构进行描述,简称这样的虚拟内存区域为 VMA。VMA 的属性包括: 读、写、执行、私有、共享、匿名、文件、大页 等,只要在同一个 VMA 区域内存的虚拟内存,那么他们的属性一定相同。VMA 的属性构成了分页机制的权限管理,例如有的 VMA 是只读的,那么不能对该区域的内存进行写操作,否则会引发页表故障; 另外 VMA 区域内存的内存行为是一致的,例如都采用同样内存分配逻辑和缺页逻辑。说这么多,记下来从数据结构角度进行分析:
struct vm_area_struct 数据结构绝对是一个非常复杂的数据结构体,这里只对缺页机制相关的成员进行讲解, 其余的成员或多或少与缺页机制有关,但不是起到核心作用的成员:
- vm_start/vm_end: 两个成员限定了 VMA 区域的访问, 其值是虚拟地址
- vm_mm: 指向了进程的地址空间数据结构 struct mm_struct
- vm_page_prot: 记录 VMA 区域虚拟内存构建页表时的页表属性内容
- vm_flags: 记录了 VMA 区域的标志
- vm_ops: 当需要对虚拟内存操作时,提供指定的处理函数
- vm_offset: 相对与 VMA 起始地址的偏移值
- vm_file: 虚拟区域映射的文件
VMA 由 mmap() 系统调用分配并创建,其会根据进程对虚拟内存的请求,分配指定大小的 VMA,并根据虚拟内存映射方式提供 VMA 的操作函数 vm_ops, 并为 VMA 分配不同的属性并由 vm_flags 成员进行维护,最后虚拟内存创建页表的内容维护在 vm_page_prot 成员里。
当进程采用 LazyAlloc 方式分配虚拟内存之后,进程第一次访问虚拟内存将会引起缺页异常,在缺页中断处理函数中会根据虚拟地址找到对应的 VMA,那么缺页处理函数在处理过程中,会根据 VMA vm_ops 提供的回调函数加入到缺页处理过程中,协助为 VMA 分配物理内存并建立相应的页表,那么这些与缺页相关的回调函数作用是:
- fault: 当 VMA 缺页时该函数会被调用,可以介入缺页中断处理,例如分配物理内存、构建页表、修改页表等, 但只涉及小页的缺页
- hugefault: 当 VMA 缺页时符合一定的条件之后,可以参与 VMA 与大页内存构建页表,例如 EXT4/XFS 提供的 DAX PMDMAPPED 机制
- map_pages: 当 VMA 映射文件时,文件系统为了提升从磁盘读数据的效率,采用 Fault Around 机制同时读取多个页到内存,并干预页表的建立
- page_mkwrite: 当 VMA 映射区域被写入引发缺页,那么在将页表的 Dirty 标志位置位同时可以执行私有化操作
- pfn_mkwrite: 当 VMA 采用 PFNMAP 的区域被写入引发缺页,那么在将页表的 Dirty 标志位同时可以执行私有化操作
每个 VMA 都有不同的属性,不同的属性导致缺页流程产生巨大的差异,所有属性都存储在 struct vm_area_struct vm_flags 成员里,目前 VMA 支持的属性标志如下:
- VM_NONE: 表示虚拟内存区域没有分配任何实际的物理页面,也没有进行内存映射操作.
- VM_READ: 表示虚拟区域是可读的
- VM_WRITE: 表示虚拟区域是可写的
- VM_EXEC: 表示虚拟区域是可执行的
- VM_SHARED: 表示虚拟区域映射的物理内存与其他进程共享
- VM_MAYREAD: 表示虚拟区域允许读操作
- VM_MAYWRITE: 表示虚拟区域允许写操作
- VM_MAYEXEC: 表示虚拟区域允许执行
- VM_MAYSHARE: 表示虚拟区域映射物理内存运行被其他进程共享
- VM_GROWSDOWN: 虚拟区域向下生长
- VM_UFFD_MISSING: 标志表示虚拟内存区域内的页是通过用户态文件描述符(User-Mode File Descriptor,UFD)来管理的,但在当前状态下有缺失的页.
- VM_PFNMAP: 虚拟区域直接映射到物理页帧上,即物理内存没有对应的 struct page
- VM_UFFD_WP: 标志表示一个虚拟内存区域是通过用户态文件描述符(Userfaultfd)机制来管理的,并且在此区域内的页面发生写入操作时会触发用户态文件描述符的通知
- VM_LOCKED: 标志表示虚拟内存对应的物理内存被锁主不能被交换到 SWAP Space
- VM_IO: 标志用于表示一个虚拟内存区域是用于 I/O 操作,例如通过内存映射访问设备寄存器、DMA 缓冲区等
- VM_SEQ_READ: 标志用于表示一个虚拟内存区域是适合用于顺序读取操作
- VM_RAND_READ: 标志用于表示一个虚拟内存区域是适合用于随机读取操作
- VM_DONTCOPY: 标志可以指示内核在执行 fork() 时不复制相应的虚拟内存区域到子进程中,从而避免不必要的复制操作
- VM_DONTEXPAND: 标志在未来尝试对该映射区域进行扩展时,内核会阻止这种扩展操作,即不会分配更多的页面来扩展该区域
- VM_LOCKONFAULT: 标志可以指示内核在页面缺失时将相应的页面锁定到内存中,从而确保这些页面不会被换出
- VM_ACCOUNT: 标志用于标识一个虚拟内存区域需要被记账,即需要跟踪统计信息
- VM_NORESERVE: 标志可以指示内核在进行内存映射时不分配交换空间
- VM_HUGETLB: 虚拟区域可以使用 HugeTLB 大页
- VM_WIPEONFORK: 标志表示一个虚拟内存区域在进程执行 fork() 系统调用创建子进程时,该区域的数据会被清零
- VM_SOFTDIRTY: 标志内核可以知道哪些页面已经被用户空间程序修改过,而这些修改是通过软件方式进行的
- VM_MIXEDMAP: 标志表示一个虚拟内存区域是一个混合映射区域,即这个区域可以映射不同类型的物理页面,如设备内存、普通内存等
- VM_HUGEPAGE: 标志表示一个虚拟内存区域可以使用大页面
- VM_NOHUGEPAGE: 标志表示一个虚拟内存区域不可以使用大页面
- VM_MERGEABLE: 标志表示一个虚拟内存区域可以被合并