fault_dirty_shared_page是处理共享文件映射写缺页的最后一步,核心职责是:将页面标记为脏、解锁页面、更新时间戳,并执行脏页速率限制(throttling)。
核心实现
根据最新的 folio 化版本,其实现如下:
static vm_fault_t fault_dirty_shared_page(struct vm_fault *vmf) { struct vm_area_struct *vma = vmf->vma; struct address_space *mapping; struct folio *folio = page_folio(vmf->page); bool dirtied; bool page_mkwrite = vma->vm_ops && vma->vm_ops->page_mkwrite; dirtied = folio_mark_dirty(folio); VM_BUG_ON_FOLIO(folio_test_anon(folio), folio); /* * Take a local copy of the address_space - folio.mapping may be zeroed * by truncate after folio_unlock(). */ mapping = folio_raw_mapping(folio); folio_unlock(folio); if (!page_mkwrite) file_update_time(vma->vm_file); /* * Throttle page dirtying rate down to writeback speed. * * mapping may be NULL here because some device drivers do not * set page.mapping but still dirty their pages * * Drop the mmap_lock before waiting on IO, if we can. */ if ((dirtied || page_mkwrite) && mapping) { struct file *fpin; fpin = maybe_unlock_mmap_for_io(vmf, NULL); balance_dirty_pages_ratelimited(mapping); if (fpin) { fput(fpin); return VM_FAULT_COMPLETED; } } return 0; }关键设计点
1. 标记页面为脏
folio_mark_dirty(folio)是核心操作。注释指出,即使实际的写入操作尚未发生,在缺页处理阶段就标记脏位可以避免一些竞态条件,并可能提高效率。VM_BUG_ON_FOLIO断言该 folio 不是匿名页——这个函数只处理共享文件映射。
2. 在解锁前保存 mapping
注释解释了一个微妙的竞态:folio_unlock(folio)之后,另一个线程可能通过truncate将folio->mapping清零。因此必须在解锁之前通过folio_raw_mapping()取一份address_space的本地副本。address_space本身由vma->vm_file的引用保持存活,不会消失。
3. 更新时间戳
如果文件系统没有实现page_mkwrite回调,说明文件系统不关心写缺页的通知,此时内核直接调用file_update_time()更新文件的修改时间。
4. 脏页速率限制
这是该函数最复杂的部分。balance_dirty_pages_ratelimited(mapping)会让当前进程在脏页过多时睡眠等待,将脏页产生速率限制在回写速度之内。
由于等待可能睡眠,函数通过maybe_unlock_mmap_for_io()临时释放mmap_lock。如果确实释放了锁,函数返回VM_FAULT_COMPLETED而不是0,告知调用者缺页已处理完毕且锁已释放,无需重试。
历史演进
2023 年 folio 化:该函数从使用struct page迁移到struct folio,将四次隐式的compound_head()调用合并为一次,由 ZhangPeng 提交并经 Matthew Wilcox 和 Sidhartha Kumar 审查通过。
VM_FAULT_COMPLETED的引入:2022 年 Peter Xu 发现,对于共享文件映射的首次写缺页,由于fault_dirty_shared_page会释放mmap_lock进行脏页限流,调用者收到VM_FAULT_RETRY后必须重新执行整个缺页流程。他引入了VM_FAULT_COMPLETED返回值,让调用者知道“缺页已完成且锁已释放”,避免了不必要的重试。在 aarch64 测试中,这为顺序脏化 400MB shmem 文件的场景带来了约12% 的性能提升。
2025 年空指针修复:一个补丁修复了vma->vm_file为 NULL 时调用file_update_time导致的空指针解引用,将条件改为if (!page_mkwrite && vma->vm_file)。
调用场景
该函数由do_shared_fault()在页面映射建立完成后调用。完整流程是:__do_fault读取页面 →do_page_mkwrite通知文件系统 →finish_fault建立 PTE →fault_dirty_shared_page标记脏页并限流。