首页/目录/全部文章

全部文章

八个专题的源码、算法与协议笔记都在这里。

笔记列表

RK3588 kernel-6.1 fs/ext4/file.c 普通文件 VFS 层分析

RK3588 kernel-6.1 fs/ext4/file.c 普通文件 VFS 层分析

1. 分析范围

  • rk3588/kernel-6.1/fs/ext4/file.c普通文件file_operations / 部分 inode_operations 配套、读写、mmap、open/release、llseek)
  • 强依赖:iomap.cext4_iomap_opsext4_iomap_overwrite_opsext4_iomap_report_ops)、inode.cext4_map_blocks)、extents.c(未写区转换等)

2. 代码作用(一句话)

file.c 把 ext4 常规文件接到 Linux VFS:在 forced shutdown、加密、verity、DAX 等前提下分流 缓冲读/写iomap_dio_rw 直读直写** 与 DAX;用 i_rwsem 共享/独占inode_dio_wait 协调 未对齐 DIO、扩展写、unwritten extent 填洞 的安全性;open 里采样挂载路径写超块、挂 journal inodemmap 区分 DAX 大页缺页 与普通 filemap_fault / ext4_page_mkwrite。**


3. 解决了什么问题

问题 file.c 中的处理方式
直 I/O 与特性组合(对齐、inline、加密等) ext4_should_use_dio:用 ext4_dio_alignment 判定;不成立则清 IOCB_DIRECTgeneric_file_read_iter,或写路径走 ext4_buffered_write_iter,保持 「不适用 DIO 时回落缓冲」 的历史语义
错对齐 DIO + unwritten extent 填洞并发 注释 ext4_unaligned_io同一块上两路 dio_zero 可能互清零。做法:未对齐 DIO 必须独占 i_rwsem,且 inode_dio_wait 串行化在途 DIO
扩展写 DIO 与 i_size / i_disksize / orphan 扩展写前 ext4_orphan_addext4_dio_write_end_ioext4_convert_unwritten_extents;仅在超过当前 disk/逻辑 sizeext4_handle_inode_extension;失败 ext4_inode_extension_cleanup + ext4_truncate_failed_write
纯覆盖写可减少锁竞争 ext4_overwrite_ioext4_map_blocks(..., 0)** 判断是否已映射且非洞;覆盖路径可走 **共享 i_rwsem** + **ext4_iomap_overwrite_ops`
DIO 只完成部分长度 末尾 ext4_buffered_write_iter 吃剩余 iov_iter,再 filemap_write_and_wait_range + invalidate_mapping_pages,尽量保持 「要么全 DIO 要么语义一致」
非 extent(bitmap)布局的最大文件 ext4_generic_write_checksEXT4_INODE_EXTENTS 未置时用 s_bitmap_maxbytes 截断 iov_iter
不可变文件 IS_IMMUTABLE → -EPERM
关闭文件时把延迟分配落盘 / 释放预分配 ext4_release_fileEXT4_STATE_DA_ALLOC_CLOSEext4_alloc_da_blocks;最后一个写者且无 i_reserved_data_blocksext4_discard_preallocations
管理员排查「盘从哪挂载过」 ext4_sample_last_mounted:首次写挂载采样 d_path 写入 s_last_mounted(带日志与校验和)
journal ordered 模式与写 fd ext4_file_open 写打开时 ext4_inode_attach_jinode
SEEK_HOLE / SEEK_DATA ext4_llseekiomap_seek_hole / iomap_seek_data + ext4_iomap_report_ops
maxbytes 随布局变化 ext4_llseek:无 EXTENTS 标志用 s_bitmap_maxbytes,否则 s_maxbytes
DAX 共享映射写 vs COW ext4_dax_huge_fault:仅 VM_SHARED 真写ext4_journal_start;避免只读挂载上无意义 journal 失败

4. 核心组件

4.1 ext4_file_read_iter

  • ext4_forced_shutdown → -EIO
  • DAXext4_dax_read_iterdax_iomap_rw
  • IOCB_DIRECText4_dio_read_iteriomap_dio_rw(..., ext4_iomap_ops);否则 generic_file_read_iter

4.2 ext4_buffered_write_iter

  • IOCB_NOWAIT → -EOPNOTSUPP(缓冲路径)
  • inode_lock + ext4_write_checks(内含 ext4_generic_write_checksfile_modified
  • generic_perform_write + generic_write_sync

4.3 ext4_dio_write_iter

  • 初判 未对齐可能扩展独占锁;否则先 共享锁
  • ext4_dio_write_checks:扩展写 extend=true;若 共享锁下需要改安全配置、扩展、或非覆盖写 → 升级为 独占 并重算
  • 扩展写ext4_orphan_add(短事务)
  • iomap_dio_rw:覆盖用 ext4_iomap_overwrite_ops未对齐或扩展IOMAP_DIO_FORCE_WAIT
  • ext4_dio_write_ops.end_iounwritten → ext4_convert_unwritten_extents;按需 ext4_handle_inode_extension

4.4 ext4_file_write_iter

三分流:forced shutdownDAXDIRECT缓冲

4.5 DAX 写路径(CONFIG_FS_DAX

ext4_dax_write_iterext4_write_checks → orphan(若越 i_disksize)→ dax_iomap_rwext4_handle_inode_extension / cleanup

4.6 ext4_file_mmap

  • daxdev_mapping_supported:不支持 MAP_SYNC 等则 -EOPNOTSUPP
  • DAXext4_dax_vm_opsVM_HUGEPAGE);否则 ext4_file_vm_opsfilemap_fault / ext4_page_mkwrite

4.7 ext4_file_open / ext4_release_file

  • openext4_sample_last_mountedfscrypt_file_openfsverity_file_open、写打开 attach jinodeFMODE_NOWAIT | FMODE_BUF_RASYNCdquot_file_open
  • release:DA 块刷盘、预分配丢弃;若存在 is_dxprivate_dataext4_htree_free_dir_info(多数普通文件不命中;与 htree 私有数据生命周期相关的兜底)

4.8 ext4_file_operations / ext4_file_inode_operations

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const struct file_operations ext4_file_operations = {
.llseek = ext4_llseek,
.read_iter = ext4_file_read_iter,
.write_iter = ext4_file_write_iter,
.iopoll = iocb_bio_iopoll,
.unlocked_ioctl = ext4_ioctl,
// ...
.mmap = ext4_file_mmap,
.mmap_supported_flags = MAP_SYNC,
.open = ext4_file_open,
.release = ext4_release_file,
.fsync = ext4_sync_file,
// ...
.fallocate = ext4_fallocate,
};

const struct inode_operations ext4_file_inode_operations = {
.setattr = ext4_setattr,
.getattr = ext4_file_getattr,
// ...
.fiemap = ext4_fiemap,
// ...
};

5. 小结

file.c 不负责盘上 extent/块分配细节(在 inode.c / extents.c / iomap.c),而是 VFS 入口策略:何时 DIO/DAX/缓冲、如何 加锁与 orphan 以保证 ext4 直写 + unwritten + 延迟块 组合下的正确性,并挂载 加密、verity、OSyncError、配额、seek 洞/数据。阅读 write(2)/read(2)/mmap 在 ext4 上的行为时,应与本文件及 ext4_iomap_* 实现对照。

RK3588 kernel-6.1 fs/ext4/fsmap.c 与 fs/ext4/fsync.c 分析

RK3588 kernel-6.1 fs/ext4/fsmap.cfs/ext4/fsync.c 分析

1. 分析范围

文件 作用定位
fs/ext4/fsmap.c FS_IOC_GETFSMAP(getfsmap)在 ext4 上的实现:把盘上 物理块区间 归类为 所有者(空闲 / 固定元数据 / 日志 /「未知」缝隙等) 并返回给用户态
fs/ext4/fsync.c fsync / fdatasync / msync 触发的 ext4_sync_file:先刷 用户数据页,再按 是否启用 journal、data 模式 提交元数据(含 fast commit 路径),必要时 blkdev_issue_flush

通用 VFS 的 linux/fs/fsync.c(如 do_fsync)不在本分析范围内;用户所问的 fsync.cext4 子目录下的实现


2. fsmap.c:代码作用(一句话)

块组 buddy / 空闲查询ext4_mballoc_query_range)与 预先枚举的固定元数据块列表(超级块、GDT、位图、inode 表等)之间做 物理地址序上的归并,通过 ext4_getfsmap_helper 输出连续的 fsmap/ext4_fsmap 记录(含 洞状「未知所有者」 区间);支持 数据设备 + 外置 journal 设备 两段 handler,并对 大簇(bigalloc) 以簇为单位查询后再换算成块区间。


3. fsmap.c:解决了什么问题

问题 处理方式
用户态需要「整块设备上谁占了哪一段」的物理视图(调试、迁移、审计、xfs_io fsmap 等) 实现 ext4_getfsmap,对接 struct fsmap_headformatter 回调,填 fmh_entries / fmh_oflags
空闲区来自 mballoc,元数据位置来自组描述符 固定元数据ext4_getfsmap_find_fixed_metadata 聚合成 有序链表;每个块组对 空闲簇ext4_mballoc_query_rangeext4_getfsmap_datadev_helper,与 gfi_next_fsblk 同步推进,中间 空隙EXT4_FMR_OWN_UNKNOWN 报告
块组边界处空闲 extent 断裂 gfi_lastfree:块组末尾的 free extent 暂存,与下一组 邻接则合并,否则先 ext4_getfsmap_helper 吐出
外置 journal 占用独立设备 ext4_getfsmap_logdev:在 设备号匹配查询起点为物理 0 时,构造一条 EXT4_FMR_OWN_LOGj_blk_offset / j_total_len 的合成记录
用户态用字节、内核 ext4 用文件系统块 ext4_fsmap_from_internal / ext4_fsmap_to_internals_blocksize_bits 换算
跨调用分页遍历 fmh_keys低 key 可带非零 fmr_lengthext4_getfsmap 将其 折到 dkeys[0].fmr_physical += length,避免重复返回同一段
仅统计映射条数而不拷贝缓冲区 fmh_count == 0ext4_getfsmap_helper 只增 fmh_entries,用于 计数模式

所有者常量(节选,见 fs/ext4/fsmap.h):EXT4_FMR_OWN_FREEUNKNOWNFS(超级块等静态元数据)、GDT / RESV_GDT / BLKBM / INOBM / INODESLOG


4. fsmap.c:核心流程摘要

  1. ext4_getfsmap:校验 fmh_iflags设备号s_bdevs_journal_bdev);组装 handlers[]排序,逐设备调 gfd_fn
  2. ext4_getfsmap_datadev:把 keys 裁到 [s_first_data_block, blocks_count);对每个相关 块组 设置 gfi_low / gfi_high(簇边界);ext4_mballoc_query_range空闲;最后用 gfi_last=true 再跑一次 helper 收口尾部缝隙。
  3. ext4_getfsmap_helper:跳过 低于起始 key 的记录;rec_fsblk > gfi_next_fsblk 时先 填 UNKNOWN 间隙;再输出 当前记录;维护 gfi_next_fsblk;支持 信号中断返回 -EINTR

5. fsync.c:代码作用(一句话)

ext4_sync_file只读/FS_ABORTED/强制关机 等早退之后,先 file_write_and_wait_range 刷数据页;无日志走 sync_mapping_buffers + sync_inode_metadata + 新目录项链上的父目录同步;有日志且非 data=journalext4_fsync_journalext4_fc_commit(按 datasync 选用 i_datasync_tidi_sync_tid);data=journalext4_force_commit;最后按 barrier / journal 情况 可能 blkdev_issue_flush,并 检查 writeback 错误序号


6. fsync.c:解决了什么问题

问题 处理方式
无日志时新建文件只靠回写,崩溃窗口大 ext4_sync_parent:沿 EXT4_STATE_NEWENTRY dentry 链 向上 sync_mapping_buffers + sync_inode_metadata,把 新建路径上的目录 尽快落盘
有 journal 时 fsync 与事务 TID 对齐 ext4_fsync_journal:提交 包含该 inode 已记录更改的事务(通过 i_sync_tid / i_datasync_tid),并纳入 fast commitext4_fc_commit
data=journal 时数据在 journal 里 依赖 ext4_force_commit文件数据随事务 处理(注释说明 filemap_fdatawait 可能遇到 commit 弄脏的页,但块已在日志中)
设备乱序写需要屏障 needs_barrier无 journalBARRIER 挂载journal 带 JBD2_BARRIER 且当前 commit 未带 data barrierblkdev_issue_flush
静默写失败 file_check_and_advance_wb_err
只读挂载与 EXT4_MF_FS_ABORTED sb_rdonly + mount flag 返回 -EROFS

7. fsync.c:函数关系简图

1
2
3
4
5
6
7
8
9
10
ext4_sync_file
├─ file_write_and_wait_range
├─ 无 s_journal → ext4_fsync_nojournal
│ ├─ sync_mapping_buffers
│ ├─ sync_inode_metadata(视 datasync 与 I_DIRTY_*)
│ └─ ext4_sync_parent(NEWENTRY 链)
├─ data journal → ext4_force_commit
└─ else → ext4_fsync_journal → ext4_fc_commit(journal, commit_tid)
└─ blkdev_issue_flush(若 needs_barrier)
└─ file_check_and_advance_wb_err

8. 小结

  • fsmap.c 面向 「物理块 → 类别/所有者」整卷查询,与 mballoc、组描述符、外置日志 紧耦合,是 Oracle getfsmap / xfs_io 生态在 ext4 上的后端。
  • fsync.c 面向 与应用契约的持久化数据先等页缓存元数据(及可选 fast commit)再等 journal,并处理 无日志目录新建barrier/写错

分析 ioctl 入口时可在 ext4_ioctl.c 中搜索 FS_IOC_GETFSMAP/ext4_getfsmap;分析 fsync 与 fast commit 细节可对照 fast_commit.cext4_jbd2.h 中的 ext4_update_inode_fsync_trans

RK3588 kernel-6.1 fs/ext4/ialloc.c inode 分配与回收分析

RK3588 kernel-6.1 fs/ext4/ialloc.c inode 分配与回收分析

1. 分析范围

  • rk3588/kernel-6.1/fs/ext4/ialloc.cinode 位图读写校验、新建 inode释放 inodeOrlov/目录投递策略orphan 校验统计与 lazyinit inode 表清零
  • 协作:namei.c / inode.c(调用 __ext4_new_inode)、ext4_jbd2(位图与组描述符日记)、xattr.c/acl.c/fscrypt(新 inode 初始化)、mballoc/flex(组内空闲簇统计与 s_flex_groups

2. 代码作用(一句话)

ialloc.c 用每个块组一张 inode 位图 + 组描述符中的空闲 inode/目录计数 管理 inode 号的分配与回收;新建时按 目录 Orlov、普通文件近父组+二次探测block group,在位图中 find_next_zero_bit事务内置位,顺带处理 BG_INODE_UNINIT/BG_BLOCK_UNINITflex_bg 计数与 配额/ACL/extent 树 初始化;释放时先 ext4_clear_inode 再清位图,避免 inode 号快速复用导致别名


3. 解决了什么问题

问题 处理方式
目录集中导致单块组 inode 或目录密度过大 find_group_orlov:顶级/标记 TOPDIR 的目录可按 目录名 hash 或随机 分散到 flex_bg;一般子目录在 空闲 inode/簇不低于均值、目录数不过阈 的组中挑选,失败再 fallback 到「空闲 inode ≥ 组均值」的线性搜索
同一目录下普通文件尽量同组、又避免与邻目录过度混放 find_group_other:优先 父组(flex 内扫描);不行则用 (parent_group + i_ino) % ngroups 为种子做 二次增量探测 + 最后线性扫
块组 inode 位图未初始化(INODE_UNINIT ext4_read_inode_bitmap:在 gdt csum 下可将位图 内存填 0 并 ext4_mark_bitmap_end 标记已用尾部,避免读盘;新建 inode 时可能触发 block bitmap 首次初始化BLOCK_UNINIT
inode 位图损坏或未授权重放 ext4_validate_inode_bitmapCRC 校验EXT4_SIM_*EXT4_MB_GRP_IBITMAP_CORRUPT拒绝使用
释放 inode 后立即复用同一编号与旧 dentry/缓存打架 ext4_free_inode:注释强调须先 ext4_clear_inode清位图无 journalfind_inode_bit + recently_deleted 尽量避免 刚删未落盘的 inode 槽
组内最后一位被并发抢走 __ext4_new_inodeext4_test_and_set_bit 失败持锁重搜 或换 next_group 循环
goal/保留 inode 范围 goal 可强制 起始组与位偏移EXT4_FIRST_INO 以下视为保留,若被清出则 报错并标位图损坏
orphan 链表恢复 ext4_orphan_get位图须仍置位 表示 inode 仍被占用;检查 ext4_can_truncate/NEXT_ORPHAN 等,防止 坏 orphan 死循环
lazyinit 未完成时 inode 表含垃圾 ext4_init_inode_tablealloc_sem 写锁 阻塞同组 ext4_new_inode,对 INODE_ZEROED 的表 sb_issue_zeroout 并置 BG_INODE_ZEROED
ext4 与内核/调试统计 ext4_count_free_inodes / ext4_count_dirs;DEBUG 下可 逐组对账位图与 gdp 计数

4. 核心流程

4.1 inode 位图读入:ext4_read_inode_bitmap

  • gdpinode bitmap 块号,范围自检。
  • INODE_UNINIT零填充 + 尾部 mark used,标 uptodate/verified,不读盘。
  • 否则 读盘ext4_validate_inode_bitmapext4_inode_bitmap_csum_verify)。

4.2 新建:__ext4_new_inode

  1. new_inode、owner/ fscrypt_prepare_new_inodedquot_initialize,按需累加 xattr 事务额度
  2. 选组goal → 否则目录 find_group_orlov,文件 find_group_other;更新 dir->i_last_alloc_group
  3. 每个候选组:gdp 空闲计数为 0ibitmap corrupt 则跳过;读 inode bitmapfind_inode_bit 找 0 bit。
  4. journal get_write_access 位图 → 置位(冲突则重找)→ dirty 位图
  5. BLOCK_UNINIT:读并 dirty block bitmap,清 EXT4_BG_BLOCK_UNINIT,初始化 free_clusters
  6. 更新 gdpfree_inodes--、目录则 used_dirs++,维护 bg_itable_unused / INODE_UNINITinode bitmap / gdp csum
  7. 维护 percpu_counterflex free_inodes/used_dirs
  8. 填写 ext4_inode_infoi_block_groupi_csum_seedEXTENT 标志 + ext4_ext_tree_init(目录/ reg / symlink)、i_sync_tid 等,insert_inode_lockedACL/security/encrypt xattrext4_mark_inode_dirty

4.3 释放:ext4_free_inode

前置条件检查i_count/i_nlink)→ dquot_free_inodeext4_clear_inode → 算 group/bit → 读位图、journal 访问清 bit(若已清则 报错 corrupt)→ 更新 gdp 空闲 inode、目录计数、flex、percpu重算 inode bitmap csum / gdp csumdirty

4.4 辅助出口

  • ext4_mark_inode_used:重放或工具路径 强制在位图中置位 并更新计数(与 __ext4_new_inode 不同,不创建 VFS inode)。
  • ext4_orphan_get:挂载 orphan 处理。
  • ext4_init_inode_tablelazyinit 后台 inode 表零填充

5. 小结

ialloc.c 是 ext4 inode 编号空间 的核心:位图一致性(校验和 + corrupt 标记)目录/文件的局部性与负载均衡(Orlov + flex)与 lazyinit、日记、配额、extent 初始化 的衔接都在此收敛。分析 creat/mkdir/unlink 时,分配侧看 __ext4_new_inodefind_group_*,回收侧看 ext4_free_inodeext4_evict_inode(在 inode.c)的调用关系。

RK3588 kernel-6.1 fs/ext4/indirect.c 传统间接块映射分析

RK3588 kernel-6.1 fs/ext4/indirect.c 传统间接块映射分析

1. 分析范围

  • rk3588/kernel-6.1/fs/ext4/indirect.c未启用 extent 时 inode 内 i_data[]直接块 + 一/二/三级间接 树:查找、分配、截断、打洞
  • 调用方:inode.cext4_map_blocks(无 EXT4_INODE_EXTENTS 时走 ext4_ind_map_blocks)、截断与 punch hole 路径
  • 不覆盖:extent 树(见 extents.c

2. 代码作用(一句话)

indirect.c 实现 classic ext2 式 「inode 根部指针 + 多级间接块」:把逻辑块号 ext4_block_to_path 解析为各级索引,ext4_get_branch** 读链;若缺块且允许创建则 ext4_alloc_branch 分配元数据与数据块并 ext4_splice_branch 挂回树;ext4_ind_truncate / ext4_ind_remove_space 通过 ext4_find_shared + ext4_free_branches / ext4_free_data 回收子树并配合日记。** 全程依赖 i_data_semext4_jbd2。**


3. 解决了什么问题

问题 处理方式
无 extent 时如何把逻辑块映射到物理块 ext4_ind_map_blocks:路径深度 + ext4_get_branch;命中则尽量 向前合并连续指针first_block + count)扩大 m_len
逻辑块号超出三间接可表示范围 ext4_block_to_path:越界 ext4_warningdepth=0
间接块中指针越界或损坏 ext4_get_branchkey > ext4_blocks_count-EFSCORRUPTED;读盘后 ext4_check_indirect_blockref
分配新链时崩溃中间态 注释:先分配整条链再 splice,失败则 ext4_alloc_branchext4_free_blocks 释放,不从未完成链可达,减少恢复复杂度
块分配局部性 ext4_find_near:向左找最近已分配块 → 否则靠近 间接块本体 → 否则 ext4_inode_to_goal_block(同组 + PID 着色减少并发冲突);ext4_find_goal 截断到 32 位物理块上限
单次创建需要多少数据块(跨 indirect 边界) ext4_blks_to_allocateblocks_to_boundary(来自 ext4_block_to_path
bigalloc 与非 extent 不兼容 ext4_ind_map_blocks 创建路径:ext4_has_feature_bigalloc-EFSCORRUPTED(须 extent)
截断到更短 i_size ext4_ind_truncateext4_es_remove_extent 同步 ES 树;i_disksize = i_sizeext4_find_shared 找与 最后一个保留块 共享的路径,只释放 尾部;再按 offsets[0] 逐级 ext4_free_branches 拆掉 整棵一级/二级/三级 子树
punch hole(中间挖洞) ext4_ind_remove_space:对 [start,end)同层深度 / 跨层 情况,组合 ext4_find_sharedext4_free_branches,与 ext4_punch_hole 衔接
日记事务额度估算 ext4_ind_trans_blocks~N/每块指针数 + 4(上界经验公式)
事务重启与 i_data_sem 死锁 ext4_ind_trunc_restart_fn / ext4_ind_truncate_ensure_credits:与 extents.cext4_ext_trunc_restart_fn 同类——放下 i_data_semext4_discard_preallocations

4. 核心数据结构:Indirect

1
2
3
4
5
typedef struct {
__le32 *p;
__le32 key;
struct buffer_head *bh;
} Indirect;
  • p:当前层 指向下一层块号的槽位(inode 内或某间接块 buffer)。
  • key:该槽位上的 块号(le32)
  • bh:本层若为间接块则非 NULL。

ext4_get_branchinode->i_data 起沿 offsets[] 填充 chain[],遇 0 指针 返回 残缺链 供分配。


5. 分配路径概要

  1. ext4_block_to_pathdepthoffsetsblocks_to_boundary
  2. ext4_get_branch → 若完整:映射成功(可扫连续块)。
  3. 无 CREATE:按 子树空洞大小m_len(hole)
  4. CREATEindirect_blks = 从断点到叶子还差几层元数据;ext4_alloc_branchext4_new_meta_blocks 建中间层、ext4_mb_new_blocks 建数据;ext4_splice_branch 写回父指针、更新 i_blocks 等。
  5. delallocEXT4_GET_BLOCKS_DELALLOC_RESERVEext4_da_update_reserve_space

6. 锁定约定(与注释一致)

  • 仅查找down_read(i_data_sem)
  • 可能分配down_write(i_data_sem)(由 inode.c 在调用 ext4_ind_map_blocks 前保证)。

7. 小结

indirect.c 是 ext4 对 传统 bitmap+i_block[] 布局 的完整实现;现代默认 extent 文件 主要走 extents.c,本文件仍用于 未转换的老布局、特殊场景或 tests。阅读 ext4_map_blocks 时:EXT4_INODE_EXTENTS 置位 → extent 路径否则 → ext4_ind_map_blocks。与 s_bitmap_maxbytesfile.c/truncate 与本文 max_block)共同约束 非 extent 文件最大逻辑长度

RK3588 kernel-6.1 fs/ext4/inline.c 内联数据与内联目录分析

RK3588 kernel-6.1 fs/ext4/inline.c 内联数据与内联目录分析

1. 分析范围

  • rk3588/kernel-6.1/fs/ext4/inline.csmall file / symlink / directoryinode 体内 存放数据或目录项;与 xattr.cinode.cdir.ciomap.c 协作)
  • 盘上表示:EXT4_INODE_INLINE_DATA;内容分为 ext4_inode->i_block 前一段固定长度 + 可选 system xattr "data" 的值域(见 EXT4_XATTR_SYSTEM_DATA

2. 代码作用(一句话)

inline.ci_extra_isize 与 inode 内 xattr 空间 允许时,把 小文件数据、快符号链接文本或整块小目录 塞进 inode 块;通过 ext4_find_inline_data_nolockiget 时定位 "data" xattr 与 i_inline_off/i_inline_size;读路径 readpage/iomapinline 映射为 IOMAP_INLINE 或填充第 0 页;写/增长若超出 ext4_get_max_inline_sizeext4_convert_inline_data_to_extent 等形式 转 extent + 普通块;目录在 i_block 里编码 .. 与线性 ext4_dir_entry_2,满则 ext4_convert_inline_data_nolock 落盘为传统目录块。


3. 解决了什么问题

问题 处理方式
极小文件仍占一整数据块,浪费空间与元数据 I/O inline data:数据在 i_block[] + xattr 扩展区;ext4_try_to_write_inline_data 优先扩 xattr,失败再 转换
符号链接目标短却占块 ext4_read_inline_link 等从 inline 区域读 快链 内容
空/小目录仅含少量项却要数据块 inline diri_block 头存 .. 的 ino,后继 ext4_dir_entry_2ext4_try_add_inline_entry 先塞 inode 内槽,再尝试 xattr 扩容,再 ext4_convert_inline_data_nolock
与 extent/块映射路径统一 超过容量或 ext4_prepare_inline_data 返回 -ENOSPCext4_convert_inline_data_to_extentext4_destroy_inline_data_nolock__block_write_begin + ext4_get_block 写道 → 置 extent;失败走 orphan + ext4_truncate_failed_write
延迟分配与 inline 交织 ext4_da_write_inline_data_begin / ext4_da_convert_inline_data_to_extentext4_convert_inline_dataMAY_INLINE 已清filemap_flush 收尾中途转换
readdir/htree 与 inline 目录 ext4_read_inline_dirext4_inlinedir_to_tree:合成 ./.. 假项,校验 ext4_check_dir_entry,向 fname 树 填哈希项(与 dir.c 一致)
iomap/DIO 需知道数据在 inode 里 ext4_inline_data_iomapIOMAP_INLINEaddr 指向 盘上 inode 缓冲区内 i_block 偏移
截断缩短 inline 文件 ext4_inline_data_truncate:同步缩 xattr 值i_block 尾部;必要时 ext4_es_remove_extent 清 stale ES
xattr 与 inline 空间竞争 get_max_inline_xattr_value_size:在 ibody xattr 布局下算 剩余可用值大小ext4_get_max_inline_size = 该值 + EXT4_MIN_INLINE_DATA_SIZE
外部 xattr inode 不允许挂 inline ext4_find_inline_data_nolocke_value_inum 非 0 → -EFSCORRUPTED
加密目录名等 inline 路径仍走 ext4_filename / fscrypt 与上层 namei(本文件侧重 存储格式

4. 存储布局(概念)

区域 含义
raw_inode->i_blockEXT4_MIN_INLINE_DATA_SIZE 文件/链接:内联数据前段;目录:前 EXT4_INLINE_DOTDOT_*.. parent ino,后方为目录项区起始
system xattr "data" 额外内联字节;i_inline_off 指向 xattr 项在 inode 映像中的偏移i_inline_size = 固定头 + xattr 值长

ext4_has_inline_dataEXT4_INODE_INLINE_DATA 等标志(定义见 ext4.h)。


5. 核心入口(导航)

符号 用途
ext4_get_max_inline_size / ext4_find_inline_data_nolock 容量与 iget 时发现 inline
ext4_create_inline_data / ext4_update_inline_data / ext4_prepare_inline_data 新建或扩容 inline
ext4_destroy_inline_data* 去掉 "data" xattr 与标志,恢复 extent 可建
ext4_readpage_inline / ext4_read_inline_data 读页/内存拷贝
ext4_try_to_write_inline_data / ext4_write_inline_data_end 缓冲写路径
ext4_convert_inline_data_to_extent 同步转块
ext4_try_add_inline_entry / ext4_delete_inline_entry / empty_inline_dir 目录项增删与空目录判断
ext4_try_create_inline_dir 创建时尝试 inline 目录
ext4_read_inline_dir / ext4_inlinedir_to_tree 遍历 / htree 装树
ext4_inline_data_iomap / ext4_inline_data_truncate / ext4_convert_inline_data iomap、截断、强制转换

6. 锁与兼容性

  • xattr_sem 保护 inline 元数据与 xattr 布局ext4_write_lock_xattr 在修改 xattr/inline 时用。
  • i_data_sem截断 等与 extent 共享逻辑时使用。
  • EXT4_STATE_MAY_INLINE_DATA尚允许继续 inlineENOSPCDIO 路径(见 file.c)可 清除,迫使 走块设备

feature:需 inline_data 等与 足够大的 s_inode_size/extra isizebigalloc 与旧式 indirect 组合由其它文件约束,inline 转换后统一走 extent 映射


7. 小结

inline.c「元数据即数据」 做到极致,显著减少 小文件/小目录/短 symlink块分配与碎片;代价是实现 与 xattr、日记、page cache、extent、目录哈希、加密 的多路径桥接。分析某 inode 是否 inline,看 EXT4_INODE_INLINE_DATAext4_has_inline_data;分析读写失败是否 触发转换,搜索 ext4_convert_inline 调用链。

RK3588 kernel-6.1 fs/ext4/inode.c 分析

RK3588 kernel-6.1 fs/ext4/inode.c 分析

1. 分析范围

  • rk3588/kernel-6.1/fs/ext4/inode.c(约 6300+ 行,ext4 inode 生命周期与块映射的核心枢纽
  • 不替代子模块细节:extent / indirect / inline / xattr / quota 等仍要以对应 .c 文件 为准;inode.c 负责 编排、锁、日记额度、VFS 回调入口

2. 代码作用(一句话)

inode.c 实现 盘上 inode 与内存 inode/ext4_inode_info 的同步__ext4_iget** 读 inode 表、校验 inode 校验和非法/已删 inode 规则ext4_map_blocksextent 状态树ext4_ext_map_blocks/ext4_ind_map_blocks 之间搭桥并维护 i_data_semext4_setattr/ext4_truncate/ext4_evict_inode 处理 截断、orphan、unlink 回收ext4_write_inode/ext4_mark_iloc_dirty 对接 回写与 journal;并包含 延迟分配(delalloc)get_block/iomap 辅助inode 标志与 jbd2 jinode 等大量横切逻辑。**


3. 解决了什么问题

问题 inode.c 中的处理方式
逻辑块如何映射到物理块(extent / indirect 统一入口) ext4_map_blocks:非 FC_REPLAY 时先 ext4_es_lookup_extent;未创建则 i_data_sem 读锁只读映射EXT4_GET_BLOCKS_CREATE 等则 写锁ext4_ext_map_blocks / ext4_ind_map_blocks;新映射 ext4_es_insert_extentEXT4_GET_BLOCKS_ZEROext4_issue_zerooutcheck_block_validity 防越界
从磁盘装入 VFS inode __ext4_igetiget_locked__ext4_get_inode_loc_noinmemextra_isize / metadata_csum / i_size / i_flags 检查;死 inode(nlink==0)配额 inode 号 等边界;ext4_find_inline_data_nolockextent 树初始化unlock_new_inode
截断与崩溃恢复 ext4_truncate:inline 先 ext4_inline_data_truncateext4_orphan_addi_data_sem + ext4_ext_truncate / ext4_ind_truncate;注释说明 orphan + 重放 语义
删除文件的最后阶段 ext4_evict_inodei_nlink!=0 分支处理 data=journal 下页缓存与 jbd2i_nlink==0ordered truncatejournal start、快 symlink 清 i_dataext4_truncateext4_xattr_delete_inodeext4_free_inode;失败路径 orphan 清理fast commiti_fc_listineligible 标记
chown/chmod/截断属性 ext4_setattrimmutable/appendfscrypt/fsverity配额 dquot_transferATTR_SIZEordered 数据DIO waitlayoutorphan、i_disksize 与截断(长注释说明与页刷盘的顺序约束)
回写 super 的 write_inode 有 journalWB_SYNC_ALL 且非 for_syncext4_fc_commit(..., i_sync_tid)无 journal:取 ilocsync_dirty_buffer
延迟分配簇预留 ext4_da_update_reserve_spaceext4_da_get_block_prepext4_alloc_da_blocksext4_da_release_space 等(与 mballocES 树 协作)
页缓存与块设备 ext4_get_block / ext4_get_block_unwrittenext4_bread_batchext4_walk_page_buffersext4_set_aops(对 address_space_operations 赋值)
打洞、部分块清零 ext4_punch_holeext4_zero_partial_blocksext4_update_disksize_before_punch
DAX / lease / mmap 冲突 ext4_break_layouts
ordered journal 需要跟踪 inode 数据事务 ext4_inode_attach_jinode
inode 元数据校验和 ext4_inode_csum_verify / ext4_inode_csum_setext4_mark_iloc_dirty 路径写回时设置)
扩大 inode extra 区域 ext4_expand_extra_isize
切换 inode 的 data journal 标志 ext4_change_inode_journal_flag
估算事务块数 ext4_writepage_trans_blocksext4_chunk_trans_blocks

4. 模块结构(阅读地图)

1
2
3
4
5
6
7
8
9
10
11
inode.c(逻辑分组)
├── 校验与标志:inode csum,ext4_set_inode_flags,ext4_inode_is_fast_symlink
├── 生命周期:__ext4_iget,ext4_write_inode,ext4_evict_inode,ext4_dirty_inode
├── 块映射核心:ext4_map_blocks(+ check_block_validity, issue_zeroout)
├── get_block / mpage / buffer 行走:ext4_get_block*,bread_batch,walk_page_buffers
├── 延迟分配:ext4_da_*,ext4_alloc_da_blocks,da_update_reserve_space
├── address_space:ext4_set_aops,与 readpage/write_begin 等衔接
├── 截断与洞:ext4_truncate,punch_hole,zero_partial_blocks,can_truncate
├── 属性:ext4_setattr,ext4_getattr,ext4_file_getattr
├── iloc / 脏:ext4_get_inode_loc,ext4_mark_iloc_dirty,expand_extra_isize
└── journal 辅助:attach_jinode,change_inode_journal_flag,wait_for_tail_page_commit

5. ext4_map_blocks 要点(与 extents.c / indirect.c / extents_status.c 的分界)

  1. ext4_es_lookup_extent:命中 written/unwritten/delayed/hole 则直接填 mapCACHED_NOWAIT 可早退)。
  2. 只查盘上树i_data_semextent → ext4_ext_map_blocks(..., create=0),否则 ext4_ind_map_blocks(..., create=0);若有结果则 写 ES(混合 delayed 标记逻辑)。
  3. 创建 / 转换 unwritten:清 m_flagsi_data_sem,再 create=flagsext/ind
  4. 新块:可选 zeroout 后再 ext4_es_insert_extent
  5. 释放写锁out_sem 处理错误码。

6. __ext4_iget 要点

  • 非法 ino(保留 quota/orphan_file、越界等)→ -EFSCORRUPTED/-ESTALE
  • 已缓存check_igot_inode 做一致性断言。
  • i_extra_isize、负 i_size、dir_index 与 INDEX 标志不兼容 metadata_csum 等 → 损坏
  • 填充 uid/gid/projid、nlink、i_flags、i_blocks ACL 块、inline、extent、symlink 等后 unlock_new_inode

7. 小结

inode.c 是 ext4 的 「中心调度」:把 VFS、日志、块分配、extent/indirect/inline、ES 缓存、orphan、配额、加密/verity 连成可断言的序。深入 某一算法(例如 extent 插入)应进入 extents.c;深入 iget 之后的 raw 字段解析 可对照 ext4.hstruct ext4_inode

阅读顺序建议:ext4_map_blocksext4_truncate/ext4_setattr(ATTR_SIZE)__ext4_igetext4_evict_inode,再按需下钻 delallocext4_set_aops 相关符号。

RK3588 kernel-6.1 fs/ext4/ioctl.c 用户态控制接口分析

RK3588 kernel-6.1 fs/ext4/ioctl.c 用户态控制接口分析

1. 分析范围

  • rk3588/kernel-6.1/fs/ext4/ioctl.cext4_ioctl / ext4_compat_ioctl 及若干 ioctl 辅助函数
  • 入口挂载于 ext4_file_operations.unlocked_ioctl(见 file.c);ext4_file_inode_operations.fileattr_get/set通用 fsxattr 转到本文件的 ext4_fileattr_*ext4_ioctl_setflags / ext4_ioctl_setproject

2. 代码作用(一句话)

ioctl.c管理员与工具链 对 ext4 的控制请求集中到 __ext4_ioctlswitch:含 在线扩容、extent 迁移、延迟块刷盘、在线整理(move extent)、TRIM、卷标/UUID、加密/fsverity、getfsmap、journal checkpoint/shutdown、extent 状态缓存查询 等;并实现 超块(主+备份)批量更新inode 标志位(chattr 语义)与 project id 的安全修改。


3. 解决了什么问题

问题 处理方式
用户态需要改 i_flags(immutable、append、extent、DAX、casefold dir 等) ext4_ioctl_setflagsCAP_SYS_RESOURCE 才能动 JOURNAL_DATADAX 与 verity/互斥标志 校验 dax_compatibleCASEFOLD 仅空目录且具备 casefold featureEXTENTS_FL 切换可触发 migrate;改前 inode_dio_wait + 写回脏页ext4_fileattr_set 先掩 EXT4_FL_USER_MODIFIABLE 再调 ext4_ioctl_check_immutable
project quota ext4_ioctl_setproject(feature 开关下编译)
NFS 等依赖的 i_generation EXT4_IOC_GET/SETVERSION(_OLD)metadata_csumSET 被拒绝(-ENOTTY) 以免破坏校验模型
在线扩卷 / 加块组 EXT4_IOC_GROUP_EXTENDEXT4_IOC_GROUP_ADDEXT4_IOC_RESIZE_FS:与 resize.c 协作;journal 路径 lock + flushbigalloc 对部分命令 -EOPNOTSUPPresizefast commit ineligible
间接块文件迁到 extent EXT4_IOC_MIGRATEext4_ext_migrate
强制落延迟分配 EXT4_IOC_ALLOC_DA_BLKSext4_alloc_da_blocks
交换启动扇区 inode EXT4_IOC_SWAP_BOOT
块设备 discard 告知 SSD FITRIMext4_trim_fsCAP_SYS_ADMINNOLOAD+有 journal-EROFS
把另一文件的 extent 搬到本文件(defrag) EXT4_IOC_MOVE_EXText4_move_extentsbigalloc/DAX 不支持
卷标、UUID FS_IOC_GET/SETFSLABELEXT4_IOC_GET/SETFSUUID;UUID 变更在 metadata_csum 且无 csum_seed 等条件下 -EOPNOTSUPP
物理块占用图 FS_IOC_GETFSMAPext4_ioc_getfsmap(见 fsmap.c 文档
fscrypt FS_IOC_SET/GET_*_ENCRYPTION_*ADD/REMOVE_ENCRYPTION_KEYfscrypt 子系统
fsverity FS_IOC_ENABLE/MEASURE/READ_VERITY_METADATA
extent 预读 EXT4_IOC_PRECACHE_EXTENTS
调试/一致性:看内存 ES 缓存 EXT4_IOC_GET_ES_CACHEfiemap 形态)、EXT4_IOC_CLEAR_ES_CACHE
超块状态查询、强制关机 EXT4_IOC_GETSTATEEXT4_IOC_SHUTDOWNext4_shutdown:默认 / flush log / no flush)
journal checkpoint EXT4_IOC_CHECKPOINTCAP_SYS_ADMIN,标志控制 flush 等
32 位兼容 ioctl 编号 ext4_compat_ioctl:把 EXT4_IOC32_* 映射到 native 或 手工 unpack compat_ext4_new_group_input

超块一致性: ext4_update_superblocks_fn:抢 EXT4_FLAGS_RESIZING;事务内更新 主超块 + 至多两个备份(其余备份 非日记或同步写);ext4_update_backup_sb 校验 备份超块 csum


4. __ext4_ioctl 命令表(按功能归类)

类别 命令(节选)
映射/空间 FS_IOC_GETFSMAPFITRIM
inode 代际 *EXT4_IOC_GETVERSION / SETVERSION / _OLD
卷管理 EXT4_IOC_GROUP_EXTENDEXT4_IOC_GROUP_ADDEXT4_IOC_RESIZE_FS
文件布局 EXT4_IOC_MIGRATEEXT4_IOC_MOVE_EXTEXT4_IOC_PRECACHE_EXTENTSEXT4_IOC_ALLOC_DA_BLKS
启动扇区 EXT4_IOC_SWAP_BOOT
加密 FS_IOC_ENCRYPTION
verity FS_IOC_ENABLE/MEASURE/READ_VERITY_METADATA
ES 树 EXT4_IOC_GET_ES_CACHEEXT4_IOC_CLEAR_ES_CACHE
运行状态 EXT4_IOC_GETSTATEEXT4_IOC_SHUTDOWNEXT4_IOC_CHECKPOINT
卷标识 FS_IOC_GET/SETFSLABELEXT4_IOC_GET/SETFSUUID

未在 switch 中出现的 旧式 EXT4_IOC_GET/SETFLAGSVFS fileattr 路径覆盖(ext4_fileattr_get/set)。


5. 与其它文件的边界

需求 实现主体
实际搬 extent move_extent.c
resize / group add resize.c
trim 算法 mballoc.c / ioctl.c 仅转发
getfsmap 遍历 fsmap.c
设置加密策略细节 fscrypt

6. 小结

ioctl.c 几乎不负责 「算法本身」,负责 权限、拷贝用户内存、与 mnt_want_write、日记 handle、feature 位、bigalloc/DAX 限制 的守门,并把请求 委派到 ext4 其它模块或内核通用子系统。新增 ioctl 时应在本文件 __ext4_ioctl 增加 case,并评估 compatCAP_ / mnt* 语义。

RK3588 kernel-6.1 fs/ext4/mballoc.c 多块分配器(mballoc)分析

RK3588 kernel-6.1 fs/ext4/mballoc.c 多块分配器(mballoc)分析

1. 分析范围

  • rk3588/kernel-6.1/fs/ext4/mballoc.c(约 6700+ 行,ext4 数据块/簇 的主分配与释放实现)
  • 头文件与结构:mballoc.hext4_allocation_request / ext4_allocation_contextext4_group_info 中的 buddy/prealloc 字段
  • 调用方:extents.c / indirect.cext4_mb_new_blocks)、inode.c(延迟分配、ext4_claim_free_clusters 等)、ioctl.cFITRIMext4_trim_fs)、fsmap.cext4_mballoc_query_range

2. 代码作用(一句话)

mballoc.c块组粒度 上维护 位图 + buddy 树(通过 s_buddy_cache 伪 inode 的页缓存 装载),按 goal、stripe、CR(cr 0/1) 与可选 mb_optimize_scan 选择块组;分配路径 优先吃 inode 预分配 / per-CPU 局部性预分配,否则 规范化请求长度 后在 buddy 上 查找、分裂、标记已用,并写回 块位图、gdt 空闲计数、簇计数bigalloc 下以 为 accounting 单位);释放路径 ext4_free_blocks 清位图、buddy、quota,并支持 日记 forget/revoke;另提供 TRIM(discard)对空闲区间的枚举回调(getfsmap)。


3. 解决了什么问题

问题 处理方式
传统按块扫描位图,大块分配慢且碎片严重 buddy:按 2 的幂阶 合并/分裂空闲区,快速找到 足够长的连续空闲簇
每分配一次就写位图,小文件随机写放大 预分配(PA)inode 级 i_prealloc_list(按 逻辑块 覆盖)、per-CPU s_locality_groups(按 剩余空闲 消费);未用完的规范化余量 挂回 PA 列表
小文件也希望物理相邻 小请求(低于 s_mb_stream_request,可 sysfs 调)倾向 组级预分配,使小文件 同组聚集
大块/顺序写希望一次拿长连续区 ext4_mb_normalize_request:按 文件大小启发mb_group_preallocstripe放大申请长度,多拿的进 PA
多 CPU 争用同一 free list locality group 按 CPU 分桶;buddy 页在组锁 ext4_lock_group 下更新
buddy 与位图占用内存 s_buddy_cache:按需 ext4_mb_load_buddy,位图+buddy 两 block/组 映射到 页缓存;卸载丢弃
选组从 O(N) 扫描 可选 mb_optimize_scans_mb_largest_free_orders(最大空闲阶链表)、s_mb_avg_fragment_size(按 bb_free/bb_fragments 分桶),注释称 CR0/CR1 阶段近似 O(1) 选组
RAID stripe 对齐 s_stripeext4_mb_scan_aligned 等,尝试 与条带对齐的连续分配
配额与全局空闲簇 ext4_claim_free_clusters / dquot_alloc_blockext4_mb_new_blocks 开头);失败时 缩短 len 重试-EDQUOT/-ENOSPC
延迟分配已预留簇 EXT4_MB_DELALLOC_RESERVED跳过 claim/quot 中的 “必须有可用块” 分支,与 s_dirtyclusters_counter 配合
fast commit 重放 ext4_mb_new_blocks_simple / ext4_free_blocks_simple:简化路径
释放时集群边界 ext4_free_blocksEXT4_PBLK_COFF / EXT4_LBLK_COFF 处理 首尾部分簇EXT4_FREE_BLOCKS_NOFREE_* 控制是否动边界簇
释放块合法性 ext4_inode_block_valid(除非 VALIDATED);防 释放数据区外块
FITRIM / SSD discard ext4_trim_fs:按 fstrim_range 遍历块组,ext4_trim_all_free够长的空闲簇 sb_issue_discard(受 granularity、minlen 约束)
getfsmap 需要枚举组内空闲 ext4_mballoc_query_range:在 [start,end] 簇 内对 0 bit 段formatterfsmap.c 使用)
在线加组后 buddy 初始化 ext4_mb_init_groupext4_mb_add_groupinfo 等与 resize 协作

4. 主路径概要

4.1 分配:ext4_mb_new_blocks

  1. TRACE / FC_REPLAY → simple。
  2. 非 delalloc reservedext4_claim_free_clusters + dquot_alloc_block(配额文件可用 nofail)。
  3. ext4_mb_initialize_context:填充 ext4_allocation_context(含 goal、flags)。
  4. ext4_mb_use_preallocated:命中 inode PA 则直接 ext4_mb_mark_diskspace_used
  5. 否则 ext4_mb_normalize_requestext4_mb_pa_alloc(创建/填充上下文中的 PA)→ ext4_mb_regular_allocatorext4_mb_find_by_goal / simple / complex / scan_aligned` 等)。
  6. ext4_mb_mark_diskspace_used:日记 get_access、位图置位、buddy 更新、gdt、ext4_es_insert_extent 由调用方负责。
  7. 失败:ext4_mb_pa_free / ext4_discard_allocated_blocks、释放配额预留。

