RK3588 kernel-6.1 fs/ext4/block_validity.c 元数据块保护区分析
1. 分析范围
rk3588/kernel-6.1/fs/ext4/block_validity.c
ext4.h 中 struct ext4_system_blocks、s_system_blks
- 挂载选项:
block_validity / noblock_validity(super.c)
- 调用方:
inode.c 中 __check_block_validity,以及 ext4_check_blockref
2. 代码作用(一句话)
block_validity.c 在挂载时建立一棵 红黑树,记录全文件系统中所有 仅允许作元数据使用 的物理块区间(超级块备份、GDT、位图、inode 表、以及日记 inode 所映射的数据块等);之后当 inode 的块映射被解释时,若某物理块落在这些区间内且不属于「被允许的 inode」(如 journal inode),则判定为 无效引用,报 损坏 并阻止继续使用错误映射。
文件头注释:
1 2
| * Track which blocks in the filesystem are metadata blocks that * should never be used as data blocks by files or directories.
|
3. 解决了什么问题
3.1 元数据被误当「文件数据块」
磁盘或软件缺陷可能导致:
- extent / 间接块中写入的 物理块号 落在 块组位图、inode 表、超级块备份 等区域;
- 若不做检查,缓冲写或 DIO 可能 覆写元数据,造成 整卷损坏,且比单文件损坏更难恢复。
block validity 在 ext4_map_blocks 等路径更新映射前(见 inode.c),对即将采用的 m_pblk 区间 调用检查,重叠系统区则 拒绝(-EFSCORRUPTED / ext4_error_inode),把问题 限制在发现点。
3.2 日记 inode 的合法占用
Journal 以普通 inode 形式存放,其 文件块 在物理上仍是数据区,但 从「普通文件」视角 这些块应视为 由该保留 inode 专用。因此树节点带 ino:
1 2 3 4 5 6
| else { ret = 0; if (inode) ret = (entry->ino == inode->i_ino); break; }
|
- 重叠系统区且
entry->ino == 0:一律 无效(普通文件不得占用)。
- 重叠且
entry->ino == inode->i_ino:允许(例如正在访问的是 journal inode 自身映射)。
ext4_protect_reserved_inode 把 journal inode 的 ext4_map_blocks 结果逐段 add_system_zone(..., ino),与固定元数据区一起进入同一棵树。
3.3 重叠检测与布局自洽
add_system_zone 在插入 RB 树时若发现新区间与已有区间 几何重叠(非简单相邻合并),返回 -EFSCORRUPTED。这能发现 系统区描述自相矛盾 或 错误重复登记 等异常(例如 ext4_protect_reserved_inode 中 journal 块与已有区重叠会打错误日志)。
4. 核心数据结构
1 2 3 4 5 6
| struct ext4_system_zone { struct rb_node node; ext4_fsblk_t start_blk; unsigned int count; u32 ino; };
|
start_blk + count:半开或闭区间意义上的 连续物理块(实现里判断用 start_blk + count)。
ino == 0:通用元数据(位图、inode 表等),任何文件 inode 不可占用。
ino != 0:该 inode 专属 的保留块(当前实际用于 journal inode)。
struct ext4_system_blocks(在 ext4.h):根为 rb_root,挂到 sbi->s_system_blks(RCU 指针),读后路径 只读树 + rcu_read_lock。
5. 主要流程
5.1 ext4_setup_system_zone(挂载时)
- 分配新的
ext4_system_blocks,对每个块组:
ext4_num_base_meta_blocks:超级块备份 + GDT 等 基座元数据 连续块;
- 块位图、inode 位图 各 1 块;
- inode 表
s_itb_per_group 块。
- 若有 journal inode 号:
ext4_protect_reserved_inode —— iget 后 ext4_map_blocks 扫出所有物理块,add_system_zone(..., journal_ino)。
- 整棵树建完后
rcu_assign_pointer(sbi->s_system_blks, system_blks),避免与 ext4_inode_block_valid 并发读旧指针。
注释强调:在 sb->s_umount 保护下换人指;读者侧仅用 RCU。
5.2 ext4_release_system_zone(卸载或 noblock_validity)
rcu_assign_pointer(..., NULL) 后 call_rcu 延迟释放整棵树(ext4_destroy_system_zone → release_system_zone),保证 RCU 读者退出后再 kfree。
5.3 ext4_sb_block_valid / ext4_inode_block_valid
- 先做 边界:相对
s_first_data_block、ext4_blocks_count、无溢出。
- 若
s_system_blks == NULL(未启用 validity),直接视为合法(返回 1)。
- 否则在 RB 树中查找
[start_blk, start_blk+count) 是否与某系统区 相交:
- 不相交 → 合法;
- 相交 → 默认 不合法(
ret = 0),除非 inode 非空且 entry->ino == inode->i_ino。
5.4 ext4_check_blockref
遍历 __le32 * 块指针数组(最多 max 个),对每个非零块号调用 ext4_inode_block_valid。
特例:若当前 inode 即 journal inode,整段跳过(日记内部结构自行维护块引用)。
用于 indirect 块、extent 索引块 等结构中 批量校验子块号。
5.5 模块 init/exit
ext4_init_system_zone / ext4_exit_system_zone:为 ext4_system_zone 分配 kmem_cache(super.c 的 ext4_init_fs 路径注册)。
6. 挂载选项与默认行为
block_validity:默认 开启(super.c 注释:默认启用,可用 noblock_validity 关闭)。
- 关闭后
s_system_blks 为空,ext4_sb_block_valid 恒通过 → 略少 CPU / 锁开销,但 失去这类交叉引用检查。
7. 与其它模块的关系
1 2 3 4 5 6 7 8 9 10 11 12
| 挂载(super.c) | v ext4_setup_system_zone(block_validity.c) | v s_system_blks RCU 指针
ext4_map_blocks / 写路径(inode.c) | v check_block_validity → ext4_inode_block_valid
|
balloc.c 的 ext4_num_base_meta_blocks 等用于 计算每组基座元数据块数,与 本文件登记的区间 一致,形成「布局算出来」与「运行期校验」的衔接。
8. RK3588 / 工程注意点
- 嵌入式上 一般保持默认
block_validity,用较小代价换 更早发现元数据交叉引用。
- 若 profiling 证明热点在极密集小块 IO,可评估
noblock_validity(需接受 失去该层防护)。
- 出现
invalid block / overlap system zone 类 ext4_error_inode 时,多属 磁盘损坏、旧 bug、或非法离线改动,应 完整备份 + fsck,而非仅忽略。
9. 小结
| 维度 |
结论 |
| 作用 |
维护 系统块区 RB 树,在映射块号时校验 不得将元数据区当作普通文件块(journal inode 除外) |
| 解决问题 |
防止损坏或错误的 inode 指向元数据物理块 导致 元数据被数据写覆盖;尽早报 EFSCORRUPTED |
| 并发 |
RCU 切换 s_system_blks;释放延迟到 grace period |
源码:rk3588/kernel-6.1/fs/ext4/block_validity.c。文档:linuxDoc/fs/ext4/。