第一阶段 从内至外视角

第一部分:VFS核心架构

1.1 文件系统三层架构

Linux文件系统架构
│
├── 【系统调用层】fs/open.c, fs/read_write.c
│   ├── sys_open() / sys_read() / sys_write() / sys_close()
│   ├── sys_mkdir() / sys_rmdir() / sys_unlink()
│   └── sys_stat() / sys_fstat() / sys_lstat()
│
├── 【VFS抽象层】核心数据结构
│   │
│   ├── struct super_block (超级块) 
│   │   ├── struct list_head s_list           # 超级块链表
│   │   ├── struct file_system_type *s_type   # 文件系统类型
│   │   ├── struct super_operations *s_op     # 超级块操作
│   │   └── struct list_head s_instances      # 同类实例链表
│   │
│   ├── struct inode (索引节点) 
│   │   ├── struct inode_operations *i_op     # inode操作
│   │   ├── struct file_operations *i_fop     # 文件操作
│   │   ├── struct super_block *i_sb          # 所属超级块
│   │   ├── loff_t i_size                     # 文件大小
│   │   ├── struct timespec i_atime/mtime/ctime # 时间戳
│   │   └── struct address_space *i_mapping   # 页缓存映射
│   │
│   ├── struct dentry (目录项) 
│   │   ├── struct inode *d_inode             # 关联的inode
│   │   ├── struct dentry *d_parent           # 父目录项
│   │   ├── struct qstr d_name                # 目录项名称
│   │   ├── struct list_head d_subdirs        # 子目录链表
│   │   ├── struct dentry_operations *d_op    # 目录项操作
│   │   └── struct super_block *d_sb          # 超级块
│   │
│   ├── struct file (文件对象) 
│   │   ├── struct path f_path                # 文件路径
│   │   ├── struct inode *f_inode             # 关联的inode
│   │   ├── const struct file_operations *f_op # 文件操作
│   │   ├── loff_t f_pos                      # 当前文件位置
│   │   └── fmode_t f_mode                    # 打开模式
│   │
│   └── struct vfsmount (挂载点)
│       ├── struct dentry *mnt_root           # 挂载根目录
│       ├── struct super_block *mnt_sb        # 超级块
│       └── struct vfsmount *mnt_parent       # 父挂载点
│
└── 【具体文件系统层】
    ├── 磁盘文件系统: ext2/ext3/ext4, xfs, btrfs
    ├── 虚拟文件系统: procfs, sysfs, tmpfs, debugfs 
    ├── 网络文件系统: NFS, CIFS, 9p
    └── 特殊文件系统: sockfs, pipefs, futexfs

1.2 VFS虚函数表结构

1.2.1 超级块操作表 (struct super_operations)
struct super_operations {
    /* 在给定的超级块下创建并初始化一个新的索引节点对象 */
    struct inode *(*alloc_inode)(struct super_block *sb);
    
    /* 销毁索引节点对象 */
    void (*destroy_inode)(struct inode *);
    
    /* 从磁盘上读取索引节点,填充内存中的inode对象 */
    void (*read_inode)(struct inode *);
    
    /* 将inode写回磁盘 */
    void (*write_inode)(struct inode *, struct writeback_control *wbc);
    
    /* 释放超级块 */
    void (*put_super)(struct super_block *);
    
    /* 同步超级块到磁盘 */
    int (*sync_fs)(struct super_block *sb, int wait);
    
    /* 统计文件系统状态 */
    int (*statfs)(struct dentry *, struct kstatfs *);
    
    /* 重新挂载文件系统 */
    int (*remount_fs)(struct super_block *, int *, char *);
    
    /* 清除inode (最后一个引用释放时) */
    void (*clear_inode)(struct inode *);
    
    /* 在文件系统上显示挂载选项 */
    int (*show_options)(struct seq_file *, struct dentry *);
    
    /* 配额管理 */
    int (*quota_read)(struct super_block *, int, char *, size_t, loff_t);
    int (*quota_write)(struct super_block *, int, const char *, size_t, loff_t);
    
    /* 预读inode */
    void (*readahead)(struct readahead_control *, struct dentry *);
};
1.2.2 索引节点操作表 (struct inode_operations)
struct inode_operations {
    /* 查找目录中的dentry对应的inode */
    struct dentry *(*lookup)(struct inode *, struct dentry *, unsigned int);
    
    /* 创建普通文件 */
    int (*create)(struct mnt_idmap *, struct inode *, struct dentry *, umode_t, bool);
    
    /* 创建硬链接 */
    int (*link)(struct dentry *, struct inode *, struct dentry *);
    
    /* 删除硬链接 */
    int (*unlink)(struct inode *, struct dentry *);
    
    /* 创建符号链接 */
    int (*symlink)(struct mnt_idmap *, struct inode *, struct dentry *, const char *);
    
    /* 创建目录 */
    int (*mkdir)(struct mnt_idmap *, struct inode *, struct dentry *, umode_t);
    
    /* 删除目录 */
    int (*rmdir)(struct inode *, struct dentry *);
    
    /* 重命名文件/目录 */
    int (*rename)(struct mnt_idmap *, struct inode *, struct dentry *,
                  struct inode *, struct dentry *, unsigned int);
    
    /* 设置属性 (chmod, chown等) */
    int (*setattr)(struct mnt_idmap *, struct dentry *, struct iattr *);
    
    /* 获取属性 */
    int (*getattr)(struct mnt_idmap *, const struct path *, struct kstat *, u32, unsigned int);
    
    /* 设置扩展属性 (xattr) */
    int (*setxattr)(struct mnt_idmap *, struct dentry *, const char *,
                    const void *, size_t, int);
    
    /* 获取扩展属性 */
    ssize_t (*getxattr)(struct dentry *, const char *, void *, size_t);
    
    /* 列出扩展属性 */
    ssize_t (*listxattr)(struct dentry *, char *, size_t);
    
    /* 原子打开+创建 (支持NFS等网络文件系统)  */
    struct dentry *(*atomic_open)(struct inode *, struct dentry *, struct file *,
                                   unsigned, umode_t);
};
1.2.3 文件操作表 (struct file_operations)
struct file_operations {
    struct module *owner;
    
    /* 文件偏移定位 */
    loff_t (*llseek)(struct file *, loff_t, int);
    
    /* 读取文件 */
    ssize_t (*read)(struct file *, char __user *, size_t, loff_t *);
    
    /* 写入文件 */
    ssize_t (*write)(struct file *, const char __user *, size_t, loff_t *);
    
    /* 就绪查询 (用于poll/select/epoll)  */
    __poll_t (*poll)(struct file *, struct poll_table_struct *);
    
    /* 异步I/O */
    ssize_t (*read_iter)(struct kiocb *, struct iov_iter *);
    ssize_t (*write_iter)(struct kiocb *, struct iov_iter *);
    
    /* 打开文件 */
    int (*open)(struct inode *, struct file *);
    
    /* 释放文件 (关闭) */
    int (*release)(struct inode *, struct file *);
    
    /* 同步到磁盘 (fsync) */
    int (*fsync)(struct file *, loff_t, loff_t, int);
    
    /* 内存映射 */
    int (*mmap)(struct file *, struct vm_area_struct *);
    
    /* 文件锁 */
    int (*flock)(struct file *, int, struct file_lock *);
    
    /* 建议性锁 */
    int (*lock)(struct file *, int, struct file_lock *);
    
    /* 文件系统特定操作 (ioctl) */
    long (*unlocked_ioctl)(struct file *, unsigned int, unsigned long);
    long (*compat_ioctl)(struct file *, unsigned int, unsigned long);
    
    /* fallocate (预分配空间) */
    long (*fallocate)(struct file *, int, loff_t, loff_t);
    
    /* 复制文件范围 (reflink) */
    ssize_t (*copy_file_range)(struct file *, loff_t, struct file *, loff_t, size_t, unsigned int);
    
    /* 克隆/去重 */
    int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t, u64);
    int (*dedupe_file_range)(struct file *, loff_t, struct file *, loff_t, u64);
    
    /* 重新映射文件范围 (用于数据块移动) */
    loff_t (*remap_file_range)(struct file *, loff_t, struct file *, loff_t, loff_t, unsigned int);
    
    /* 打开目录 (用于iterate_shared) */
    int (*iterate_shared)(struct file *, struct dir_context *);
};

1.3 open/read系统调用完整流程

1.3.1 sys_open调用链
sys_open() 完整调用链
│
├── 【系统调用入口】 fs/open.c
│   └── SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
│       └── do_sys_open(AT_FDCWD, filename, flags, mode)
│
├── 【路径解析】 fs/namei.c 
│   │
│   ├── do_sys_open()
│   │   └── do_filp_open()
│   │       ├── path_openat()              # 路径解析核心函数
│   │       │   ├── set_nameidata()        # 设置路径解析上下文
│   │       │   ├── link_path_walk()       # 逐级解析路径组件
│   │       │   │   ├── walk_component()   # 解析单个路径组件
│   │       │   │   │   ├── lookup_fast()  # 从dcache快速查找
│   │       │   │   │   └── lookup_slow()  # 调用文件系统lookup
│   │       │   │   │       └── inode->i_op->lookup()  # 实际文件系统操作
│   │       │   │   └── handle_dots()      # 处理"."和".."
│   │       │   ├── do_last()              # 处理最后一个路径组件
│   │       │   │   ├── lookup_open()      # 查找或创建文件
│   │       │   │   │   └── atomic_open()  # 原子打开+创建 
│   │       │   │   ├── vfs_open()         # 调用文件系统open
│   │       │   │   │   └── file->f_op->open()  # 实际文件系统open
│   │       │   │   └── may_open()         # 权限检查
│   │       │   └── terminate_walk()       # 清理路径解析状态
│   │       └── alloc_file()               # 分配struct file
│   │
│   └── 关键数据结构流转
│       └── struct nameidata {
│               struct path     path;       # 当前路径
│               struct qstr     last;       # 最后一个组件
│               struct dentry   *root;      # 根目录
│               unsigned int    flags;      # LOOKUP_*标志
│           };
│
├── 【文件描述符管理】 fs/file.c
│   └── fd_install()                       # 将file绑定到文件描述符
│
└── 【返回用户态】
    └── 返回 fd (文件描述符)
1.3.2 sys_read调用链
sys_read() 完整调用链 
│
├── 【系统调用入口】 fs/read_write.c
│   └── SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
│       └── ksys_read(fd, buf, count)
│           └── vfs_read(file, buf, count, &file->f_pos)
│
├── 【VFS层】 fs/read_write.c
│   └── vfs_read()
│       ├── 权限检查: file->f_mode & FMODE_READ
│       ├── 文件偏移检查
│       └── 调用文件系统读函数
│           ├── 如果定义了read_iter → file->f_op->read_iter()
│           └── 否则调用 → file->f_op->read()
│
├── 【页缓存层】 mm/filemap.c (普通文件)
│   │
│   ├── generic_file_read_iter()           # 通用读实现
│   │   └── filemap_read()
│   │       ├── filemap_get_pages()        # 获取页缓存页
│   │       │   ├── find_get_page()        # 查找页缓存
│   │       │   └── page_cache_sync_readahead() # 预读
│   │       ├── copy_page_to_iter()        # 拷贝到用户空间
│   │       └── filemap_fault()            # 缺页处理
│   │
│   └── address_space_operations 
│       ├── .readpage = ext4_readpage
│       ├── .readahead = ext4_readahead
│       └── .bmap = ext4_bmap
│
├── 【实际文件系统层】 fs/ext4/file.c
│   │
│   ├── ext4_file_read_iter()
│   │   ├── 检查是否direct I/O
│   │   │   ├── 是 → ext4_dio_read_iter()
│   │   │   └── 否 → generic_file_read_iter()
│   │   └── 更新访问时间 (atime)
│   │
│   └── ext4_readpage()                    # 读取单个页面
│       ├── ext4_mpage_readpages()         # 多页读取
│       │   ├── ext4_map_blocks()          # 逻辑块→物理块映射
│       │   ├── do_mpage_readpage()        # 实际读取
│       │   └── submit_bio()               # 提交块I/O请求
│       └── ext4_bio_write_page()          # 写回脏页
│
├── 【块设备层】 block/blk-core.c
│   │
│   ├── submit_bio()                       # 提交bio到块层
│   │   ├── generic_make_request()         # 通用请求构造
│   │   ├── blk_mq_submit_bio()            # 多队列提交
│   │   └── blk_mq_sched_insert_request()  # 插入调度队列
│   │
│   └── 块设备驱动处理
│       └── scsi_request_fn()              # SCSI/ATA驱动
│
└── 【返回用户态】
    └── 返回读取字节数

第二部分:文件系统类型源码库树形分析

2.1 主要文件系统源码目录结构