4.2 释放:ext4_free_blocks

可选 ext4_forget(元数据或 data journal 语义)→ ext4_mb_clear_bb(清位图、更新 buddy、gdt、percpu_counter 等)。


5. 调试与 sysfs(文件头注释摘录)

可调参数包括 mb_stream_reqmb_group_preallocmb_min_to_scanmb_max_to_scanmb_order2_reqmb_linear_limit 等与 stripemb_optimize_scan 挂载选项;/sys/fs/ext4/<dev>/ 下暴露。


6. 小结

mballoc.c 是 ext4 数据区空间管理 的核心:在块组内用 buddy 做连续空闲管理,在文件系统级用 PA 做吞吐与局部性;与 bigalloc(簇)quota、journal、FC 重放、discard、fsmap 深度绑定。阅读 ext4_map_blocksext4_mb_new_blocks 时,应以 ext4_allocation_request 各字段(goal/logical/len/flags 为线索;阅读 碎片与延迟分配 时结合 extents_status.cinode.c delalloc

深层算法细节(mb_mark_used 分裂 buddy、ext4_mb_good_group_nolock、prefetch 等)可在本文件内按符号继续下钻,本文不展开到函数级伪代码。

RK3588 kernel-6.1 fs/ext4/move_extent.c 在线 extent 迁移(defrag)分析

RK3588 kernel-6.1 fs/ext4/move_extent.c 在线 extent 迁移(defrag)分析

1. 分析范围

  • rk3588/kernel-6.1/fs/ext4/move_extent.cEXT4_IOC_MOVE_EXTe4defrag 等工具触发的 两文件间逻辑区间与底层块映射交换
  • 协作:extents.c 中的 ext4_swap_extentsioctl.c(命令分发与 bigalloc/DAX 拒绝)、inode.cext4_get_block

2. 代码作用(一句话)

同一 superblock均为 extent 普通文件非 data=journal / 非加密 等前提下,ext4_move_extents** 按 原文件逻辑块范围 遍历 extent,逐 调用 move_extent_per_page加日记双文件页锁 → 若仍为 unwritten 可仅 ext4_swap_extents;否则 把原页数据读 uptodatefilemap_release_folio 尝试丢旧映射ext4_swap_extents 交换两文件在该区间的物理映射ext4_get_block + block_commit_write 把旧数据写回 原 inode 上新的物理块ext4_jbd2_inode_add_write;失败时 repair_branches 尝试 再 swap 回去 以防元数据与数据不一致。**


3. 解决了什么问题

问题 处理方式
大文件 extent 碎片严重,顺序读性能差 用户态用 卷上空白 donor 文件 预占位,再通过 move extent原文件某段 映射 换到 donor 已持有的连续物理区,从而减少碎片(在线 defrag 核心思路之一)
两 inode 同时改 i_data_sem 死锁 ext4_double_down_write_data_sem:按 inode 指针地址顺序 加锁;ext4_double_up_write_data_semorig/donor 实参顺序 释放(与加锁顺序配对)
journal_start、页fault、read_folio 交叉死锁 进入 move_extent_per_page 的事务后 抬起 i_data_sem(注释 a/b),只用 页锁 与 extent 路径.serialize
unwritten extent 在换映射前被其它路径初始化 持页锁后 mext_check_coverage 再扫一遍;若 已非全 unwritten 则走 数据拷贝路径mext_page_mkuptodate)而非纯 swap
swap 成功但 block_commit_write 失败 repair_branches:再 ext4_swap_extents 试图复原;若仍失败 ext4_error_inode_block
ENOSPC / 缓冲区被 journal 钉住 -EBUSY ext4_should_retry_allocjbd2_journal_force_commit_nested重试
非法参数与安全 mext_check_arguments仅 extent同 FSregular非 swapfiledonor 非 immutable/append/suid 组合限制逻辑起点按页对齐len 按 EOF 截断
与 DIO、截断并发 外层 lock_two_nondirectoriesinode_dio_waitext4_double_down_write_data_sem 包住整段循环
预分配与旧 goal 干扰 循环结束后若 moved_len!=0ext4_discard_preallocationsorig/donor

