ceph: convert writeback path to folios(v2 完整系列进展更新)

内存管理 · page→folio 迁移收尾 · ceph 写回路径整条 folio 化 + 删除 3 个 mm 兼容 API

💡 一句话总结

在内核把内存管理 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

📊 速览卡片

核心机制
folio 化收尾
优化目标
统一 mm API
适用场景
ceph 写回路径
实测提升
未提供(重构)

🎯 解决什么问题

系列整体定位(内核 page→folio 迁移大方向)
内核自 2022 年起由 Matthew Wilcox 主导,把以单页 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())可以被删除。
本补丁(本报告主体)在系列中的角色
本报告是整个 v2 系列的进展更新。系列 10 个补丁可分两组:
  • ceph 侧(8 个,主体):1/10 新增 folio 版快照上下文助手 → 2/10 迁移写回等待 → 4/10 迁移数据长度计算(引入 fscrypt folio 助手)→ 5/10 迁移提交写 ceph_submit_write()(本系列最核心的 ceph 转换) → 7/10 清 write_folio_nounlock 残留 → 8/10 迁移 writepages_finish 清理循环 → 9/10 删旧 page 版 fscrypt 助手 → 10/10 改名 check 函数。
  • mm 侧(2 个,收尾):3/10 删 wait_on_page_writeback()(08-03 日报已单独分析过其 v1 形态);6/10 删 thp_size()/thp_order()(新增)。
  • 依赖关系:mm 侧删除补丁严格依赖 ceph 侧迁移补丁先把全部调用者清空,必须按系列顺序合入。
    背景 / 原始动机
    v1(08-02,4 补丁)只覆盖"删 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)
    系统层面:page API 与 folio API 之间的"摩擦"
    ceph 的写回路径用 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 快照写回与大规模文件写入
    ceph(Ceph 分布式文件系统客户端)的写回路径 ceph_writepages_start() 在每次同步写回(WB_SYNC_ALL)或快照切换时高频触发:需要遍历脏页 folio_batch、按快照上下文(snapc,存在 folio->private 中)分组、逐 folio 提交 OSD 写请求、等待写回完成。在大文件顺序写 / 快照频繁的负载下,这条路径的循环次数=脏页数,任何 per-folio 的 page↔folio 转换开销都会被放大。ceph_submit_write() 里的两个循环(fbatch.folios[] 与 locked_pages[])正是转换最密集处。
    受影响负载:ceph 客户端大规模文件写入 / 快照切换写回 · 为什么此系列解决此场景:把 folio_batch 迭代器与写回函数统一为 folio 粒度,消除循环内反复 &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 版写回调用残留
    ceph 写回路径 folio 化转换点与 mm 兼容 API 移除示意
    图 1:ceph 写回路径 folio 化——转换点与 mm 兼容 API 移除示意
    来源:基于 lore 真实补丁 diff(v2 系列)绘制
    最核心的代码证据(1)——5/10 把 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。

    最核心的代码证据(2)——6/10 删除 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 版 API 对照
    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 私有助手(本系列新增/删除)

    📈 性能影响

    提升角度(方法论分类)
    这是一个"内存管理 API 统一"重构而非性能优化。作者 cover letter 明确声明无功能变化,未提供任何 benchmark。按 Brendan Gregg 的 on-CPU / off-CPU 视角分类:
  • on-CPU 计算效率(微省,主要角度):在 ceph_submit_write() 等 per-folio 循环里省去 &folio->page + page_folio() 两次指针运算与 compound_head() 链式寻址;thp_size(page) 换成 folio_size(folio) 后不再需要 page_folio() 还原。量级很小,且仅限 ceph 写回路径执行时。
  • off-CPU(无):不涉及锁、I/O 或等待路径行为变化——folio_wait_writeback() 与 wait_on_page_writeback() 内部完全等价。
  • 结构性收益(长期):删除 3 个 EXPORT_SYMBOL/内联符号,缩小 mm API 表面积,降低后续 folio 化与 THP 重构的维护负担。
  • 受益场景
    复用"改动路径 → 高频触发场景"推断:改动在 ceph 写回提交循环,该循环的触发频率正比于脏页数(大文件顺序写、快照频繁切换时最大)。因此理论上脏页批量越大、循环次数越多,省去的 per-folio 转换越多。但这属于逻辑推断——该路径本身是低频 I/O 路径,指令级收益远小于一次 OSD 网络往返,实际可测影响预计可忽略(解读(AI 分析))。
    场景/用例运行环境改进前改进后
    ceph 大规模写回———(补丁未提供基准)

    说明:补丁未提供基准。作者声明无功能变化;上表空缺即如实反映"无数据"。性能叙述全部为基于 diff 的逻辑分析,标注"解读(AI 分析)",未编造任何数字。

    🔄 方案演进

    本系列从 08-02 的 4 补丁 v1 演进到 08-04 的 10 补丁 v2,扩容完全由 review 讨论推动,是"讨论推进设计"的典型样本:

    v1 → v2 演进脉络
    v1(08-02,4 补丁):"mm, ceph: remove wait_on_page_writeback()"。仅覆盖 ①新增 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()。
    review 如何推动扩容
  • Matthew Wilcox(v1 1/4):建议删掉 folio_test_private() 检查,直接 return folio->private——文件系统 folio 应保证 PG_private 未置位时 private 为 NULL,ceph 正确用了 folio_attach_private()/folio_detach_private()。(DKETLELGS8KI.1B0MCAPS99CXH@nvidia.com)
  • Zi Yan(v1 1/4):同意删检查,"So I do not need to handle another exceptional user when I am trying to remove PG_private[1]"——直接引用他自己的 PG_private 移除系列。(同上 message-id)
  • Matthew Wilcox(v1 4/4):对 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)
  • 作者回应:"If we're adding these, I might as well do a few more conversions…"(08c19cc8-c28e-4098-b137-fc9721deebbb@columbia.edu)——这句话直接促成了 4 补丁扩成 10 补丁。
  • v2 cover letter 还提到 "Propagate tags (thanks David and Matthew!)"——v1 已获得 David Hildenbrand 与 Matthew Wilcox 的 tag。
  • 注:截至 lore 索引时点(2026-08-04 19:08 UTC),v2 系列尚无公开回复,上述讨论全部来自 v1 线程(08-02/08-03)。

    ⚠️ 风险与局限

    收益成立的前提
    本系列收益的前提是"减少 page↔folio 转换"确实发生且量级可感——而它本质是重构,收益前提实际落在可维护性而非性能:只有当内核持续推进 folio 化、THP 语义重构时,删除这些 page 版符号的收益才体现。若只看运行时,该路径为低频 I/O 路径,性能收益可忽略(解读(AI 分析))。
    生产落地影响(落地场景角度)
  • 合入顺序硬依赖:mm 侧删除补丁(3/10、6/10)必须在 ceph 侧迁移补丁全部落地的同一合并窗口内合入,否则 fs/ceph/addr.c 仍引用 wait_on_page_writeback()/thp_size() 会直接编译失败——需要 mm 与 ceph 两条维护线协调合入。
  • 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 代码影响:删除 thp_size()/thp_order() 后,若其他子系统(如 shmem、khugepaged 相关路径)还有 page 版调用残留而未被本系列清掉,会编译失败——删除前提是"全部调用者清零",需确认除 ceph 外没有其他调用者(本系列 cover letter 声明已清零)。
  • 生态/兼容性
  • 删除 EXPORT_SYMBOL_GPL(wait_on_page_writeback) 会破坏依赖它的外部 GPL 模块(如有);thp_size()/thp_order() 是内联函数,影响面在编译期。
  • 基于 ceph-client testing 分支,与 mainline 的 mm-new 基线及 Zi Yan 的 PG_private 移除系列存在跨系列 rebase 协调需求(v1 cover letter 已提示与 Zi Yan 系列可能冲突,v2 通过去掉 folio_test_private() 检查部分消解)。
  • review 质疑(v1 已解决 / v2 待观察)
  • Matthew Wilcox 质疑 page_snap_context() 删法:多一次 compound_head() 不划算 → 作者在 v2 引入 fscrypt folio 助手一次转完(已解决)。来源 message-id:am_3oyOJAoFeBFTC@casper.infradead.org
  • Matthew / Zi 质疑 folio_test_private() 检查:应为多余检查 → v2 删除(已解决)。来源:DKETLELGS8KI.1B0MCAPS99CXH@nvidia.com
  • v2 状态:尚无公开回复;与 Zi Yan PG_private 系列的交互仍是后续最值得跟踪的点(解读(AI 分析))。
  • 严重度:LOW(MINOR 落地关注)· 纯死代码清理 + API 迁移,无运行行为变化;实质风险是合入顺序(mm/ceph 协同)与跨系列 rebase

    🔗 交叉引用

    📌 关联工作 / 系列其他补丁
  • 08-03 日报(本报告的"前情"):mm: remove wait_on_page_writeback() —— 当时分析的是 v1 系列(4 补丁)中的 mm 删除补丁;本报告是同一工作扩成完整 10 补丁 v2 系列后的进展更新。
  • v1 系列 cover letter:[PATCH 0/4] mm, ceph: remove wait_on_page_writeback()(08-02)。
  • v2 系列各补丁(lore 链接):01/10 ceph_folio_snap_context · 02/10 写回等待迁移 · 03/10 删 wait_on_page_writeback · 04/10 数据长度计算 · 05/10 ceph_submit_write · 06/10 删 thp_size/thp_order · 07/10 write_folio_nounlock · 08/10 writepages_finish · 09/10 删 fscrypt page 助手 · 10/10 改名。
  • Zi Yan PG_private 移除系列:Remove PG_private by using page/folio->private checks instead —— 与本系列 1/10 直接相关(去掉 folio_test_private() 检查后语义对齐)。
  • 作者同门工作:Tal Zussman 同时推进 block RWF_DONTCACHE(v7) 与 mm/filemap folio wait 拆分(RFC),均属 page→folio 迁移主线。
  • 内核现树对照:删除对象位于 include/linux/mm.h(thp_size/thp_order)与 mm/folio-compat.c/include/linux/pagemap.h(wait_on_page_writeback)——08-03 日报已核实过当前树位置。
  • ⚠️ 免责声明

    本站内容均由 AI 基于公开知识辅助生成,仅供学习参考,请勿直接引用作为依据。作者不对信息的准确性、完整性及适用性作保证,亦不对因使用本站内容产生的任何损失承担责任。