RK3588 kernel-6.1 page-io.c 与 readpage.c 分析
1. 分析范围
| 文件 | 职责 |
|---|---|
page-io.c |
写路径:把 带 buffer_head 的页 打成 写 BIO 提交;fscrypt 写时加密 bounce;bio 完成 时清 buffer_async_write、end_page_writeback;ext4_io_end/ext4_io_end_vec 在 unwritten extent 写完后 转换元数据(可经 workqueue 延迟) |
readpage.c |
读路径:ext4_mpage_readpages 多页 readahead/readpage,尽量 连续块合并为一个读 BIO;读完成后 可选 decrypt → verity 校验(mempool + 工作队列);复杂页回退 block_read_full_folio/ext4_get_block |
协作:ext4.h(ext4_map_blocks)、extents.c(ext4_convert_unwritten_io_end_vec)、fscrypt、fsverity、inode.c/super.c(rsv_conversion_wq)。
2. page-io.c:作用与解决的问题
2.1 一句话
在 writeback / 显式回写 场景下,ext4_bio_write_page** 对 已映射、已 dirty 的普通块 设 buffer_async_write,按 设备上连续块 和 fscrypt 可合并 规则 组装 ext4_io_submit 中的写 BIO;完成回调 ext4_end_bio/ext4_finish_bio 处理 错误位、bounce 页释放、写回结束;若写的是 unwritten 预分配区,用 ext4_io_end + io_end_vec 记录区间,在 I/O 全部完成 后 开 journal handle 做 “unwritten → written”(同步 ext4_put_io_end 或异步 ext4_put_io_end_defer → i_rsv_conversion_work)。**
2.2 问题对照表
| 问题 | 处理方式 |
|---|---|
| 页内多块不能先 submit 再标 async | 先 遍历整页 标记 buffer_async_write / 清 dirty,再第二遍 塞进 BIO,避免 先完成的 bh 触发 end_page_writeback 而后面还在组装 |
i_size 跨页空洞 |
len < PAGE_SIZE 时 zero_user_segment(注释引用 mmap 语义) |
| 洞、delay、unmapped、unwritten 暂不写 | 跳过 submit;若仍 dirty 则 redirty_page_for_writepage(如 事务提交写回) |
| 加密文件落盘必须是密文 | fscrypt_encrypt_pagecache_blocks → bounce page;BIO 指向 bounce;完成时 fscrypt_free_bounce_page;mempool/死锁用 GFP_NOWAIT 与 先 submit 再重试 策略 |
| 多块连续写合并 | io_submit_add_bh:块号不连续或 fscrypt 不可合并 则 先 ext4_io_submit |
| sync 写 | WB_SYNC_ALL 时 BIO 加 REQ_SYNC |
| bio 完成与 buffer 竞态 | b_uptodate_lock 下扫描 是否仍有 buffer_async_write,再决定是否 end_page_writeback |
| unwritten 转换需 journal 且不能与 truncate 竞态 | 注释:truncate 等 DIO/PageWriteback;转换在 ext4_free_ioend 路径;失败时 ext4_msg(..., KERN_EMERG, ...) 提示潜在丢数据 |
大量并发 bio 完成争 io_end |
EXT4_IO_END_UNWRITTEN 时 bio->bi_private 链到 io_end->bio,refcount 最后一棒 ext4_put_io_end_defer |
| journal 存在时避免在 softirq 里长事务 | ext4_add_complete_io 挂 i_rsv_conversion_list,queue_work(rsv_conversion_wq) |
2.3 主要 API
ext4_init_io_end/ext4_get_io_end/ext4_put_io_end(_defer):io_end生命周期ext4_alloc_io_end_vec/ext4_last_io_end_vec:描述 待转换的逻辑块范围ext4_io_submit_init/ext4_io_submit:写 BIO 批次边界ext4_bio_write_page:writepages路径写单页(与mpage.c/ext4 包装 协作)
3. readpage.c:作用与解决的问题
3.1 一句话
ext4_mpage_readpages 在 page_size == block_size 的常见形态 下,用 ext4_map_blocks(带 上一段 map 缓存)把一页(或多页 readahead)整理成 连续物理块列表;能合并则 单个大读 BIO;读完成走 mpage_end_io,若需 解密/verity 则分配 bio_post_read_ctx,按序 fscrypt_enqueue_decrypt_work → fsverity_enqueue_verify_work,最后 SetPageUptodate/unlock_page;若页 已有 buffer、先洞后数据、块不连续 等,则 放弃 mpage,block_read_full_folio(..., ext4_get_block)。**
3.2 问题对照表
| 问题 | 处理方式 |
|---|---|
| 通用 mpage 无法挂 ext4 读完成回调 | 专用 ext4_mpage_readpages + mpage_end_io |
| 加密文件读回需异步解密 | STEP_DECRYPT:decrypt_work → fscrypt_decrypt_bio |
| fs-verity 需在读后验哈希 | STEP_VERITY:verity_work 先 释放 ctx 防 mempool 递归死锁,再 fsverity_verify_bio |
| 解密与 verity 应用不同 workqueue | 注释:verity 可能再读元数据页,又要解密,避免 同一队列递归 |
| POST_READ ctx OOM | mempool_create_slab_pool(NUM_PREALLOC_POST_READ_CTXS),分配 不因内存失败 |
| 多 bio 完成顺序与一页多 bh | 不尝试 复刻 end_buffer_async_read 复杂度;此类页 goto confused |
page_size != block_size |
易出现 尾洞/非连续,回退 block_read_full_folio |
verity 下 i_size 与 merkle |
ext4_readpage_limit:verity/in_progress 时用 s_maxbytes,否则 i_size_read |
| 全洞页且 verity | fsverity_verify_page 短路;失败用 错误页 路径 |
| fscrypt 读 BIO 元数据 | fscrypt_set_bio_crypt_ctx / fscrypt_mergeable_bio 与写路径一致 |
3.3 初始化
ext4_init_post_read_processing/ext4_exit_post_read_processing:slab + mempool,由super.c模块__init/__exit调用。
4. 读与写的分工小结
| 方向 | 核心抽象 | 与 ext4 元数据 |
|---|---|---|
| 读 | 合并读 BIO + post-read 管道 | ext4_map_blocks 只读映射;不在这里改 extent |
| 写 | 合并写 BIO + buffer_head 异步位 |
普通块 仅刷脏数据;unwritten 在 ext4_convert_unwritten_io_end_vec 中 提交 extent 更新 |
5. 小结
page-io.c解决 “如何把页缓存安全、可加密、可合并地写到盘,并在 unwritten 分配后把元数据事务化地变成正式写入”。readpage.c解决 “如何在高性能 mpage 读与加密、verity、非平凡页布局之间折衷,必要时回退到 buffer 层单块读”。
若需 read_folio/address_space_operations 与 ext4_mpage_readpages 的衔接,可在 file.c/inode.c 中继续追踪。
正在加载留言…