rmap 怎么找回 VMA:两把钥匙
folio
mapping:去哪棵树
index:从哪开始
mapping → anon_vma
匿名页去 anon_vma 树
mapping → address_space
文件页去 page cache 树
index = 对象内起始偏移
文件页:文件页偏移
匿名页:虚拟页号
(首次 fault 时的地址页号)
mapping 决定去哪棵树,index 决定从哪个偏移开始
两者必须能换算到同一套地址,rmap 才不会找错
纯匿名:index 左移 PAGE_SHIFT 就是虚拟地址
匿名 VMA 的 vm_pgoff 初始化为 vm_start >> PAGE_SHIFT
四种「匿名」内存,先分清
纯匿名
匿名 VMA + 匿名 folio
swap 支撑,回收按匿名对待
shmem
文件 VMA + 文件 folio
挂在 page cache,但由 swap 支撑
MAP_PRIVATE 的 /dev/zero
vm_file 在,vm_ops 被清空
恰好满足 vma_is_anonymous()
其他 MAP_PRIVATE 文件映射
文件 VMA + 文件 folio
CoW 之后产生的 folio 是匿名
本系列解决的是最后一类
它的 folio 已经是匿名,索引却还是文件的
问题:CoW 出的匿名 folio 有两套坐标
纯匿名 folio
index = 匿名页偏移
即首次 fault 的虚拟页号
CoW 出的匿名 folio
index 仍按文件页偏移写入
mapping 已指向 anon_vma
同一个 index 字段,两套坐标系:
文件页偏移 ≠ VMA 的虚拟地址
纯匿名与它,索引语义对不上
scalable CoW 的快路径,正好踩在这两套坐标上
它按 index << PAGE_SHIFT 换算地址
后果:scalable CoW 快路径永远 miss
folio->index
文件页偏移
index << PAGE_SHIFT
还原成地址
maple tree
按地址找 VMA
落到别的 VMA
快路径 miss
退回 remap 追踪
这类 folio 全挤过来,争用严重
换算出错不是偶发,是坐标定义决定的
文件页偏移本来就不是虚拟地址
方案:给 VMA 记一个匿名页偏移
vm_area_struct
vm_pgoff:文件页偏移
anonymous pgoff:新增
CoW folio 按它建 rmap 索引
64 位下打包
拆成高低两半
塞进现有 cacheline
不占额外空间
取值规则一:初始值
anon pgoff = vm_start >> PAGE_SHIFT
与纯匿名 VMA 的 vm_pgoff 一致
规则二:remap 移动 VMA
未 fault
anon_vma 为空 → 跟着新地址更新
已 fault
anon_vma 非空 → 保持首次 fault 的值
把读写索引的入口全部换坐标
__folio_set_anon()
写索引:改用 linear_anon_page_index()
给 CoW folio 写匿名页偏移
vma_address()
读索引:按用途拆成两个入口
vma_filebacked_address() / vma_anon_address()
page_vma_mapped_walk
增加 pgoff_is_anon 标记
vma_address_end()
匿名 pgoff 按 VMA 的匿名页偏移计算
写入端与读取端必须同时换坐标
否则会一边写新偏移、一边按旧文件偏移查
预期收益:快路径命中,边角可控
CoW folio 与纯匿名统一坐标
index << PAGE_SHIFT
maple tree
VMA 命中
scalable CoW 快路径对这类 folio 也能命中
未来 remap 可 unshare 深层 fork 的匿名 folio,少走 remap 追踪
merge 行为只在边角变化
纯匿名:两个偏移恒等 → 合并不变
共享文件映射:仍只按文件偏移 → 合并不变
MAP_PRIVATE 文件映射:两个偏移都对齐才能合并
要先 CoW、再遇到文件相同且偏移兼容的相邻 VMA,才会合并失败;极偏边角
坐标统一之后,快路径才谈得上 scalable
预期收益;作者未提供性能数据
空格
播放/暂停
←
→
逐动作
R
重播
⚠
1x