RK3588 kernel-6.1 fs/ext4/dir.c 目录与遍历机制分析
1. 分析范围
rk3588/kernel-6.1/fs/ext4/dir.c- 与
namei.c(lookup/create/rename等)、hash.c(ext4_htree_fill_tree等 htree 填充)、inline.c(ext4_read_inline_dir)、inode.c(ext4_map_blocks)的协作
2. 代码作用(一句话)
dir.c 实现 ext4 目录的文件层行为:readdir/getdents 遍历(含 htree 哈希目录 与非索引线性目录)、目录项格式合法性校验、目录块校验和、加密目录名解密后上报、inline 目录 回退,以及 ext4_dir_operations(含 llseek/release/ioctl/fsync);大目录通过 ext4_dx_readdir 按 哈希序 缓存到红黑树再 dir_emit,避免纯字节线性扫全块。
3. 解决了什么问题
| 问题 | dir.c 中的处理方式 |
|---|---|
大目录 readdir 过慢 |
启用 htree(EXT4_INODE_INDEX 等)时走 ext4_dx_readdir:按 major/minor hash 批量装入 fname 红黑树,顺序 dir_emit;与 hash.c 协作 |
| 目录项损坏导致越界/错 inode | __ext4_check_dir_entry:检查 rec_len、对齐、与 name_len 关系、不越出 buffer、inode 不超过 s_inodes_count;失败 ext4_error_* |
telldir/seekdir 在哈希目录下的语义 |
ext4_dir_llseek:htree 时 f_pos 表示哈希空间(hash2pos/pos2maj_hash/pos2min_hash),兼容 32/64 位 API 与 FMODE_32BITHASH/FMODE_64BITHASH(如 NFS) |
| 目录块静默损坏 | 读块后 ext4_dirblock_csum_verify,失败则报错并跳块 |
| 加密目录展示明文名 | fscrypt_fname_disk_to_usr + dir_emit;casefold 时用条目内 hash/minor_hash |
| 小目录 inline 数据 | ext4_read_inline_dir,无则回退映射块 |
| htree 元数据异常 | ext4_dx_readdir 若返回 ERR_BAD_DX_DIR:无 metadata_csum 时可 ext4_clear_inode_flag(INDEX) 回退线性读(有风险,仅旧场景) |
并发改目录导致 f_pos 悬垂 |
用 inode_eq_iversion/inode_query_iversion:目录改变时从块内重扫对齐 offset |
fsck/一致性扫描 |
ext4_check_all_de:对整块顺序校验所有 ext4_dir_entry_2 |
4. 核心组件
4.1 is_dx_dir
在 ext4_has_feature_dir_index 前提下,满足 其一 则视为可走目录索引路径:
- inode 置
EXT4_INODE_INDEX;或 - 逻辑上仅 1 个目录块(
i_size == 1 block);或 - inline data 目录(可能参与转换路径)。
具体 htree 读由 hash.c 等与 ext4_dx_readdir 衔接。
4.2 __ext4_check_dir_entry / is_fake_dir_entry
- 假项:
./..(名长 1–2 且为点);或EXT4_FT_DIR_CSUM校验占位项 —— 不参与最小ext4_dir_rec_len(1, dir)的严格名规则(用fake分支)。 - 正常项则按
dir做 casefold 下的最小记录长度等检查。
返回 0 表示合法;1 表示有问题(与历史上 ext2 返回值语义相反,注释已说明)。
4.3 ext4_readdir(非 dx 或 dx 失败回退后的线性路径)
主要步骤:
fscrypt_prepare_readdir(加密目录)。- 若
is_dx_dir:先试ext4_dx_readdir;仅当ERR_BAD_DX_DIR时再尝试清INDEX标志并走线性。 ext4_read_inline_dir处理 inline。- 加密时准备
fscrypt_fname缓冲。 - 按
ctx->posext4_map_blocks定位目录数据块,readahead,ext4_bread读块。 - 目录块校验和 →
set_buffer_verified。 - iversion 不一致时从块头重扫
rec_len对齐offset。 - 循环
ext4_check_dir_entry+ dir_emit;洞(err==0)则推进ctx->pos;dir_relax_shared用于并发下让出。
4.4 htree:fname 树与 ext4_dx_readdir
struct fname:存 hash、minor_hash、inode、file_type、name(可变长),挂在rb_hash上;哈希碰撞 时用链表next串在同一 RB 节点逻辑下(少见)。ext4_htree_store_dirent(可被hash.c调用):插入/合并碰撞链;加密目录 树内存放 解密后名(ent_name)。ext4_dx_readdir:首次分配dir_private_info;若f_pos被用户改写则 清空树 并重解析curr_hash;优先吐extra_fname(上次dir_emit缓冲区满 未发完的碰撞链);需要时调ext4_htree_fill_tree(hash.c)继续装树。ext4_release_dir:ext4_htree_free_dir_info释放整棵树。
4.5 hash2pos / ext4_get_htree_eof
f_pos在 dx 目录中编码 (major_hash, minor_hash),不是单纯字节偏移。- EOF:
EXT4_HTREE_EOF_32BIT或EXT4_HTREE_EOF_64BIT,取决于f_mode与 是否 compat 32 位 syscall。
4.6 ext4_check_all_de
工具函数:从 buffer 头到尾按 rec_len 跳转,逐项 ext4_check_dir_entry;指针越过 top 则 -EFSCORRUPTED。供 namei/校验路径 快速扫整块目录项。
4.7 ext4_dir_operations
1 | const struct file_operations ext4_dir_operations = { |
目录 open 后 file->f_op 指向此处;iterate_shared 为现代 readdir 接口(与 sharedlock 语义配合)。
5. 数据流示意
1 | getdents / readdir |
6. 与 namei.c / hash.c 的分工
| 文件 | 侧重 |
|---|---|
dir.c |
只读列举、目录 file_operations、项格式校验辅助 |
namei.c |
lookup / create / unlink / rename、路径解析与目录改写的 inode 语义 |
hash.c |
dx 索引块 解析、ext4_htree_fill_tree、改名/插入时 htree 维护 |
7. RK3588 / 工程注意点
- 大目录(如海量小文件缓存目录)强烈依赖 htree + dir_index;若磁盘上 htree 损坏且 无 metadata_csum,内核可能 降级清 INDEX(注释称不标脏),应以备份+fsck 为主,勿依赖长期使用降级态。
- 加密 (
fscrypt) 与 casefold 目录会走dir.c中额外分支,调试 readdir 异常时核对 密钥与编码。 ext4_check_dir_entry** 告警** 多指向 磁盘目录块损坏 或 不兼容工具写盘,需 dmesg + fsck。
8. 小结
| 维度 | 结论 |
|---|---|
| 作用 | 目录 readdir/哈希目录遍历、目录项与目录块校验、f_pos/llseek 语义、ext4_dir_operations |
| 解决问题 | 大目录性能(htree)、损坏检测、crypt/casefold、inline、与 VFS dir_context 对接 |
源码:rk3588/kernel-6.1/fs/ext4/dir.c。文档:linuxDoc/fs/ext4/。
正在加载留言…