RK3588 kernel-6.1 fs/ext4/resize.c 在线扩容(resize)分析
1. 分析范围
rk3588/kernel-6.1/fs/ext4/resize.c(挂载状态下扩大 ext4 卷:superblock、块组描述符(GDT)、位图与 inode 表、meta_bg 转换、备份超级块/GDT)- 用户态入口:
ioctl.c中EXT4_IOC_GROUP_EXTEND、EXT4_IOC_GROUP_ADD、EXT4_IOC_RESIZE_FS;均配合ext4_resize_begin/ext4_resize_end与能力CAP_SYS_RESOURCE
不在此文件范围内:离线 shrink、缩小已用块数;注释与代码均明确 在线收缩不支持。
2. 代码作用(一句话)
resize.c 在 jbd2 事务内(及受控的预置阶段事务中)把 底层块设备末端 对应的新空间 纳入已有 ext4:要么 只拉长最后一个块组(ext4_group_extend),要么 按块组追加(ext4_group_add** / ext4_flex_group_add),要么 ext4_resize_fs 自动循环 _extend + 批量 flex 组;当 resize_inode 预留的 GDT 槽位用尽 时 转为 meta_bg 布局(ext4_convert_meta_bg);并通过 update_backups 维护 稀疏超级块/GDT 备份;用 ext4_kvfree_array_rcu 等在 扩大 s_group_desc 等 RCU 可见数组 后安全释放旧指针。**
3. 解决了什么问题
| 问题 | resize.c 中的处理方式 |
|---|---|
| 磁盘/LUN 扩大后文件系统仍只有旧容量 | 在线更新 s_blocks_count/s_free_blocks_count(及 per-group 结构),使 mballoc/分配路径 能用新块 |
| 只做「最后一组补满」的应急路径 | ext4_group_extend:要求当前 逻辑末尾不在块组边界(last != 0),否则需 GROUP_ADD/RESIZE_FS;适合 remount,resize= 等 不依赖预留 GDT 的场景(注释说明) |
| 新增整块组(含位图、inode 表、备份元数据) | ext4_group_add:校验 ext4_new_group_data(verify_group_input:位图/itb 与 GDT 重叠、越界、最后一块可读等),扩 flex_bg/groupinfo,再 ext4_flex_group_add |
| 一次加多组、控制内存与日记压力 | ext4_resize_fs 用 ext4_setup_next_flex_gd + alloc_flex_gd 按 flex 大小批量;MAX_RESIZE_BG 等限制;约 每 10 秒打进度日志 |
| GDT 块数量随块组增加而增长 | ext4_add_new_descs:add_new_gdb 从 EXT4_RESIZE_INO 的双间接树 抠出预留 GDT 块;或 add_new_gdb_meta_bg 在 meta_bg 下挂新描述符块 |
| 预留 GDT 用完仍要继续涨 | ext4_resize_fs:先 缩小目标 n_blocks_count 到预留能撑住的最大组数,ext4_convert_meta_bg 开启 meta_bg 并 释放 resize inode 占用的元数据块,再 带着原目标大小 retry |
resize_inode 与 meta_bg 互斥 |
ext4_resize_fs:若两者同时 feature 置位则 -EINVAL(不一致布局) |
| JBD 难以整事务回滚 | 注释反复强调:在改盘前尽量做完所有可能失败检查;add_new_gdb 等路径 先 verify 再改 dind |
| 扩容与并发访问 | ext4_resize_begin 置 EXT4_FLAGS_RESIZING(-EBUSY 互斥);sparse_super2、非主超级块挂载、EXT4_ERROR_FS 下 拒绝扩容 |
| bigalloc 边界 | ext4_resize_fs 将 n_blocks_count 向下对齐 cluster,避免 半 cluster 不可用语义 |
| 内核侧数据结构随组数变长 | ext4_alloc_flex_bg_array、ext4_mb_alloc_groupinfo;旧数组 ext4_kvfree_array_rcu RCU 后再 kvfree |
| 新组块位图与 inode 表初始化 | setup_new_flex_group_blocks:复制 GDT 备份、zeroout inode 表、bclean 位图、set_flexbg_block_bitmap 在位图中 标记元数据占用 |
| 元数据 checksum | ext4_setup_new_descs / ext4_set_bitmap_checksums 等写 组描述符与位图校验(与 ext4_has_group_desc_csum 等配合) |
EXT4_IOC_RESIZE_FS 结束后统计 |
ext4_resize_end(..., true) → ext4_update_overhead 刷新 overhead/free_clusters 等 |
4. 主要入口与协作关系
4.1 ext4_resize_begin / ext4_resize_end
- 权限:
CAP_SYS_RESOURCE,否则-EPERM - 一致性:
s_reserved_gdt_blocks非零则须resize_inodefeature;主 sb 与s_first_data_block对齐(备份超级块挂载则拒绝) - 安全:
EXT4_ERROR_FS、sparse_super2下不允许在线 resize - 结束:清除 RESIZING;
RESIZE_FS成功路径update_backups=true触发 overhead 更新
4.2 ext4_group_extend
- 仅处理 最后一个块组内「从当前
o_blocks_count到n_blocks_count(或补满本组)」 last == 0(已由完整块组边界结尾)→-EPERM,提示需GROUP_ADD/在线工具ext4_group_extend_no_check:日记 3 credits,改 sb 块总数与 free 计数,ext4_group_add_blocks把新块在位图中标为空闲可用,update_backups(super)
4.3 ext4_group_add(EXT4_IOC_GROUP_ADD)
- 只能在尾数追加:
input->group == s_groups_count - 非 sparse 且 新 GDT 落到新块(
gdb_off==0)时-EPERM(无法再分稀疏备份) - 需要预留时打开
EXT4_RESIZE_INO - 分配 flex/mballoc groupinfo 后,以
count=1调ext4_flex_group_add
4.4 ext4_resize_fs(EXT4_IOC_RESIZE_FS)
高层循环(逻辑顺序):
- 读
n_blocks_count - 1,确认设备够长;bigalloc 对齐 - 禁止缩小;计算目标 最后一组
n_group - 若
resize_inode:检查 GDT 块数是否超过「旧描述符块数 + s_reserved_gdt_blocks」 → 必要时 临时降低n_blocks_count,记n_blocks_count_retry ext4_convert_meta_bg:在 无需再动 resize_inode 或已缩到仅扩组内 的边界上 切换到 meta_bg- 若 最后一组塞不进 inode 表等元数据 → 回退
n_group重算(retry) - 与当前尾组同组 →
ext4_group_extend_no_check;否则先 补满当前尾组(add为簇/块公式) while ext4_setup_next_flex_gd:ext4_alloc_group_tables(用户空间/内核算好的表布局)+ext4_flex_group_add- 若有
n_blocks_count_retry→ 释放 flex 结构,goto retry 扩到用户原定大小
4.5 ext4_flex_group_add
- 前置:
setup_new_flex_group_blocks(独立大事务:初始化 新组上的 备份 GDT、位图、inode 表 等) - 主事务:
EXT4_HT_RESIZE,ext4_add_new_descs→ext4_setup_new_descs→ext4_update_super(块数、inode 数、flex 计数器、s_overhead_clusters等) - 成功后:
update_backups写 超级块备份 与 新增/变更的 GDT 块备份(区分 meta_bg 与 padding)
4.6 ext4_convert_meta_bg
- 打开
meta_bgfeature,从 resize inode 释放 DIND 及所连块(ext4_free_blocks),清除 inode 映射 - 要求:进入时
s_reserved_gdt_blocks已为 0(否则报错)
5. 与用户态、其它子系统的关系
| 组件 | 关系 |
|---|---|
ioctl.c |
EXT4_IOC_* 封装 resize_begin/resize_end;RESIZE_FS 结束 update_backups=true |
balloc.c/mballoc |
新块通过 ext4_group_add_blocks、位图 EXT4_BG_BLOCK_UNINIT 等与 簇分配器 一致 |
super.c |
superblock checksum、挂载标志 |
| 工具链 | resize2fs 等通常组合 ioctl 与 设备容量查询;本节仅述内核实现 |
6. 小结
resize.c 解决的是 「块设备变大了,ext4 如何在不卸载的前提下把新末端块变成合法块组并接入 GDT/位图」:涵盖 仅尾组延长、用户提供单组几何的 GROUP_ADD、以及 RESIZE_FS 的自动分批与 meta_bg 切换。设计核心是 预留 GDT(resize_inode)或 meta_bg 二选一、日志事务与「先校验再改」、以及 备份超级块/GDT 与 flex/mballoc 元数据扩容 的一致性。
若需对照 组描述符与块组静态布局,可结合 rk3588-kernel-6.1-ext4-balloc块位图与块组辅助分析.md 等文档。
正在加载留言…