页表项里除了 PFN,还记着一批状态位
dirty bit:页面被写入,硬件自动把它置上
它告诉内核「这页的数据被改过了」
内核许多决策都先看这一位
回收内存
判断这页数据要不要保留
文件页
判断这页要不要回写磁盘
x86 的页表分多级
PTE 一级管 4 KiB 普通页,PMD 一级可直接映射 2 MiB 的 THP
dirty bit 在 PMD 项里同样存在
只改权限、不改映射的操作,统一走这一族函数
mprotect()
NUMA hinting
userfaultfd 写保护
pte_modify() / pmd_modify() / pud_modify()
做法分两步
第一步:旧值与掩码相与
PFN、accessed、dirty 原样留下
第二步:并入 newprot
换上新权限位
PTE 一级用 _PAGE_CHG_MASK,PMD 与 PUD 一级用 _HPAGE_CHG_MASK
两张掩码都经 _PAGE_DIRTY_BITS 涵盖 _PAGE_DIRTY 与 _PAGE_SAVED_DIRTY
掩码为什么必须带上 dirty,要从 shadow stack 说起
shadow stack 把 Write=0、Dirty=1 的页项编码为 shadow stack 内存
普通内存一旦只读,就不许再用硬件 Dirty 表达脏状态
可写,Dirty=1
脏状态记在硬件位
只读,SavedDirty=1
脏状态挪进软件位
写保护时 Dirty → SavedDirty,恢复可写时再转回来
转移由 pmd_mksaveddirty() 与 pmd_clear_saveddirty() 完成
只在旧值可写时才做 Dirty → SavedDirty,避免动到真正的 shadow stack 页项
前提:掩码得先把脏状态留下来
否则后面无脏可转
pte_modify()
掩码始终涵盖 dirty
保持对称
pud_modify()
掩码始终涵盖 dirty
保持对称
pmd_modify()
掩码剔除 _PAGE_DIRTY
对称性被打破
pmd_modify() 里掉了队的那一行
val &= (_HPAGE_CHG_MASK & ~_PAGE_DIRTY);
~_PAGE_DIRTY 就是丢脏的根
PMD 一级的脏状态从此失去保障
shadow stack 支持引入 SavedDirty 转移时只改了 pmd_modify() 这一处
pte_modify() 与 pud_modify() 始终涵盖 dirty bit,三者的对称性被打破
memset 写入数据,硬件置上 dirty bit
MADV_FREE 清脏,folio 标记 lazyfree
再次 memset,硬件重新置上 dirty bit
mprotect 改权限,dirty bit 被丢弃
回收误判 folio 干净,__discard_anon_folio_pmd_locked() 释放它
第四步丢了脏:pmd_modify() 把刚置上的 dirty 剔掉
回收路径还以为这页从没被改过,再读只是缺页补上的全零页
NUMA hinting 连 mprotect 都不需要,单独就能踩中
do_huge_pmd_numa_page() 恢复 PMD 时同样经过 pmd_modify()
靠反复调整页表权限工作的机制,正好反复丢脏
PMD 映射的 file THP 也走同一条路
丢脏之后,重写的数据无法被回写
内核以为这页一直干净,回写永远等不到
回收路径
lazyfree folio 被当成干净页释放
回写路径
file THP 重写的数据留在内存里
两条链路,都从掩码少留下一位开始
改动只有一行
-val &= (_HPAGE_CHG_MASK & ~_PAGE_DIRTY);
+val &= _HPAGE_CHG_MASK;
掩码重新把脏状态留下来
旧值可写时,pmd_mksaveddirty() 按新权限决定是否搬进 SavedDirty
旧值只读时,pmd_clear_saveddirty() 决定是否搬回来
旧值与掩码相与
并入新权限
可写存、只读取回
写回 PMD
三个 modify 函数重新对齐
shadow stack 的编码规则不被破坏,脏状态也不再凭空消失
MADV_FREE 后重写的 THP
MADV_FREE 只清脏、不写保护
之后的写入由硬件重新置脏
再改权限,脏位仍在,回收不丢数据
PMD 映射的 file THP
权限变更后脏状态保留
回写路径看到的是真实脏状态
重写的数据正常回写
一行改动,把 _PAGE_DIRTY 加回掩码
三个 modify 函数恢复对称,脏状态不再凭空消失
回收路径与回写路径看到的都是真实的脏状态
作者未提供性能数据,以上为代码逻辑推断的预期收益
空格
播放/暂停
←
→
逐动作
R
重播
⚠
1x