fs/ (Linux内核文件系统源码根目录)
│
├── 1. 虚拟文件系统核心
│   ├── open.c                    # open/close系统调用
│   ├── read_write.c              # read/write系统调用
│   ├── file.c                    # 文件描述符管理
│   ├── namei.c                   # 路径查找核心 
│   ├── super.c                   # 超级块管理
│   ├── inode.c                   # inode管理
│   ├── dentry.c                  # dcache目录项缓存
│   ├── namespace.c               # 命名空间和挂载 
│   ├── mount.h                   # 挂载相关
│   └── stat.c                    # stat/fstat系统调用
│
├── 2. 页缓存与I/O
│   ├── filemap.c                 # 页缓存核心 
│   ├── buffer.c                  # 块缓冲层
│   ├── iomap.c                   # iomap框架 (现代块映射)
│   ├── direct-io.c               # Direct I/O实现
│   ├── readahead.c               # 预读机制
│   ├── writeback.c               # 回写机制
│   └── splice.c                  # splice系统调用
│
├── 3. 内存管理接口
│   ├── mmap.c                    # 内存映射
│   └── madvise.c                 # madvise系统调用
│
├── 4. 扩展属性与锁
│   ├── xattr.c                   # 扩展属性
│   └── locks.c                   # 文件锁
│
├── 5. 磁盘文件系统
│   ├── ext2/                     # Second Extended FS
│   │   ├── ext2.h
│   │   ├── super.c
│   │   ├── inode.c
│   │   ├── file.c
│   │   ├── dir.c
│   │   └── balloc.c              # 块分配
│   │
│   ├── ext4/                     # Fourth Extended FS
│   │   ├── ext4.h
│   │   ├── ext4_jbd2.h
│   │   ├── super.c               # 超级块操作 
│   │   ├── inode.c               # inode操作 
│   │   ├── file.c                # 文件操作 
│   │   ├── namei.c               # 目录查找
│   │   ├── dir.c                 # 目录操作
│   │   ├── extent.c              #  extent树管理 
│   │   ├── mballoc.c             # 多块分配器
│   │   ├── ialloc.c              # inode分配
│   │   ├── block_validity.c
│   │   ├── migrate.c
│   │   ├── resize.c
│   │   └── sysfs.c
│   │
│   ├── xfs/                      # XFS (高性能64位)
│   │   ├── xfs_super.c
│   │   ├── xfs_inode.c
│   │   ├── xfs_bmap.c
│   │   ├── xfs_alloc.c
│   │   └── xfs_log.c
│   │
│   ├── btrfs/                    # B-tree FS (CoW)
│   │   ├── super.c
│   │   ├── extent-tree.c
│   │   ├── file.c
│   │   └── compression.c
│   │
│   └── fat/                      # FAT12/16/32
│       ├── fat.h
│       ├── namei_msdos.c
│       ├── file.c
│       └── dir.c
│
├── 6. 虚拟文件系统
│   ├── proc/                     # /proc 进程信息
│   │   ├── proc_root.c
│   │   ├── proc_pid.c
│   │   ├── inode.c
│   │   └── proc_net.c
│   │
│   ├── sysfs/                    # /sys 系统信息
│   │   ├── sysfs.h
│   │   ├── file.c
│   │   ├── dir.c
│   │   └── symlink.c
│   │
│   ├── devfs/                    # 设备文件系统
│   │   ├── devfs_core.c
│   │   └── devfs_util.c
│   │
│   ├── tmpfs/                    # 内存文件系统
│   │   ├── shmem.c
│   │   └── inode.c
│   │
│   └── debugfs/                  # 调试文件系统 
│       ├── file.c
│       ├── inode.c
│       └── debugfs.c
│
├── 7. 网络文件系统
│   ├── nfs/                      # NFS客户端
│   │   ├── nfs3proc.c
│   │   ├── nfs4proc.c
│   │   ├── nfs4state.c
│   │   └── nfs4xdr.c
│   │
│   ├── nfsd/                     # NFS服务端
│   │   ├── nfs4proc.c 
│   │   ├── nfs4state.c
│   │   ├── vfs.c
│   │   └── export.c
│   │
│   └── cifs/                     # SMB/CIFS
│       ├── cifsfs.c
│       ├── file.c
│       └── dir.c
│
└── 8. 其他文件系统
    ├── fuse/                     # FUSE (用户空间文件系统)
    │   ├── dev.c
    │   ├── file.c
    │   ├── dir.c
    │   └── inode.c
    │
    ├── overlayfs/                # 联合挂载
    │   ├── overlayfs.h
    │   ├── file.c
    │   ├── dir.c
    │   └── inode.c
    │
    ├── squashfs/                 # 压缩只读文件系统
    │   ├── squashfs_fs.h
    │   ├── file.c
    │   ├── inode.c
    │   └── block.c
    │
    └── ramfs/                    # 简单内存文件系统
        ├── file-mmu.c
        └── inode.c

第三部分:Rootfs与磁盘布局树形分析

3.1 rootfs初始化与挂载流程

rootfs初始化完整流程 
│
├── 【阶段1: 注册rootfs文件系统】
│   │
│   ├── init_rootfs()                     # rootfs注册入口
│   │   └── register_filesystem(&rootfs_fs_type)
│   │
│   └── rootfs_fs_type结构体 
│       struct file_system_type rootfs_fs_type = {
│           .name = "rootfs",
│           .read_super = ramfs_read_super,   # 使用ramfs实现
│           .fs_flags = FS_NOMOUNT | FS_LITTER,
│           .owner = THIS_MODULE,
│       };
│
├── 【阶段2: 创建VFS根目录并挂载rootfs】
│   │
│   ├── init_mount_tree() 
│   │   │
│   │   ├── 1. do_kern_mount("rootfs", 0, "rootfs", NULL)
│   │   │   │
│   │   │   ├── alloc_vfsmnt()            # 分配vfsmount结构
│   │   │   │
│   │   │   ├── get_sb_nodev()            # 分配超级块
│   │   │   │   └── ramfs_read_super()    # rootfs的read_super
│   │   │   │       │
│   │   │   │       ├── ramfs_get_inode() # 分配inode
│   │   │   │       │   ├── i_op = &ramfs_dir_inode_operations
│   │   │   │       │   └── i_fop = &dcache_dir_ops
│   │   │   │       │
│   │   │   │       ├── d_alloc_root()    # 创建根dentry
│   │   │   │       └── inode->i_sb = sb
│   │   │   │
│   │   │   └── 返回mnt (vfsmount结构)
│   │   │
│   │   ├── 2. 创建namespace
│   │   │   └── init_task.namespace = namespace
│   │   │
│   │   └── 3. 设置当前进程的根目录
│   │       ├── set_fs_root(current->fs, mnt, mnt->mnt_root)
│   │       └── set_fs_pwd(current->fs, mnt, mnt->mnt_root)
│   │
│   └── 结果数据结构关系 
│       ┌─────────────────────────────────────────────┐
│       │  vfsmount (mnt)                             │
│       │  ├── mnt_root → dentry (根目录)             │
│       │  ├── mnt_sb → super_block                  │
│       │  └── mnt_parent → 指向自身                 │
│       └─────────────────────────────────────────────┘
│                           │
│                           ▼
│       ┌─────────────────────────────────────────────┐
│       │  super_block (sb)                          │
│       │  ├── s_type → &rootfs_fs_type              │
│       │  └── s_op → &ramfs_ops                     │
│       └─────────────────────────────────────────────┘
│                           │
│                           ▼
│       ┌─────────────────────────────────────────────┐
│       │  dentry (根目录)                           │
│       │  ├── d_inode → inode                       │
│       │  ├── d_parent → 指向自身                   │
│       │  └── d_name = "/"                          │
│       └─────────────────────────────────────────────┘
│                           │
│                           ▼
│       ┌─────────────────────────────────────────────┐
│       │  inode (根目录inode)                       │
│       │  ├── i_op = &ramfs_dir_inode_operations   │
│       │  ├── i_fop = &dcache_dir_ops              │
│       │  └── i_sb → sb                             │
│       └─────────────────────────────────────────────┘
│
├── 【阶段3: 挂载其他文件系统到rootfs】
│   │
│   ├── /proc → procfs
│   │   └── mount("proc", "/proc", "proc", 0, NULL)
│   │
│   ├── /sys → sysfs
│   │   └── mount("sysfs", "/sys", "sysfs", 0, NULL)
│   │
│   ├── /dev → devtmpfs
│   │   └── mount("devtmpfs", "/dev", "devtmpfs", 0, NULL)
│   │
│   └── /run → tmpfs
│       └── mount("tmpfs", "/run", "tmpfs", 0, "size=10%")
│
└── 【阶段4: 切换根文件系统】
    │
    ├── 内核加载initramfs
    │   └── populate_rootfs()           # 解压initramfs到rootfs
    │
    ├── 启动init进程
    │   └── run_init_process("/init")
    │
    └── 可选: pivot_root()切换到真实根文件系统
        └── 将磁盘文件系统挂载到/root,然后切换

3.2 磁盘布局树形分析 (以EXT4为例)

EXT4磁盘布局 
│
├── 【块组布局】
│   └── 磁盘被划分为多个块组 (Block Group)
│       │
│       ├── 块组0 (Group 0)
│       │   ├── 超级块 (Super Block) - 块0
│       │   │   ├── s_inodes_count      # inode总数
│       │   │   ├── s_blocks_count      # 块总数
│       │   │   ├── s_free_blocks_count # 空闲块数
│       │   │   ├── s_free_inodes_count # 空闲inode数
│       │   │   ├── s_log_block_size    # 块大小 (2^10+s_log_block_size)
│       │   │   ├── s_magic = 0xEF53    # EXT4魔数
│       │   │   ├── s_mtime             # 挂载时间
│       │   │   ├── s_wtime             # 写入时间
│       │   │   └── s_first_data_block  # 第一个数据块
│       │   │
│       │   ├── 组描述符表 (Group Descriptors)
│       │   │   └── struct ext4_group_desc
│       │   │       ├── bg_block_bitmap_lo   # 块位图块号
│       │   │       ├── bg_inode_bitmap_lo   # inode位图块号
│       │   │       ├── bg_inode_table_lo    # inode表起始块
│       │   │       ├── bg_free_blocks_count # 空闲块数
│       │   │       └── bg_free_inodes_count # 空闲inode数
│       │   │
│       │   ├── 块位图 (Block Bitmap)
│       │   │   └── 1位/块, 标记块是否空闲
│       │   │
│       │   ├── inode位图 (Inode Bitmap)
│       │   │   └── 1位/inode, 标记inode是否空闲
│       │   │
│       │   ├── inode表 (Inode Table)
│       │   │   └── struct ext4_inode [4KB/个]
│       │   │       ├── i_mode             # 文件类型和权限
│       │   │       ├── i_uid/gid          # 所有者
│       │   │       ├── i_size             # 文件大小
│       │   │       ├── i_atime/mtime/ctime # 时间戳
│       │   │       ├── i_blocks           # 占用块数
│       │   │       ├── i_block[EXT4_N_BLOCKS] # 数据块指针
│       │   │       │   ├── 0-11: 直接块
│       │   │       │   ├── 12: 一级间接块
│       │   │       │   ├── 13: 二级间接块
│       │   │       │   └── 14: 三级间接块
│       │   │       ├── i_flags            # 文件标志
│       │   │       ├── i_generation       # 版本号
│       │   │       ├── i_file_acl         # 扩展属性块
│       │   │       ├── i_size_high        # 高32位大小 (大文件)
│       │   │       └── i_checksum         # 校验和
│       │   │
│       │   └── 数据块 (Data Blocks)
│       │       └── 实际文件数据
│       │
│       ├── 块组1 (Group 1)
│       │   ├── 超级块备份 (Super Block Backup)
│       │   ├── 组描述符备份
│       │   ├── 块位图
│       │   ├── inode位图
│       │   ├── inode表
│       │   └── 数据块
│       │
│       └── 块组2 ~ N (类似结构,无超级块备份)
│
├── 【Extent树结构】 (EXT4引入,替代间接块)
│   │
│   └── struct ext4_extent_header
│       ├── eh_magic = 0xF30A
│       ├── eh_entries         # extent条目数
│       ├── eh_max             # 最大条目数
│       ├── eh_depth           # 树深度
│       └── eh_generation      # 生成号
│
│   ├── 叶节点: struct ext4_extent
│   │   ├── ee_block           # 起始逻辑块号
│   │   ├── ee_len             # 连续块数
│   │   ├── ee_start_lo        # 起始物理块号(低32位)
│   │   └── ee_start_hi        # 起始物理块号(高16位)
│   │
│   └── 索引节点: struct ext4_extent_idx
│       ├── ei_block           # 覆盖的逻辑块号
│       ├── ei_leaf_lo         # 下一级节点块号(低32位)
│       └── ei_leaf_hi         # 下一级节点块号(高16位)
│
└── 【日志区域】 (JBD2 - Journaling Block Device 2)
    │
    ├── 日志超级块 (Journal Super Block)
    │   ├── s_sequence          # 当前序列号
    │   ├── s_start             # 起始块号
    │   └── s_errno             # 错误码
    │
    ├── 描述符块 (Descriptor Block)
    │   └── 记录本次事务涉及的块号
    │
    ├── 数据块 (Data Blocks)
    │   └── 实际元数据/数据副本
    │
    └── 提交块 (Commit Block)
        └── 标记事务完成

第四部分:关键函数树形分析

4.1 ext4文件写入关键函数调用链

ext4文件写入完整调用链 
│
├── 【用户层】
│   └── write(fd, buf, count)
│
├── 【系统调用层】
│   └── sys_write()
│       └── vfs_write()
│           └── file->f_op->write_iter()
│
├── 【EXT4文件操作层】 fs/ext4/file.c
│   │
│   └── ext4_file_write_iter()
│       │
│       ├── 1. 检查O_DIRECT标志
│       │   │
│       │   ├── 如果iocb->ki_flags & IOCB_DIRECT
│       │   │   └── ext4_dio_write_iter()      # Direct I/O路径
│       │   │       ├── 对齐检查
│       │   │       ├── iomap_dio_rw()          # 使用iomap框架
│       │   │       └── ext4_handle_inode_extension() # 处理文件扩展
│       │   │
│       │   └── 否则 (Buffered I/O)
│       │       └── ext4_buffered_write_iter()
│       │
│       └── 2. 更新atime (如果需要)
│           └── file_accessed()
│
├── 【缓冲写路径】 fs/ext4/file.c
│   │
│   └── ext4_buffered_write_iter()
│       │
│       ├── ext4_ilock()                        # 锁inode
│       │
│       ├── generic_perform_write()             # 通用写函数 
│       │   │
│       │   └── 循环处理每个页面
│       │       │
│       │       ├── a_ops->write_begin() = ext4_write_begin()
│       │       │   │
│       │       │   ├── ext4_da_write_begin()   # 延迟分配写开始
│       │       │   │   ├── 查找或分配页缓存页
│       │       │   │   ├── ext4_journal_start() # 启动事务
│       │       │   │   ├── ext4_map_blocks()    # 块映射/分配
│       │       │   │   └── block_write_begin()
│       │       │   │
│       │       │   └── 如果失败 → ext4_write_begin()
│       │       │       └── 标准写开始路径
│       │       │
│       │       ├── 拷贝用户数据到页缓存
│       │       │   └── copy_page_from_iter_atomic()
│       │       │
│       │       └── a_ops->write_end() = ext4_write_end()
│       │           │
│       │           ├── ext4_da_write_end()     # 延迟分配写结束
│       │           │   ├── generic_write_end()
│       │           │   ├── 标记页为脏
│       │           │   └── ext4_journal_stop()  # 提交事务
│       │           │
│       │           └── ext4_journal_stop()
│       │
│       └── ext4_iunlock()                      # 解锁inode
│
├── 【块分配核心】 fs/ext4/extents.c 
│   │
│   └── ext4_map_blocks()                       # 逻辑块→物理块映射
│       │
│       ├── 1. 查找现有映射
│       │   └── ext4_ext_find_extent()          # 遍历extent树
│       │       │
│       │       ├── 读取extent树节点
│       │       └── 返回包含目标块的extent
│       │
│       ├── 2. 如果不存在且需要分配
│       │   └── ext4_ext_map_blocks()           # 分配新块
│       │       │
│       │       ├── ext4_mb_new_blocks()        # 多块分配器
│       │       │   ├── ext4_mb_regular_allocator() # 常规分配
│       │       │   │   ├── 遍历块组
│       │       │   │   ├── 检查块位图
│       │       │   │   └── 更新位图
│       │       │   └── ext4_mb_use_best_found()    # 使用最佳匹配
│       │       │
│       │       ├── ext4_ext_create_new_leaf()  # 创建新叶节点
│       │       │
│       │       └── ext4_ext_insert_extent()    # 插入extent树
│       │
│       └── 3. 返回物理块号
│
├── 【页缓存管理】 mm/filemap.c
│   │
│   ├── find_get_page()                         # 查找页缓存
│   │
│   ├── __page_cache_alloc()                    # 分配新页面
│   │
│   ├── add_to_page_cache_lru()                 # 加入LRU缓存
│   │
│   └── mark_buffer_dirty()                     # 标记块为脏
│
├── 【日志层】 fs/jbd2/
│   │
│   ├── jbd2_journal_start()                    # 开始事务
│   │   ├── 获取当前事务句柄
│   │   └── 增加事务引用计数
│   │
│   ├── jbd2_journal_get_write_access()         # 获取块写权限
│   │   └── 将块加入事务元数据列表
│   │
│   ├── jbd2_journal_dirty_metadata()           # 标记元数据脏
│   │   └── 加入提交列表
│   │
│   └── jbd2_journal_stop()                     # 停止事务
│       └── 触发日志写入 (如果达到阈值)
│
└── 【回写机制】 fs/fs-writeback.c
    │
    ├── writeback_sb_inodes()                    # 回写超级块的inode
    │   ├── 遍历脏inode链表
    │   ├── __writeback_single_inode()           # 回写单个inode
    │   │   ├── do_writepages()                  # 实际写页面
    │   │   └── 调用 a_ops->writepages = ext4_writepages()
    │   └── 更新回写统计
    │
    └── wb_workfn()                              # 回写工作线程
        └── 定期或达到阈值时触发回写

