首页/目录/全部文章

全部文章

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

笔记列表

RK3588 kernel-6.1 fs/ext4/orphan.c 孤儿 inode 处理分析

RK3588 kernel-6.1 fs/ext4/orphan.c 孤儿 inode 处理分析

1. 分析范围

  • rk3588/kernel-6.1/fs/ext4/orphan.c
  • 磁盘与内存概念:超级块 s_last_orphan单链表头)、可选 orphan_file feature专用 s_orphan_file_inum 文件内多块槽位)、**内存链表 s_orphan + s_orphan_lock
  • 协作:ext4_jbd2(事务、EXT4_JTR_ORPHAN_FILE)、super.c(挂载/卸载、ext4_init_orphan_info/ext4_release_orphan_info)、inode.cext4_orphan_gettruncateevict 路径上 add/del

2. 代码作用(一句话)

**orphan.c 维护「崩溃后仍须收尾的 inode」:**对已 unlink 但仍被打开、或 大截断跨事务 的 inode,在 有 journal 时把它们 登记到盘上老式 s_last_orphan + 盘上 i 链表新式 orphan 文件槽位),以便 ext4_orphan_cleanup下次 rw 挂载删无链接 inode按记录尺寸截断仍有链接 inode;正常关闭/截断完成时 ext4_orphan_del 从盘上与内存链表移除。


3. 两套盘上表示

机制 盘上结构 内存标记
经典孤儿链表 es->s_last_orphan 指向链表头;每个 inode 盘上 i_links_count 邻域的 next 指针NEXT_ORPHAN)链成单链表 list_head i_orphan 挂在 sbi->s_orphan
orphan 文件(feature) s_orphan_file_inum 指向普通 inode,其数据块为 __le32 ino 槽数组 + 块尾 ext4_orphan_block_tail(magic、checksum) EXT4_STATE_ORPHAN_FILE + i_orphan_idx(线性下标)

ext4_orphan_add:若 s_orphan_info.of_blocks != 0ext4_orphan_file_add;仅当返回 -ENOSPC回退 超级块链表路径(不扩 orphan 文件,避免每件 orphan 操作预留过多 credits)。


4. 解决了什么问题

问题 处理方式
崩溃时文件已 unlink 仍被 FD 占用,须最终释放数据与 inode 进 orphan 表;恢复时 i_nlink==0ext4_process_orphaniput 触发真正删除
截断很大,跨多个已提交事务,中途崩溃导致元数据与数据不一致 截断过程中把 inode 挂 orphan;恢复时 i_nlink!=0truncate_inode_pages + ext4_truncate 收到 i_size/i_disksize 已记录的安全目标
超级块只存一个链表头,高并发 unlink 争用同一块磁盘中超级块/头 inode orphan 文件:多块、按 CPU 简单散列选起始块 atomic_dec_if_positive(ob_free_entries) 找空槽,cmpxchg 占槽,减少 所有 orphan 都改 s_last_orphan 的热点(槽满仍回退链表
orphan 文件块损坏难检测 块尾 magicmetadata_csumext4_orphan_file_block_csum_verify;jbd2 ext4_orphan_file_block_trigger 写时更新 tail checksum
内存与盘上链表一致性 ext4_orphan_add:改 sb 或前驱 inodeNEXT_ORPHAN + ext4_handle_dirty_metadata;失败则从 内存 i_orphan 摘除(注释:避免卸载 panic
ext4_orphan_del 无 handle(错误路径) list_del_init 更新 内存链表跳过 盘上更新
只读挂载但需要清孤儿 ext4_orphan_cleanup临时清 SB_RDONLYCONFIG_QUOTA 下必要时 开 quotaoff
文件系统带错/只读设备 只读 bdev未知 rocompat跳过EXT4_ERROR_FS 时策略性 s_last_orphan跳过恢复(见代码分支)
卸载释放 orphan 文件缓冲 ext4_release_orphan_infobrelseob_bhkfree(of_binfo)

5. 主要函数摘要

5.1 ext4_orphan_add / ext4_orphan_del

  • 前提s_journal 存在 且 inode 正常;无 journal 直接 0(无孤儿恢复契约)。
  • 加锁约定:注释 须在 i_rwsem 下调用I_NEW/I_FREEING 例外)。
  • del:若 EXT4_STATE_ORPHAN_FILEext4_orphan_file_del(清槽、atomic_inc(ob_free_entries));否则链表路径上 可能是 sb 头或前驱 inode 改写 next

5.2 ext4_process_orphan

  • dquot_initialize
  • i_nlink > 0加锁截断页缓存ext4_truncate;失败 ext4_orphan_del(NULL, ...) 清内存链防泄漏
  • i_nlink == 0:计数为 delete
  • iput(inode):触发 实际 evict/释放

5.3 ext4_orphan_cleanup

  • s_last_orphanorphan 文件块数 皆为 0 则返回
  • es->s_last_orphan 循环 ext4_orphan_gets_orphanext4_process_orphan
  • 双重循环orphan 文件 各块非零 ino(设置 EXT4_STATE_ORPHAN_FILEi_orphan_idx
  • 日志:删除/截断数量
  • 恢复 sb->s_flags只读 原样)

5.4 ext4_init_orphan_info

  • feature orphan_fileext4_iget(s_orphan_file_inum),按 i_size 得块数
  • 每块 ext4_bread,校验 tail magicchecksum
  • 统计每块 空槽数atomic_set(ob_free_entries)
  • of_csum_seed 来自 orphan inode 的 i_csum_seed

5.5 ext4_orphan_file_empty

  • 若 feature 未开 返回 1(空)
  • 否则所有块的 ob_free_entries 均等于 每块槽位数(用于 fast commit 等判断,见其它文件调用点)

6. 小结

orphan.c 解决的是 日志文件系统下「逻辑上的删除/截断」与「最终盘上状态」在崩溃边界上的缺口:用 持久化的孤儿集合 记住 哪些 inode 仍需删或收尾截断,并在 挂载恢复幂等地 处理。orphan 文件 是对 经典超级块头链表可扩展、可校验 扩展;空间满透明回退 旧机制。

ext4_orphan_get 实现在其它源文件(通常为 inode.c),orphan_cleanup 依赖其 安全 iget(坏/已删 inode 可中止链表)。

RK3588 kernel-6.1 page-io.c 与 readpage.c 分析

RK3588 kernel-6.1 page-io.creadpage.c 分析

1. 分析范围

文件 职责
page-io.c 写路径:把 buffer_head 的页 打成 写 BIO 提交;fscrypt 写时加密 bouncebio 完成 时清 buffer_async_writeend_page_writebackext4_io_end/ext4_io_end_vecunwritten extent 写完后 转换元数据(可经 workqueue 延迟
readpage.c 读路径ext4_mpage_readpages 多页 readahead/readpage,尽量 连续块合并为一个读 BIO;读完成后 可选 decrypt → verity 校验mempool + 工作队列);复杂页回退 block_read_full_folio/ext4_get_block

协作:ext4.hext4_map_blocks)、extents.cext4_convert_unwritten_io_end_vec)、fscryptfsverityinode.c/super.crsv_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_deferi_rsv_conversion_work)。**

2.2 问题对照表