不支持(由 ioctl.c/ext4_move_extents 组合):bigallocDAXdata=journalingfscrypt(本文件内 IS_ENCRYPTED 检查)。


4. 调用链简图

1
2
3
4
5
6
7
8
9
10
11
12
ioctl: EXT4_IOC_MOVE_EXT
→ ext4_move_extents(o_filp, d_filp, orig_blk, donor_blk, len, moved_len)
→ lock_two_nondirectories + inode_dio_wait
→ ext4_double_down_write_data_sem
→ mext_check_arguments
→ while (逻辑块):
get_ext_path / 跳过洞 / 对齐页内 cur_len
ext4_double_up_write_data_sem
move_extent_per_page(...)
ext4_double_down_write_data_sem
→ discard preallocations
→ unlock

5. 与 ext4_swap_extents 的关系

move_extent.c 负责 页级 I/O、日记额度、锁升降序extents.cext4_swap_extents事务 handle改写两棵 extent 树 中对应逻辑区间的 物理指针(并处理 脏元数据)。两者必须 一致使用,分析实现时需 extents.c 对照阅读


6. 小结

move_extent.c 实现 内核级「换腱接骨」:在 不扩大用户可见文件布局语义 的前提下,交换两段文件的块映射并保证数据落到新映射上,供 e4defragonline defrag。正确性依赖 extent 类型再检查失败回滚 swapjbd2 inode ranged write 标记。

