我的问题是它如何检查 H/W PTE 上页面的脏、已访问文件位?
TL;DR - 通过在初始访问时出现页面错误来模拟它们。
答案在pgtable-2-level.h,
“脏”位仅通过授予硬件写入权限来模拟
如果页面在 Linux PTE 中被标记为“可写”和“脏”。这
意味着对干净页面的写入将导致权限错误,并且
Linux MM 层将通过 handle_pte_fault() 将页面标记为脏。
为了让硬件注意到权限变化,TLB 条目必须
被刷新,而 ptep_set_access_flags() 会为我们做这件事。
以 dirty 为例,页面的初始 MMU 映射被标记为只读。当一个进程写入它时,会产生一个页面错误。这是引用的handle_pte_fault,主要代码在fault.c as do_page_fault 中,并将调用最终以handle_pte_fault 结尾的通用handle_mm_fault。可以看代码,
if (flags & FAULT_FLAG_WRITE) {
if (!pte_write(entry))
return do_wp_page(mm, vma, address,
pte, pmd, ptl, entry);
entry = pte_mkdirty(entry); /** Here is the dirty emulation. **/
}
所以Linux通用代码会检查页面的权限,认为它是可写的,并调用pte_mkdirty将页面标记为脏;整个过程通过故障处理程序启动或模拟。在 Linux PTE 中将页面标记为 dirty 后,ARM PTE 被标记为可写,因此后续写入不会导致错误。
accessed 是相同的,只有读取和写入最初都会出错。 file 位也是完全未映射的,当发生故障时,会咨询 Linux PTE 以查看它是由文件支持还是完全未映射 页面错误。
在用新的权限更新硬件表并记账后,用户态程序在故障指令处重新启动,除了处理故障的时间间隔外,它不会注意到差异。
ARM Linux 使用 4k 页,ARM 2 级页表大小为 1k(256 个条目 * 4 字节)。从 pgtable-2-level.h cmets,
因此,我们稍微调整了实现——我们告诉 Linux 我们在第一级有 2048 个条目,每个条目是 8 个字节(iow,两个指向第二级的硬件指针。)第二级包含两个硬件 PTE 表连续排列,前面是包含 Linux 所需状态信息的 Linux 版本。因此,我们最终在“PTE”级别中有 512 个条目。
为了使用完整的 4K 页面,PTE 条目的结构类似于,
- Linux PTE [n]
- Linux PTE [n+1]
- ARM PTE [n]
- ARM PTE [n+1]
一个完整的 4k 页面的四个 1k 项目。这些页面集合必须按进程进行管理,以便为每个进程提供唯一的内存视图,并且共享一些信息以节省实际 RAM。函数cpu_set_pte_ext 用于更改物理ARM 条目。由于每个 ARM CPU 版本使用稍微不同的表结构和功能,processor function table 中有一个条目指向一个汇编程序例程。例如,cpu_v7_set_pte_ext 是 ARMv7 或典型的原始 Cortex CPU 实现。该例程负责检查 Linux 标志并相应地更新硬件位。可以看出,r3 在该例程结束时被写入pte+2048(从 Linux PTE 到硬件 PTE 的偏移量)。 proc-marcos.S 中的汇编器宏 armv3_set_pte_ext 被许多较旧的 CPU 变体使用。
见:Tim's notes on ARM MM
Page table entry (PTE) descriptor in Linux kernel for ARM