问题 处理方式
页内多块不能先 submit 再标 async 遍历整页 标记 buffer_async_write / 清 dirty,再第二遍 塞进 BIO,避免 先完成的 bh 触发 end_page_writeback 而后面还在组装
i_size 跨页空洞 len < PAGE_SIZEzero_user_segment(注释引用 mmap 语义
洞、delay、unmapped、unwritten 暂不写 跳过 submit;若仍 dirty 则 redirty_page_for_writepage(如 事务提交写回
加密文件落盘必须是密文 fscrypt_encrypt_pagecache_blocksbounce pageBIO 指向 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_UNWRITTENbio->bi_private 链到 io_end->biorefcount 最后一棒 ext4_put_io_end_defer
journal 存在时避免在 softirq 里长事务 ext4_add_complete_ioi_rsv_conversion_listqueue_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_pagewritepages 路径写单页(与 mpage.c/ext4 包装 协作)

3. readpage.c:作用与解决的问题

3.1 一句话

ext4_mpage_readpagespage_size == block_size 的常见形态 下,用 ext4_map_blocks(带 上一段 map 缓存)把一页(或多页 readahead)整理成 连续物理块列表;能合并则 单个大读 BIO;读完成走 mpage_end_io,若需 解密/verity 则分配 bio_post_read_ctx,按序 fscrypt_enqueue_decrypt_workfsverity_enqueue_verify_work,最后 SetPageUptodate/unlock_page;若页 已有 buffer先洞后数据块不连续 等,则 放弃 mpageblock_read_full_folio(..., ext4_get_block)。**

3.2 问题对照表

问题 处理方式
通用 mpage 无法挂 ext4 读完成回调 专用 ext4_mpage_readpages + mpage_end_io
加密文件读回需异步解密 STEP_DECRYPTdecrypt_workfscrypt_decrypt_bio
fs-verity 需在读后验哈希 STEP_VERITYverity_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_limitverity/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_processingslab + mempool,由 super.c 模块 __init/__exit 调用。

4. 读与写的分工小结

方向 核心抽象 与 ext4 元数据
合并读 BIO + post-read 管道 ext4_map_blocks 只读映射;不在这里改 extent
合并写 BIO + buffer_head 异步位 普通块 仅刷脏数据;unwrittenext4_convert_unwritten_io_end_vec提交 extent 更新

5. 小结

  • page-io.c 解决 “如何把页缓存安全、可加密、可合并地写到盘,并在 unwritten 分配后把元数据事务化地变成正式写入”
  • readpage.c 解决 “如何在高性能 mpage 读与加密、verity、非平凡页布局之间折衷,必要时回退到 buffer 层单块读”

若需 read_folio/address_space_operationsext4_mpage_readpages 的衔接,可在 file.c/inode.c 中继续追踪。

RK3588 kernel-6.1 fs/ext4/resize.c 在线扩容(resize)分析

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.cEXT4_IOC_GROUP_EXTENDEXT4_IOC_GROUP_ADDEXT4_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_dataverify_group_input:位图/itb 与 GDT 重叠、越界、最后一块可读等),扩 flex_bg/groupinfo,再 ext4_flex_group_add
一次加多组、控制内存与日记压力 ext4_resize_fsext4_setup_next_flex_gd + alloc_flex_gdflex 大小批量;MAX_RESIZE_BG 等限制;约 每 10 秒打进度日志
GDT 块数量随块组增加而增长 ext4_add_new_descsadd_new_gdbEXT4_RESIZE_INO 的双间接树 抠出预留 GDT 块;或 add_new_gdb_meta_bgmeta_bg 下挂新描述符块
预留 GDT 用完仍要继续涨 ext4_resize_fs:先 缩小目标 n_blocks_count 到预留能撑住的最大组数ext4_convert_meta_bg 开启 meta_bg释放 resize inode 占用的元数据块,再 带着原目标大小 retry
resize_inodemeta_bg 互斥 ext4_resize_fs:若两者同时 feature 置位则 -EINVAL(不一致布局)
JBD 难以整事务回滚 注释反复强调:在改盘前尽量做完所有可能失败检查add_new_gdb 等路径 先 verify 再改 dind
扩容与并发访问 ext4_resize_beginEXT4_FLAGS_RESIZING-EBUSY 互斥);sparse_super2非主超级块挂载EXT4_ERROR_FS拒绝扩容
bigalloc 边界 ext4_resize_fsn_blocks_count 向下对齐 cluster,避免 半 cluster 不可用语义
内核侧数据结构随组数变长 ext4_alloc_flex_bg_arrayext4_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_inode feature主 sb 与 s_first_data_block 对齐(备份超级块挂载则拒绝)
  • 安全EXT4_ERROR_FSsparse_super2 下不允许在线 resize
  • 结束:清除 RESIZINGRESIZE_FS 成功路径 update_backups=true 触发 overhead 更新

4.2 ext4_group_extend

  • 仅处理 最后一个块组内「从当前 o_blocks_countn_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_addEXT4_IOC_GROUP_ADD

  • 只能在尾数追加input->group == s_groups_count
  • 非 sparse新 GDT 落到新块gdb_off==0)时 -EPERM(无法再分稀疏备份)
  • 需要预留时打开 EXT4_RESIZE_INO
  • 分配 flex/mballoc groupinfo 后,以 count=1ext4_flex_group_add

4.4 ext4_resize_fsEXT4_IOC_RESIZE_FS

高层循环(逻辑顺序):

  1. n_blocks_count - 1,确认设备够长;bigalloc 对齐
  2. 禁止缩小;计算目标 最后一组 n_group
  3. resize_inode:检查 GDT 块数是否超过「旧描述符块数 + s_reserved_gdt_blocks」 → 必要时 临时降低 n_blocks_count,记 n_blocks_count_retry
  4. ext4_convert_meta_bg:在 无需再动 resize_inode 或已缩到仅扩组内 的边界上 切换到 meta_bg
  5. 最后一组塞不进 inode 表等元数据回退 n_group 重算(retry
  6. 与当前尾组同组ext4_group_extend_no_check;否则先 补满当前尾组add 为簇/块公式)
  7. while ext4_setup_next_flex_gdext4_alloc_group_tables(用户空间/内核算好的表布局)+ ext4_flex_group_add
  8. 若有 n_blocks_count_retry释放 flex 结构goto retry 扩到用户原定大小

4.5 ext4_flex_group_add

  • 前置setup_new_flex_group_blocks独立大事务:初始化 新组上的 备份 GDT、位图、inode 表 等)
  • 主事务EXT4_HT_RESIZEext4_add_new_descsext4_setup_new_descsext4_update_super(块数、inode 数、flex 计数器、s_overhead_clusters 等)
  • 成功后update_backups超级块备份新增/变更的 GDT 块备份(区分 meta_bg 与 padding)

4.6 ext4_convert_meta_bg

  • 打开 meta_bg feature,从 resize inode 释放 DIND 及所连块ext4_free_blocks),清除 inode 映射
  • 要求:进入时 s_reserved_gdt_blocks 已为 0(否则报错)

5. 与用户态、其它子系统的关系

组件 关系
ioctl.c EXT4_IOC_* 封装 resize_begin/resize_endRESIZE_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 等文档。

RK3588 kernel-6.1 fs/ext4/super.c 超级块与挂载生命周期分析

RK3588 kernel-6.1 fs/ext4/super.c 超级块与挂载生命周期分析

1. 分析范围

  • rk3588/kernel-6.1/fs/ext4/super.c(体量大,单一源文件覆盖注册文件系统类型fill_super/挂载上下文超级块与块组描述符校验journal 装载与回收super_operations/配额/NFS export错误处理与只读降级freeze/synclazy inode table 初始化、以及 ext4_read_bh 等跨模块公用辅助)
  • 协作:ext4_jbd2.c(日志)、inode.cext4_iget、root)、mballoc.csysfs.cresize.cext4_alloc_flex_bg_array 等同名能力在别文件亦有入口)、xattr/acl/fscrypt