若需补充 用户态 e4defrag 典型步骤( donor 分配策略等),属于 工具链文档,超出本内核文件范围。

RK3588 kernel-6.1 fs/ext4/namei.c 路径解析与目录元数据操作分析

RK3588 kernel-6.1 fs/ext4/namei.c 路径解析与目录元数据操作分析

1. 分析范围

  • rk3588/kernel-6.1/fs/ext4/namei.c目录 inode 的 VFS inode_operationslookup / create / link / unlink / symlink / mkdir / rmdir / mknod / tmpfile / rename,以及 目录块读写、htree 增删分裂、目录项校验和 等底层支撑)
  • dir.creaddir、线性/dx 路径)、inline.c(内联目录项增删转普通目录)、hash.cext4_htree_fill_tree遍历填充)、xattr.c/acl.clistxattr/get_acl 等透传)的协作

分工提示dir.c 文档已覆盖 大目录按 hash 遍历f_pos 编码;本文重点在 「写名字」:如何在磁盘目录结构里 查找、插入、删除、改名,以及 htree 随写入而分裂、升层


2. 代码作用(一句话)

namei.c 实现 ext4 目录层名空间操作:通过 ext4_read_dirblock索引块 / 叶子块 预期做 一致性校验与元数据 CRCext4_lookup/ext4_get_parent** 解析 目录项 → ext4_igetext4_add_entry 统一 内联目录htree线性目录,必要时 make_indexed_dir 把单块目录 提升为索引目录ext4_dx_add_entry/do_split 在叶子满或索引满时 分裂叶子或分裂/升高索引ext4_delete_entry/__ext4_unlink 合并 rec_len 回收槽位;ext4_rename/ext4_cross_rename/ext4_rename2jbd2 事务 内完成 换名、目录 .. 维护、白化(whiteout)、fast commit 跟踪或置为不合格;并向 VFS 导出 ext4_dir_inode_operations