第五部分:epoll事件分发机制

5.1 epoll核心数据结构

epoll核心数据结构 
│
├── 【epoll实例】 struct eventpoll
│   │
│   ├── struct rb_root rbr;              # 红黑树,存储所有监视的fd
│   ├── struct list_head rdllist;        # 就绪队列 (双链表)
│   ├── wait_queue_head_t wq;            # 等待队列 (epoll_wait阻塞的进程) 
│   ├── struct list_head fds;            # 文件描述符列表
│   ├── struct file *file;               # 关联的文件对象
│   ├── int ovflist;                     # 溢出列表
│   └── struct user_struct *user;        # 用户信息
│
├── 【epoll条目】 struct epitem
│   │
│   ├── struct rb_node rbn;              # 红黑树节点
│   ├── struct list_head rdllink;        # 就绪队列链接
│   ├── struct epoll_filefd ffd;         # 关联的文件和fd
│   │   ├── struct file *file
│   │   └── int fd
│   ├── struct eventpoll *ep;            # 所属epoll实例
│   ├── struct list_head pwqlist;        # 轮询等待队列链表
│   ├── struct epoll_event event;        # 用户关注的事件
│   └── int nwait;                       # 等待队列计数
│
├── 【轮询等待队列】 struct eppoll_entry
│   │
│   ├── struct list_head llink;          # 链接到epitem
│   ├── struct epitem *base;             # 指向epitem
│   ├── wait_queue_entry_t wait;         # 等待队列项
│   └── struct epoll_event *event;       # 事件
│
└── 【文件操作】 struct file_operations epoll_fops
    ├── .poll = ep_eventpoll_poll        # epoll本身可被poll
    ├── .release = ep_eventpoll_release  # 释放
    └── .unlocked_ioctl = ep_ioctl

5.2 epoll事件分发流程

epoll事件分发完整流程 
│
├── 【epoll_create】创建epoll实例
│   │
│   ├── SYSCALL_DEFINE1(epoll_create, int, size)
│   │   └── epoll_create1(0)
│   │       └── do_epoll_create()
│   │           ├── 分配struct eventpoll
│   │           ├── 初始化红黑树(rbr)
│   │           ├── 初始化就绪队列(rdllist)
│   │           ├── 初始化等待队列(wq)
│   │           └── 返回文件描述符
│   │
│   └── 返回epoll实例fd
│
├── 【epoll_ctl】注册监视fd
│   │
│   ├── SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd, struct epoll_event __user *, event)
│   │   │
│   │   ├── 1. ep_find()                  # 在红黑树中查找epitem
│   │   │
│   │   ├── 2. 根据op执行操作
│   │   │   │
│   │   │   ├── EPOLL_CTL_ADD
│   │   │   │   ├── ep_insert()           # 插入新epitem
│   │   │   │   │   ├── 分配epitem
│   │   │   │   │   ├── 初始化epitem
│   │   │   │   │   ├── 添加到红黑树 (rb_insert_color)
│   │   │   │   │   ├── 调用目标文件的poll函数 
│   │   │   │   │   │   └── file->f_op->poll()
│   │   │   │   │   │       │
│   │   │   │   │   │       └── 返回当前事件状态
│   │   │   │   │   │           │
│   │   │   │   │   │           ├── 如果有就绪事件 → 加入rdllist
│   │   │   │   │   │           └── 同时注册等待队列回调
│   │   │   │   │   │
│   │   │   │   │   └── 注册回调: ep_ptable_queue_proc()
│   │   │   │   │       └── 将epitem加入文件私有等待队列
│   │   │   │   │
│   │   │   │   └── 返回
│   │   │   │
│   │   │   ├── EPOLL_CTL_DEL
│   │   │   │   └── ep_remove()           # 从红黑树删除
│   │   │   │
│   │   │   └── EPOLL_CTL_MOD
│   │   │       └── ep_modify()           # 修改监视事件
│   │   │
│   │   └── 返回
│   │
│   └── 关键: 只有实现了poll的文件类型才能被epoll监视 
│
├── 【epoll_wait】等待事件
│   │
│   ├── SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events, int, maxevents, int, timeout)
│   │   │
│   │   └── ep_poll()
│   │       │
│   │       ├── 1. 检查就绪队列 (rdllist)
│   │       │   └── 如果不为空,直接返回
│   │       │
│   │       ├── 2. 如果就绪队列为空
│   │       │   │
│   │       │   ├── 准备等待
│   │       │   │   └── init_waitqueue_entry()   # 初始化等待队列项
│   │       │   │
│   │       │   ├── 加入等待队列 (wq) 
│   │       │   │   └── __add_wait_queue()
│   │       │   │
│   │       │   ├── 设置超时
│   │       │   │   └── schedule_timeout()       # 进程睡眠
│   │       │   │
│   │       │   └── 被唤醒后移除等待队列
│   │       │
│   │       └── 3. 返回就绪事件
│   │           └── ep_send_events()
│   │               ├── 遍历rdllist
│   │               ├── 将事件拷贝到用户空间
│   │               ├── 触发电平触发/边沿触发逻辑
│   │               │   ├── 电平触发 (LT): 保留在rdllist
│   │               │   └── 边沿触发 (ET): 从rdllist移除
│   │               └── 返回事件数量
│   │
│   └── 返回就绪事件数
│
└── 【事件回调机制】
    │
    ├── 当被监视fd有事件发生时
    │   │
    │   └── 驱动调用 poll_wait() 注册的回调
    │       │
    │       └── ep_poll_callback()             # 核心回调函数
    │           │
    │           ├── 1. 将epitem加入就绪队列 (rdllist)
    │           │
    │           ├── 2. 检查是否有进程在epoll_wait等待
    │           │   │
    │           │   ├── 如果有等待进程
    │           │   │   └── wake_up(&ep->wq)   # 唤醒等待进程 
    │           │   │
    │           │   └── 如果有进程在等待文件本身
    │           │       └── wake_up(&ep->poll_wait)
    │           │
    │           └── 返回
    │
    └── 说明: epoll的O(1)性能来自于:
        ├── 红黑树管理 → O(log n) 增删改
        ├── 就绪队列 → O(1) 事件分发
        └── 回调机制 → 主动通知,无需轮询

5.3 epoll惊群效应分析

epoll惊群效应 
│
├── 【定义】
│   └── 多个进程等待同一个epoll实例,事件触发时所有进程被唤醒,
│       但只有1个进程能成功处理事件,其他进程白白被唤醒。
│
├── 【原因分析】
│   │
│   ├── epoll_wait()中,多个进程同时调用epoll_wait
│   │   └── 都加入ep->wq等待队列
│   │
│   ├── 事件触发时
│   │   └── ep_poll_callback()调用wake_up(&ep->wq)
│   │       └── 唤醒所有等待进程 
│   │
│   └── 只有一个进程能成功获取事件
│
├── 【解决方案】
│   │
│   ├── 方案1: EPOLLEXCLUSIVE标志 (Linux 4.5+)
│   │   └── epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event)
│   │       └── event.events |= EPOLLEXCLUSIVE
│   │           └── 只唤醒一个进程
│   │
│   ├── 方案2: SO_REUSEPORT socket选项
│   │   └── 允许多个socket绑定同一端口
│   │       └── 内核负载均衡分发连接
│   │
│   └── 方案3: 使用SO_REUSEADDR + accept锁
│       └── 进程间使用锁同步accept
│
└── 惊群示例代码 
    for (k = 0; k < PROCESS_NUM; k++) {
        int pid = fork();
        if (pid == 0) {
            while (1) {
                n = epoll_wait(efd, events, MAXEVENTS, -1);
                printf("process %d return from epoll_wait!\n", getpid());
                // 处理连接...
            }
        }
    }
    // 一个连接到达时,所有PROCESS_NUM个进程都会被唤醒

第六部分:调试流程与工具树形分析

6.1 文件系统调试流程树

文件系统调试决策树
│
├── 【第1层: 用户空间观察】
│   │
│   ├── 基本状态检查
│   │   ├── df -h                     # 磁盘空间使用
│   │   ├── df -i                     # inode使用
│   │   ├── mount                     # 挂载点状态
│   │   └── cat /proc/mounts          # 内核挂载信息
│   │
│   ├── 文件系统统计
│   │   ├── iostat -x 1               # 磁盘I/O统计 
│   │   ├── sar -d 1                  # 设备活动报告
│   │   └── vmstat 1                  # 内存和I/O统计
│   │
│   └── 性能分析
│       ├── blktrace                  # 块层追踪
│       └── fio                       # I/O性能测试 
│
├── 【第2层: 内核跟踪点】
│   │
│   ├── trace-cmd / ftrace
│   │   │
│   │   ├── 启用文件系统跟踪点
│   │   │   echo 1 > /sys/kernel/tracing/events/ext4/ext4_writepages/enable
│   │   │   echo 1 > /sys/kernel/tracing/events/ext4/ext4_map_blocks/enable
│   │   │   echo 1 > /sys/kernel/tracing/events/vfs/vfs_read/enable
│   │   │   echo 1 > /sys/kernel/tracing/events/filemap/mm_filemap_add_to_page_cache/enable
│   │   │
│   │   ├── 查看追踪输出
│   │   │   cat /sys/kernel/tracing/trace_pipe
│   │   │
│   │   └── trace-cmd record -e ext4 -e vfs -e filemap -p function
│   │
│   ├── perf事件
│   │   │
│   │   ├── perf record -e ext4:ext4_* -a
│   │   ├── perf record -e block:block_rq_*
│   │   └── perf report
│   │
│   └── bpftrace动态追踪
│       │
│       ├── 追踪ext4_write_begin
│       │   bpftrace -e 'kprobe:ext4_write_begin { printf("%s: %d\n", comm, arg2); }'
│       │
│       └── 追踪文件打开
│           bpftrace -e 'kprobe:do_sys_open { printf("open: %s\n", str(arg1)); }'
│
├── 【第3层: 文件系统专用调试】
│   │
│   ├── debugfs (ext2/3/4专用) 
│   │   │
│   │   ├── debugfs /dev/sda1
│   │   │   ├── stats                     # 文件系统统计
│   │   │   ├── ls /                      # 列出目录
│   │   │   ├── cat <inode>               # 查看inode内容
│   │   │   ├── dump_extents <inode>      # 查看extent树 
│   │   │   ├── filefrag <file>           # 查看文件碎片
│   │   │   ├── blocks <inode>            # 查看块映射
│   │   │   └── find_free_block           # 查找空闲块
│   │   │
│   │   └── 常用调试命令
│   │       debugfs -R "stat <2>" /dev/sda1    # 查看根目录inode
│   │       debugfs -R "dump_extents /path"    # 查看extent
│   │       debugfs -R "logdump" /dev/sda1     # 查看日志
│   │
│   ├── xfs_db (XFS专用)
│   │   ├── xfs_db -c "sb" /dev/sda1
│   │   ├── xfs_db -c "blockget" -c "print"
│   │   └── xfs_db -c "frag" -c "info"
│   │
│   ├── btrfs工具
│   │   ├── btrfs inspect-internal dump-super /dev/sda1
│   │   ├── btrfs filesystem show
│   │   └── btrfs check
│   │
│   └── dumpe2fs (ext2/3/4)
│       └── dumpe2fs -h /dev/sda1        # 查看超级块信息
│
├── 【第4层: 块层调试】
│   │
│   ├── blktrace + blkparse
│   │   │
│   │   ├── blktrace -d /dev/sda -o trace
│   │   ├── blkparse -i trace > trace.txt
│   │   └── 分析I/O模式和延迟
│   │
│   ├── seekwatcher
│   │   └── 可视化I/O模式
│   │
│   └── iowatcher
│       └── 生成I/O时间线图
│
├── 【第5层: 内核调试器】
│   │
│   ├── kgdb + gdb
│   │   ├── 设置断点: break ext4_map_blocks
│   │   ├── 检查变量: p *inode
│   │   └── 单步执行
│   │
│   └── kdump / crash
│       └── 分析崩溃转储
│
└── 【第6层: 硬件层】
    │
    ├── smartctl                    # 硬盘SMART信息
    │   └── smartctl -a /dev/sda
    │
    └── 硬件诊断
        ├── 检查SATA/PCIe链路
        ├── 检查电源和线缆
        └── 磁盘坏道检测: badblocks

6.2 调试工具树形图