2. 代码作用(一句话)

super.c 定义 ext4 在 VFS 层的 「整卷对象」:模块 __init 顺序注册 ext4(及可选 ext3/ext2 别名)ext4_init_fs_context + ext4_get_treeext4_fill_super__ext4_fill_super** 读 主超级块、校验 feature/metadata 校验和、解析并一致性检查 挂载参数、初始化 ext4_sb_info 几何与 per-cpu 计数器ext4_group_desc_init/ext4_check_descriptors、按需 ext4_load_and_init_journal(含 recovery)、ext4_iget(ROOT)ext4_setup_super 更新 挂载计数与状态位ext4_mb_init/ext4_ext_init 等子系统;卸载时 ext4_put_super 按序 拆 sysfs、停 lazyinit、销毁 journal、写回干净 superblock、释放 GDT/flex/mballoc 资源;并通过 ext4_sops 向内核提供 inode 生命周期、sync、statfs、quota、show_options 等回调。**


3. 解决了什么问题

问题 super.c 中的处理方式
如何安全挂卷 __ext4_fill_superext4_load_superext4_superblock_csum_*ext4_init_metadata_csumext4_feature_set_ok/ext4_check_feature_compatibility;几何 ext4_geometry_check/generic_check_addressableext4_group_desc_init + ext4_check_descriptors(位图/itb 与 sb/GDT 不交叠、ext4_group_desc_csum_verify
卷级参数与内核能力是否匹配 ext4_parse_paramfs_parameter_spec):data=journal_*、quotaDAXblock_validityfast commit debug 等;ext4_check_opt_consistency(如 bigalloc 与 data=journalDAX 与 inline_data
ext2/ext3 兼容名 条件编译 ext2_fs_type/ext3_fs_type 共享 ext4_init_fs_contextext2_feature_set_ok/ext3_feature_set_ok
日志与崩溃恢复 ext4_load_and_init_journalneeds_recoverys_last_orphan/orphan_present/journal_needs_recovery);NOLOAD 且需 recovery 则拒绝 rw 挂载
多挂载保护与 MMP ext4_multi_mount_protect(feature mmp 且 rw)
根 inode 先于业务可用 先 journal 再 ext4_iget(EXT4_ROOT_INO)(注释:根可能在日志中未回放)
元数据错误如何反映到卷 __ext4_error/ext4_handle_errorEXT4_ERROR_FSsave_error_info(首/末错与时间线);errors=continue/remount-ro/panicERRORS_CONT 时用 s_error_work 经 journal 刷 super
卸载时资源与顺序 ext4_put_superext4_unregister_sysfs(防 sysfs 触 journal);flush_stashed_error_workjbd2_journal_destroy;清 recovery/orphan featureext4_commit_superbrelse GDT、kvfree flex_groupsext4_mb_release
s_maxbytes 与 extent 格式极限 ext4_max_size:与 ee_block 32 位huge_fileblkcnt_t 等约束一致
flex_bg 元数据内存 ext4_fill_flex_info/ext4_alloc_flex_bg_array(RCU 替换 + ext4_kvfree_array_rcu
buffer 重读后必须重验 ext4_read_bh/ext4_read_bh_nowaitclear_buffer_verified
statfs/df 信息 ext4_statfs(与 bigalloc/overheadquota 等协作)
快照/一致性冻结 ext4_freeze/ext4_unfreeze
NFS exportfs ext4_export_opsext4_fh_to_dentry 等)
延迟 inode 表初始化 ext4_lazyinit_thread/ext4_register_li_request:大巻后台 zero table 减轻挂载时间
锁顺序文档 文件头注释:page fault / buffered write / truncate / DIO / writepagessb_start_writei_mutextransactioni_data_sem 的约定

4. 核心流程摘要

4.1 挂载主路径:__ext4_fill_super(节选逻辑顺序)

  1. ext4_load_super → 内存 s_ess_sbh
  2. 校验和与默认挂载相关字段ext4_set_def_opts 等)
  3. parse_apply_sb_mount_optionsext4_check_opt_consistencyext4_apply_options
  4. 编码、journal data 模式、ACL、feature 兼容性
  5. s_maxbytes、描述符大小、blocks_per_group、hash 版本、cluster(bigalloc)
  6. ext4_group_desc_init错误报告 timerext4_es_register_shrinker
  7. sb->s_op/s_export_op/s_xattr/加密 verity/配额操作向量
  8. ext4_fast_commit_initMMPjournal 或 nojournal 路径
  9. xattr cache、verity blocksize、overhead 计算、rsv_conversion_wq
  10. root inode + d_make_root
  11. ext4_setup_supers_mnt_counts_state、journal orphan 标志)
  12. 可选 block_validityext4_setup_system_zone
  13. ext4_ext_initext4_mb_init、journal commit callback
  14. free clusters/inodes per-cpu 计数器、ext4_register_li_request、sysfs 等收尾(后续代码在 failed_mount* 标签周边大量 回滚,保证失败可解挂)

4.2 ext4_setup_super(rw 挂载副作用)

  • 无 journal 时 EXT4_VALID_FS(与历史 ext2 语义一致)
  • 调整 s_max_mnt_count/s_mnt_count/s_mtime
  • 有 journal:journal_needs_recovery;有 orphan fileorphan_present
  • ext4_commit_super

4.3 ext4_put_super(卸载)

要点:**sysfs → lazyinit → quota → error work → orphan 文件元数据 → journal destroy → shrinker/timer/system_zone/mballoc/extents → rw 时清 recovery 位并提交 super → 释放 GDT/flex/counters → 同步/丢弃块设备 cache → 外部 journal bdev → xattr cache → MMP → kobject_put(s_kobj)s_chksum_driver 等。

4.4 ext4_sops(对上 VFS)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static const struct super_operations ext4_sops = {
.alloc_inode = ext4_alloc_inode,
.free_inode = ext4_free_in_core_inode,
.destroy_inode = ext4_destroy_inode,
.write_inode = ext4_write_inode,
.dirty_inode = ext4_dirty_inode,
.drop_inode = ext4_drop_inode,
.evict_inode = ext4_evict_inode,
.put_super = ext4_put_super,
.sync_fs = ext4_sync_fs,
.freeze_fs = ext4_freeze,
.unfreeze_fs = ext4_unfreeze,
.statfs = ext4_statfs,
.show_options = ext4_show_options,
#ifdef CONFIG_QUOTA
.quota_read = ext4_quota_read,
.quota_write = ext4_quota_write,
.get_dquots = ext4_get_dquots,
#endif
};

write_inode/evict_inode 等实现在 inode.c 等文件中;此处完成 函数向量绑定

4.5 模块入口

1
2
3
4
5
6
7
8
static struct file_system_type ext4_fs_type = {
.owner = THIS_MODULE,
.name = "ext4",
.init_fs_context = ext4_init_fs_context,
.parameters = ext4_param_specs,
.kill_sb = kill_block_super,
.fs_flags = FS_REQUIRES_DEV | FS_ALLOW_IDMAP,
};

ext4_init_fs 另初始化 ext4__ioend_wqext4_init_es/pending/post_read/pageio/system_zone/sysfs/mballocinode slabfast commit dentry cache,再 register_filesystem.


5. 小结

super.c 是 ext4 的 挂载轴心:把 块设备上的超级块与块组描述符 变成 可用的 super_block + ext4_sb_info,决定 本机是否接受该盘的 feature 组合,接上 jbd2 与 mballoc,并在全生命周期内维护 错误语义与卸载清理具体 I/O 路径readpageget_block)不在此文件;目录/extent/inline 等逻辑见各自子模块。

