ceph: convert writeback path to folios(v2 完整系列进展更新)
💡 一句话总结
在内核把内存管理 API 从单页 struct page 统一到复合页 struct folio 的大方向下,ceph 是少数仍以 page 操作写回路径的重量级文件系统;本系列(v2,10 补丁)把 ceph 的快照写回等待、数据长度计算、提交写与完成清理等整条写回路径改为直接操作 folio,并删除因此失去全部调用者的 wait_on_page_writeback()、thp_size()、thp_order() 三个 mm 兼容 API——纯重构、作者明确声明无功能变化,未提供性能基准,从减少 page↔folio 往返与缩小 mm API 表面积角度的收益为逻辑推断(解读(AI 分析))。
📋 补丁基本信息
| 项目 | 内容 |
|---|---|
| 补丁类型 | 重构(API 迁移 / 死代码清理,语义不变) |
| 状态 | In Review(v2,尚无 v2 公开回复) |
| 当前版本 | v2(10 补丁)· cover letter |
| 版本演进 |
v1(08-02,4 补丁)→ v2(08-04,10 补丁) v1→v2 由 review 推动扩容(Matthew Wilcox / Zi Yan,详见「方案演进」) |
| 作者机构 | Tal Zussman <tz2294@columbia.edu>(Columbia University) |
| 提交日期 | 2026-08-04 18:52 UTC(08-05 02:52 +0800) |
| 改动范围 | 5 文件(fs/ceph/addr.c、fs/ceph/crypto.h、include/linux/mm.h、include/linux/pagemap.h、mm/folio-compat.c),+65/−95 |
| 系列规模 | 10 补丁(8 个 ceph 侧 folio 化 + 2 个 mm 侧 API 移除) |
| 核心函数 | ceph_submit_write() / ceph_folio_snap_context() / get_writepages_data_length() / folio_size() / folio_order() |
| 原始链接 | [PATCH v2 00/10] cover letter |
📊 速览卡片
🎯 解决什么问题
struct page 为中心的 mm API 逐步迁移到以复合页 struct folio 为中心(一个 folio 可代表一页或多页)。迁移期的旧 page 版 API 被收在 mm/folio-compat.c 等处,以兼容包装形式继续存在,供尚未迁移的调用者使用。每清空一个兼容包装的全部调用者,就可以删除这个包装——这是 folio 化工程"增量迁移、逐批拆除"的标准节奏。本系列就是这条大方向上的一个里程碑:把 ceph(重量级分布式文件系统)最后一块还以 page 操作的写回路径整条 folio 化,从而让 3 个失去全部调用者的兼容 API(wait_on_page_writeback()、thp_size()、thp_order())可以被删除。
ceph_submit_write()(本系列最核心的 ceph 转换) → 7/10 清 write_folio_nounlock 残留 → 8/10 迁移 writepages_finish 清理循环 → 9/10 删旧 page 版 fscrypt 助手 → 10/10 改名 check 函数。wait_on_page_writeback()(08-03 日报已单独分析过其 v1 形态);6/10 删 thp_size()/thp_order()(新增)。wait_on_page_writeback()"这一条,当时 get_writepages_data_length() 还在用 page 版 page_snap_context() 与 thp_size()。review 中 Matthew Wilcox 指出(am_3oyOJAoFeBFTC@casper.infradead.org):与其在 page 与 folio 之间反复转换,"I'd feel better about it if it started with struct folio *folio = page_folio(page)… If we're adding these, I might as well do a few more conversions"。作者顺势把系列扩成完整的 ceph 写回路径 folio 化(v2,10 补丁),并把 thp_size()/thp_order() 也一并删掉——cover letter 原话:"Convert ceph's writeback path to work on folios. With these conversions, remove three mm compatibility APIs whose last callers are eliminated: wait_on_page_writeback(), thp_size(), and thp_order()."(数据来源:v2 cover letter,Message-ID 20260804-…-v2-0-81f0ab065284@columbia.edu)
folio_batch 收集脏页(迭代器天然产出 folio),但后续处理函数却仍以 page 为参数。于是每个 folio 都要做 &folio->page 转回单页、喂给 page 版 API,page 版内部再用 page_folio() 还原成 folio——一次操作里出现两次无谓的 folio↔page 往返。此外 thp_size(page)/thp_order(page) 这类 THP 尺寸助手参数仍是 struct page,必须拿到 head page 才能调用。本系列把函数参数与局部变量统一改成 struct folio,直接调用 folio 版 API,删掉中间的 page 转换层。
ceph_writepages_start() 在每次同步写回(WB_SYNC_ALL)或快照切换时高频触发:需要遍历脏页 folio_batch、按快照上下文(snapc,存在 folio->private 中)分组、逐 folio 提交 OSD 写请求、等待写回完成。在大文件顺序写 / 快照频繁的负载下,这条路径的循环次数=脏页数,任何 per-folio 的 page↔folio 转换开销都会被放大。ceph_submit_write() 里的两个循环(fbatch.folios[] 与 locked_pages[])正是转换最密集处。
&folio->page + page_folio() 的转换,使脏页数越多收益越明显(解读(AI 分析))🧩 核心机制
整个系列的目标是让 ceph 写回路径从"folio 收集 + page 处理"改成"全 folio 操作",并在 mm 侧删除三个因此失去调用者的 page 版 API。作者在 cover letter 中明确保留了两处 page 边界(见下方机制图):libceph 的 OSD 数据面与 fscrypt 的 bounce page,这两处由底层 API 决定仍以 page 数组为接口,folio 只在边界处解包。
| 补丁 | 改动 | 为什么是机制关键 |
|---|---|---|
1/10 ceph_folio_snap_context() | 新增助手直读 folio->private | 快照上下文存在 folio 的 private 字段;按 Matthew/Zi 意见去掉 folio_test_private() 检查,与 Zi Yan 的 PG_private 移除系列兼容 |
4/10 get_writepages_data_length() | 参数 page→folio,引入 ceph_fscrypt_pagecache_folio()/ceph_fscrypt_folio_offset() | 按 Matthew 的 review 建议一次转完;用 folio_size() 取代 thp_size() |
5/10 ceph_submit_write() | 提交写整段 page→folio | 本系列最核心的 ceph 转换:两个 per-folio 循环 + 写回标记 + 长度计算全部 folio 化 |
| 3/10 + 6/10(mm 侧) | 删除 wait_on_page_writeback()、thp_size()/thp_order() | 调用者清零后的例行收尾;删除后 mm 兼容层与 mm.h 各少一个 page 版符号 |
| 7-10/10(ceph 收尾) | 清残留、迁移清理循环、删旧助手、改名 | 确保 ceph 侧不再有任何 page 版写回调用残留 |
来源:基于 lore 真实补丁 diff(v2 系列)绘制
ceph_submit_write() 整段转为 folio 操作@@ -1467,29 +1476,28 @@ int ceph_submit_write(struct address_space *mapping,
BUG_ON(IS_ERR(req));
}
- page = ceph_wbc->pages[ceph_wbc->locked_pages - 1];
- BUG_ON(len < ceph_fscrypt_page_offset(page) + thp_size(page) - offset);
+ folio = page_folio(ceph_wbc->pages[ceph_wbc->locked_pages - 1]);
+ BUG_ON(len < ceph_fscrypt_folio_offset(folio) + folio_size(folio) - offset);
if (!ceph_inc_osd_stopping_blocker(fsc->mdsc)) {
for (i = 0; i < folio_batch_count(&ceph_wbc->fbatch); i++) {
- struct folio *folio = ceph_wbc->fbatch.folios[i];
+ folio = ceph_wbc->fbatch.folios[i];
if (!folio)
continue;
- page = &folio->page;
- redirty_page_for_writepage(wbc, page);
- unlock_page(page);
+ folio_redirty_for_writepage(wbc, folio);
+ folio_unlock(folio);
}
for (i = 0; i < ceph_wbc->locked_pages; i++) {
- page = ceph_fscrypt_pagecache_page(ceph_wbc->pages[i]);
+ folio = ceph_fscrypt_pagecache_folio(page_folio(ceph_wbc->pages[i]));
- if (!page)
+ if (!folio)
continue;
- redirty_page_for_writepage(wbc, page);
- unlock_page(page);
+ folio_redirty_for_writepage(wbc, folio);
+ folio_unlock(folio);
}▲ 为什么这是最核心的:folio_batch 迭代器天然产出 folio,旧代码却在每个循环里 &folio->page 转回单页、用 page 版 redirty_page_for_writepage()/unlock_page(),page 版内部又 page_folio() 还原——两次无谓往返。改成 folio_redirty_for_writepage()/folio_unlock() 后,循环体直接以 folio 操作;thp_size(page) 也换成 folio_size(folio)。第二个循环里的 page_folio(ceph_wbc->pages[i]) 是作者刻意保留的 libceph page 数组边界——pages[] 数组不能动,只能在取用时解包一次。其余 diff 读者可自查 lore。
thp_size()/thp_order()diff --git a/include/linux/mm.h b/include/linux/mm.h
index 485df9c2dbdd..48e821454ca0 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -1966,27 +1966,6 @@ static inline unsigned int page_shift(struct page *page)
return PAGE_SHIFT + compound_order(page);
}
-/**
- * thp_order - Order of a transparent huge page.
- * @page: Head page of a transparent huge page.
- */
-static inline unsigned int thp_order(struct page *page)
-{
- VM_BUG_ON_PGFLAGS(PageTail(page), page);
- return compound_order(page);
-}
-
-/**
- * thp_size - Size of a transparent huge page.
- * @page: Head page of a transparent huge page.
- *
- * Return: Number of bytes in this page.
- */
-static inline unsigned long thp_size(struct page *page)
-{
- return PAGE_SIZE << thp_order(page);
-}
-
#ifdef CONFIG_MMU
/*
* Do pte_mkwrite, but only if the vma says VM_WRITE. We do this when▲ 这两个助手是"透明大页尺寸"的 page 版接口,参数要求 head page 且有 VM_BUG_ON_PGFLAGS(PageTail(page)) 断言——传了非 head 的 tail page 会直接触发内核警告。folio 化后 ceph 侧改用 folio_size()/folio_order()(无此断言风险,语义等价),最后一个调用者清零,于是整个删除(+0/-21)。
| page 版(本系列删除/弃用) | folio 版(本系列改用) | 语义 |
|---|---|---|
wait_on_page_writeback(page) | folio_wait_writeback(folio) | 等待写回完成 |
set_page_writeback(page) | folio_start_writeback(folio) | 置写回标记 |
redirty_page_for_writepage(wbc, page) | folio_redirty_for_writepage(wbc, folio) | 标记脏页重写 |
unlock_page(page) | folio_unlock(folio) | 解锁页 |
page_offset(page) | folio_pos(folio) | 页内起始偏移 |
thp_size(page) / thp_order(page) | folio_size(folio) / folio_order(folio) | 复合页尺寸 / 阶数(新增删除) |
page_snap_context(page) / ceph_fscrypt_pagecache_page() | ceph_folio_snap_context(folio) / ceph_fscrypt_pagecache_folio() | ceph 私有助手(本系列新增/删除) |
📈 性能影响
ceph_submit_write() 等 per-folio 循环里省去 &folio->page + page_folio() 两次指针运算与 compound_head() 链式寻址;thp_size(page) 换成 folio_size(folio) 后不再需要 page_folio() 还原。量级很小,且仅限 ceph 写回路径执行时。folio_wait_writeback() 与 wait_on_page_writeback() 内部完全等价。EXPORT_SYMBOL/内联符号,缩小 mm API 表面积,降低后续 folio 化与 THP 重构的维护负担。| 场景/用例 | 运行环境 | 改进前 | 改进后 |
|---|---|---|---|
| ceph 大规模写回 | — | — | —(补丁未提供基准) |
说明:补丁未提供基准。作者声明无功能变化;上表空缺即如实反映"无数据"。性能叙述全部为基于 diff 的逻辑分析,标注"解读(AI 分析)",未编造任何数字。
🔄 方案演进
本系列从 08-02 的 4 补丁 v1 演进到 08-04 的 10 补丁 v2,扩容完全由 review 讨论推动,是"讨论推进设计"的典型样本:
ceph_folio_snap_context()(带 folio_test_private() 检查)②迁移 ceph_wait_until_current_writes_complete() ③删 wait_on_page_writeback() ④删 page_snap_context()。v2(08-04,10 补丁):"ceph: convert writeback path to folios"。重命名系列、rebase 到 ceph-client testing 分支、按 review 去掉
folio_test_private() 检查、引入 fscrypt folio 助手,并把 get_writepages_data_length()、ceph_submit_write()、writepages_finish() 等整条写回路径转完,新增删除 thp_size()/thp_order()。
folio_test_private() 检查,直接 return folio->private——文件系统 folio 应保证 PG_private 未置位时 private 为 NULL,ceph 正确用了 folio_attach_private()/folio_detach_private()。(DKETLELGS8KI.1B0MCAPS99CXH@nvidia.com)page_snap_context() 的删法"deeply conflicted",认为在 get_writepages_data_length() 里加 page_folio() 多了一次 compound_head() 却不划算,建议引入 ceph_fscrypt_pagecache_folio()/ceph_fscrypt_folio_offset() 一次转完。(am_3oyOJAoFeBFTC@casper.infradead.org)注:截至 lore 索引时点(2026-08-04 19:08 UTC),v2 系列尚无公开回复,上述讨论全部来自 v1 线程(08-02/08-03)。
⚠️ 风险与局限
fs/ceph/addr.c 仍引用 wait_on_page_writeback()/thp_size() 会直接编译失败——需要 mm 与 ceph 两条维护线协调合入。folio_unlock()/folio_start_writeback() 与 page 版在单页 folio 上等价,但 folio 化后若未来 ceph 支持大 folio(multi-page folio),这些操作会一次作用于整个 folio——当前 ceph_writeback_ctl.pages[] 仍按单页解包,语义不变;若未来放开大 folio,需要同步确认 writepages_finish() 的清理按 folio 而非 page 计数(解读(AI 分析))。thp_size()/thp_order() 后,若其他子系统(如 shmem、khugepaged 相关路径)还有 page 版调用残留而未被本系列清掉,会编译失败——删除前提是"全部调用者清零",需确认除 ceph 外没有其他调用者(本系列 cover letter 声明已清零)。EXPORT_SYMBOL_GPL(wait_on_page_writeback) 会破坏依赖它的外部 GPL 模块(如有);thp_size()/thp_order() 是内联函数,影响面在编译期。mm-new 基线及 Zi Yan 的 PG_private 移除系列存在跨系列 rebase 协调需求(v1 cover letter 已提示与 Zi Yan 系列可能冲突,v2 通过去掉 folio_test_private() 检查部分消解)。page_snap_context() 删法:多一次 compound_head() 不划算 → 作者在 v2 引入 fscrypt folio 助手一次转完(已解决)。来源 message-id:am_3oyOJAoFeBFTC@casper.infradead.orgfolio_test_private() 检查:应为多余检查 → v2 删除(已解决)。来源:DKETLELGS8KI.1B0MCAPS99CXH@nvidia.com🔗 交叉引用
folio_test_private() 检查后语义对齐)。include/linux/mm.h(thp_size/thp_order)与 mm/folio-compat.c/include/linux/pagemap.h(wait_on_page_writeback)——08-03 日报已核实过当前树位置。本站内容均由 AI 基于公开知识辅助生成,仅供学习参考,请勿直接引用作为依据。作者不对信息的准确性、完整性及适用性作保证,亦不对因使用本站内容产生的任何损失承担责任。