当程序调用mmap
分配一个匿名页(也称为零需求页)时,对应的页表项 (PTE) 的地址字段中会出现什么?我假设内核不会在物理内存中创建一个零初始化页面(并将该物理页面的页码输入到 PTE 中),直到请求进程实际接触到该页面 - 因此术语demand-zero。由于它不是磁盘地址,也不是 0(用于未分配的页面),所以那里会出现什么值?作为一个不同但相关的问题,内核如何“知道”该页面将作为需求零页面处理,即故障处理程序应该找到一个物理页面并将其初始化为 0 而不是从磁盘?
1 回答
我假设内核不会在物理内存中创建零初始化页面
事实上,通常情况就是这样。除非特殊情况,例如 ifMAP_POPULATE
指定显式请求初始化页面(也称为“pre-fauting”)。
对应页表条目 (PTE) 的地址字段中出现了什么?
在mmap
您甚至没有为页面分配 PTE 之后(或者通常,您在任何页表级别都没有任何条目)。对于CPU而言,页面甚至不存在。如果您要遍历页表,您只会到达一个点(在任意级别),相应的条目被标记为“不存在”。
由于它不是磁盘地址,也不是 0(用于未分配的页面),所以那里会出现什么值?
对于 CPU 而言,页面是未分配的。在第一个页面错误时,可能会发生两件事:
- 对于读取页面错误,PTE 被更新为指向零页面:这是一个始终完全归零的特殊页面,并且由系统中任何匿名(请求零)页面的 PTE 指向尚未修改。
- 对于写页面错误,将分配一个实际的物理页面,并更新相应的 PTE 以指向其物理地址。
直接从文档中引用:
匿名内存或匿名映射表示不受文件系统支持的内存。这种映射是为程序的堆栈和堆隐式创建的,或者是通过显式调用 mmap(2) 系统调用来创建的。通常,匿名映射只定义允许程序访问的虚拟内存区域。读取访问将导致创建一个页表条目,该条目引用一个用零填充的特殊物理页。当程序执行写入时,将分配一个常规物理页面来保存写入的数据。该页面将被标记为脏页面,如果内核决定重新使用它,脏页面将被换出。
内核如何“知道”该页面将作为零需求页面处理,即故障处理程序应该找到一个物理页面并将其初始化为 0 而不是从磁盘复制页面?
当页面错误发生时,内核页面错误处理程序(依赖于架构)确定页面属于哪个 VMA,并检索相应的struct vm_area_struct
(由内核本身或mmap
系统调用较早创建)。然后将该结构连同所需的故障信息 ( )传递给与架构无关的代码 ( )。do_fault()
struct vm_fault
然后vm_area_struct
包含处理故障所需的所有剩余信息(例如,在文件支持的映射情况下的->vm_file
字段!= NULL
)。该字段->vm_ops
指向struct vm_operations_struct
定义了一组函数指针以在不同场合调用。特别是匿名 VMA 具有->vm_ops == NULL
.
对于其他类型的页面,->fault()
是处理页面错误时使用的函数。该函数知道要检查什么以及如何实际处理故障。
B&O 也描述了 VMA,但没有解释内核如何使用 VMA 来区分未分配页面和要创建和零初始化的已分配页面。
很简单,只需检查一下vma->vm_ops == NULL
,在这种情况下,您就知道该页面是零需求匿名页面。然后在页面错误上根据需要采取行动(读取错误 -> 更新 PTE 以指向全局零页面,写入错误 -> 分配页面并更新 PTE)。