若需对照 块组几何与位图,可结合 rk3588-kernel-6.1-ext4-balloc块位图与块组辅助分析.md日志细节rk3588-kernel-6.1-ext4-ext4_jbd2与JBD2对接分析.md

RK3588 kernel-6.1 fs/ext4/sysfs.c sysfs / proc 调试与调参接口分析

RK3588 kernel-6.1 fs/ext4/sysfs.c sysfs / proc 调试与调参接口分析

1. 分析范围

  • rk3588/kernel-6.1/fs/ext4/sysfs.c
  • 挂载/卸载钩子:ext4_register_sysfs / ext4_unregister_sysfs(由 super.c 等在合适生命周期调用;注释要求 卸载时先于 journal teardown
  • 协作:super.cext4_seq_options_show、错误计数 notify)、mballoc.cext4_mb_seq_*)、extents.cext4_seq_es_shrinker_info_show)、fast_commit.cext4_fc_info_show

2. 代码作用(一句话)

sysfs.c 为每个已挂载的 ext4 卷在 /sys/fs/ext4/<设备名> 注册 s_kobj,用统一的 struct ext4_attr + calc_ptrext4_sb_info/ext4_super_block 字段特例回调 暴露为 可读/可写 sysfs 属性;并在 /proc/fs/ext4/<设备名> 下挂 optionsmb_groups、fast commit、extent shrinkerseq_file 接口;全局 /sys/fs/ext4/features 下列出 本内核编译进 ext4 的可选能力ext4_notify_error_sysfs** 在错误计数变化时 sysfs_notify 便于用户态轮询。**


3. 解决了什么问题