3. 解决了什么问题

问题 namei.c 中的处理方式
按路径打开/创建需要「在目录里找名字」 ext4_lookup_entry/ext4_find_entry(含 htree dx_probe 与线性扫描);ext4_lookup 校验 inum、加密上下文(目录与目标子项)、d_splice_alias
.. 与父 dentry 解析 ext4_get_parent:查找 .. 项并 ext4_iget
超大目录 O(n) 插入 索引目录ext4_dx_add_entry 按 hash 落到叶子;叶子满 do_split;索引项满则 分裂索引块indirect_levels++ 升层(受 ext4_dir_htree_level 限制)
单块目录长高后首次建索引 make_indexed_dir:把原块改为 dx root,分配新叶子等(与 ext4_add_entry 中「仅一块且无 dx 回退」路径衔接)
htree 损坏时能否继续用 ext4_dx_add_entry 失败且返回 ERR_BAD_DX_DIR无 metadata_csum 时可 ext4_clear_inode_flag(EXT4_INODE_INDEX) 回退线性(有风险);有 checksumEFSCORRUPTED,避免 silent 损坏
目录块读错类型 / 洞 / 越界 __ext4_read_dirblockblocki_size首块非洞期望 INDEX 却读到叶子ext4_error_inode;索引块 CRCext4_dx_csum_verify);叶子 目录尾 CRC(与 ext4_dirblock_csum_* 协作)
目录无限膨胀 ext4_append:可选 s_max_dir_size_kb 限制 i_size
fscrypt / casefold 文件名 fscrypt_is_nokey_namefscrypt_prepare_*ext4_fname_setup_*ext4_ci_compare;casefold 下 negative dentrylookup 缓存 策略(d_invalidate / 查找失败 不缓存
删除目录项后空间碎片 ext4_generic_delete_entry:与前项 合并 rec_len清空 inode/name
重命名与目录链接计数、.. ext4_rename_dir_prepare/ext4_rename_dir_finishext4_update_dx_flag;跨目录移动目录时 ext4_inc_count/ext4_dec_count
overlay 等需要的 RENAME_WHITEOUT ext4_whiteout_for_renameext4_setent 把原项改成 whiteout inode,失败 ext4_resetent
RENAME_EXCHANGE ext4_cross_rename:双方向 ext4_setent
rename 目录与 fast commit ext4_fc_mark_ineligible(EXT4_FC_REASON_RENAME_DIR)(回放尚不能正确处理 .. 改写);非目录 rename 则 __ext4_fc_track_link/unlink/create
内联目录与普通目录形态切换 ext4_try_add_inline_entryext4_delete_inline_entry;rename 时 inline → blockforce_reread 避免删项时还按旧形态解释缓冲
sticky 位与「目录项 inode 与 dentry 不一致」 注释说明:须用 de->inoded_inode 比对,防 cwd 被删后同名占位 等竞态

4. 核心流程摘要

4.1 目录块读取与校验类型

  • dirblock_type_tEITHER / INDEX / DIRENT / DIRENT_HTREE,告知 __ext4_read_dirblock 期望值,用于 区分索引块与叶子选择性做强校验(避免空叶子被误判为索引块时误验索引 CRC)。
  • ext4_read_dirblock 为带 调用点 的封装,便于 ext4_error_inode(..., func, line, ...) 定位。

4.2 添加目录项:ext4_add_entry

  1. ext4_fname_setup_filename(加密名、casefold 等)。
  2. ext4_has_inline_data(dir)ext4_try_add_inline_entry;成功则返回。
  3. is_dx(dir)ext4_dx_add_entry;失败则按是否 metadata_csum 决定 降级或报错
  4. 否则 块号 0…blocks-1add_dirent_to_buf-ENOSPC单块 + dir_index featuremake_indexed_dir
  5. 仍满则 ext4_append 新目录块再 add_dirent_to_buf
  6. 成功则 ext4_set_inode_state(..., EXT4_STATE_NEWENTRY)

4.3 htree 写路径:ext4_dx_add_entry

  • dx_probe 定位帧栈;读 叶子 DIRENT_HTREE
  • 叶子 add_dirent_to_bufENOSPC索引项满分裂索引增加 indirect_levels;否则 do_split 分裂叶子dx_make_map/dx_sort_map 均分条目),再在目标叶 add_dirent_to_buf
  • restart重新 dx_probe,因索引路径已变。
  • ext4_delete_entry:先尝试 inline 路径,再 ext4_journal_get_write_access + ext4_generic_delete_entry + ext4_dirblock_csum_set + ext4_handle_dirty_dirblock
  • __ext4_unlinkext4_find_entry 与 inode 校验;事务内删项;drop_nlinki_nlink==0 ext4_orphan_addfast commit ext4_fc_track_unlink(带 dentry 时)。