文件系统调试工具全景
│
├── 【监控工具】 
│   │
│   ├── df / du
│   │   ├── df -hT                    # 显示类型和人类可读大小
│   │   ├── du -sh *                  # 目录大小汇总
│   │   └── ncdu                      # 交互式磁盘使用分析
│   │
│   ├── iostat
│   │   ├── iostat -x 1               # 扩展统计 (await, svctm, %util)
│   │   ├── iostat -k 1               # KB/s统计
│   │   └── iostat -m 1               # MB/s统计
│   │
│   ├── pidstat
│   │   └── pidstat -d 1              # 进程I/O统计
│   │
│   ├── dstat
│   │   ├── dstat -d                  # 磁盘统计
│   │   ├── dstat -f                  # 完整统计
│   │   └── dstat -ta 6               # 每6秒更新 
│   │
│   ├── glances
│   │   └── 综合监控工具 
│   │
│   ├── inotify-tools
│   │   ├── inotifywait -m -r /path   # 递归监控文件变化 
│   │   └── inotifywatch               # 统计文件事件
│   │
│   └── fatrace
│       └── 追踪文件系统访问
│
├── 【分析工具】
│   │
│   ├── strace
│   │   ├── strace -e open,read,write  # 追踪特定系统调用
│   │   ├── strace -p PID              # 附加到进程
│   │   └── strace -tt -T              # 时间戳和耗时
│   │
│   ├── ltrace
│   │   └── 追踪库函数调用
│   │
│   ├── perf
│   │   ├── perf stat                  # 性能计数器
│   │   ├── perf record                # 记录性能数据
│   │   ├── perf report                # 生成报告
│   │   └── perf top                   # 实时性能分析
│   │
│   ├── ftrace
│   │   ├── function tracer            # 函数调用追踪
│   │   ├── function_graph tracer      # 函数图追踪
│   │   └── wakeup tracer              # 唤醒延迟追踪
│   │
│   └── trace-cmd
│       ├── trace-cmd record           # 记录跟踪数据
│       ├── trace-cmd report           # 报告生成
│       └── trace-cmd list             # 列出跟踪点
│
├── 【诊断工具】
│   │
│   ├── fsck家族
│   │   ├── e2fsck (ext2/3/4)
│   │   │   ├── e2fsck -p              # 自动修复
│   │   │   ├── e2fsck -n              # 只读检查
│   │   │   └── e2fsck -y              # 自动回答yes
│   │   ├── xfs_repair (XFS)
│   │   ├── btrfs check (btrfs)
│   │   └── fsck.fat (FAT)
│   │
│   ├── debugfs 
│   │   ├── debugfs -w /dev/sda1       # 读写模式
│   │   ├── debugfs -c /dev/sda1       # 灾难模式
│   │   ├── debugfs -R "command"       # 单命令执行
│   │   └── debugfs -f script.cmd      # 批量执行
│   │
│   ├── blktrace
│   │   ├── blktrace -d /dev/sda       # 追踪块设备
│   │   └── blkparse                   # 解析blktrace输出
│   │
│   └── filefrag
│       └── filefrag -v <file>         # 查看文件碎片
│
├── 【测试工具】
│   │
│   ├── fio 
│   │   ├── fio --filename=/dev/sda --rw=read --bs=4k --size=1G
│   │   ├── fio --filename=/test --rw=randwrite --bs=4k --size=1G
│   │   └── fio --filename=/test --rw=rw --bs=64k --size=10G
│   │
│   ├── bonnie++
│   │   └── 文件系统基准测试
│   │
│   ├── iozone
│   │   └── 综合I/O性能测试
│   │
│   ├── xfstests
│   │   └── 文件系统一致性测试套件
│   │
│   └── ltp (Linux Test Project)
│       └── 全面的Linux测试套件
│
└── 【可视化工具】
    │
    ├── iowatcher
    │   └── 生成blktrace数据的可视化图
    │
    ├── seekwatcher
    │   └── I/O模式可视化
    │
    ├── gnuplot
    │   └── 性能数据绘图
    │
    └── perf script
        └── 生成火焰图数据

第七部分:性能分析树形分析

7.1 文件系统性能指标体系

文件系统性能指标
│
├── 【吞吐量指标】
│   ├── 顺序读吞吐量 (MB/s)
│   ├── 顺序写吞吐量 (MB/s)
│   ├── 随机读IOPS
│   ├── 随机写IOPS
│   ├── 混合读写吞吐量
│   └── 元数据操作速率 (create/delete/stat)
│
├── 【延迟指标】
│   ├── 读延迟 (平均/95th/99th)
│   │   ├── 页缓存命中延迟
│   │   └── 磁盘I/O延迟
│   ├── 写延迟 (平均/95th/99th)
│   │   ├── 异步写返回延迟
│   │   └── fsync/fdatasync延迟
│   ├── 元数据操作延迟
│   │   ├── open()延迟
│   │   ├── stat()延迟
│   │   └── unlink()延迟
│   └── 端到端应用延迟
│
├── 【缓存效率指标】
│   ├── 页缓存命中率
│   ├── dentry缓存命中率
│   ├── inode缓存命中率
│   ├── 脏页比例
│   └── 回写效率
│
├── 【空间利用率】
│   ├── 磁盘空间使用率
│   ├── inode使用率
│   ├── 文件碎片率
│   ├── 目录深度分布
│   └── 文件大小分布
│
└── 【资源消耗指标】
    ├── CPU使用率 (系统态/用户态)
    ├── 内存占用 (页缓存/元数据缓存)
    ├── I/O带宽利用率
    ├── 设备队列深度
    └── 锁竞争统计

7.2 性能瓶颈定位流程

性能瓶颈定位决策树
│
├── 【瓶颈分类】
│   │
│   ├── 类型1: 吞吐量不足
│   │   │
│   │   ├── 检查设备层
│   │   │   ├── iostat -x 1
│   │   │   │   ├── %util接近100% → 设备饱和
│   │   │   │   ├── await > 10ms → 设备延迟高
│   │   │   │   └── r_await/w_await差异 → 读写不平衡
│   │   │   └── blktrace分析
│   │   │       └── 检查I/O模式是否连续
│   │   │
│   │   ├── 检查文件系统层
│   │   │   ├── 碎片分析: filefrag -v
│   │   │   ├── 挂载选项: mount
│   │   │   │   ├── noatime → 减少atime更新
│   │   │   │   ├── nobarrier → 禁用屏障(谨慎)
│   │   │   │   └── data=writeback → 提高写性能
│   │   │   └── 日志模式: tune2fs -l /dev/sda1
│   │   │
│   │   └── 检查页缓存
│   │       ├── free -h               # 查看可用内存
│   │       ├── cat /proc/meminfo | grep -E "(Dirty|Writeback|Cached)"
│   │       └── vmstat 1              # 观察bi/bo
│   │
│   ├── 类型2: 延迟过高
│   │   │
│   │   ├── 分析I/O调度器
│   │   │   ├── cat /sys/block/sda/queue/scheduler
│   │   │   ├── 选择合适调度器
│   │   │   │   ├── none (NVMe)
│   │   │   │   ├── mq-deadline (SAS/SSD)
│   │   │   │   └── bfq (机械硬盘)
│   │   │   └── 调整队列深度
│   │   │
│   │   ├── 分析fsync行为
│   │   │   ├── strace -e fsync,fdatasync -T
│   │   │   ├── 应用程序是否频繁fsync
│   │   │   └── 考虑批量提交
│   │   │
│   │   └── 分析锁竞争
│   │       ├── perf lock record
│   │       ├── perf lock report
│   │       └── 检查inode锁竞争
│   │
│   └── 类型3: 缓存效率低
│       │
│       ├── 页缓存分析
│       │   ├── /proc/meminfo
│       │   │   ├── Cached > 80% → 缓存充足
│       │   │   └── Dirty > 20% → 回写滞后
│       │   ├── 调整脏页阈值
│       │   │   ├── /proc/sys/vm/dirty_ratio
│       │   │   └── /proc/sys/vm/dirty_background_ratio
│       │   └── 预读效率
│       │       ├── readahead统计
│       │       └── 调整预读大小: blockdev --setra
│       │
│       └── dcache/icache分析
│           ├── slabtop
│           ├── cat /proc/slabinfo | grep dentry
│           └── cat /proc/slabinfo | grep inode
│
└── 【优化建议矩阵】
    │
    ├── 应用层优化
    │   ├── 使用O_DIRECT绕过页缓存 (数据库场景)
    │   ├── 使用aio/liburing异步I/O
    │   ├── 批量操作减少系统调用
    │   └── 使用mmap替代read/write
    │
    ├── 文件系统层优化
    │   ├── 挂载选项: noatime, nodiratime
    │   ├── 调整日志模式: data=writeback
    │   ├── 禁用访问时间更新
    │   └── 使用ext4的fast_commit特性
    │
    ├── 块层优化
    │   ├── 选择合适的I/O调度器
    │   ├── 调整队列深度: /sys/block/sda/queue/nr_requests
    │   ├── 启用RQS (Request Queue Sort)
    │   └── 对齐分区到SSD擦除块
    │
    └── 硬件层优化
        ├── 使用NVMe SSD替代SATA
        ├── RAID配置优化 (条带大小)
        └── 使用持久内存 (DAX模式) 

第八部分:自定义文件系统实现框架

8.1 最小文件系统实现框架

/* 最小文件系统实现 - 基于内存的简单文件系统 */
​
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/pagemap.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
​
/* ========== 1. 文件系统私有数据结构 ========== */
struct myfs_inode_info {
    struct inode vfs_inode;
    char *data;              /* 文件数据 */
    size_t size;             /* 文件大小 */
};
​
/* 从inode获取私有数据 */
static inline struct myfs_inode_info *MYFS_I(struct inode *inode)
{
    return container_of(inode, struct myfs_inode_info, vfs_inode);
}
​
/* ========== 2. 超级块操作 ========== */
static struct inode *myfs_alloc_inode(struct super_block *sb)
{
    struct myfs_inode_info *info;
    info = kmalloc(sizeof(struct myfs_inode_info), GFP_KERNEL);
    if (!info)
        return NULL;
    