问题 sysfs.c 中的处理方式
运维需要看清「这一_mount」的写量、预分配、延迟分配 delayed_allocation_blockss_dirtyclusters_counter 换块)、session_write_kbytes/lifetime_write_kbytespart_stat 与挂载初值、s_kbytes_written
在线调 mballoc / inode 读预读 / extent zeroout 等行为 mb_*inode_goalextent_max_zeroout_kbmb_prefetch*last_trim_minblks 等映射到 ext4_sb_info 字段(多数 0644 可写)
预留簇(quota/管理用) reserved_clusterskstrtoull + 上限为卷簇数,写入 s_resv_clusters
日志线程是谁、是否存活 journal_task:输出 jbd2 j_task 的 pid,无 journal 为 <none>
Correlating 内核报错与盘上 s_error_* 只读errors_countfirst/last_error_{ino,block,line,func,errcode,time},时间用 superblock 中 32+8 位时间戳time64_t 输出
限流 spam 可写:err/warning/msg_ratelimit_{interval_ms,burst}sbiratelimit_state
回归/故障注入 trigger_fs_error0200CAP_SYS_ADMINext4_error);CONFIG_EXT4_DEBUGsimulate_fail
用户态要知道「当前内核 ext4 编进了哪些特性」 /sys/fs/ext4/features/*:各属性 show 固定 supported,列表随 Kconfig 变化(encryption、casefold、verity、fast_commit 等)
/proc/mounts 以外还要看实际 mount 选项 /proc/fs/ext4/xxx/optionsext4_seq_options_show
大块分配器内部状态难查 mb_groupsmb_statsmb_structs_summaryseq opsmballoc.c
extent 状态树 shrinker / fast commit 状态 es_shrinker_infofc_info
错误监控事件 ext4_notify_error_sysfs:通知 errors_count,便于 inotify/udev 类工具
卸载顺序与 sysfs 并发 ext4_unregister_sysfsremove_proc_subtree + kobject_delsuper.c 要求 先于 jbd2_journal_destroy,避免 sysfs 仍访问 j_task

4. 机制摘要

4.1 属性分发器

  • calc_ptr:按 attr_ptr 把属性绑定到 sbi 某偏移s_es 某偏移,或 显式全局指针(如 max_writeback_mb_bump 指向静态 old_bump_val=128,只读兼容项)。
  • ext4_attr_show/ext4_attr_store:根据 attr_id/attr_id_t 分支;对 ext4_super_blockpointer_ui 在 show/store 中使用 小端 le32 / le64 转换
  • inode_readahead_blks:写入要求 2 的幂且不超过上界,否则 -EINVAL

4.2 每文件系统 kobject

  • ext4_sb_ktypedefault_groups = ext4_groups(由 ext4_attrs[] + ATTRIBUTE_GROUPS(ext4) 生成)、release = ext4_sb_releasecomplete(s_kobj_unregister),与 super.ckobject_put + wait_for_completion 配对。

4.3 全局 features kobject

  • ext4_feat_ktypereleasekfree(kobj)(该 kobject 为 kzalloc 的单独对象);子属性全部 attr_feature → show supported\n

4.4 proc 目录

  • ext4_proc_rootproc_mkdir("fs/ext4", NULL),与 sysfs fs/ext4 命名并列,路径为 /proc/fs/ext4/<sb->s_id>
  • proc_create_*sb 传入 single/seq,供 options 与 mballoc 导出

5. 与用户可见路径对照(逻辑)

路径前缀 含义
/sys/fs/ext4/<s_id>/... 该卷 sysfs 调参/只读统计
/sys/fs/ext4/features/... 本内核支持的 ext4 编译期特性占位文件
/proc/fs/ext4/<s_id>/... options / mb_ / fc / es_shrinker* 等 proc seq

<s_id> 一般为块设备标识字符串(如 sda1),与 super_block->s_id 一致。


6. 小结

sysfs.c 不参与数据路径 I/O,负责 可观测性与可调参数:把 内存/超级块中的计数与调参位 暴露给 sysfs,把 更长的调试报表 放在 proc seq;并通过 features 目录 区分 「文件系统特性位」「内核是否支持某能力」。修改 ext4_sb_info 运行参数 时需注意 与 mount 选项、锁及与其他子系统的语义(例如 reserved_clusters 影响可用空闲统计),此类细节仍以 mballoc/super 实现为准。

若需对照 挂载选项序列化,见 super.cext4_seq_options_showmballoc 导出字段含义mballoc.c

RK3588 kernel-6.1 ext4 扩展属性(xattr)模块分析

RK3588 kernel-6.1 ext4 扩展属性(xattr)模块分析

1. 分析范围

文件 角色
xattr.c 盘上格式inode 内 / 外部块 / xattr inode 存取、listxattr/get/set共享块与 mbcache、校验和、日记事务ext4_xattr_handlershandler_map
xattr_user.c user. 前缀:依赖挂载选项 user_xattr
xattr_trusted.c trusted. 前缀:列表需 CAP_SYS_ADMIN
xattr_security.c security. 前缀:与 LSM 安全标 对接;ext4_init_security 创建 inode 时写入初始 xattr
xattr_hurd.c gnu.XATTR_HURD_PREFIX)前缀:GNU Hurd 兼容命名空间,与 user 相同依赖 user_xattr

xattr.h 定义 magic、索引号、ext4_xattr_header/entry磁盘布局常量


2. 整体作用(一句话)

ext4 把扩展属性存在 inode 尾部 extra 区(ibody) 和/或 i_file_acl 指向的外部 4K 块(可 多 inode 共享mbcache 去重**),大值可走 xattr inode(e_value_inumxattr.c 负责 校验、读写、日记、配额与删除 inode 时拆引用xattr_*.c 仅实现 VFS xattr_handler,把 带前缀的用户态名 映射到 EXT4_XATTR_INDEX_* 并调用 ext4_xattr_get/ext4_xattr_set,外加 权限/挂载策略安全模块初始化。**


3. xattr.c 解决的核心问题

问题 处理方式
空间不足且要兼容 128B 老 inode ibody:依赖 i_extra_isize;不够则 分配外部属性块xattr inode
同名索引与 VFS 前缀 e_name_index 映射到 xattr_handlerext4_xattr_handler_map
get 顺序 xattr_sem 读锁先 ibody ext4_xattr_ibody_get-ENODATA 再块 ext4_xattr_block_get
list 合并与过滤 ext4_listxattr:ibody + block;每项经 handler->list(dentry) 决定是否出现在列表里
损坏与篡改 ext4_xattr_check_blockmagic、h_blocks=1、metadata_csum 时 CRCext4_xattr_check_entries(边界、XATTR_SIZE_MAX 上界
外部块共享 h_refcountext4_xattr_block_cache_*mbcache)找 内容相同块节省空间
写路径与崩溃一致性 ext4_journal_start/ext4_xattr_set_handleext4_xattr_set_credits 估算 credits;**xattr 会使 fast commit 对该事务 EXT4_FC_REASON_XATTR 不合格
删文件泄漏 EA 块 ext4_xattr_delete_inodeinode.c evict 路径):减 refcount、释放 块/xattr inode
加密目录等 fscrypt 协同(EXT4_XATTR_INDEX_ENCRYPTION 等在其它路径;本批文件不展开)
注释:i_file_aclxattr_sem 保护独占块xattr_sem + buffer lock

盘上条目布局(摘自 xattr.c 头注释):头 + 自顶向下增长的 entry + 自底向上对齐存放 value块内 entry 有序,inode 内无序


4. 各 xattr_*.c:分工与解决的问题

4.1 xattr_user.cuser.

  • 前缀XATTR_USER_PREFIXuser.
  • 索引EXT4_XATTR_INDEX_USER
  • 策略list/get/set 均要求 test_opt(sb, XATTR_USER),否则 -EOPNOTSUPP
  • 解决的问题:与挂载选项 user_xattr 对齐,默认不启用户 EA 时可 拒绝 普通用户的 user.*,避免旧策略或不信任场景

4.2 xattr_trusted.ctrusted.

  • 前缀XATTR_TRUSTED_PREFIX
  • 索引EXT4_XATTR_INDEX_TRUSTED
  • 策略ext4_xattr_trusted_list 仅当 capable(CAP_SYS_ADMIN) 为真才列出;get/set 直接进 ext4_xattr_*
  • 解决的问题trusted.* 仅管理员可见列表;写入权 仍由 内核通用 xattr 与 capability 约束(与 user. 分离命名空间)

4.3 xattr_security.csecurity.

  • 前缀XATTR_SECURITY_PREFIX
  • 索引EXT4_XATTR_INDEX_SECURITYCONFIG_EXT4_FS_SECURITY
  • handlerlist 成员(由 LSM/通用层 处理枚举逻辑)
  • ext4_init_securitysecurity_inode_init_security + 回调 ext4_initxattrsext4_xattr_set_handle XATTR_CREATE多条 security xattr
  • 解决的问题新建 inode(如 create/mkdir)时 自动生成 SELinux/SMACK 等标签,与 磁盘 security. 索引 一致

4.4 xattr_hurd.c:GNU Hurd

  • 前缀XATTR_HURD_PREFIX
  • 索引EXT4_XATTR_INDEX_HURDxattr.h 注明 reserved for Hurd
  • 策略:与 user 相同,**list/get/set 都受 XATTR_USER 挂载选项 门控
  • 解决的问题Hurd 工具链使用独立索引,与 user. 不混桶,仍共享 「是否允许用户 EA」 的挂载策略

5. 处理器注册关系

ext4_xattr_handlers[](供 super.csb->s_xattr)包含 user、trusted、ACL(可选)、security(可选)、hurd

ext4_xattr_handler_map[]name_index 解析盘上 e_name_index,在 list/get 遍历时 绑定 前缀与权限回调


6. 小结

  • xattr.cext4 扩展属性的「引擎」——格式、一致性、缓存共享、日记与生命周期。
  • 四个小文件薄适配层,把 VFS/LSM/挂载策略 接到 EXT4_XATTR_INDEX_*
  • 重复说明:仅需一份 xattr_security.c 分析;xattr_userxattr_hurd 共用 user_xattr 门控,但磁盘索引不同

若需 POSIX ACL 与 system.posix_acl_*,见 acl.c加密 xattr 名fs/crypto 与 ext4 加密索引 路径。

RK3588 kernel-6.1 ext4 与块设备 I/O 下发路径

RK3588 kernel-6.1 ext4 与块设备 I/O 下发路径

1. 结论(先记住)

ext4 不直接调用具体存储驱动(如某 MMC/SD/NVMe 驱动的符号);它通过 struct super_block 关联的 sb->s_bdevstruct block_device,使用内核统一的:

  • buffer_head + submit_bh(元数据、大量基于块的读写在 ext2/3/4 传统路径中仍很普遍)
  • struct bio + submit_bio(页缓存写回、ext4_mpage_readpages 等多页读)

块层再将请求交给挂在该 bdev 上的 队列与驱动。日志文件系统场景下,jbd2 还会对 journal 设备/分区 单独发 I/O。


2. buffer_head 路径(典型:超级块、inode 表块、目录块、xattr 块等)

  1. sb_getblk / sb_getblk_gfp:按 文件系统内逻辑块号 绑定 buffer_headbh->b_bdevsb->s_bdev
  2. 读:ext4_read_bh / ext4_read_bh_nowaitfs/ext4/super.c)内部 submit_bh(REQ_OP_READ | flags, bh)
  3. 写:缓冲区经 mark_buffer_dirtyjbd2_journal_dirty_metadata 等;直接或经 journal 最终 submit_bh(REQ_OP_WRITE | …)(例如 super.c 写 super、fast_commit.cext4_fc_submit_bhmmp.c 等)。

ext4_sb_bread__ext4_sb_bread_gfpsb_getblk_gfp + ext4_read_bh_lock,读元数据块的标准入口之一。

与 inode 表inode.c__ext4_get_inode_loc 使用 sb_getblk(sb, block) 拿到 inode 表所在磁盘块,再 ext4_read_bh_nowait 等读入。


3. bio 路径(典型:用户数据 readpage/writepage)

场景 文件 行为
写回 page-io.c ext4_io_submitsubmit_bio(bio),可按 WB_SYNC_ALLREQ_SYNC
多页读 / readahead readpage.c ext4_mpage_readpagesREQ_OP_READ bio,末尾 submit_bio(bio)

bio 上设置 bi_bdev、起始扇区、end_iofscrypt 时另有 bounce 页fscrypt_set_bio_crypt_ctx 等。


4. 设备级原语(仍经 bdev,不进 ext4 私有驱动)

  • sync_blockdev(sb->s_bdev)super.c 卸载等路径,整设备缓存与队列协调刷写。
  • blkdev_issue_flush(sb->s_bdev)fsync.c、fast commit、部分分配路径,向设备发 flush,配合 FUA/排序 语义。
  • __blkdev_issue_discardmballoc.cdiscard/TRIM 路径。

5. jbd2 与 ext4 的分工

有 journal 时,大量元数据 先记入 journal,由 jbd2j_fs_dev / 外部 journal bdev 下发 I/O;ext4 业务路径里常见的是 ext4_fsblk_t 映射 + get_write_access/dirty_metadata,而不是每个 buffer 都由 ext4 直接 submit_bh 写最终位置


6. 调试阅读顺序建议

  1. super.cext4_read_bh / submit_bhext4_sb_bread
  2. inode.c__ext4_get_inode_loc(块设备上 inode 表块 的定位与读)
  3. page-io.cext4_io_submitext4_end_bio
  4. readpage.csubmit_bio、读完成链
  5. fs/block_dev.cdrivers/scsidrivers/mmc:块层与 SoC 存储栈(不在 fs/ext4

7. 小结

ext4 只认 block_device 与块层抽象;RK3588 上无论是 eMMC、UFS 还是 NVMe,只要在挂载时 同一个 bdev 上挂了文件系统,ext4 的 submit_bh/submit_bio 就会由 Linux 块子系统 路由到对应驱动。排查 I/O 问题时,应同时看 ext4 栈blktrace/驱动队列

RK3588 kernel-6.1 ext4 目录项(文件名)与 inode 绑定及增删改查

RK3588 kernel-6.1 ext4 目录项(文件名)与 inode 绑定及增删改查

1. 核心结论

  • 文件名不存放在「子文件」自己的 inode 里,而存放在 父目录的数据(目录块或 inline 目录)中,以 struct ext4_dir_entry_2 记录排列。
  • 名字与文件的绑定在盘上就是:目录项里的 de->inode(32 位 inode 号)+ de->name;不是 C 指针,而是 磁盘上的数值关联
  • VFSdentry 缓存「路径上某一分量 → struct inode」,避免每次访问都扫目录块;权威映射仍以盘上目录项为准

2. 盘上目录项格式(绑定字段)

每条目录项(ext4_dir_entry_2,见 ext4.h)主要包括:

  • inode:小端 inode 号 —— 名字指向哪个 inode
  • name / name_len:文件名(加密目录则为密文名等)
  • rec_len:本条总长度(含对齐,用于链接成变长数组)
  • file_type:类型提示(普通文件/目录/软链…)

写入「名字 + inode 号」的典型实现:namei.c ext4_insert_dentry

1
2
3
4
5
de->file_type = EXT4_FT_UNKNOWN;
de->inode = cpu_to_le32(inode->i_ino);
ext4_set_de_type(inode->i_sb, de, inode->i_mode);
de->name_len = fname_len(fname);
memcpy(de->name, fname_name(fname), fname_len(fname));

3. 查(lookup / 路径解析 / readdir)

3.1 lookupopenstat 等)

  1. VFS 对父目录 inode 调 inode_operations.lookup,ext4 为 ext4_lookupnamei.c)。
  2. ext4_lookup_entry:在目录数据里按名查找 ext4_dir_entry_2(线性块或 htree)。
  3. 读出 de->inodeext4_iget(sb, ino, ...) 装入 struct inode
  4. d_splice_alias(inode, dentry)dentryinode 接到目录树上。
1
2
3
4
5
if (bh) {
__u32 ino = le32_to_cpu(de->inode);
brelse(bh);
// ... ino 合法性校验 ...
inode = ext4_iget(dir->i_sb, ino, EXT4_IGET_NORMAL);

3.2 readdir(列目录)

dir.c ext4_readdir / ext4_dx_readdir 扫描目录项,经 dir_emit名字与 inode 号 等交给用户态;每条记录仍对应盘上 de->inode


4. 增(create / mkdir / link)

  1. ext4_new_inode... 分配 新 inodeinode 表中有对应槽位,得到 i_ino)。
  2. ext4_add_entry:在父目录中找空间或分配新目录块(inline → 块htree、线性路径见 namei.c)。
  3. add_dirent_to_bufext4_insert_dentry:在缓冲区写入 名字de->inode = cpu_to_le32(inode->i_ino),经 journal 落盘。

注意ext4_add_entry 注释说明,若条目 de->inode 暂时为 0,须在同逻辑路径内尽快写入 ino,中间不可随意丢锁长睡,否则槽位可能被占用。


  1. ext4_find_entry 定位目录项。
  2. ext4_delete_entry合并 rec_len清零 inode/名,目录中不再出现该名(盘上解绑「名 → ino」)。
  3. drop_nlinkiput、可能 ext4_orphan_add仅当 i_nlink 等条件满足 才在 evict 中回收 inode 与数据块

删名 ≠ 立刻删 inode:硬链接下同一 inode 可对应多条目录项。


6. 改(rename / 覆盖)

  • ext4_rename2ext4_rename / ext4_cross_renamenamei.c):在新位置 ext4_add_entryext4_setent 更新 目标 inode 号与/或名字,再删旧项;覆盖时处理 被覆盖 inode 的 nlink
  • 本质:仍在修改目录块里的 ext4_dir_entry_2inode 与/或 name

7. 磁盘布局与代码地图

概念 位置
目录项增删改查、lookup/rename namei.c
getdents、目录块校验、htree 读 dir.c
htree 遍历时与 hash 协作 hash.c
小目录 inline 形态 inline.c
inode 表定位与 ext4_iget inode.c

8. 与「块设备 I/O」的关系

目录块与普通数据块一样经 页缓存 / buffer_head / bio 读写到 sb->s_bdev本节只描述名字与 inode 的逻辑关系,块层路径见 rk3588-kernel-6.1-ext4-块设备与IO下发路径分析.md


9. 小结

增删改查的对象是 父目录里的目录项序列与 inode 的绑定就是 de->inodestruct inode 存权限、大小、extent 等;文件名只出现在 父目录 的条目中。多篇文档已覆盖 namei 全貌的,可交叉阅读 rk3588-kernel-6.1-ext4-namei路径解析与目录元数据操作分析.md

RK3588 kernel-6.1 fs/ext4 源码目录架构详解

RK3588 kernel-6.1 fs/ext4 源码目录架构详解

本文基于 Linux 6.1 树中 rk3588/kernel-6.1/fs/ext4 目录,从 VFS 注册、内存与磁盘结构、各 .c 文件职责、子系统协作 四个维度给出可对照源码阅读的说明,便于在 RK3588(eMMC/UFS 等块设备上常用 ext4)上做调试与裁剪。


1. 目录与构建

1.1 源文件清单(与 Makefile 一致)

ext4 驱动在 fs/ext4/Makefile 中链接为单一模块对象 ext4.o,由以下 核心 .o 组成(必选):

对象 源文件 主要职责
balloc.o balloc.c 传统/compat 块分配、块位图与组内计数(与 mballoc 配合)
bitmap.o bitmap.c 块组位图、inode 位图读校验与内存侧辅助
block_validity.o block_validity.c 系统块区域 rbtree,防止数据块与元数据区重叠
dir.o dir.c 目录项(非(htree)线性部分)、readdir、目录块 I/O
ext4_jbd2.o ext4_jbd2.c JBD2 日志对接:事务、handle、元数据提交
extents.o extents.c extent 树 读写、分裂合并、与 map_blocks 核心逻辑
extents_status.o extents_status.c ES 树:内存中逻辑块→物理/状态的缓存,优化延迟分配与回写
file.o file.c open/read_iter/write_iter/mmap/fallocate、DAX/iomap、ioctl(部分)、O_DIRECT
fsmap.o fsmap.c FS_IOC_GETFSMAP 块设备映射查询
fsync.o fsync.c fsync/fdatasync、journal 与 data=ordered 语义
hash.o hash.c 目录 htree 哈希(与 namei 配合)
ialloc.o ialloc.c inode 分配、inode 位图、组内策略
indirect.o indirect.c 非 extent 兼容路径下的间接块映射(老格式)
inline.o inline.c 内联数据(小文件数据在 inode 内)
inode.o inode.c inode 读写、时间戳、ext4_map_blocks、页缓存配合、truncate 协作
ioctl.o ioctl.c ext4_ioctl:标签、feature、group desc、迁移、物理整理等
mballoc.o mballoc.c 多块分配器 (mballoc):buddy、预分配、CR 扫描策略
migrate.o migrate.c extent 与 non-extent 间迁移等
mmp.o mmp.c Multi-Mount Protection,防同一设备被并发挂载损坏
move_extent.o move_extent.c EXT4_IOC_MOVE_EXT:块在文件间交换(高级维护)
namei.o namei.c 路径解析、lookup/create/link/unlink/rename、htree
page-io.o page-io.c 元数据页/日志相关页写下的 bio 提交封装
readpage.o readpage.c address_space read_folio/readahead 与extent 预读
resize.o resize.c 在线扩容:组描述符、位图增长
super.o super.c 挂载/卸载、fill_super、super 读写、feature 检查、file_system_type
symlink.o symlink.c 符号链接 inode 与快/慢路径
sysfs.o sysfs.c sysfs 导出统计与可调参数
xattr.o + hurd/trusted/user xattr*.c 扩展属性后端(内部块、trusted、user、hurd)
fast_commit.o fast_commit.c 快速提交:增量 journal 路径
orphan.o orphan.c orphan 文件/链表:崩溃恢复时截断已删仍打开的 inode

按 Kconfig 条件编译:

  • acl.o:POSIX ACL(CONFIG_EXT4_FS_POSIX_ACL
  • xattr_security.o:SELinux 等安全标(CONFIG_EXT4_FS_SECURITY
  • verity.o:fsverity(CONFIG_FS_VERITY
  • crypto.o:fscrypt 加密(CONFIG_FS_ENCRYPTION
  • inode-test.o:KUnit(CONFIG_EXT4_KUNIT_TESTS

头文件要点:ext4.h(超级块/inode 内存结构、宏与内联)、ext4_extents.hext4_jbd2.hmballoc.hxattr.hacl.hfsmap.htruncate.h

1.2 Kconfig 依赖

CONFIG_EXT4_FSselect JBD2CRC16CRYPTOCRYPTO_CRC32CFS_IOMAP 等;可选 EXT4_USE_FOR_EXT2 在禁用独立 ext2 驱动时用 ext4 同时注册 ext2。详见 fs/ext4/Kconfig


2. VFS 注册与挂载路径

2.1 file_system_type

super.c 中定义 ext4 文件类型并注册:

1
2
3
4
5
6
7
8
static struct file_system_type ext4_fs_type = {
.owner = THIS_MODULE,
.name = "ext4",
.init_fs_context = ext4_init_fs_context,
.parameters = ext4_param_specs,
.kill_sb = kill_block_super,
.fs_flags = FS_REQUIRES_DEV | FS_ALLOW_IDMAP,
};
  • init_fs_context:挂载参数解析(fs_context_operationsparse_paramget_treereconfigurefree)。
  • get_tree:典型为 get_tree_bdev(fc, ext4_fill_super)(6.1 新挂载 API)。
  • kill_sb:通用 kill_block_supersuper.c 释放块设备与 super)。

同一文件中还注册 ext3;在 EXT4Use_FOR_EXT2 且未编 ext2 时可注册 ext2 别名。

2.2 模块初始化顺序(子系统就绪)

ext4_init_fs() 中顺序(摘自 super.c 尾部逻辑):
ratelimitext4_init_es(extent status)→ ext4_init_pendingext4_init_post_read_processingext4_init_pageioext4_init_system_zoneext4_init_sysfsext4_init_mballocinit_inodecachefast commit dentry cache → 注册 ext3/ext2 → register_filesystem(&ext4_fs_type)

卸载时按相反顺序撤销。理解此顺序有助于排查 初始化失败 时哪一步返回错误。

2.3 锁顺序(官方注释)

super.c 文件头给出 ext4 在 缺页、缓冲写、truncate、DIO、writepages 上的锁依赖,调试死锁时应优先对照:

1
2
3
4
5
6
7
8
9
10
11
* Lock ordering
*
* page fault path:
* mmap_lock -> sb_start_pagefault -> invalidate_lock (r) -> transaction start
* -> page lock -> i_data_sem (rw)
*
* buffered write path:
* sb_start_write -> i_mutex -> mmap_lock
* sb_start_write -> i_mutex -> transaction start -> page lock ->
* i_data_sem (rw)
* ... (truncate / direct IO / writepages)

3. 核心数据结构(内存与磁盘)

3.1 struct ext4_inode_info

每个 VFS struct inode 通过 EXT4_I(inode) 得到 ext4_inode_info,挂载在 inode 尾部 vfs_inode。要点(见 ext4.h):

  • i_data[]:未转换的 15 项传统块指针(与 extent 共存时代的兼容/起点)。
  • i_block_group:inode 所在块组,用于 局部性分配
  • i_disksize vs i_sizei_disksize 表示 盘上 已提交长度,truncate 与恢复关键。
  • i_data_sem:截断与 get_block/extent 修改之间的串行。
  • i_es_tree / i_es_lockextent status 缓存树。
  • i_prealloc_*mballoc 预分配 链表与锁。
  • i_fc_*fast commit 跟踪。
  • xattr_sem:扩展属性与数据写分离锁。
  • jinode:JBD2 对该 inode 的附加信息。

3.2 struct ext4_sb_info

EXT4_SB(sb) 指向 per-mount 的超级块运行态:s_es 指向磁盘 super 在 buffer_head 中的镜像;s_journals_mount_opt(s)mballoc 的 s_group_info/s_mb_*s_es_shrinkerquota/fscrypt/dax 等均在此。细节字段见 ext4.hstruct ext4_sb_info(篇幅长,适合按需 grep)。

3.3 磁盘布局(概念)

  • 超级块 + 块组描述符表 + 每块组:块位图、inode 位图、inode 表、数据区。
  • extent:大部分现代 ext4 文件用 extent B+ 树 描述 (logical block → physical block, len)
  • bigalloccluster(多 block 为分配单位),宏 EXT4_CLUSTER_*ext4.h
  • journal:默认 JBD2ext4_jbd2.c 包装 start_commitext4_journal_get_write_access 等。

4. 子系统协作(数据路径)

4.1 块映射总线:ext4_map_blocksinode.c

用户读写、缺页、get_block 最终都汇聚到 逻辑块 → 物理块 映射:

  • 查/更新 extent 树extents.c);
  • 参考 extent statusextents_status.c)避免重复扫描、支持 DELALLOC;
  • 延迟分配:可先只更新 ES 与 inode,稍后 mballoc 落地;
  • 未写 extent: unwritten 标记,回写后转换。

4.2 块分配:mballoc.c + balloc.c

  • mballoc 为主路径:按 buddy、per-group 信息、prealloc 做高性能分配;
  • balloc 补充位图读写、计数维护及与老接口的衔接。

4.3 日志:ext4_jbd2.c + ext4_jbd2.h

  • 元数据块通过 journal 先行记录 再写原位;
  • data=ordered / writeback / journal 等 data 模式影响 数据块 与日志顺序;
  • fast_commitfast_commit.c)在支持的配置上减少全事务开销。

4.4 目录与 Htree:namei.c + dir.c + hash.c

  • namei.cVFS inode_operationslookup/mkdir/...、rename 复杂路径;
  • dir.c目录块 格式、readdir
  • hash.chtree 哈希与冲突处理。

4.5 页缓存与 I/O:readpage.cfile.cpage-io.cfsync.c

  • 缓冲 I/Oaddress_space_operationsinode.c/readpage.c 等分散设置;
  • file.ciomap/daxext4_iomap_ops、Direct I/O、fallocate
  • page-io.c:下层 bio 提交与日志相关的页写出;
  • fsync.c:保证 journal + 脏页序。

4.6 扩展属性与安全:xattr*.cacl.cxattr_security.c

  • xattr.c:EA 与 inode、块存储格式;
  • acl.c:POSIX ACL 与 generic_acl 桥接;
  • xattr_security.c:安全模块使用的 xattr 名空间。

4.7 运维与特性:ioctl.cresize.cmmp.csysfs.c

  • ioctl.c:在线 resize 协助、feature 操作、组参数、物理丢弃/搬迁等 EXT4_IOC_*
  • resize.c:扩容时增长 group desc、位图;
  • mmp.c:多挂载防护周期写块;
  • sysfs.c:导出 mballoc 等统计与参数。

4.8 仅当配置开启

  • crypto.cfscrypt: per-file/目录密钥、块内加密;
  • verity.c:merkle 树校验只读内容;
  • veritydaxs_daxdevext4_sb_info):读路径旁路页缓存需与 extent/一致性小心配合。

5. 整体数据流简图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
用户 read/write/mmap
|
V
VFS (file_operations / address_space)
|
v
file.c / inode.c / readpage.c
|
v
ext4_map_blocks + extents + extents_status
|
+--> mballoc/balloc (新块)
|
v
page cache / bio 或 iomap DIO
|
v
ext4_jbd2 (+ fast_commit) 元数据顺序
|
v
块设备 (eMMC/UFS/NVMe/...)

6. RK3588 相关注意点

  • 根文件系统 / userdata:RK 方案常见 ext4 + f2fs;ext4 多用于 boot、vendor、system 等只读/读写分区。
  • 性能与磨损:关注 barrier/nobarrier(随内核演进注意语义变化)journal_async_commitdelalloc、挂载选项与存储控制器 cache;MMP 在生产上避免双挂同卷。
  • 裁剪:若不需要 ACL/security/verity/encryption,可在 Kconfig 关掉对应选项以减小 ext4 模块体积。
  • 调试CONFIG_EXT4_DEBUG、tracepoints(CREATE_TRACE_POINTS + trace/events/ext4.h)、sysfs 统计。

7. 推荐阅读顺序(读源码)

  1. super.cext4_fill_superext4_parse_paramext4_reconfigure、feature 与错误处理。
  2. ext4.hext4_inode_infoext4_sb_infoext4_map_blocks
  3. inode.cext4_map_blocks、dirty 与 writeback 交互。
  4. extents.c + extents_status.c:现代 ext4 的核心。
  5. mballoc.c + balloc.c:分配策略。
  6. ext4_jbd2.c + fast_commit.c:一致性与性能。
  7. namei.c + dir.c:目录与 rename。
  8. file.c:mmap/DIO/fallocate。
  9. 按需:resize.cioctl.corphan.cmmp.c

8. 小结

fs/ext4 是完整的 fourth extended 实现:在 VFS 层注册为块设备文件系统,通过 ext4_sb_info / ext4_inode_info 维护挂载与 inode 状态;extent + extent status + mballoc + JBD2(及 fast commit) 构成现代数据与元数据路径;namei/dir/hash 负责命名空间;xattr/acl/security/verity/crypto 提供策略与保密完整性;resize/ioctl/sysfs/mmp/orphan 覆盖工程与可靠性需求。上述与 Makefile 中 .o 列表一一对应,可按表索骥阅读。


文档对应内核树路径:rk3588/kernel-6.1/fs/ext4/。生成位置:linuxDoc/fs/ext4/

Linux 文件系统架构与源码总览

Linux 文件系统架构与源码总览

1. 范围

fs/ 同时包含:

  1. VFS:所有文件系统共享的对象模型和系统调用;
  2. 文件 I/O 基础设施:页缓存、写回、direct I/O、iomap、pipe、poll;
  3. 元数据基础设施:ACL、xattr、quota、locks、notify;
  4. 挂载和 namespace;
  5. 具体本地、内存、叠加、用户态及网络文件系统。

2. 分层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
userspace
open/read/write/stat/mmap/fsync/mount/ioctl/poll
|
syscall entry: fs/*.c |
v
VFS: file_system_type / super_block / inode / dentry / file
|
operation tables:
super_operations / inode_operations / dentry_operations
file_operations / address_space_operations / export_operations
|
ext4/f2fs/xfs/btrfs/erofs/overlay/fuse/nfs/smb/ceph/...
|
page cache / block layer / MTD / network / userspace daemon

VFS 不把所有文件系统变成相同实现,而是统一对象生命周期、权限、路径和调用约定。

3. 核心对象

对象 含义
file_system_type 文件系统类型注册项
fs_context 一次挂载配置过程
super_block 已挂载文件系统实例
vfsmount/mount namespace 中挂载关系
inode 文件对象元数据和操作
dentry 名称到 inode 的缓存关系
path dentry + mount
file 一次打开实例,绑定 flags/position/cred
address_space inode 页缓存与 I/O 操作
files_struct 进程 fdtable
fs_struct 进程 root/pwd

4. 打开文件

fdtablefilesystemdcachenamei.copen.cuserspacefdtablefilesystemdcachenamei.copen.cuserspaceopenat2()get_unused_fd_flags()do_filp_open()RCU path walk / lookup_fasthit or missinode_operations.lookup/createfile_operations.openfd_install()fd

路径解析优先走 dcache 的 RCU-walk,遇到需要阻塞、重验证或复杂符号链接时退化为 ref-walk。

5. 读写

1
2
3
4
5
6
7
8
read/write syscall
-> fdget_pos()
-> vfs_read/vfs_write
-> rw_verify_area + security hooks
-> file->f_op->read_iter/write_iter
-> buffered: page cache/filemap
-> direct: filesystem DIO/iomap
-> block/network/userspace backend

VFS 负责通用检查和统计;数据一致性、extent、日志和真实 I/O 由具体文件系统实现。

6. 挂载

mount(2) 与新 API 并存。新流程:

1
2
3
4
fsopen(type) -> fsconfig(parameters) -> fsmount()
-> vfs_get_tree()
-> filesystem get_tree/fill_super
-> move_mount() attach into namespace

namespace.c 维护 mount tree、传播、bind/move/remount;super.c 管理 superblock 创建和销毁。

7. 缓存

  • dcache:路径组件和 negative dentry;
  • inode cache:内存 inode;
  • page cache:文件数据 folio;
  • buffer_head:传统块映射和 metadata buffer;
  • filesystem-specific metadata cache;
  • fscache/cachefiles:网络文件系统本地缓存。

缓存不是持久性保证。fsync()、日志提交、barrier/FUA 和设备缓存共同决定落盘语义。

8. 并发

关键同步包括:

  • rename_lockmount_lock seqlock;
  • dentry d_lock
  • inode i_rwsemi_lock
  • superblock s_umount
  • fdtable RCU;
  • pathwalk RCU;
  • page/folio lock、mapping i_pages xarray;
  • filesystem transaction/journal locks。

锁顺序必须结合 Documentation/filesystems/ 和 lockdep,不可只按对象层级猜测。

9. RK3588

典型组合:

  • eMMC/SD:ext4 或 f2fs;
  • NVMe:ext4/xfs/btrfs(按产品需求);
  • 只读根文件系统:EROFS/SquashFS + OverlayFS;
  • raw NAND:UBI + UBIFS;
  • Android/容器:tmpfs、proc、sysfs、overlay;
  • NAS/集群:NFS/SMB/Ceph。

性能同时受文件系统、块层、I/O scheduler、控制器、介质、内存回收和写回影响。