4.5 创建类操作(节选)

  • ext4_create/ext4_mknod/ext4_tmpfileext4_new_inode_start_handle + ext4_add_entry(或 tmpfile 不建目录项),设置 ext4_file_inode_operationsext4_special_inode_operations
  • ext4_mkdirext4_new_inode_start_handle + ext4_init_new_dir./..)+ ext4_add_entryext4_dir_inode_operations
  • ext4_symlinkfscrypt_prepare_symlink;短链可走 fast symlink;长链 ext4_init_symlink_block 写数据块;加密则 ext4_encrypted_symlink_inode_operations

4.6 ext4_rename2ext4_rename / ext4_cross_rename

  • fscrypt_prepare_rename
  • RENAME_EXCHANGEext4_cross_rename
  • 否则 ext4_renamequotaext4_find_entryjournal_start、目录则 empty_dir/EMLINK 检查、ext4_setent/ext4_add_entry、删旧项、更新 ctime/nlink/orphanwhiteout 回滚路径。

5. 与 ext4_dir_inode_operations 的对应关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const struct inode_operations ext4_dir_inode_operations = {
.create = ext4_create,
.lookup = ext4_lookup,
.link = ext4_link,
.unlink = ext4_unlink,
.symlink = ext4_symlink,
.mkdir = ext4_mkdir,
.rmdir = ext4_rmdir,
.mknod = ext4_mknod,
.tmpfile = ext4_tmpfile,
.rename = ext4_rename2,
.setattr = ext4_setattr,
.getattr = ext4_getattr,
.listxattr = ext4_listxattr,
.get_acl = ext4_get_acl,
.set_acl = ext4_set_acl,
.fiemap = ext4_fiemap,
.fileattr_get = ext4_fileattr_get,
.fileattr_set = ext4_fileattr_set,
};

目录 inode 的 setattr/getattr/xattr/acl 等在其它文件中实现,经此表挂到 目录 上。


6. 小结

namei.c 是 ext4 「名字 ↔ inode」 的枢纽:读目录块时防呆与校验写目录时维护线性与 htree 两种形态(含 分裂与索引升高),并把 POSIX 语义(link 计数、目录 ..、加密与白化、journaling/fast commit)落到 事务化的目录项更新 上。深入 htree 读侧与 readdir 请结合 dir.c/hash.c 文档;内联目录 细节见 inline 相关源码与 rk3588-kernel-6.1-ext4-inline内联数据与内联目录分析.md