vmf_can_call_fault 是内核为每-VMA 锁(per-VMA lock)缺页处理引入的“守门人”函数,由 Matthew Wilcox 在 2023 年 10 月提交。它的职责是:在只持有 VMA 读锁的情况下,判断当前是否安全调用文件系统的 ->fault 回调;如果不安全,则主动释放 VMA 锁并返回 VM_FAULT_RETRY 要求调用者以 mmap_lock 重试。
核心实现
static inline vm_fault_t vmf_can_call_fault(const struct vm_fault *vmf)
{
struct vm_area_struct *vma = vmf->vma;
if (vma->vm_ops->map_pages || !(vmf->flags & FAULT_FLAG_VMA_LOCK))
return 0;
vma_end_read(vma);
return VM_FAULT_RETRY;
}
两项放行条件
函数返回 0 表示“可以继续”,有两种情况:
1. VMA 实现了 ->map_pages 回调
这是最关键的判定依据。Matthew Wilcox 在提交信息中解释:所有实现了 ->map_pages 的 vm_ops 最终都会调用 filemap_fault(),而 filemap_fault() 已经过审计,不依赖 mmap_lock。因此,只要 VMA 有 ->map_pages,就可以安全地在 VMA 读锁下调用其 ->fault 回调。
2. 当前缺页根本没有使用 VMA 锁
!(vmf->flags & FAULT_FLAG_VMA_LOCK) 意味着本次缺页是在传统的 mmap_lock 保护下进行的,自然不需要额外检查。
拒绝路径:释放 VMA 锁并重试
如果 VMA 没有 ->map_pages,且当前缺页持有 FAULT_FLAG_VMA_LOCK,函数会:
调用 vma_end_read(vma) 释放 VMA 读锁。
返回 VM_FAULT_RETRY。
调用者收到 VM_FAULT_RETRY 后,会放弃当前缺页处理,重新以 mmap_lock 获取路径开始。这是因为那些没有 ->map_pages 的 ->fault 实现可能依赖 mmap_lock 来保证正确性,不能冒险在仅持有 VMA 锁的情况下调用它们。
调用场景与引入背景
该函数在以下路径中被调用,用于替换原先硬编码的 FAULT_FLAG_VMA_LOCK 检查:
-
do_read_fault:读缺页路径,在调用 __do_fault 之前检查。
-
do_cow_fault:COW 写缺页路径。
-
do_shared_fault:共享映射写缺页路径。
-
wp_pfn_shared 和 wp_page_shared:写时复制的共享映射路径,在释放 PTE 锁后调用。
在 vmf_can_call_fault 引入之前,这些位置都重复着相同的模式:
if (vmf->flags & FAULT_FLAG_VMA_LOCK) {
vma_end_read(vmf->vma);
return VM_FAULT_RETRY;
}
Matthew Wilcox 将其提取为独立函数,既消除了代码重复,又将“哪些 vm_ops 可以安全地在 VMA 锁下调用”这一知识集中到一处,便于未来扩展。注释中提到的“We could add a bitflag somewhere”暗示,未来可能会用显式标志替代对 ->map_pages 存在的依赖,但当前这一启发式规则已经足够。
设计意义
vmf_can_call_fault 是 per-VMA 锁缺页优化 的安全阀。它让内核能够在大多数文件映射缺页场景下避免获取昂贵的 mmap_lock,只在遇到未经审计的 ->fault 实现时才回退到全局锁。这一机制显著降低了高并发文件映射缺页场景下的锁争用。
网硕互联帮助中心



评论前必须登录!
注册