    info->data = NULL;
    info->size = 0;
    return &info->vfs_inode;
}
​
static void myfs_free_inode(struct inode *inode)
{
    struct myfs_inode_info *info = MYFS_I(inode);
    kfree(info->data);
    kfree(info);
}
​
static int myfs_write_inode(struct inode *inode, struct writeback_control *wbc)
{
    /* 内存文件系统,无需写回磁盘 */
    return 0;
}
​
static const struct super_operations myfs_sops = {
    .alloc_inode = myfs_alloc_inode,
    .free_inode = myfs_free_inode,
    .write_inode = myfs_write_inode,
    .statfs = simple_statfs,
    .drop_inode = generic_delete_inode,
};
​
/* ========== 3. 索引节点操作 ========== */
static struct dentry *myfs_lookup(struct inode *parent, struct dentry *dentry,
                                   unsigned int flags)
{
    struct inode *inode = NULL;
    const char *name = dentry->d_name.name;
    
    /* 简单实现: 只支持根目录和普通文件 */
    if (parent->i_ino == 1) {
        /* 根目录下可以创建文件 */
        inode = new_inode(parent->i_sb);
        if (inode) {
            inode->i_ino = get_next_ino();
            inode->i_mode = S_IFREG | 0644;
            inode->i_uid = current_fsuid();
            inode->i_gid = current_fsgid();
            inode->i_atime = inode->i_mtime = inode->i_ctime = current_time(inode);
            inode->i_size = 0;
            inode->i_op = &simple_file_inode_operations;
            inode->i_fop = &myfs_file_ops;
            
            struct myfs_inode_info *info = MYFS_I(inode);
            info->size = 0;
            info->data = NULL;
        }
    }
    
    d_add(dentry, inode);
    return NULL;
}
​
static const struct inode_operations myfs_dir_inode_ops = {
    .lookup = myfs_lookup,
    .create = myfs_create,
};
​
/* ========== 4. 文件操作 ========== */
static ssize_t myfs_read(struct file *filp, char __user *buf, size_t len,
                         loff_t *off)
{
    struct inode *inode = file_inode(filp);
    struct myfs_inode_info *info = MYFS_I(inode);
    size_t read_size;
    
    if (*off >= info->size)
        return 0;
    
    read_size = min_t(size_t, len, info->size - *off);
    if (copy_to_user(buf, info->data + *off, read_size))
        return -EFAULT;
    
    *off += read_size;
    return read_size;
}
​
static ssize_t myfs_write(struct file *filp, const char __user *buf, size_t len,
                          loff_t *off)
{
    struct inode *inode = file_inode(filp);
    struct myfs_inode_info *info = MYFS_I(inode);
    loff_t new_size = *off + len;
    char *new_data;
    
    /* 扩展文件大小 */
    if (new_size > info->size) {
        new_data = krealloc(info->data, new_size, GFP_KERNEL);
        if (!new_data)
            return -ENOMEM;
        info->data = new_data;
        memset(info->data + info->size, 0, new_size - info->size);
        info->size = new_size;
        inode->i_size = new_size;
    }
    
    if (copy_from_user(info->data + *off, buf, len))
        return -EFAULT;
    
    *off += len;
    return len;
}
​
static const struct file_operations myfs_file_ops = {
    .read = myfs_read,
    .write = myfs_write,
    .llseek = default_llseek,
};
​
/* ========== 5. 文件系统类型注册 ========== */
static int myfs_fill_super(struct super_block *sb, void *data, int silent)
{
    struct inode *root_inode;
    
    sb->s_magic = 0x20241225;
    sb->s_op = &myfs_sops;
    sb->s_time_gran = 1;
    
    /* 创建根目录inode */
    root_inode = new_inode(sb);
    if (!root_inode)
        return -ENOMEM;
    
    root_inode->i_ino = 1;
    root_inode->i_mode = S_IFDIR | 0755;
    root_inode->i_uid = current_fsuid();
    root_inode->i_gid = current_fsgid();
    root_inode->i_atime = root_inode->i_mtime = root_inode->i_ctime = current_time(root_inode);
    root_inode->i_op = &myfs_dir_inode_ops;
    root_inode->i_fop = &simple_dir_operations;
    
    /* 创建根目录dentry */
    sb->s_root = d_make_root(root_inode);
    if (!sb->s_root)
        return -ENOMEM;
    
    return 0;
}
​
static struct dentry *myfs_mount(struct file_system_type *fs_type, int flags,
                                  const char *dev_name, void *data)
{
    return mount_nodev(fs_type, flags, data, myfs_fill_super);
}
​
static void myfs_kill_sb(struct super_block *sb)
{
    kill_anon_super(sb);
}
​
static struct file_system_type myfs_fs_type = {
    .name = "myfs",
    .mount = myfs_mount,
    .kill_sb = myfs_kill_sb,
    .owner = THIS_MODULE,
};
​
/* ========== 6. 模块初始化和退出 ========== */
static int __init myfs_init(void)
{
    return register_filesystem(&myfs_fs_type);
}
​
static void __exit myfs_exit(void)
{
    unregister_filesystem(&myfs_fs_type);
}
​
module_init(myfs_init);
module_exit(myfs_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Simple memory-based filesystem");

8.2 文件系统数据处理Pipeline

自定义文件系统数据处理Pipeline
│
├── 【数据写入Pipeline】
│   │
│   ├── 用户层
│   │   └── write(fd, buf, len)
│   │
│   ├── VFS层
│   │   └── vfs_write() → file->f_op->write_iter()
│   │
│   ├── 文件系统层 (自定义)
│   │   │
│   │   ├── myfs_write_iter()
│   │   │   ├── 加锁 (inode->i_rwsem)
│   │   │   │
│   │   │   ├── 检查文件大小限制
│   │   │   │
│   │   │   ├── 扩展文件 (如果需要)
│   │   │   │   ├── myfs_expand_file()
│   │   │   │   │   ├── 分配新块
│   │   │   │   │   ├── 更新块映射表
│   │   │   │   │   └── 更新inode大小
│   │   │   │   └── 更新inode时间戳
│   │   │   │
│   │   │   ├── 拷贝数据 (用户空间 → 内核空间)
│   │   │   │   └── copy_from_user()
│   │   │   │
│   │   │   ├── 写入实际存储
│   │   │   │   ├── 内存文件系统: memcpy到缓冲区
│   │   │   │   ├── 磁盘文件系统: 写入页缓存
│   │   │   │   └── 标记脏页
│   │   │   │
│   │   │   └── 更新inode元数据
│   │   │
│   │   └── 解锁
│   │
│   ├── 页缓存层 (磁盘文件系统)
│   │   ├── generic_perform_write()
│   │   ├── write_begin()
│   │   └── write_end()
│   │
│   └── 块层 (磁盘文件系统)
│       ├── 标记缓冲区脏
│       ├── 回写线程写回
│       └── submit_bio()
│
├── 【数据读取Pipeline】
│   │
│   ├── 用户层
│   │   └── read(fd, buf, len)
│   │
│   ├── VFS层
│   │   └── vfs_read() → file->f_op->read_iter()
│   │
│   ├── 文件系统层 (自定义)
│   │   │
│   │   ├── myfs_read_iter()
│   │   │   ├── 加读锁 (inode->i_rwsem)
│   │   │   │
│   │   │   ├── 计算读取位置和大小
│   │   │   │
│   │   │   ├── 查找数据位置
│   │   │   │   └── myfs_get_block() → 逻辑块→物理块映射
│   │   │   │
│   │   │   ├── 读取数据
│   │   │   │   ├── 内存文件系统: memcpy到用户空间
│   │   │   │   ├── 页缓存命中: 直接返回
│   │   │   │   └── 页缓存未命中: 从磁盘读取
│   │   │   │
│   │   │   ├── 拷贝到用户空间
│   │   │   │   └── copy_to_user()
│   │   │   │
│   │   │   └── 解锁
│   │   │
│   │   └── 更新访问时间 (atime)
│   │
│   └── 块层 (磁盘文件系统)
│       ├── 提交bio请求
│       ├── 磁盘DMA读取
│       └── 数据返回页缓存
│
├── 【元数据操作Pipeline】
│   │
│   ├── 创建文件: myfs_create()
│   │   ├── 分配inode
│   │   ├── 初始化inode
│   │   ├── 添加目录项
│   │   └── 更新父目录时间戳
│   │
│   ├── 删除文件: myfs_unlink()
│   │   ├── 检查权限
│   │   ├── 从目录项删除
│   │   ├── 释放inode
│   │   │   ├── 释放数据块
│   │   │   └── 标记inode空闲
│   │   └── 更新父目录
│   │
│   └── 重命名文件: myfs_rename()
│       ├── 检查源和目标
│       ├── 更新目录项
│       └── 更新时间戳
│
└── 【缓存管理Pipeline】
    │
    ├── 页缓存
    │   ├── 读缓存: find_get_page()
    │   ├── 写缓存: add_to_page_cache_lru()
    │   ├── 淘汰: page_cache_delete()
    │   └── 回写: writeback_single_inode()
    │
    ├── dcache (目录项缓存)
    │   ├── 查找: d_lookup()
    │   ├── 添加: d_add()
    │   ├── 淘汰: shrink_dcache_parent()
    │   └── 回收: prune_dcache_sb()
    │
    └── icache (inode缓存)
        ├── 查找: ilookup()
        ├── 添加: insert_inode_locked()
        ├── 淘汰: invalidate_inodes()
        └── 回收: prune_icache_sb()

总结与参考

核心要点总结
  1. VFS是Linux文件系统的核心抽象层,通过超级块、索引节点、目录项、文件对象四大数据结构统一所有文件系统接口

  2. 系统调用路径: sys_open → path_openat → vfs_open → 文件系统特定操作;sys_read → vfs_read → generic_file_read_iter → address_space_operations

  3. rootfs初始化是VFS建立的基础,通过init_mount_tree()创建根目录并挂载rootfs,为后续挂载其他文件系统提供挂载点

  4. epoll事件分发基于文件poll操作,通过红黑树管理监视fd,就绪队列分发事件,回调机制实现主动通知

  5. 调试工具链从用户空间(df, iostat)到内核跟踪点(ftrace, trace-cmd),再到专用工具(debugfs, blktrace),形成完整调试体系

  6. 性能优化需从应用层、文件系统层、块层、硬件层分层分析,针对性调整参数和配置

关键源码文件索引
模块路径说明
VFS核心fs/namei.c路径查找
VFS核心fs/read_write.cread/write系统调用
VFS核心fs/super.c超级块管理
VFS核心fs/dcache.c目录项缓存
页缓存mm/filemap.c页缓存核心
EXT4fs/ext4/inode.cinode操作
EXT4fs/ext4/extents.cextent树管理
epollfs/eventpoll.cepoll实现
debugfsfs/debugfs/调试文件系统

第二阶段 由外至内视角:从mkdir到数据持久化的完整旅程

第一部分:实战场景 - 在Buildroot Linux中创建目录

1.1 场景重现:在终端执行mkdir

# 在Buildroot Linux终端执行
root@buildroot:~# mkdir /mnt/data/testdir
root@buildroot:~# ls -la /mnt/data/
drwxr-xr-x 2 root root 4096 Mar 26 10:30 testdir

1.2 mkdir命令背后的完整调用链

mkdir /mnt/data/testdir 完整调用链
│
├── 【用户空间】BusyBox的mkdir命令
│   │
│   ├── 源代码位置: busybox/coreutils/mkdir.c
│   │
│   └── mkdir_main() 函数
│       ├── 1. 解析命令行参数
│       ├── 2. 循环处理每个目录路径
│       │   └── 对于 "/mnt/data/testdir"
│       │       ├── 调用 mkdir() 系统调用
│       │       │   └── syscall(__NR_mkdir, path, 0755)
│       │       │
│       │       └── 如果父目录不存在,根据-p选项决定是否递归创建
│       │
│       └── 3. 返回结果
│
├── 【系统调用入口】内核 fs/namei.c
│   │
│   ├── SYSCALL_DEFINE2(mkdir, const char __user *, pathname, umode_t, mode)
│   │   └── do_mkdirat(AT_FDCWD, pathname, mode)
│   │
│   └── do_mkdirat()
│       └── user_path_create()          # 路径查找和创建准备
│           │
│           ├── filename_lookup()       # 查找父目录
│           │   └── path_lookupat()     # 路径解析核心
│           │       │
│           │       ├── walk_component() # 逐级解析
│           │       │   ├── "/" → 根目录dentry
│           │       │   ├── "mnt" → 查找mnt目录
│           │       │   └── "data" → 查找data目录
│           │       │
│           │       └── 返回父目录的路径结构
│           │
│           └── 准备创建新的dentry
│
├── 【VFS核心层】fs/namei.c
│   │
│   ├── vfs_mkdir()                     # VFS层mkdir
│   │   │
│   │   ├── 1. 权限检查
│   │   │   ├── 父目录可写检查
│   │   │   └── CAP_DAC_OVERRIDE能力检查
│   │   │
│   │   ├── 2. 调用具体文件系统的mkdir操作
│   │   │   └── dir->i_op->mkdir()
│   │   │
│   │   └── 3. 更新目录项缓存(dcache)
│   │
│   └── 实际文件系统调用 (以ext4为例)
│
├── 【EXT4文件系统层】fs/ext4/namei.c
│   │
│   ├── ext4_mkdir()                    # EXT4的mkdir实现
│   │   │
│   │   ├── 1. 开始日志事务
│   │   │   └── ext4_journal_start()    # JBD2日志开始
│   │   │
│   │   ├── 2. 分配新的inode
│   │   │   └── ext4_new_inode_start_handle()
│   │   │       │
│   │   │       ├── ext4_claim_inode()  # 分配inode号
│   │   │       │   └── 从inode位图找空闲位
│   │   │       │
│   │   │       ├── ext4_init_new_dir() # 初始化目录inode
│   │   │       │   ├── 设置i_mode = S_IFDIR | mode
│   │   │       │   ├── 设置i_uid, i_gid
│   │   │       │   ├── 设置时间戳 (atime, mtime, ctime)
│   │   │       │   └── 分配目录数据块
│   │   │       │       └── ext4_mb_new_blocks() # 多块分配器分配块
│   │   │       │
│   │   │       └── 初始化目录内容
│   │   │           └── 写入"."和".."条目
│   │   │
│   │   ├── 3. 将新目录项添加到父目录
│   │   │   └── ext4_add_entry()
│   │   │       │
│   │   │       ├── 在父目录的数据块中查找空闲位置
│   │   │       ├── 构建目录项结构 ext4_dir_entry_2
│   │   │       │   ├── inode = 新分配的inode号
│   │   │       │   ├── rec_len = 条目长度
│   │   │       │   ├── name_len = 8 ("testdir")
│   │   │       │   ├── file_type = EXT4_FT_DIR
│   │   │       │   └── name = "testdir"
│   │   │       └── 写入父目录的数据块
│   │   │
│   │   ├── 4. 更新父目录元数据
│   │   │   ├── 父目录inode的i_size增加
│   │   │   ├── 父目录inode的i_links_count增加
│   │   │   └── 父目录时间戳更新
│   │   │
│   │   ├── 5. 更新块组统计
│   │   │   ├── 更新块位图 (已用块数+1)
│   │   │   ├── 更新inode位图 (已用inode+1)
│   │   │   └── 更新组描述符
│   │   │
│   │   └── 6. 提交日志事务
│   │       └── ext4_journal_stop()
│   │
│   └── 关键数据结构变化
│       │
│       ├── 超级块变化
│       │   └── s_free_inodes_count-- (空闲inode数减1)
│       │
│       ├── 块组描述符变化
│       │   ├── bg_free_inodes_count--
│       │   └── bg_used_dirs_count++ (目录数+1)
│       │
│       └── 内存缓存变化
│           ├── dentry缓存: 添加新目录项
│           └── inode缓存: 添加新inode
│
└── 【块设备层】最终写入eMMC
    │
    ├── 数据写入时机
    │   ├── 日志提交时立即写入
    │   └── 元数据写入回写机制
    │
    └── eMMC物理写入
        └── eMMC控制器将数据写入NAND Flash

第二部分:实战场景 - touch文件创建

2.1 touch命令调用链

root@buildroot:~# touch /mnt/data/testdir/myfile.txt
touch文件创建完整调用链
│
├── 【用户空间】BusyBox touch命令
│   │
│   ├── busybox/coreutils/touch.c
│   │
│   └── touch_main()
│       ├── 解析参数
│       ├── 对每个文件路径
│       │   └── 调用 utimensat() 系统调用
│       │       ├── 如果文件存在: 更新时间戳
│       │       └── 如果文件不存在: 创建空文件
│       │
│       └── 实际调用: open() + utimensat()
│
├── 【系统调用入口】
│   │
│   ├── SYSCALL_DEFINE4(open, ...) → do_sys_open()
│   │   └── do_filp_open()           # 打开或创建文件
│   │
│   └── vfs_open() → ext4_file_open() (已存在) 或 ext4_create() (新建)
│
├── 【EXT4文件创建】fs/ext4/namei.c
│   │
│   └── ext4_create()                 # 创建普通文件
│       │
│       ├── 1. 开始日志事务
│       │   └── ext4_journal_start()
│       │
│       ├── 2. 分配inode
│       │   └── ext4_new_inode_start_handle()
│       │       │
│       │       ├── 从inode位图分配空闲inode
│       │       │   └── 例如分配inode号 = 12345
│       │       │
│       │       └── 初始化inode
│       │           ├── i_mode = S_IFREG | 0644 (普通文件)
│       │           ├── i_size = 0 (空文件)
│       │           ├── i_blocks = 0 (无数据块)
│       │           ├── i_atime/mtime/ctime = 当前时间
│       │           └── i_links_count = 1
│       │
│       ├── 3. 添加到父目录
│       │   └── ext4_add_entry()
│       │       └── 在父目录(/mnt/data/testdir)的目录块中添加
│       │           └── ext4_dir_entry: name="myfile.txt", inode=12345
│       │
│       ├── 4. 更新父目录
│       │   ├── 父目录i_size增加 (如果新条目导致扩展)
│       │   └── 父目录mtime/ctime更新
│       │
│       ├── 5. 更新统计
│       │   ├── 超级块: s_free_inodes_count--
│       │   └── 块组描述符: bg_free_inodes_count--
│       │
│       └── 6. 提交日志
│           └── ext4_journal_stop()
│
└── 【文件打开】ext4_file_open()
    │
    └── 对于空文件,无需分配数据块
        └── 文件的数据块为0,直到第一次写入才分配

2.2 写入数据到文件的流程

root@buildroot:~# echo "Hello World" > /mnt/data/testdir/myfile.txt
写入数据完整调用链
│
├── 【用户空间】shell重定向
│   │
│   ├── echo命令输出到stdout
│   └── shell将stdout重定向到文件
│       └── open("myfile.txt", O_WRONLY|O_CREAT|O_TRUNC)
│           └── write(fd, "Hello World\n", 12)
│
├── 【VFS层】fs/read_write.c
│   │
│   └── vfs_write() → file->f_op->write_iter()
│
├── 【EXT4写操作】fs/ext4/file.c
│   │
│   └── ext4_file_write_iter()
│       │
│       ├── 检查文件大小 (之前是0)
│       │
│       ├── ext4_buffered_write_iter() (缓冲写)
│       │   │
│       │   ├── generic_perform_write()
│       │   │   │
│       │   │   └── 第一次写入时分配数据块
│       │   │       │
│       │   │       ├── ext4_da_write_begin() # 延迟分配写开始
│       │   │       │   │
│       │   │       │   ├── ext4_map_blocks()  # 分配新块
│       │   │       │   │   │
│       │   │       │   │   ├── 查找extent树 (空)
│       │   │       │   │   ├── ext4_ext_map_blocks()
│       │   │       │   │   └── ext4_mb_new_blocks() # 多块分配器
│       │   │       │   │       │
│       │   │       │   │       ├── 选择最佳块组
│       │   │       │   │       ├── 分配物理块: 例如块号 102400
│       │   │       │   │       ├── 更新块位图
│       │   │       │   │       └── 更新组描述符
│       │   │       │   │
│       │   │       │   └── 创建extent项
│       │   │       │       └── extent: ee_block=0, ee_len=1, ee_start=102400
│       │   │       │
│       │   │       └── ext4_da_write_end()
│       │   │           ├── 拷贝数据到页缓存
│       │   │           ├── 标记页为脏 (PG_dirty)
│       │   │           └── 更新inode大小: i_size = 12
│       │   │
│       │   └── 更新inode时间戳
│       │       └── i_mtime = i_ctime = 当前时间
│       │
│       └── 返回写入字节数
│
├── 【页缓存】mm/filemap.c
│   │
│   ├── 数据存储在内存页中
│   │   └── struct page (4KB页) 包含 "Hello World\n"
│   │
│   └── 脏页标记,等待回写
│
└── 【回写机制】异步写入eMMC
    │
    └── 当满足以下条件时触发回写:
        ├── 脏页比例超过阈值 (dirty_ratio = 20%)
        ├── 定时器触发 (dirty_expire_centisecs = 3000)
        └── sync命令或系统关机时

2.3 写入后的eMMC物理布局

eMMC物理布局变化
│
├── 创建目录 /mnt/data/testdir 后的布局
│   │
│   ├── 块组描述符
│   │   ├── bg_free_inodes_count: 减少1
│   │   └── bg_used_dirs_count: 增加1
│   │
│   ├── Inode位图
│   │   └── 某一位从0变1 (inode号被占用)
│   │
│   ├── Inode表
│   │   ├── inode 12344: 目录 /mnt/data 的inode
│   │   └── inode 12345: 新目录 testdir 的inode
│   │       ├── i_mode = 0x41ED (目录)
│   │       ├── i_size = 4KB (一个块)
│   │       └── i_block[0] = 102400 (数据块)
│   │
│   └── 数据块
│       └── 块 102400: 目录内容
│           ├── ".", inode=12345
│           └── "..", inode=12344
│
└── 创建文件 myfile.txt 并写入后的布局
    │
    ├── Inode表
    │   ├── inode 12346: 新文件 myfile.txt
    │   │   ├── i_mode = 0x81A4 (普通文件, 0644)
    │   │   ├── i_size = 12
    │   │   ├── i_blocks = 8 (512字节块数,实际1个4KB块)
    │   │   ├── i_block[0] = 102401 (数据块)
    │   │   └── i_extent树: extent(0, 1, 102401)
    │   │
    │   └── 父目录 inode 12345
    │       ├── i_size从4KB增加到8KB (如果目录扩展)
    │       └── 目录数据块增加新条目
    │
    ├── 数据块
    │   └── 块 102401: 文件内容
    │       └── "Hello World\n\0\0..." (填充到4KB)
    │
    └── 块位图
        └── 块 102401 被标记为已用

第三部分:开机后从rootfs读取文件

3.1 系统启动流程与文件系统挂载

Buildroot Linux启动与文件系统加载
│
├── 【Bootloader阶段】U-Boot
│   │
│   ├── 从eMMC读取内核和设备树
│   │   ├── mmc read 0x48000000 0x800 0x8000
│   │   └── bootz 0x48000000 - 0x49000000
│   │
│   └── 传递内核参数
│       └── root=/dev/mmcblk0p2 rootfstype=ext4
│
├── 【内核初始化】init/main.c
│   │
│   ├── start_kernel()
│   │   ├── vfs_caches_init()          # 初始化VFS缓存
│   │   │   ├── dcache_init()          # 目录项缓存
│   │   │   ├── inode_init()           # inode缓存
│   │   │   └── files_init()           # 文件表
│   │   │
│   │   └── kernel_init()              # 内核线程
│   │
│   └── init_post()                    # 启动init进程
│
├── 【根文件系统挂载】init/do_mounts.c
│   │
│   ├── prepare_namespace()
│   │   │
│   │   ├── 解析root=参数: /dev/mmcblk0p2
│   │   │
│   │   ├── mount_block_root()         # 挂载块设备根文件系统
│   │   │   │
│   │   │   ├── do_mount_root()
│   │   │   │   ├── sys_mount(dev_name, "/root", fs_type, flags, data)
│   │   │   │   │   └── 调用文件系统的mount函数
│   │   │   │   └── sys_chdir("/root")
│   │   │   │
│   │   │   └── 将rootfs切换到真正的根文件系统
│   │   │       └── sys_pivot_root("/root", "/root/old")
│   │   │
│   │   └── 初始化devtmpfs挂载 /dev
│   │
│   └── 关键: 此时内核从eMMC读取超级块
│       └── ext4_fill_super()
│           ├── 读取超级块 (eMMC块0)
│           ├── 验证s_magic = 0xEF53
│           ├── 读取组描述符
│           └── 创建根目录dentry和inode
│
├── 【用户空间init】BusyBox init
│   │
│   ├── /sbin/init (指向busybox)
│   │   ├── 解析 /etc/inittab
│   │   ├── 挂载 proc, sysfs, tmpfs
│   │   └── 启动shell (/bin/sh)
│   │
│   └── 用户获得shell提示符
│
└── 【读取文件】ls /mnt/data/testdir/myfile.txt
    │
    └── 当用户执行命令时
        │
        ├── 路径解析过程
        │   ├── "/" → 根目录dentry (已缓存在dcache)
        │   ├── "mnt" → 查找dentry
        │   │   └── 从根目录inode读取目录数据块
        │   │       └── 从eMMC读取目录块 (可能已缓存)
        │   ├── "data" → 查找子目录
        │   │   └── 从mnt的inode读取目录块
        │   └── "testdir" → 查找子目录
        │       └── 从data的inode读取目录块
        │
        ├── 最终找到文件inode (12346)
        │   ├── 从inode表读取inode信息
        │   │   └── 从eMMC读取inode块
        │   │       └── 读取到 i_block[0]=102401
        │   │
        │   └── 读取文件内容
        │       └── 从数据块102401读取"Hello World\n"
        │
        └── 显示文件信息

3.2 页面缓存的作用 - 加速后续读取

页面缓存工作原理
│
├── 【第一次读取】读取myfile.txt
│   │
│   ├── 1. VFS调用 generic_file_read_iter()
│   │
│   ├── 2. 查找页面缓存
│   │   └── find_get_page(mapping, index=0)
│   │       └── 返回 NULL (未命中)
│   │
│   ├── 3. 分配新页面
│   │   └── page_cache_alloc()
│   │
│   ├── 4. 从磁盘读取数据到页面
│   │   ├── submit_bio() → eMMC读取
│   │   ├── eMMC DMA传输到页面内存
│   │   └── 等待I/O完成
│   │
│   ├── 5. 将页面加入页面缓存
│   │   └── add_to_page_cache_lru(page, mapping, index)
│   │
│   └── 6. 拷贝数据到用户空间
│
├── 【第二次读取】再次读取同一个文件
│   │
│   ├── 1. 查找页面缓存
│   │   └── find_get_page(mapping, index=0)
│   │       └── 返回缓存的页面 (命中!)
│   │
│   └── 2. 直接从内存拷贝到用户空间
│       └── 无需访问eMMC,速度极快
│
└── 【页面淘汰】内存不足时
    │
    ├── LRU链表管理
    │   └── 最近最少使用的页面被淘汰
    │
    └── 脏页回写后释放
3.3 dcache和icache的作用
dcache和icache加速路径查找
│
├── 【第一次查找】ls /mnt/data/testdir
│   │
│   ├── 1. 查找根目录dentry
│   │   └── d_lookup(root, "mnt")
│   │       ├── 在dcache中查找
│   │       └── 未命中 → 从磁盘读取
│   │
│   ├── 2. 查找mnt的dentry
│   │   ├── d_lookup(mnt_dentry, "data")
│   │   └── 未命中 → 从磁盘读取
│   │
│   ├── 3. 查找data的dentry
│   │   ├── d_lookup(data_dentry, "testdir")
│   │   └── 未命中 → 从磁盘读取
│   │
│   └── 所有dentry被缓存
│
├── 【第二次查找】再次执行ls
│   │
│   ├── d_lookup(root, "mnt") → 缓存命中
│   ├── d_lookup(mnt_dentry, "data") → 缓存命中
│   ├── d_lookup(data_dentry, "testdir") → 缓存命中
│   │
│   └── 完全无需访问eMMC,性能提升巨大
│
└── icache类似,缓存inode信息

第四部分:eMMC存储管理综合架构

4.1 eMMC物理结构与分区布局

eMMC存储布局 (以16GB eMMC为例)
│
├── 【硬件结构】
│   │
│   ├── eMMC内部架构
│   │   ├── MMC控制器
│   │   ├── NAND Flash芯片 (多个Die)
│   │   │   ├── SLC区域 (高速,寿命长)
│   │   │   ├── MLC/TLC区域 (容量大)
│   │   │   └── 备用块 (替换坏块)
│   │   └── 嵌入式固件 (FTL层)
│   │       ├── 坏块管理
│   │       ├── 磨损均衡 (Wear Leveling)
│   │       ├── 垃圾回收 (GC)
│   │       └── 映射表 (逻辑块到物理块)
│   │
│   └── 主机看到的设备: /dev/mmcblk0
│
├── 【分区布局】通过U-Boot或Linux分区工具
│   │
│   ├── /dev/mmcblk0p1: boot分区 (64MB)
│   │   └── U-Boot, kernel, dtb
│   │
│   ├── /dev/mmcblk0p2: rootfs分区 (4GB)
│   │   └── ext4文件系统,只读或读写
│   │
│   ├── /dev/mmcblk0p3: data分区 (8GB)
│   │   └── ext4文件系统,用户数据
│   │
│   └── /dev/mmcblk0p4: swap分区 (512MB)
│       └── swap空间
│
└── 【分区表】GPT或MBR
    └── 存储在eMMC开头扇区

4.2 块设备层到eMMC驱动的完整路径

从文件系统到eMMC物理写入的完整路径
│
├── 【文件系统层】ext4
│   │
│   ├── ext4_writepages()            # 回写脏页
│   │   └── mpage_writepages()       # 多页回写
│   │       └── submit_bio()         # 提交块I/O请求
│   │
│   └── BIO结构 (block I/O)
│       struct bio {
│           struct bio *bi_next;
│           struct block_device *bi_bdev;  # /dev/mmcblk0p2
│           unsigned int bi_opf;           # REQ_OP_WRITE
│           struct bio_vec bi_io_vec[];    # 数据段
│           bio_end_io_t *bi_end_io;       # 完成回调
│       };
│
├── 【通用块层】block/blk-core.c
│   │
│   ├── submit_bio()                 # BIO提交入口
│   │   ├── blk_mq_submit_bio()      # 多队列块层
│   │   │   ├── 选择硬件队列
│   │   │   ├── 分配请求结构
│   │   │   │   └── struct request
│   │   │   └── 插入调度器
│   │   └── 调用队列的request_fn
│   │
│   └── I/O调度器 (可选)
│       ├── mq-deadline (默认)
│       │   └── 合并相邻请求,排序
│       └── none (绕过调度器)
│
├── 【MMC块设备层】drivers/mmc/core/block.c
│   │
│   ├── mmc_blk_issue_rq()           # MMC请求处理
│   │   │
│   │   ├── 构建MMC命令
│   │   │   ├── CMD25 (多块写)
│   │   │   ├── CMD18 (多块读)
│   │   │   └── CMD24 (单块写)
│   │   │
│   │   ├── 设置块地址 (LBA)
│   │   │   └── 逻辑块地址 → eMMC内部逻辑地址
│   │   │
│   │   └── mmc_blk_rw_cmd()
│   │
│   └── 数据准备
│       ├── 构建MMC数据包
│       ├── 设置DMA传输
│       └── 发送命令到eMMC
│
├── 【MMC核心层】drivers/mmc/core/core.c
│   │
│   ├── mmc_start_request()          # 启动MMC请求
│   │   │
│   │   ├── 检查总线状态
│   │   ├── 发送CMD (通过主机控制器)
│   │   └── 启动DMA传输
│   │   │
│   │   └── mmc_wait_for_req()       # 等待完成
│   │       └── 中断处理函数唤醒
│   │
│   └── mmc_request_done()           # 请求完成回调
│       ├── 通知块层完成
│       └── 释放资源
│
├── 【MMC主机控制器驱动】drivers/mmc/host/
│   │
│   ├── dw_mmc.c (DesignWare MMC)
│   │   ├── dw_mci_request()         # 发送请求
│   │   ├── 设置描述符表
│   │   ├── 写入命令寄存器
│   │   └── 启动传输
│   │
│   └── 硬件操作
│       ├── 配置CLK (时钟频率)
│       ├── 配置总线宽度 (1/4/8-bit)
│       ├── 发送命令
│       └── DMA传输数据
│
└── 【物理层】eMMC芯片
    │
    ├── 接收CMD和DATA信号
    │   ├── 命令线 (CMD)
    │   ├── 数据线 (DAT0-DAT7)
    │   └── 时钟线 (CLK)
    │
    ├── eMMC内部处理
    │   ├── FTL映射: LBA → 物理块
    │   ├── 坏块检查
    │   ├── 磨损均衡选择物理块
    │   └── 垃圾回收触发
    │
    └── 写入NAND Flash
        ├── 页编程 (Page Program)
        └── 块擦除 (Block Erase) (如需要)

4.3 多设备文件系统统一管理

Linux多设备文件系统架构
│
├── 【设备文件】/dev目录
│   │
│   ├── /dev/mmcblk0     # eMMC块设备
│   │   ├── /dev/mmcblk0p1 (boot)
│   │   ├── /dev/mmcblk0p2 (rootfs)
│   │   └── /dev/mmcblk0p3 (data)
│   │
│   ├── /dev/sda         # USB硬盘
│   │   ├── /dev/sda1 (FAT32)
│   │   └── /dev/sda2 (NTFS)
│   │
│   ├── /dev/mmcblk1     # SD卡
│   │   └── /dev/mmcblk1p1 (exFAT)
│   │
│   └── /dev/mtdblock0   # SPI Flash
│       └── /dev/mtdblock0 (jffs2)
│
├── 【挂载点】/mnt和/media
│   │
│   ├── 根文件系统: / (ext4 on /dev/mmcblk0p2)
│   │
│   ├── /mnt/data: ext4 on /dev/mmcblk0p3
│   │   └── 用户数据分区
│   │
│   ├── /mnt/usb: vfat on /dev/sda1
│   │   └── 自动挂载的U盘
│   │
│   ├── /mnt/sdcard: exfat on /dev/mmcblk1p1
│   │   └── SD卡
│   │
│   └── /mnt/spi: jffs2 on /dev/mtdblock0
│       └── 配置存储
│
├── 【统一操作接口】VFS层
│   │
│   └── 无论底层是什么设备,用户看到统一接口
│       ├── open("/mnt/data/file.txt")
│       ├── open("/mnt/usb/file.txt")
│       ├── open("/mnt/sdcard/file.txt")
│       └── 都是通过相同的系统调用
│
└── 【块设备操作差异】
    │
    ├── eMMC: mmc_blk_ops
    │   └── 调用dw_mmc驱动
    │
    ├── USB硬盘: usb_storage_ops
    │   └── 调用USB mass storage驱动
    │
    └── SD卡: mmc_blk_ops
        └── 调用SDHCI驱动

第五部分:实战 - USB硬盘和SD卡操作流程

5.1 插入USB硬盘的完整流程

USB硬盘热插拔流程
│
├── 【硬件层】USB接口检测
│   │
│   ├── USB控制器检测设备插入
│   ├── 总线枚举
│   └── 分配USB地址
│
├── 【USB核心层】drivers/usb/core/
│   │
│   ├── usb_new_device()
│   │   ├── 读取设备描述符
│   │   ├── 分配配置
│   │   └── 匹配驱动
│   │
│   └── usb_stor_probe()               # USB存储驱动
│       ├── 初始化SCSI层
│       └── scsi_add_host()
│
├── 【SCSI层】drivers/scsi/
│   │
│   ├── scsi_scan_host()
│   │   ├── 发送INQUIRY命令
│   │   ├── 读取容量: READ CAPACITY
│   │   └── 创建sd设备: /dev/sda
│   │
│   └── sd_probe()                      # SCSI磁盘驱动
│       ├── 读取分区表
│       ├── 创建分区: /dev/sda1, /dev/sda2
│       └── 通知用户空间: uevent
│
├── 【用户空间】udev/mdev
│   │
│   ├── 接收uevent事件
│   │   └── ACTION=add, DEVPATH=/devices/...
│   │
│   ├── 执行挂载脚本
│   │   └── /etc/udev/rules.d/automount.rules
│   │       └── ACTION=="add", RUN+="/bin/mount /dev/sda1 /mnt/usb"
│   │
│   └── 挂载文件系统
│       ├── 探测文件系统类型 (blkid)
│       ├── mount -t vfat /dev/sda1 /mnt/usb
│       └── 通知用户
│
└── 【用户操作】
    └── ls /mnt/usb/                    # 访问USB硬盘内容

5.2 从USB硬盘读取文件的调用链

从USB硬盘读取文件的完整调用链
│
├── 【用户命令】
│   └── cat /mnt/usb/movie.mp4
│
├── 【VFS层】
│   └── vfs_read() → file->f_op->read_iter()
│
├── 【具体文件系统】FAT32 (vfat)
│   │
│   ├── fat_file_read_iter()            # fs/fat/file.c
│   │   │
│   │   ├── 查找文件簇链
│   │   │   └── fat_get_cluster()       # FAT表查找
│   │   │
│   │   └── 读取数据
│   │       └── fat_readpages()
│   │           └── mpage_readpages()
│   │               └── submit_bio()
│   │
│   └── 关键: FAT32的元数据组织
│       ├── 引导扇区 (MBR)
│       ├── FAT表 (文件分配表)
│       ├── 根目录
│       └── 数据区
│
├── 【块层】提交到USB硬盘
│   │
│   ├── 构建BIO,目标设备 /dev/sda1
│   │
│   ├── 通用块层处理
│   │
│   └── SCSI层转换
│       └── sd_init_command()
│           ├── 构建SCSI命令: READ(10)
│           ├── 设置LBA地址
│           └── 设置传输长度
│
├── 【USB存储驱动】
│   │
│   ├── usb_stor_control_thread()
│   │   ├── 封装为USB Bulk-Only传输
│   │   └── 发送URB (USB Request Block)
│   │
│   └── USB核心层
│       └── usb_submit_urb()
│
├── 【USB主机控制器】EHCI/xHCI
│   │
│   ├── 构建USB事务
│   ├── 通过USB总线发送
│   └── USB硬盘响应
│
└── 【数据返回路径】
    ├── USB中断
    ├── 数据DMA到内存
    ├── URB完成回调
    ├── SCSI完成
    ├── BIO完成
    └── 数据返回用户空间

5.3 不同文件系统的操作差异对比

文件系统操作对比表
│
├── 【创建文件】open("new.txt", O_CREAT)
│   │
│   ├── ext4 (eMMC)
│   │   ├── 分配inode
│   │   ├── 写入inode表
│   │   ├── 更新目录项
│   │   ├── 更新块位图
│   │   ├── 日志记录 (JBD2)
│   │   └── 最终写入eMMC
│   │
│   ├── FAT32 (USB硬盘)
│   │   ├── 查找空闲簇 (FAT表)
│   │   ├── 分配目录项 (32字节)
│   │   ├── 更新FAT表
│   │   ├── 写入目录项
│   │   └── 直接写入 (无日志)
│   │
│   ├── NTFS (Windows硬盘)
│   │   ├── 分配MFT记录
│   │   ├── 更新$MFT文件
│   │   ├── 更新目录索引
│   │   ├── 日志记录 ($LogFile)
│   │   └── 写入块设备
│   │
│   └── jffs2 (SPI Flash)
│       ├── 遍历擦除块
│       ├── 分配空闲节点
│       ├── 写入压缩数据
│       ├── 更新inode节点
│       └── 垃圾回收清理旧节点
│
├── 【写入数据】write(fd, data, size)
│   │
│   ├── ext4
│   │   ├── 页缓存
│   │   ├── 延迟分配
│   │   ├── 回写机制
│   │   └── 日志提交
│   │
│   ├── FAT32
│   │   ├── 页缓存
│   │   ├── 直接分配簇
│   │   ├── 立即更新FAT表
│   │   └── 同步写入 (通常)
│   │
│   ├── NTFS
│   │   ├── 日志记录
│   │   ├── 属性更新
│   │   └── 延迟写入
│   │
│   └── jffs2
│       ├── 直接写入Flash
│       ├── 节点追加
│       ├── 原地更新 (新节点)
│       └── 垃圾回收标记
│
└── 【读取文件】read(fd, buf, size)
    │
    ├── ext4
    │   ├── 页缓存查找
    │   ├── extent树定位
    │   └── 块映射读取
    │
    ├── FAT32
    │   ├── 页缓存查找
    │   ├── FAT链遍历
    │   └── 簇映射读取
    │
    ├── NTFS
    │   ├── 页缓存查找
    │   ├── MFT属性读取
    │   ├── extent列表定位
    │   └── 簇映射读取
    │
    └── jffs2
        ├── 读取节点链表
        ├── 解压缩 (如果压缩)
        └── 页缓存 (可选)

第六部分:调试与监控实战

6.1 实时监控文件系统操作

# 1. 使用strace跟踪mkdir命令
root@buildroot:~# strace -e trace=file mkdir /mnt/data/testdir
execve("/bin/mkdir", ["mkdir", "/mnt/data/testdir"], 0xbea6d830 /* 12 vars */) = 0
...
mkdir("/mnt/data/testdir", 0755) = 0
+++ exited with 0 +++
​
# 2. 使用inotify监控目录变化
root@buildroot:~# inotifywait -m -r /mnt/data/
Setting up watches.  Beware: since -r was given, this may take a while!
Watches established.
/mnt/data/ CREATE testdir
/mnt/data/ OPEN testdir
/mnt/data/ CLOSE_NOWRITE_CLOSE testdir
​
# 3. 使用ftrace追踪内核函数
root@buildroot:~# echo ext4_mkdir > /sys/kernel/tracing/set_ftrace_filter
root@buildroot:~# echo function > /sys/kernel/tracing/current_tracer
root@buildroot:~# mkdir /mnt/data/testdir
root@buildroot:~# cat /sys/kernel/tracing/trace
# tracer: function
#
#           TASK-PID    CPU#     TIMESTAMP  FUNCTION
#              |         |          |         |
           mkdir-1234  [001]  123.456789: ext4_mkdir <-vfs_mkdir
           mkdir-1234  [001]  123.456790: ext4_new_inode_start_handle <-ext4_mkdir
           mkdir-1234  [001]  123.456791: ext4_mb_new_blocks <-ext4_new_inode_start_handle
           ...
​
# 4. 使用blktrace追踪eMMC写入
root@buildroot:~# blktrace -d /dev/mmcblk0 -o trace
^C
root@buildroot:~# blkparse -i trace > trace.txt
root@buildroot:~# head trace.txt
  8,0    1        1     0.000000000  1234  A  WS 102400 + 8 <- (mmcblk0p2)
  8,0    1        2     0.000001000  1234  Q  WS 102400 + 8 [mkdir]
  8,0    1        3     0.000002000  1234  G  WS 102400 + 8 [mkdir]
  8,0    1        4     0.000003000  1234  I  WS 102400 + 8 [mkdir]
  8,0    1        5     0.000004000  1234  D  WS 102400 + 8 [mkdir]

6.2 查看文件系统状态

# 查看挂载信息
root@buildroot:~# mount
/dev/mmcblk0p2 on / type ext4 (rw,relatime)
devtmpfs on /dev type devtmpfs (rw,relatime,size=1024k)
proc on /proc type proc (rw,relatime)
sysfs on /sys type sysfs (rw,relatime)
/dev/mmcblk0p3 on /mnt/data type ext4 (rw,relatime)
/dev/sda1 on /mnt/usb type vfat (rw,relatime)
​
# 查看磁盘使用情况
root@buildroot:~# df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/mmcblk0p2  3.9G  500M  3.2G  14% /
/dev/mmcblk0p3  7.8G  100M  7.3G   2% /mnt/data
/dev/sda1       28G   15G   13G  54% /mnt/usb
​
# 查看inode使用情况
root@buildroot:~# df -i
Filesystem     Inodes IUsed IFree IUse% Mounted on
/dev/mmcblk0p2 262144  1234 260910    1% /
/dev/mmcblk0p3 524288    56 524232    1% /mnt/data
/dev/sda1            0     0      0     - /mnt/usb  # FAT32无inode概念
​
# 查看文件系统详细信息
root@buildroot:~# tune2fs -l /dev/mmcblk0p2
Filesystem volume name:   rootfs
Last mounted on:          /
Filesystem UUID:          a1b2c3d4-...
Filesystem magic number:  0xEF53
Filesystem revision #:    1 (dynamic)
Filesystem features:      has_journal ext_attr resize_inode dir_index
...
Inode count:              262144
Block count:              1048576
Reserved block count:     52428
Free blocks:              820000
Free inodes:              260910

6.3 性能分析实战

# 1. 使用iostat监控I/O性能
root@buildroot:~# iostat -x 1
avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           2.50    0.00    5.00   10.00    0.00   82.50
​
Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
mmcblk0           0.00     5.00    0.00   10.00     0.00   200.00    40.00     0.50   50.00    0.00   50.00  10.00  10.00
sda               0.00     0.00   50.00    0.00  1024.00     0.00    40.00     0.10    2.00    2.00    0.00   1.00   5.00
​
# 2. 使用perf分析文件系统热点
root@buildroot:~# perf top -e ext4:ext4_writepages
Samples: 5K of event 'ext4:ext4_writepages', Event count (approx.): 5000
  Children      Self  Shared Object       Symbol
+   50.00%    50.00%  [kernel]           [k] ext4_writepages
+   30.00%    30.00%  [kernel]           [k] ext4_map_blocks
+   15.00%    15.00%  [kernel]           [k] ext4_mb_new_blocks
+    5.00%     5.00%  [kernel]           [k] jbd2_journal_start
​
# 3. 使用echo向eMMC写入测试
root@buildroot:~# time dd if=/dev/zero of=/mnt/data/testfile bs=1M count=100
100+0 records in
100+0 records out
104857600 bytes (100.0MB) copied, 2.34 seconds, 42.8 MB/s
​
real    0m 2.34s
user    0m 0.00s
sys     0m 0.56s

总结

  1. mkdir到eMMC的完整路径: 用户命令 → 系统调用 → VFS → ext4 → 块层 → MMC驱动 → eMMC硬件

  2. touch文件创建: 分配inode → 添加目录项 → 更新位图 → 日志记录

  3. 开机后读取: 内核挂载rootfs → 读取超级块 → 构建VFS缓存 → 用户访问时从eMMC读取

  4. 多设备统一管理: VFS抽象层统一接口,底层驱动处理不同硬件差异

  5. USB/SD卡: 热插拔检测 → 设备创建 → 自动挂载 → 通过相同VFS接口访问

  6. 调试工具链: strace(用户层) → ftrace(内核函数) → blktrace(块层) → 完整追踪数据流

关键理解: Linux文件系统的精髓在于VFS层的抽象,让用户无论操作eMMC、USB硬盘还是SD卡,都使用相同的open/read/write接口,而底层驱动负责与具体硬件通信。

第三阶段 Linux文件系统只读问题深度解析:从现象到根因

问题场景重现

# 典型问题现象
root@buildroot:~# touch /mnt/data/test.txt
touch: /mnt/data/test.txt: Read-only file system
​
# 或是在应用程序中
FILE *fp = fopen("/mnt/data/config.ini", "w");
if (fp == NULL) {
    perror("fopen");  // 输出: fopen: Read-only file system
}

一、open与fopen的本质区别

1.1 系统调用vs库函数

open() 与 fopen() 对比
│
├── 【open()】系统调用
│   │
│   ├── 原型: int open(const char *pathname, int flags, mode_t mode)
│   │
│   ├── 返回: 文件描述符 (整数)
│   │
│   ├── 执行路径:
│   │   open() → 系统调用 → SYSCALL_DEFINE3(open) → do_sys_open() 
│   │   → do_filp_open() → vfs_open() → 具体文件系统open
│   │
│   ├── 错误返回: -1, errno设置
│   │
│   └── 特点: 直接进入内核,无缓冲
│
└── 【fopen()】标准C库函数
    │
    ├── 原型: FILE *fopen(const char *pathname, const char *mode)
    │
    ├── 返回: FILE结构体指针
    │
    ├── 执行路径:
    │   fopen() → 用户空间库函数 → open()系统调用 → ... → 返回FILE*
    │
    ├── 特点:
    │   ├── 在open()基础上增加缓冲区 (stdio缓冲)
    │   ├── 可能受限于glibc的权限检查
    │   └── 同一个文件描述符,错误码相同
    │
    └── 核心: fopen最终调用open(),只读问题根因相同

二、文件变成只读的五大根因

2.1 根因一:文件系统损坏(最致命)

文件系统损坏导致只读
│
├── 【现象】
│   │
│   ├── 内核日志: EXT4-fs error (device mmcblk0p2): 
│   │            ext4_find_entry: 1234: inode #5678: comm bash: 
│   │            reading directory lblock 0
│   │
│   └── 系统自动将文件系统重新挂载为只读
│
├── 【触发条件】
│   │
│   ├── 异常断电
│   │   └── 正在写入时掉电,元数据不一致
│   │
│   ├── 硬件故障
│   │   ├── eMMC坏块
│   │   ├── 存储芯片老化
│   │   └── 接触不良 (SD卡)
│   │
│   ├── 内核BUG
│   │   └── 文件系统驱动异常
│   │
│   └── 不正常的卸载
│       └── 未sync直接拔卡
│
├── 【内核自动保护机制】
│   │
│   ├── 检测到文件系统错误
│   │   │
│   │   └── 内核函数: ext4_handle_error()
│   │       │
│   │       ├── 打印错误日志
│   │       ├── 设置 SB_RDONLY 标志
│   │       └── 调用 remount_ro() 重新挂载为只读
│   │
│   └── 代码路径:
│       fs/ext4/super.c: ext4_handle_error()
│           ├── if (sb_rdonly(sb)) return;
│           ├── printk(KERN_CRIT "EXT4-fs error ...");
│           ├── sb->s_flags |= SB_RDONLY;
│           └── sync_filesystem(sb);
│
└── 【诊断方法】
    │
    ├── dmesg | grep -i "ext4.*error"
    │   └── [12345.678] EXT4-fs error (device mmcblk0p2): 
    │       ext4_find_entry:1452: inode #2: comm ls: reading directory lblock 0
    │
    ├── mount | grep ro
    │   └── /dev/mmcblk0p2 on / type ext4 (ro,relatime)
    │
    └── fsck修复
        └── umount /mnt/data && fsck.ext4 -y /dev/mmcblk0p2

2.2 根因二:挂载选项错误

挂载选项导致的只读
│
├── 【主动挂载为只读】
│   │
│   ├── mount -o remount,ro /mnt/data
│   │   └── 管理员主动设置只读
│   │
│   ├── fstab配置错误
│   │   └── /etc/fstab: /dev/mmcblk0p3 /mnt/data ext4 defaults,ro 0 2
│   │
│   └── 内核启动参数
│       └── root=/dev/mmcblk0p2 ro
│
├── 【被动只读】
│   │
│   ├── 设备写入错误
│   │   │
│   │   ├── eMMC/SD卡写保护开关 (SD卡物理开关)
│   │   │   └── 卡座上的写保护检测引脚
│   │   │
│   │   ├── 设备返回写保护错误
│   │   │   └── MMC命令响应: WP_VIOLATION
│   │   │
│   │   └── 代码路径:
│   │       drivers/mmc/core/block.c: mmc_blk_issue_rq()
│   │           ├── if (card->read_only)
│   │           │   └── return -EROFS
│   │           └── 块层返回错误
│   │
│   └── 文件系统检测到挂载时错误
│       └── mount时检测到需要fsck,自动只读挂载
│
├── 【诊断方法】
│   │
│   ├── 查看挂载选项
│   │   └── cat /proc/mounts | grep /mnt/data
│   │
│   ├── 检查fstab
│   │   └── grep /mnt/data /etc/fstab
│   │
│   └── 检查设备写保护
│       └── cat /sys/block/mmcblk0/ro
│           └── 0=可写, 1=只读
│
└── 【修复方法】
    └── mount -o remount,rw /mnt/data

2.3 根因三:权限问题(最常见)

权限不足导致的"只读"错误
│
├── 【文件权限】
│   │
│   ├── 用户没有写权限
│   │   │
│   │   ├── 文件所有者不是当前用户
│   │   │   └── ls -l /mnt/data/config.ini
│   │   │       -rw-r--r-- 1 root root 1024 Mar 26 10:00 config.ini
│   │   │       # 普通用户无写权限
│   │   │
│   │   └── 目录没有写权限
│   │       └── ls -ld /mnt/data/
│   │           drwxr-xr-x 2 root root 4096 Mar 26 10:00 /mnt/data/
│   │           # 普通用户无法创建文件
│   │
│   ├── 文件系统挂载时限制了权限
│   │   └── mount -o uid=1000,gid=1000,umask=022 ...
│   │
│   └── 代码路径:
│       fs/namei.c: may_open()
│           ├── if (!(file->f_mode & FMODE_WRITE))
│           │   └── return -EACCES
│           └── 检查inode权限
│
├── 【文件属性】
│   │
│   ├── 不可变属性 (immutable)
│   │   └── chattr +i /mnt/data/config.ini
│   │       └── lsattr config.ini: ----i--------e----
│   │
│   └── 只追加属性 (append-only)
│       └── chattr +a /mnt/data/log.txt
│           └── 只能追加,不能修改或删除
│
├── 【诊断方法】
│   │
│   ├── 检查用户身份
│   │   └── id
│   │       uid=1000(user) gid=1000(user)
│   │
│   ├── 检查文件/目录权限
│   │   ├── ls -l /mnt/data/config.ini
│   │   └── ls -ld /mnt/data/
│   │
│   ├── 检查扩展属性
│   │   └── lsattr /mnt/data/config.ini
│   │
│   └── 尝试以root身份测试
│       └── sudo touch /mnt/data/test.txt
│
└── 【修复方法】
    ├── 修改权限: chmod 666 /mnt/data/config.ini
    ├── 修改所有者: chown user:user /mnt/data/
    ├── 移除不可变: chattr -i /mnt/data/config.ini
    └── 使用sudo

2.4 根因四:空间不足

磁盘空间不足导致写入失败
│
├── 【现象】
│   │
│   ├── open() 返回 -1, errno = ENOSPC
│   │
│   ├── fopen() 返回 NULL, errno = ENOSPC
│   │
│   └── 错误信息: No space left on device
│
├── 【两种空间耗尽】
│   │
│   ├── 数据块耗尽
│   │   ├── df -h 显示 100% 使用
│   │   └── 无法分配新数据块
│   │
│   └── inode耗尽 (小文件很多的情况)
│       ├── df -i 显示 100% 使用
│       └── 无法创建新文件 (即使磁盘还有空间)
│
├── 【保留空间机制】
│   │
│   ├── ext4默认保留5%空间给root用户
│   │   └── tune2fs -l /dev/mmcblk0p2 | grep "Reserved block count"
│   │
│   └── 普通用户空间用尽时,root仍可写入
│
├── 【诊断方法】
│   │
│   ├── df -h (数据块空间)
│   │   └── /dev/mmcblk0p2  3.9G  3.8G  0M  100% /mnt/data
│   │
│   ├── df -i (inode空间)
│   │   └── /dev/mmcblk0p2  262144  262144  0  100% /mnt/data
│   │
│   └── 查找大文件/大量小文件
│       ├── du -sh /* 2>/dev/null | sort -h
│       └── find /mnt/data -type f -size +100M
│
└── 【修复方法】
    ├── 删除文件: rm -rf /mnt/data/large_file
    ├── 清理日志: truncate -s 0 /mnt/data/app.log
    └── 扩容分区 (需要重新分区)

2.5 根因五:文件锁/进程占用

文件被占用导致的写入失败
│
├── 【现象】
│   │
│   ├── open() 成功,但写入失败
│   ├── 或者 open() 失败,errno = EACCES
│   │
│   └── 某些情况下,文件看起来是只读的
│
├── 【文件锁】
│   │
│   ├── 强制锁 (mandatory lock)
│   │   ├── fcntl(fd, F_SETLK, &flock)
│   │   └── 其他进程无法写入
│   │
│   └── 咨询锁 (advisory lock)
│       └── 仅合作进程遵守
│
├── 【进程占用】
│   │
│   ├── 其他进程打开了文件且未释放
│   │   └── 特别是以独占模式打开 (O_EXCL)
│   │
│   ├── 文件正在被执行
│   │   └── 可执行文件正在运行中
│   │
│   └── 文件系统被其他挂载点占用
│       └── 有进程在目录中,导致无法卸载/重新挂载
│
├── 【诊断方法】
│   │
│   ├── 查看文件锁
│   │   └── lsof /mnt/data/config.ini
│   │
│   ├── 查看进程打开的文件
│   │   └── lsof | grep /mnt/data
│   │
│   ├── 查看文件被哪些进程占用
│   │   └── fuser -v /mnt/data/
│   │
│   └── 查看文件锁详情
│       └── cat /proc/locks
│
└── 【修复方法】
    ├── 杀掉占用进程: kill -9 PID
    ├── 释放文件锁: 重启应用
    └── 强制卸载: umount -l /mnt/data (lazy unmount)

三、实战诊断流程

# 完整的只读问题诊断流程
​
# Step 1: 确认错误类型
touch /mnt/data/test.txt
# 输出: touch: cannot touch 'test.txt': Read-only file system
# 或: touch: cannot touch 'test.txt': Permission denied
# 或: touch: cannot touch 'test.txt': No space left on device
​
# Step 2: 查看系统日志
dmesg | tail -30
# 重点关注:
# - EXT4-fs error (文件系统损坏)
# - I/O error (硬件错误)
# - mmcblk0: error (eMMC错误)
​
# Step 3: 查看挂载状态
mount | grep /mnt/data
# 输出: /dev/mmcblk0p3 on /mnt/data type ext4 (ro,relatime)
# 如果显示 ro,说明被只读挂载
​
# Step 4: 检查磁盘空间
df -h /mnt/data
df -i /mnt/data
​
# Step 5: 检查权限
ls -ld /mnt/data/
ls -l /mnt/data/
​
# Step 6: 检查扩展属性
lsattr /mnt/data/*
​
# Step 7: 查看块设备状态
cat /sys/block/mmcblk0/ro
# 0=可写, 1=只读
​
# Step 8: 查看进程占用
lsof +D /mnt/data
​
# Step 9: 尝试重新挂载为读写
mount -o remount,rw /mnt/data
​
# Step 10: 如果仍然失败,需要修复文件系统
umount /mnt/data
fsck.ext4 -y /dev/mmcblk0p3
mount /mnt/data

四、预防措施与最佳实践

4.1 应用程序层面

// 正确的错误处理
FILE *fp = fopen("/mnt/data/config.ini", "w");
if (fp == NULL) {
    switch (errno) {
        case EACCES:
            fprintf(stderr, "Permission denied\n");
            break;
        case ENOSPC:
            fprintf(stderr, "No space left\n");
            break;
        case EROFS:
            fprintf(stderr, "Read-only filesystem\n");
            break;
        default:
            perror("fopen");
    }
    // 考虑降级处理: 写入/tmp或备用位置
    fp = fopen("/tmp/config.ini", "w");
}

4.2 系统层面

# 1. 监控文件系统状态
# 添加cron任务,每分钟检查一次
* * * * * test -w /mnt/data || echo "WARNING: /mnt/data is read-only" | logger
​
# 2. 关键文件系统使用journald记录
# /etc/systemd/journald.conf
SystemMaxUse=500M
ForwardToSyslog=yes
​
# 3. 设置文件系统自动修复
# /etc/fstab 添加错误处理选项
/dev/mmcblk0p3 /mnt/data ext4 defaults,errors=remount-ro 0 2
​
# 4. 监控磁盘空间
# 设置告警阈值
df -h | awk 'NR>1 {print $5,$6}' | while read usage mount; do
    if [ ${usage%\%} -gt 90 ]; then
        logger -t disk "WARNING: $mount usage is $usage"
    fi
done

4.3 嵌入式设备特有考虑

// 对于eMMC/SD卡,需要优雅处理写入失败
int safe_write_file(const char *path, const char *data) {
    int retry = 3;
    int fd;
    
    while (retry--) {
        fd = open(path, O_WRONLY | O_CREAT | O_SYNC, 0644);
        if (fd < 0) {
            if (errno == EROFS) {
                // 文件系统只读,尝试重新挂载
                system("mount -o remount,rw /mnt/data");
                sleep(1);
                continue;
            }
            return -1;
        }
        
        if (write(fd, data, strlen(data)) < 0) {
            close(fd);
            continue;
        }
        
        close(fd);
        return 0;
    }
    
    // 所有重试失败,记录到内存
    save_to_ramfs(path, data);
    return -1;
}

五、总结:快速定位矩阵

错误现象可能原因快速诊断命令解决方案
Read-only file system文件系统损坏dmesg \| grep errorfsck修复
Read-only file system挂载为只读mount \| grep romount -o remount,rw
Permission denied权限不足ls -lchmod/chown
Permission denied不可变属性lsattrchattr -i
No space left磁盘满df -h删除文件
No space leftinode满df -i删除小文件
Device or resource busy进程占用lsof/fuserkill进程
I/O error硬件故障dmesg \| grep mmc更换硬件

核心要点:fopen和open的只读错误本质相同,都是内核返回EROFS、EACCES、ENOSPC等错误码。关键是要通过系统日志、挂载状态、磁盘空间、文件权限四个维度快速定位根因。嵌入式设备尤其要注意eMMC/SD卡的硬件稳定性和文件系统完整性。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