第五部分 Ext4 文件系统

八、Ext4 文件系统详细实现

8.1 Ext4 磁盘布局与数据结构

/* fs/ext4/ext4.h */
/*
 * Ext4 磁盘布局树形结构:
 * 超级块 (1KB)
 * ├── 块组描述符表
 * ├── 数据块位图
 * ├── inode位图  
 * ├── inode表
 * └── 数据块
 * 
 * 扩展特性:
 * ├── 扩展属性
 * ├── 目录索引 (HTree)
 * ├── 日志 (Journal)
 * └── 延迟分配
 */
​
/* Ext4 超级块结构 - 512字节 + 1024字节扩展 */
struct ext4_super_block {
/*00*/  __le32  s_inodes_count;           /* inodes计数 */
        __le32  s_blocks_count_lo;        /* 块计数低32位 */
        __le32  s_r_blocks_count_lo;      /* 保留块计数低32位 */
        __le32  s_free_blocks_count_lo;   /* 空闲块计数低32位 */
/*10*/  __le32  s_free_inodes_count;      /* 空闲inodes计数 */
        __le32  s_first_data_block;       /* 第一个数据块 */
        __le32  s_log_block_size;         /* 块大小对数 */
        __le32  s_log_cluster_size;       /* 簇大小对数 */
/*20*/  __le32  s_blocks_per_group;       /* 每块组块数 */
        __le32  s_clusters_per_group;     /* 每块组簇数 */
        __le32  s_inodes_per_group;       /* 每块组inodes数 */
        __le32  s_mtime;                  /* 挂载时间 */
/*30*/  __le32  s_wtime;                  /* 写时间 */
        __le16  s_mnt_count;              /* 挂载计数 */
        __le16  s_max_mnt_count;          /* 最大挂载计数 */
        __le16  s_magic;                  /* 魔术字 0xEF53 */
        __le16  s_state;                  /* 文件系统状态 */
        __le16  s_errors;                 /* 错误行为 */
        __le16  s_minor_rev_level;        /* 次版本号 */
/*40*/  __le32  s_lastcheck;              /* 最后检查时间 */
        __le32  s_checkinterval;          /* 检查间隔 */
        __le32  s_creator_os;             /* 创建者操作系统 */
        __le32  s_rev_level;              /* 修订级别 */
/*50*/  __le16  s_def_resuid;             /* 默认保留用户ID */
        __le16  s_def_resgid;             /* 默认保留组ID */
        __le32  s_first_ino;              /* 第一个非保留inode号 */
        __le16  s_inode_size;             /* inode大小 */
        __le16  s_block_group_nr;         /* 块组编号 */
        __le32  s_feature_compat;         /* 兼容特性 */
/*60*/  __le32  s_feature_incompat;       /* 不兼容特性 */
        __le32  s_feature_ro_compat;      /* 只读兼容特性 */
        __u8    s_uuid[16];               /* 128位UUID */
        char    s_volume_name[16];        /* 卷名 */
/*90*/  char    s_last_mounted[64];       /* 最后挂载路径 */
        __le32  s_algorithm_usage_bitmap; /* 压缩算法位图 */
        
        /* 性能提示 */
        __u8    s_prealloc_blocks;        /* 预分配块数 */
        __u8    s_prealloc_dir_blocks;    /* 目录预分配块数 */
        __le16  s_reserved_gdt_blocks;    /* 保留GDT块数 */
        
        /* 日志支持 */
        __u8    s_journal_uuid[16];       /* 日志UUID */
        __le32  s_journal_inum;           /* 日志inode号 */
        __le32  s_journal_dev;            /* 日志设备号 */
        __le32  s_last_orphan;            /* 最后一个孤儿inode */
        
        /* 目录索引 */
        __le32  s_hash_seed[4];           /* 哈希种子 */
        __u8    s_def_hash_version;       /* 默认哈希版本 */
        __u8    s_jnl_backup_type;        /* 日志备份类型 */
        
        /* 64位支持 */
        __le16  s_desc_size;              /* 组描述符大小 */
        __le32  s_default_mount_opts;     /* 默认挂载选项 */
        __le32  s_first_meta_bg;          /* 第一个元数据块组 */
        __le32  s_mkfs_time;              /* mkfs时间 */
        
        /* 扩展大小字段 */
        __le32  s_jnl_blocks[17];         /* 日志块备份 */
        
        /* 更多字段... 总共1024字节 */
};
​
/* Ext4 inode 结构 - 256字节基础 + 扩展 */
struct ext4_inode {
/*00*/  __le16  i_mode;                   /* 文件模式 */
        __le16  i_uid;                    /* 用户ID低16位 */
        __le32  i_size_lo;                /* 大小低32位 */
        __le32  i_atime;                  /* 访问时间 */
/*10*/  __le32  i_ctime;                  /* 改变时间 */
        __le32  i_mtime;                  /* 修改时间 */
        __le32  i_dtime;                  /* 删除时间 */
        __le16  i_gid;                    /* 组ID低16位 */
        __le16  i_links_count;            /* 链接计数 */
/*20*/  __le32  i_blocks_lo;              /* 块计数低32位 */
        __le32  i_flags;                  /* 文件标志 */
        union {
            struct {
                __le32  l_i_version;      /* 版本号 */
            } linux1;
            struct {
                __u32   h_i_translator;   /* 翻译器 */
            } hurd1;
            struct {
                __u32   m_i_reserved1;    /* 保留 */
            } masix1;
        } osd1;                          /* 操作系统相关1 */
/*30*/  __le32  i_block[EXT4_N_BLOCKS];   /* 块指针数组 */
        __le32  i_generation;             /* 文件版本 */
        __le32  i_file_acl_lo;            /* 文件ACL低32位 */
        __le32  i_size_high;              /* 大小高32位 */
/*40*/  __le32  i_obso_faddr;             /* 废弃的片段地址 */
        union {
            struct {
                __le16  l_i_blocks_high;  /* 块计数高16位 */
                __le16  l_i_file_acl_high; /* 文件ACL高16位 */
                __le16  l_i_uid_high;     /* 用户ID高16位 */
                __le16  l_i_gid_high;     /* 组ID高16位 */
                __le16  l_i_checksum_lo;  /* 校验和低16位 */
                __le16  l_i_reserved;     /* 保留 */
            } linux2;
            /* 其他操作系统... */
        } osd2;                          /* 操作系统相关2 */
/*60*/  __le16  i_extra_isize;            /* 扩展inode大小 */
        __le16  i_checksum_hi;            /* 校验和高16位 */
        __le32  i_ctime_extra;            /* 改变时间扩展 */
        __le32  i_mtime_extra;            /* 修改时间扩展 */
        __le32  i_atime_extra;            /* 访问时间扩展 */
        __le32  i_crtime;                 /* 创建时间 */
        __le32  i_crtime_extra;           /* 创建时间扩展 */
        __le32  i_version_hi;             /* 版本号高32位 */
        __le32  i_projid;                 /* 项目ID */
/*80*/  /* 扩展属性区域开始... */
};
​
/* 块指针数组布局 */
enum {
    EXT4_IND_BLOCK  = 0,                 /* 直接块指针 */
    EXT4_DIND_BLOCK = 1,                 /* 一级间接块 */
    EXT4_TIND_BLOCK = 2,                 /* 二级间接块 */
    EXT4_N_BLOCKS   = 15                 /* 总块指针数 */
};

8.2 Ext4 超级块初始化与挂载

/* fs/ext4/super.c */
/*
 * Ext4 挂载流程树形结构:
 * ext4_mount
 * ├── mount_bdev
 * │   ├── ext4_fill_super
 * │   │   ├── parse_options
 * │   │   ├── ext4_load_super
 * │   │   ├── ext4_setup_super
 * │   │   ├── ext4_group_desc_init
 * │   │   ├── ext4_register_sysfs
 * │   │   ├── ext4_enable_quotas
 * │   │   └── ext4_start_delalloc_flush
 * │   └── sget
 */
​
/* Ext4 填充超级块核心函数 */
static int ext4_fill_super(struct super_block *sb, void *data, int silent)
{
    struct buffer_head *bh;               /* 缓冲区头 */
    struct ext4_super_block *es = NULL;   /* Ext4超级块 */
    struct ext4_sb_info *sbi;             /* Ext4超级块信息 */
    ext4_fsblk_t block;                   /* 块号 */
    unsigned long sb_block = get_sb_block(&data);  /* 超级块位置 */
    unsigned long offset = 0;             /* 偏移量 */
    unsigned int journal_inum = 0;        /* 日志inode号 */
    unsigned long journal_devnum = 0;     /* 日志设备号 */
    int active, j_formatted = 0;          /* 活动标志,日志格式化标志 */
    int err = 0;                          /* 错误码 */
    int needs_recovery;                   /* 需要恢复标志 */
    ext4_group_t first_not_zeroed;        /* 第一个未清零组 */
    char *cp;                             /* 选项指针 */
    
    /* 1. 分配Ext4超级块信息结构 */
    sbi = kzalloc(sizeof(*sbi), GFP_KERNEL);  /* 分配内存 */
    if (!sbi)
        return -ENOMEM;                   /* 内存不足 */
    
    /* 2. 设置超级块私有数据 */
    sb->s_fs_info = sbi;                  /* 设置超级块私有数据指针 */
    sbi->s_sb = sb;                       /* 设置回指针 */
    sbi->s_blockgroup_lock =
        kzalloc(sizeof(struct blockgroup_lock), GFP_KERNEL);  /* 分配块组锁 */
    if (!sbi->s_blockgroup_lock) {
        err = -ENOMEM;
        goto failed_mount;                /* 内存不足 */
    }
    
    /* 3. 清理旧数据 */
    sb->s_flags = (sb->s_flags & ~MS_POSIXACL) |
                  ((flags & EXT4_MOUNT_POSIX_ACL) ? MS_POSIXACL : 0);  /* 设置标志 */
    
    /* 4. 解析挂载选项 */
    if (!(ctx->spec & EXT4_SPEC_JOURNAL_IOPRIO))
        ctx->journal_ioprio = DEFAULT_JOURNAL_IOPRIO;  /* 默认日志IO优先级 */
    
    /* 5. 设置块大小 */
    if (blocksize != sb->s_blocksize) {
        /* 重新设置块大小 */
        if (!sb_set_blocksize(sb, blocksize)) {
            ext4_msg(sb, KERN_ERR,
                    "bad block size %d", blocksize);
            err = -EINVAL;
            goto failed_mount;
        }
    }
    
    /* 6. 读取超级块 */
    bh = ext4_sb_bread(sb, sb_block, 0);  /* 读取超级块 */
    if (IS_ERR(bh)) {
        ext4_msg(sb, KERN_ERR, "unable to read superblock");
        err = PTR_ERR(bh);
        bh = NULL;
        goto failed_mount;
    }
    
    /* 7. 解析超级块数据 */
    es = (struct ext4_super_block *) bh->b_data;  /* 获取超级块数据 */
    sbi->s_es = es;                       /* 设置超级块指针 */
    sb->s_magic = le16_to_cpu(es->s_magic);  /* 设置魔术字 */
    
    /* 8. 验证超级块魔术字 */
    if (sb->s_magic != EXT4_SUPER_MAGIC) {
        if (!silent)
            ext4_msg(sb, KERN_ERR, "VFS: Can't find ext4 filesystem");
        err = -EINVAL;
        goto cantfind_ext4;
    }
    
    /* 9. 检查修订级别 */
    if (le32_to_cpu(es->s_rev_level) == EXT4_GOOD_OLD_REV &&
        (EXT4_HAS_COMPAT_FEATURE(sb, ~0U) ||
         EXT4_HAS_RO_COMPAT_FEATURE(sb, ~0U) ||
         EXT4_HAS_INCOMPAT_FEATURE(sb, ~0U)))
        ext4_msg(sb, KERN_WARNING,
                "feature flags set on rev 0 fs, "
                "running e2fsck is recommended");
    
    /* 10. 设置块大小相关参数 */
    if (le32_to_cpu(es->s_log_block_size) >
        (EXT4_MAX_BLOCK_LOG_SIZE - EXT4_MIN_BLOCK_LOG_SIZE)) {
        ext4_msg(sb, KERN_ERR,
                "Invalid log block size: %u",
                le32_to_cpu(es->s_log_block_size));
        err = -EINVAL;
        goto failed_mount;
    }
    
    /* 11. 计算各种大小参数 */
    blocksize = EXT4_MIN_BLOCK_SIZE << le32_to_cpu(es->s_log_block_size);  /* 块大小 */
    sbi->s_cluster_bits = le32_to_cpu(es->s_log_cluster_size) -
                         le32_to_cpu(es->s_log_block_size);  /* 簇位数 */
    sbi->s_clusters_per_group =
        le32_to_cpu(es->s_clusters_per_group);  /* 每组簇数 */
    
    /* 12. 设置超级块操作 */
    sb->s_op = &ext4_sops;                /* 设置超级块操作表 */
    sb->s_export_op = &ext4_export_ops;   /* 设置导出操作 */
    sb->s_xattr = ext4_xattr_handlers;    /* 设置扩展属性处理程序 */
    
    /* 13. 支持64位特性 */
    if (EXT4_HAS_RO_COMPAT_FEATURE(sb, EXT4_FEATURE_RO_COMPAT_HUGE_FILE |
                                   EXT4_FEATURE_RO_COMPAT_BIGALLOC)) {
        if (!ext4_feature_set_ok(sb, (sb->s_flags & MS_RDONLY)))
            goto failed_mount;
    }
    
    /* 14. 初始化块组描述符 */
    err = ext4_init_block_bitmap(sb, bh, block_group, desc);  /* 初始化块位图 */
    if (err)
        goto failed_mount;
    
    /* 15. 初始化日志 */
    err = ext4_load_journal(sb, es, journal_devnum);  /* 加载日志 */
    if (err)
        goto failed_mount;
    
    /* 16. 设置根inode */
    if (!(sb->s_flags & MS_RDONLY)) {
        err = ext4_setup_super(sb, es, 0);  /* 设置超级块 */
        if (err == -EROFS) {
            sb->s_flags |= MS_RDONLY;
            err = 0;
        } else if (err)
            goto failed_mount;
    }
    
    /* 17. 注册sysfs接口 */
    err = ext4_register_sysfs(sb);        /* 注册sysfs */
    if (err)
        goto failed_mount;
    
    /* 18. 启用配额 */
    err = ext4_enable_quotas(sb);         /* 启用配额 */
    if (err)
        goto failed_mount;
    
    /* 19. 启动延迟分配刷新 */
    ext4_start_delalloc_flush(sb);        /* 启动延迟分配刷新 */
    
    /* 20. 成功返回 */
    brelse(bh);                           /* 释放缓冲区 */
    return 0;                             /* 成功返回 */
​
cantfind_ext4:
failed_mount:
    if (sbi->s_chksum_driver)
        crypto_free_shash(sbi->s_chksum_driver);  /* 释放校验和驱动 */
    kfree(sbi->s_blockgroup_lock);        /* 释放块组锁 */
    kfree(sbi);                           /* 释放超级块信息 */
    sb->s_fs_info = NULL;                 /* 清空超级块信息指针 */
    return err;                           /* 返回错误 */
}

8.3 Ext4 inode 操作实现

/* fs/ext4/inode.c */
/*
 * Ext4 inode操作表 - 策略模式
 * 实现Ext4特定的inode操作
 */
const struct inode_operations ext4_file_inode_operations = {
    .setattr    = ext4_setattr,           /* 设置属性 */
    .getattr    = ext4_getattr,           /* 获取属性 */
    .listxattr  = ext4_listxattr,         /* 列出扩展属性 */
    .get_acl    = ext4_get_acl,           /* 获取ACL */
    .set_acl    = ext4_set_acl,           /* 设置ACL */
    .fiemap     = ext4_fiemap,            /* 文件扩展映射 */
};
​
/* Ext4 文件操作表 */
const struct file_operations ext4_file_operations = {
    .llseek     = ext4_llseek,            /* 文件定位 */
    .read_iter  = ext4_file_read_iter,    /* 读迭代 */
    .write_iter = ext4_file_write_iter,   /* 写迭代 */
    .unlocked_ioctl = ext4_ioctl,         /* 非锁定IO控制 */
#ifdef CONFIG_COMPAT
    .compat_ioctl   = ext4_compat_ioctl,  /* 兼容IO控制 */
#endif
    .mmap       = ext4_file_mmap,         /* 内存映射 */
    .open       = ext4_file_open,         /* 打开文件 */
    .release    = ext4_release_file,      /* 释放文件 */
    .fsync      = ext4_sync_file,         /* 文件同步 */
    .splice_read    = generic_file_splice_read,  /* 拼接读 */
    .splice_write   = iter_file_splice_write,    /* 拼接写 */
    .fallocate  = ext4_fallocate,         /* 文件预分配 */
};
​
/* Ext4 获取inode函数 */
struct inode *ext4_iget(struct super_block *sb, unsigned long ino)
{
    struct ext4_iloc iloc;                /* inode位置 */
    struct ext4_inode *raw_inode;         /* 原始inode */
    struct ext4_inode_info *ei;           /* Ext4 inode信息 */
    struct inode *inode;                  /* inode指针 */
    journal_t *journal = EXT4_SB(sb)->s_journal;  /* 日志 */
    long ret;                             /* 返回值 */
    int block;                            /* 块号 */
    uid_t i_uid;                          /* 用户ID */
    gid_t i_gid;                          /* 组ID */
    projid_t i_projid;                    /* 项目ID */
    
    /* 1. 从inode缓存中查找 */
    inode = iget_locked(sb, ino);         /* 获取锁定inode */
    if (!inode)
        return ERR_PTR(-ENOMEM);          /* 内存不足 */
    if (!(inode->i_state & I_NEW))        /* 如果已在缓存中 */
        return inode;
    
    /* 2. 获取Ext4特定信息 */
    ei = EXT4_I(inode);                   /* 获取Ext4 inode信息 */
    
    /* 3. 读取inode磁盘数据 */
    ret = __ext4_get_inode_loc(inode, &iloc, 0);  /* 获取inode位置 */
    if (ret < 0)
        goto bad_inode;                   /* 获取位置失败 */
    
    /* 4. 获取原始inode数据 */
    raw_inode = ext4_raw_inode(&iloc);    /* 获取原始inode数据 */
    
    /* 5. 验证inode魔术字 */
    if (EXT4_INODE_SIZE(inode->i_sb) > EXT4_GOOD_OLD_INODE_SIZE) {
        ei->i_extra_isize = le16_to_cpu(raw_inode->i_extra_isize);  /* 扩展inode大小 */
        if (EXT4_GOOD_OLD_INODE_SIZE + ei->i_extra_isize >
            EXT4_INODE_SIZE(inode->i_sb)) {
            ret = -EFSCORRUPTED;
            goto bad_inode;               /* 扩展inode大小无效 */
        }
    } else
        ei->i_extra_isize = 0;            /* 无扩展inode */
    
    /* 6. 设置inode标志 */
    ei->i_disksize = inode->i_size;       /* 设置磁盘大小 */
    inode->i_mode = le16_to_cpu(raw_inode->i_mode);  /* 设置文件模式 */
    i_uid = (uid_t)le16_to_cpu(raw_inode->i_uid_low);  /* 用户ID低16位 */
    i_gid = (gid_t)le16_to_cpu(raw_inode->i_gid_low);  /* 组ID低16位 */
    
    /* 7. 处理扩展的uid/gid */
    if (!(test_opt(inode->i_sb, NO_UID32))) {
        i_uid |= le16_to_cpu(raw_inode->i_uid_high) << 16;  /* 用户ID高16位 */
        i_gid |= le16_to_cpu(raw_inode->i_gid_high) << 16;  /* 组ID高16位 */
    }
    i_uid_write(inode, i_uid);            /* 设置用户ID */
    i_gid_write(inode, i_gid);            /* 设置组ID */
    
    /* 8. 设置inode时间 */
    inode->i_atime.tv_sec = (signed)le32_to_cpu(raw_inode->i_atime);  /* 访问时间 */
    inode->i_ctime.tv_sec = (signed)le32_to_cpu(raw_inode->i_ctime);  /* 改变时间 */
    inode->i_mtime.tv_sec = (signed)le32_to_cpu(raw_inode->i_mtime);  /* 修改时间 */
    
    ei->i_crtime.tv_sec = (signed)le32_to_cpu(raw_inode->i_crtime);  /* 创建时间 */
    inode->i_atime.tv_nsec = ei->i_crtime.tv_nsec = 
    inode->i_mtime.tv_nsec = inode->i_ctime.tv_nsec = 0;  /* 纳秒部分清零 */
    
    /* 9. 初始化inode操作 */
    if (S_ISREG(inode->i_mode)) {
        /* 常规文件 */
        inode->i_op = &ext4_file_inode_operations;  /* 文件inode操作 */
        inode->i_fop = &ext4_file_operations;       /* 文件操作 */
        ext4_set_aops(inode);             /* 设置地址空间操作 */
    } else if (S_ISDIR(inode->i_mode)) {
        /* 目录文件 */
        inode->i_op = &ext4_dir_inode_operations;   /* 目录inode操作 */
        inode->i_fop = &ext4_dir_operations;        /* 目录操作 */
    } else if (S_ISLNK(inode->i_mode)) {
        /* 符号链接 */
        if (ext4_inode_is_fast_symlink(inode)) {
            /* 快速符号链接 - 数据存储在inode中 */
            inode->i_link = (char *)ei->i_data;     /* 链接目标 */
            inode->i_op = &ext4_fast_symlink_inode_operations;  /* 快速符号链接操作 */
        } else {
            /* 慢速符号链接 - 数据存储在数据块中 */
            inode->i_op = &ext4_symlink_inode_operations;  /* 符号链接操作 */
            ext4_set_aops(inode);         /* 设置地址空间操作 */
        }
    } else {
        /* 其他文件类型 */
        inode->i_op = &ext4_special_inode_operations;  /* 特殊inode操作 */
        if (S_ISFIFO(inode->i_mode))
            inode->i_fop = &pipefifo_fops;  /* FIFO文件操作 */
        else if (S_ISSOCK(inode->i_mode))
            inode->i_fop = &bad_sock_fops;  /* 套接字文件操作 */
    }
    
    /* 10. 读取扩展属性 */
    if (ext4_has_feature_ea_inode(inode->i_sb) &&
        EXT4_I(inode)->i_file_acl) {
        /* 扩展属性存储在外部inode中 */
        err = ext4_xattr_inode_iget(inode, 
                                   le32_to_cpu(raw_inode->i_file_acl),
                                   le32_to_cpu(raw_inode->i_file_acl_high),
                                   &ea_inode);  /* 获取扩展属性inode */
        if (!err)
            EXT4_I(inode)->i_xattr_inode = ea_inode;  /* 设置扩展属性inode */
    }
    
    /* 11. 释放缓冲区并解锁inode */
    brelse(iloc.bh);                      /* 释放缓冲区 */
    unlock_new_inode(inode);              /* 解锁新inode */
    return inode;                         /* 返回inode */
​
bad_inode:
    brelse(iloc.bh);                      /* 释放缓冲区 */
    iget_failed(inode);                   /* 标记inode获取失败 */
    return ERR_PTR(ret);                  /* 返回错误 */
}

8.4 Ext4 数据块分配机制

/* fs/ext4/balloc.c */
/*
 * Ext4 块分配流程树形结构:
 * ext4_get_blocks
 * ├── ext4_map_blocks
 * │   ├── ext4_ext_map_blocks (扩展属性)
 * │   └── ext4_ind_map_blocks (间接块)
 * ├── ext4_mb_new_blocks
 * │   ├── ext4_mb_regular_allocator
 * │   ├── ext4_mb_use_preallocated
 * │   └── ext4_mb_use_best_found
 * └── ext4_issue_discard
 */
​
/* Ext4 获取块函数 */
ext4_fsblk_t ext4_get_blocks(handle_t *handle, struct inode *inode,
                            sector_t block, unsigned int max_blocks,
                            struct buffer_head *bh, int flags)
{
    struct ext4_map_blocks map;           /* 块映射 */
    int ret = 0;                          /* 返回值 */
    int dio_credits;                      /* 直接IO信用 */
    unsigned int blkbits = inode->i_blkbits;  /* 块位数 */
    
    /* 1. 初始化映射结构 */
    map.m_lblk = block;                   /* 逻辑块号 */
    map.m_len = max_blocks;               /* 映射长度 */
    
    /* 2. 检查延迟分配 */
    if (EXT4_I(inode)->i_flags & EXT4_EXTENTS_FL) {
        /* 扩展属性分配 */
        ret = ext4_ext_map_blocks(handle, inode, &map, flags);  /* 扩展映射 */
    } else {
        /* 间接块分配 */
        ret = ext4_ind_map_blocks(handle, inode, &map, flags);  /* 间接映射 */
    }
    
    /* 3. 处理映射结果 */
    if (ret > 0) {
        /* 映射成功 */
        if (unlikely(!(flags & EXT4_GET_BLOCKS_UNINIT_EXT) &&
                     map.m_flags & EXT4_MAP_UNINIT)) {
            /* 未初始化扩展 */
            ret = -EIO;
            goto out;
        }
        
        /* 设置缓冲区头映射 */
        if (bh) {
            bh->b_blocknr = map.m_pblk;   /* 物理块号 */
            bh->b_size = inode->i_sb->s_blocksize * map.m_len;  /* 缓冲区大小 */
            bh->b_bdev = inode->i_sb->s_bdev;  /* 块设备 */
        }
    }
    
    /* 4. 处理错误 */
    if (ret < 0) {
        /* 映射失败 */
        if (ret == -ENOSPC &&
            ext4_should_retry_alloc(inode->i_sb, &retries)) {
            /* 空间不足但应重试 */
            ret = 0;
            goto retry;
        }
    }
    
out:
    return ret;                           /* 返回结果 */
}
​
/* 扩展属性映射块 */
int ext4_ext_map_blocks(handle_t *handle, struct inode *inode,
                       struct ext4_map_blocks *map, int flags)
{
    struct ext4_ext_path *path = NULL;    /* 扩展路径 */
    struct ext4_extent newex;             /* 新扩展 */
    struct ext4_extent *ex;               /* 扩展指针 */
    ext4_fsblk_t newblock;                /* 新块 */
    int err = 0, depth;                   /* 错误码,深度 */
    unsigned int allocated = 0;           /* 已分配块数 */
    struct ext4_allocation_request ar;    /* 分配请求 */
    ext4_io_end_t *io = EXT4_I(inode)->i_cur_aio_dio;  /* IO结束 */
    
    /* 1. 查找扩展路径 */
    path = ext4_find_extent(inode, map->m_lblk, NULL, 0);  /* 查找扩展 */
    if (IS_ERR(path)) {
        err = PTR_ERR(path);
        path = NULL;
        goto out2;
    }
    
    depth = ext_depth(inode);             /* 扩展树深度 */
    
    /* 2. 查找现有扩展 */
    if (path[depth].p_ext) {
        ex = path[depth].p_ext;           /* 现有扩展 */
        if (ext4_can_extents_be_merged(inode, ex, &newex)) {
            /* 可以合并扩展 */
            err = ext4_ext_get_access(handle, inode, path + depth);  /* 获取访问权限 */
            if (err)
                goto out2;
            
            ex->ee_len = cpu_to_le16(ext4_ext_get_actual_len(ex) +
                                    map->m_len);  /* 更新长度 */
            ext4_ext_dirty(handle, inode, path + depth);  /* 标记脏 */
            allocated = map->m_len;       /* 已分配块数 */
        } else {
            /* 不能合并,需要新扩展 */
            allocated = ext4_ext_handle_unwritten_extents(
                handle, inode, map, path, flags,
                allocated, &newblock);    /* 处理未写入扩展 */
        }
    } else {
        /* 没有现有扩展,需要分配新块 */
        allocated = ext4_ext_handle_unwritten_extents(
            handle, inode, map, path, flags,
            allocated, &newblock);        /* 处理未写入扩展 */
    }
    
    /* 3. 如果需要分配新块 */
    if (allocated == 0) {
        /* 准备分配请求 */
        ar.inode = inode;                 /* inode */
        ar.goal = ext4_ext_find_goal(inode, path, map->m_lblk);  /* 目标块 */
        ar.logical = map->m_lblk;         /* 逻辑块 */
        ar.len = map->m_len;              /* 长度 */
        ar.flags = flags;                 /* 标志 */
        
        /* 执行块分配 */
        newblock = ext4_mb_new_blocks(handle, &ar, &err);  /* 分配新块 */
        if (!newblock)
            goto out2;                    /* 分配失败 */
        
        allocated = ar.len;               /* 已分配块数 */
        
        /* 插入新扩展 */
        ext4_ext_store_pblock(&newex, newblock);  /* 存储物理块 */
        newex.ee_len = cpu_to_le16(ar.len);  /* 设置长度 */
        newex.ee_start = cpu_to_le32(map->m_lblk);  /* 设置起始块 */
        
        err = ext4_ext_insert_extent(handle, inode, &path, 
                                   &newex, flags);  /* 插入扩展 */
        if (err) {
            /* 插入失败,释放分配的块 */
            ext4_free_blocks(handle, inode, NULL, newblock, 
                           allocated, 0);  /* 释放块 */
            goto out2;
        }
        
        allocated = map->m_len;           /* 更新已分配块数 */
    }
    
    /* 4. 更新映射结果 */
    map->m_flags |= EXT4_MAP_MAPPED;      /* 设置映射标志 */
    map->m_pblk = newblock;               /* 设置物理块 */
    map->m_len = allocated;               /* 设置映射长度 */
    
out2:
    ext4_ext_drop_refs(path);             /* 释放扩展引用 */
    kfree(path);                          /* 释放路径 */
    return err ? err : allocated;         /* 返回结果 */
}

8.5 Ext4 多块分配器

/* fs/ext4/mballoc.c */
/*
 * 多块分配器核心函数 - 使用伙伴系统算法
 * 状态模式:管理不同分配状态
 */
ext4_fsblk_t ext4_mb_new_blocks(handle_t *handle,
                               struct ext4_allocation_request *ar,
                               int *errp)
{
    struct ext4_allocation_context *ac = NULL;  /* 分配上下文 */
    struct ext4_sb_info *sbi = EXT4_SB(ar->inode->i_sb);  /* 超级块信息 */
    ext4_fsblk_t block = 0;               /* 分配的块 */
    unsigned int inquota = 0;             /* 配额内标志 */
    unsigned int reserved_blocks;         /* 保留块数 */
    
    /* 1. 分配分配上下文 */
    ac = kmem_cache_zalloc(ext4_ac_cachep, GFP_NOFS);  /* 分配上下文 */
    if (!ac) {
        *errp = -ENOMEM;
        return 0;                         /* 内存不足 */
    }
    
    /* 2. 初始化分配上下文 */
    ac->ac_sb = ar->inode->i_sb;          /* 超级块 */
    ac->ac_inode = ar->inode;             /* inode */
    ac->ac_o_ex.fe_logical = ar->logical; /* 原始扩展逻辑块 */
    ac->ac_flags = ar->flags;             /* 标志 */
    
    /* 3. 设置目标块 */
    ac->ac_g_ex.fe_logical = ac->ac_o_ex.fe_logical;  /* 目标扩展逻辑块 */
    ac->ac_g_ex.fe_len = ar->len;         /* 目标扩展长度 */
    
    /* 4. 检查配额 */
    if (ar->flags & EXT4_MB_DELALLOC_RESERVED) {
        /* 延迟分配保留 */
        inquota = ar->len;
    } else {
        /* 检查磁盘配额 */
        err = dquot_alloc_block(ar->inode, EXT4_C2B(sbi, ar->len));  /* 分配块配额 */
        if (err < 0) {
            *errp = err;
            goto out;
        }
        inquota = ar->len;
    }
    
    /* 5. 执行分配 */
    err = ext4_mb_regular_allocator(ac);  /* 常规分配器 */
    if (err < 0) {
        *errp = err;
        goto quota_free;
    }
    
    /* 6. 获取分配的块 */
    if (ac->ac_status == AC_STATUS_FOUND) {
        /* 分配成功 */
        block = ext4_grp_offs_to_block(ac->ac_sb, &ac->ac_b_ex);  /* 转换块号 */
        ar->len = ac->ac_b_ex.fe_len;     /* 实际分配长度 */
    }
    
    /* 7. 预分配处理 */
    if (ac->ac_flags & EXT4_MB_HINT_DATA) {
        /* 数据预分配 */
        ext4_mb_new_preallocation(ac);    /* 新预分配 */
    }
    
    /* 8. 更新统计信息 */
    if (block) {
        /* 分配成功 */
        ext4_mb_show_ac(ac);              /* 显示分配信息 */
        *errp = 0;
    } else {
        /* 分配失败 */
        *errp = -ENOSPC;                  /* 空间不足 */
    }
    
quota_free:
    /* 9. 配额清理 */
    if (inquota && ar->len < inquota)
        dquot_free_block(ar->inode, EXT4_C2B(sbi, inquota - ar->len));  /* 释放多余配额 */
    
out:
    /* 10. 释放分配上下文 */
    kmem_cache_free(ext4_ac_cachep, ac);  /* 释放上下文 */
    return block;                         /* 返回分配的块 */
}
​
/* 常规分配器实现 */
static noinline_for_stack int
ext4_mb_regular_allocator(struct ext4_allocation_context *ac)
{
    int order, i;                         /* 阶数,循环变量 */
    int err = 0;                          /* 错误码 */
    int found = 0;                        /* 找到标志 */
    
    /* 1. 从最大阶数开始尝试分配 */
    order = fls(ac->ac_g_ex.fe_len) - 1;  /* 计算阶数 */
    if (order < 0)
        order = 0;
    
    /* 2. 循环尝试不同阶数 */
    for (i = order; i >= 0; i--) {
        ac->ac_flags |= EXT4_MB_HINT_FIRST;  /* 设置首次提示 */
        
        /* 3. 尝试使用预分配 */
        err = ext4_mb_use_preallocated(ac);  /* 使用预分配 */
        if (err < 0)
            break;
        if (err == 1) {
            found = 1;                    /* 使用预分配成功 */
            break;
        }
        
        /* 4. 查找最佳扩展 */
        err = ext4_mb_find_by_goal(ac, i);  /* 按目标查找 */
        if (err < 0)
            break;
        if (err == 1) {
            found = 1;                    /* 找到最佳扩展 */
            break;
        }
        
        /* 5. 在组中查找最佳扩展 */
        err = ext4_mb_find_good_group_avg_frag_lists(ac, i);  /* 查找好组 */
        if (err < 0)
            break;
        if (err == 1) {
            found = 1;                    /* 找到好组 */
            break;
        }
    }
    
    /* 6. 处理分配结果 */
    if (found) {
        /* 分配成功 */
        ac->ac_status = AC_STATUS_FOUND;  /* 设置状态为找到 */
        return 0;                         /* 成功返回 */
    } else {
        /* 分配失败 */
        ac->ac_status = AC_STATUS_FAILED; /* 设置状态为失败 */
        return err;                       /* 返回错误 */
    }
}

第六部分 Ext4 日志

九、Ext4 日志机制与崩溃恢复

9.1 日志系统架构与数据结构

/* fs/jbd2/journal.c */
/*
 * Ext4 日志系统树形架构:
 * 日志 (Journal)
 * ├── 日志超级块
 * ├── 描述符块
 * ├── 提交块
 * └── 数据块
 * 
 * 事务处理流程:
 * 开始事务 → 写入日志 → 提交事务 → 检查点
 */
​
/* 日志超级块结构 */
struct journal_superblock_s {
/* 0x00 */  __be32  s_header;             /* 魔数 */
            __be32  s_blocksize;          /* 块大小 */
            __be32  s_maxlen;             /* 日志最大长度 */
            __be32  s_first;              /* 第一个提交块 */
            
/* 0x10 */  __be32  s_sequence;           /* 序列号 */
            __be32  s_start;              /* 块分配开始位置 */
            __be32  s_errno;              /* 错误号 */
            
/* 0x20 */  __be32  s_feature_compat;     /* 兼容特性 */
            __be32  s_feature_incompat;   /* 不兼容特性 */
            __be32  s_feature_ro_compat;  /* 只读兼容特性 */
            __be32  s_uuid[4];            /* 128位UUID */
            
/* 0x30 */  __be32  s_nr_users;           /* 用户数 */
            __be32  s_dynsuper;           /* 动态超级块 */
            __be32  s_max_transaction;    /* 最大事务 */
            __be32  s_max_trans_data;     /* 最大事务数据 */
            
/* 0x40 */  __be32  s_padding[44];        /* 填充 */
            __be32  s_checksum;           /* 校验和 */
};
​
/* 事务句柄结构 */
struct handle_s {
    transaction_t   *h_transaction;       /* 所属事务 */
    struct journal_s *h_journal;          /* 所属日志 */
    int             h_buffer_credits;     /* 缓冲区信用 */
    int             h_ref;                /* 引用计数 */
    unsigned int    h_sync:1;             /* 同步标志 */
    unsigned int    h_jdata:1;            /* 数据日志标志 */
    unsigned int    h_aborted:1;          /* 中止标志 */
    unsigned int    h_type:8;             /* 句柄类型 */
};
​
/* 事务结构 */
typedef struct transaction_s {
    transaction_t   *t_cpnext, *t_cpprev; /* 检查点链表 */
    unsigned long   t_log_start;          /* 日志开始位置 */
    tid_t           t_tid;                /* 事务ID */
    unsigned long   t_state;              /* 事务状态 */
    struct timespec t_start_time;         /* 开始时间 */
    
    /* 缓冲区管理 */
    struct journal_head *t_buffers;       /* 缓冲区列表 */
    struct journal_head *t_forget;        /* 忘记列表 */
    struct journal_head *t_shadow_list;   /* 影子列表 */
    
    /* 统计信息 */
    int             t_outstanding_credits; /* 未完成信用 */
    int             t_updates;            /* 更新计数 */
    int             t_handle_count;       /* 句柄计数 */
    
    /* 同步控制 */
    wait_queue_head_t t_wait;             /* 等待队列 */
} transaction_t;
​
/* 日志头结构 */
struct journal_head {
    struct buffer_head *b_bh;             /* 关联的缓冲区头 */
    transaction_t   *b_transaction;       /* 所属事务 */
    transaction_t   *b_next_transaction;  /* 下一个事务 */
    struct journal_head *b_tnext, *b_tprev; /* 事务链表 */
    struct journal_head *b_cpnext, *b_cpprev; /* 检查点链表 */
    unsigned long   b_jlist;              /* 日志列表 */
    int             b_jcount;             /* 日志计数 */
    unsigned int    b_modified:1;         /* 修改标志 */
    unsigned int    b_frozen:1;           /* 冻结标志 */
};

9.2 日志事务处理流程

/* fs/jbd2/transaction.c */
/*
 * 事务处理流程树形结构:
 * ext4_journal_start
 * ├── start_this_handle
 * │   ├── new_handle
 * │   ├── add_transaction_credits
 * │   └── wait_for_transaction
 * ├── 文件系统操作
 * └── ext4_journal_stop
 *     ├── journal_stop
 *     └── __journal_force_commit
 */
​
/* 开始日志事务 */
handle_t *ext4_journal_start(struct inode *inode, int nblocks)
{
    struct super_block *sb = inode->i_sb;  /* 超级块 */
    journal_t *journal = EXT4_SB(sb)->s_journal;  /* 日志 */
    handle_t *handle;                     /* 事务句柄 */
    
    /* 1. 检查日志是否可用 */
    if (!journal)                         /* 没有日志 */
        return ext4_get_nojournal();      /* 返回无日志句柄 */
    
    /* 2. 分配事务句柄 */
    handle = jbd2__journal_start(journal, nblocks, GFP_NOFS, 0, 0);  /* 开始事务 */
    if (IS_ERR(handle)) {
        ext4_std_error(sb, PTR_ERR(handle));  /* 标准错误处理 */
        return handle;                    /* 返回错误句柄 */
    }
    
    return handle;                        /* 返回事务句柄 */
}
​
/* 实际的日志开始函数 */
handle_t *jbd2__journal_start(journal_t *journal, int nblocks, gfp_t gfp_mask,
                             int type, int line)
{
    transaction_t *transaction;           /* 事务 */
    handle_t *handle;                     /* 句柄 */
    int needed_blocks = nblocks;          /* 需要的块数 */
    int ret;                              /* 返回值 */
    
    /* 1. 分配句柄结构 */
    handle = kmem_cache_zalloc(handle_cache, GFP_NOFS);  /* 分配句柄 */
    if (!handle)
        return ERR_PTR(-ENOMEM);          /* 内存不足 */
    
    /* 2. 获取当前事务或创建新事务 */
    transaction = journal->j_running_transaction;  /* 获取运行中的事务 */
    if (!transaction) {
        /* 没有运行中的事务,需要创建 */
        transaction = jbd2_get_transaction(journal, needed_blocks);  /* 获取事务 */
        if (IS_ERR(transaction)) {
            ret = PTR_ERR(transaction);
            goto out_free;                /* 获取事务失败 */
        }
    }
    
    /* 3. 初始化句柄 */
    handle->h_transaction = transaction;  /* 设置所属事务 */
    handle->h_journal = journal;          /* 设置所属日志 */
    handle->h_buffer_credits = needed_blocks;  /* 设置缓冲区信用 */
    handle->h_ref = 1;                    /* 设置引用计数 */
    handle->h_sync = 0;                   /* 清除同步标志 */
    handle->h_type = type;                /* 设置句柄类型 */
    handle->h_line_no = line;             /* 设置行号 */
    
    /* 4. 将句柄添加到事务 */
    jbd2_get_transaction_handle(transaction);  /* 增加事务引用 */
    spin_lock(&transaction->t_handle_lock);  /* 锁定句柄列表 */
    list_add(&handle->h_list, &transaction->t_handle_list);  /* 添加到句柄列表 */
    spin_unlock(&transaction->t_handle_lock);  /* 解锁句柄列表 */
    
    /* 5. 更新统计信息 */
    transaction->t_handle_count++;        /* 增加句柄计数 */
    transaction->t_outstanding_credits += needed_blocks;  /* 增加未完成信用 */
    
    return handle;                        /* 返回句柄 */
​
out_free:
    kmem_cache_free(handle_cache, handle);  /* 释放句柄 */
    return ERR_PTR(ret);                  /* 返回错误 */
}
​
/* 停止日志事务 */
int ext4_journal_stop(handle_t *handle)
{
    struct super_block *sb;               /* 超级块 */
    int err = 0;                          /* 错误码 */
    int old_handle_count;                 /* 旧句柄计数 */
    
    /* 1. 参数检查 */
    if (!handle)                          /* 空句柄 */
        return 0;
    
    if (is_handle_aborted(handle))        /* 句柄已中止 */
        err = -EIO;                       /* IO错误 */
    
    sb = handle->h_transaction->t_journal->j_private;  /* 获取超级块 */
    
    /* 2. 调用日志停止函数 */
    if (handle->h_ref > 1) {
        /* 多个引用,减少引用计数 */
        handle->h_ref--;                  /* 减少引用计数 */
        return err;                       /* 返回错误 */
    }
    
    /* 3. 记录停止时间 */
    if (handle->h_start_sec)
        ext4_update_t_trans_time(handle->h_transaction);  /* 更新事务时间 */
    
    /* 4. 实际停止事务 */
    old_handle_count = handle->h_transaction->t_handle_count;  /* 保存旧计数 */
    jbd2_journal_stop(handle);            /* 停止日志 */
    
    /* 5. 如果这是最后一个句柄,可能提交事务 */
    if (old_handle_count == 1) {
        /* 最后一个句柄 */
        err = ext4_journal_check_available_features(sb, handle);  /* 检查可用特性 */
    }
    
    return err;                           /* 返回错误码 */
}
​
/* 实际的日志停止函数 */
int jbd2_journal_stop(handle_t *handle)
{
    transaction_t *transaction = handle->h_transaction;  /* 事务 */
    journal_t *journal = transaction->t_journal;  /* 日志 */
    int err = 0;                          /* 错误码 */
    int wait_for_commit = 0;              /* 等待提交标志 */
    
    /* 1. 检查句柄状态 */
    if (is_handle_aborted(handle))        /* 句柄已中止 */
        err = -EIO;                       /* IO错误 */
    
    /* 2. 如果设置了同步标志,等待提交 */
    if (handle->h_sync) {
        /* 同步句柄 */
        wait_for_commit = 1;              /* 设置等待提交标志 */
    }
    
    /* 3. 从事务中移除句柄 */
    spin_lock(&transaction->t_handle_lock);  /* 锁定句柄列表 */
    list_del_init(&handle->h_list);       /* 从列表中删除 */
    spin_unlock(&transaction->t_handle_lock);  /* 解锁句柄列表 */
    
    /* 4. 更新事务统计 */
    transaction->t_handle_count--;        /* 减少句柄计数 */
    transaction->t_outstanding_credits -= handle->h_buffer_credits;  /* 减少未完成信用 */
    
    /* 5. 如果事务没有更多句柄,可能提交 */
    if (transaction->t_handle_count == 0) {
        /* 没有更多句柄 */
        if (wait_for_commit || transaction->t_state == T_LOCKED) {
            /* 需要等待提交 */
            jbd2_journal_commit_transaction(journal);  /* 提交事务 */
        }
    }
    
    /* 6. 释放句柄 */
    jbd2_free_handle(handle);             /* 释放句柄 */
    
    return err;                           /* 返回错误码 */
}

9.3 日志提交机制

/* fs/jbd2/commit.c */
/*
 * 事务提交流程树形结构:
 * jbd2_journal_commit_transaction
 * ├── 写回元数据
 * ├── 写入日志描述符
 * ├── 写入数据块
 * ├── 写入提交块
 * └── 释放旧事务
 */
​
/* 提交事务核心函数 */
int jbd2_journal_commit_transaction(journal_t *journal)
{
    transaction_t *commit_transaction;    /* 提交事务 */
    struct journal_head *jh, *tmp;        /* 日志头 */
    struct buffer_head *wbuf[32];         /* 写缓冲区 */
    int bufs = 0;                         /* 缓冲区计数 */
    int flags;                            /* 标志 */
    int err = 0;                          /* 错误码 */
    blk_opf_t write_flags = REQ_SYNC;     /* 写标志 */
    
    /* 1. 获取提交事务 */
    commit_transaction = journal->j_committing_transaction;  /* 获取提交事务 */
    if (!commit_transaction) {
        /* 没有提交事务,获取运行中的事务 */
        spin_lock(&journal->j_state_lock);  /* 锁定日志状态 */
        commit_transaction = journal->j_running_transaction;  /* 获取运行中的事务 */
        if (!commit_transaction) {
            spin_unlock(&journal->j_state_lock);  /* 解锁日志状态 */
            return 0;                     /* 没有事务需要提交 */
        }
        journal->j_committing_transaction = commit_transaction;  /* 设置提交事务 */
        journal->j_running_transaction = NULL;  /* 清空运行中的事务 */
        spin_unlock(&journal->j_state_lock);  /* 解锁日志状态 */
    }
    
    /* 2. 记录提交开始时间 */
    commit_transaction->t_log_start = journal->j_head;  /* 设置日志开始位置 */
    
    /* 3. 第一阶段:写回所有元数据缓冲区 */
    spin_lock(&journal->j_list_lock);     /* 锁定日志列表 */
    list_for_each_entry(jh, &commit_transaction->t_buffers, b_tnext) {
        struct buffer_head *bh = jh2bh(jh);  /* 获取缓冲区头 */
        
        if (buffer_dirty(bh)) {
            /* 脏缓冲区,需要写回 */
            get_bh(bh);                   /* 增加缓冲区引用 */
            spin_unlock(&journal->j_list_lock);  /* 解锁日志列表 */
            
            /* 异步写回缓冲区 */
            lock_buffer(bh);              /* 锁定缓冲区 */
            if (test_clear_buffer_dirty(bh)) {
                bh->b_end_io = end_buffer_write_sync;  /* 设置结束IO函数 */
                get_bh(bh);               /* 增加引用 */
                submit_bh(REQ_OP_WRITE | write_flags, bh);  /* 提交缓冲区写 */
            } else {
                unlock_buffer(bh);        /* 解锁缓冲区 */
            }
            put_bh(bh);                   /* 减少缓冲区引用 */
            
            spin_lock(&journal->j_list_lock);  /* 重新锁定日志列表 */
        }
    }
    spin_unlock(&journal->j_list_lock);   /* 解锁日志列表 */
    
    /* 4. 第二阶段:写入日志记录 */
    err = journal_submit_data_buffers(journal, commit_transaction);  /* 提交数据缓冲区 */
    if (err) {
        jbd2_journal_abort(journal, err); /* 中止日志 */
        goto out;                         /* 跳出 */
    }
    
    /* 5. 写入描述符块 */
    err = journal_write_descriptor_buffers(journal, commit_transaction);  /* 写描述符缓冲区 */
    if (err) {
        jbd2_journal_abort(journal, err); /* 中止日志 */
        goto out;                         /* 跳出 */
    }
    
    /* 6. 写入提交块 */
    err = journal_write_commit_record(journal, commit_transaction);  /* 写提交记录 */
    if (err) {
        jbd2_journal_abort(journal, err); /* 中止日志 */
        goto out;                         /* 跳出 */
    }
    
    /* 7. 第三阶段:等待所有IO完成 */
    err = journal_finish_inode_data_buffers(journal, commit_transaction);  /* 完成inode数据缓冲区 */
    if (err) {
        jbd2_journal_abort(journal, err); /* 中止日志 */
        goto out;                         /* 跳出 */
    }
    
    /* 8. 第四阶段:清理事务 */
    spin_lock(&journal->j_state_lock);    /* 锁定日志状态 */
    journal->j_committing_transaction = NULL;  /* 清空提交事务 */
    journal->j_stats.ts_requested++;      /* 增加统计计数 */
    spin_unlock(&journal->j_state_lock);  /* 解锁日志状态 */
    
    /* 9. 唤醒等待事务完成的进程 */
    wake_up(&journal->j_wait_done_commit);  /* 唤醒等待完成 */
    
    /* 10. 释放事务资源 */
    jbd2_journal_free_transaction(commit_transaction);  /* 释放事务 */
    
out:
    return err;                           /* 返回错误码 */
}
​
/* 写描述符缓冲区 */
static int journal_write_descriptor_buffers(journal_t *journal,
                                           transaction_t *commit_transaction)
{
    struct journal_head *jh;              /* 日志头 */
    struct buffer_head *wbuf[32];         /* 写缓冲区 */
    int bufs = 0;                         /* 缓冲区计数 */
    int err = 0;                          /* 错误码 */
    int i;                                /* 循环变量 */
    
    /* 1. 收集所有需要写的缓冲区 */
    spin_lock(&journal->j_list_lock);     /* 锁定日志列表 */
    list_for_each_entry(jh, &commit_transaction->t_buffers, b_tnext) {
        struct buffer_head *bh = jh2bh(jh);  /* 获取缓冲区头 */
        
        if (jh->b_jlist == BJ_Metadata || jh->b_jlist == BJ_Reserved) {
            /* 元数据或保留缓冲区 */
            if (!buffer_dirty(bh)) {
                /* 不是脏缓冲区,跳过 */
                continue;
            }
            
            /* 添加到写缓冲区数组 */
            wbuf[bufs++] = bh;            /* 添加到数组 */
            get_bh(bh);                   /* 增加引用 */
            
            if (bufs == ARRAY_SIZE(wbuf)) {
                /* 缓冲区数组已满,先提交一批 */
                spin_unlock(&journal->j_list_lock);  /* 解锁日志列表 */
                err = journal_do_submit_data(wbuf, bufs);  /* 提交数据 */
                if (err)
                    goto out;             /* 提交失败 */
                bufs = 0;                 /* 重置计数 */
                spin_lock(&journal->j_list_lock);  /* 重新锁定 */
            }
        }
    }
    spin_unlock(&journal->j_list_lock);   /* 解锁日志列表 */
    
    /* 2. 提交剩余的缓冲区 */
    if (bufs > 0) {
        err = journal_do_submit_data(wbuf, bufs);  /* 提交剩余数据 */
        if (err)
            goto out;                     /* 提交失败 */
    }
    
out:
    /* 3. 释放所有缓冲区引用 */
    for (i = 0; i < bufs; i++) {
        if (wbuf[i])
            put_bh(wbuf[i]);              /* 释放缓冲区引用 */
    }
    
    return err;                           /* 返回错误码 */
}

9.4 崩溃恢复机制

/* fs/jbd2/recovery.c */
/*
 * 日志恢复流程树形结构:
 * jbd2_journal_recover
 * ├── 读取日志超级块
 * ├── 扫描日志记录
 * ├── 重放提交的事务
 * └── 丢弃未提交的事务
 */

/* 日志恢复函数 */
int jbd2_journal_recover(journal_t *journal)
{
    int err;                              /* 错误码 */
    journal_superblock_t *sb;             /* 日志超级块 */
    
    /* 1. 读取日志超级块 */
    err = journal_get_superblock(journal);  /* 获取超级块 */
    if (err) {
        printk(KERN_ERR "JBD2: IO error reading journal superblock\n");
        goto out;                         /* 读取失败 */
    }
    
    sb = journal->j_superblock;           /* 获取超级块指针 */
    
    /* 2. 检查日志魔数 */
    if (sb->s_header.h_magic != cpu_to_be32(JBD2_MAGIC_NUMBER) ||
        sb->s_header.h_blocktype != cpu_to_be32(JBD2_SUPERBLOCK_V2)) {
        printk(KERN_ERR "JBD2: no valid journal superblock found\n");
        err = -EINVAL;                    /* 无效魔数 */
        goto out;                         /* 跳出 */
    }
    
    /* 3. 检查日志特性 */
    if (sb->s_feature_incompat & ~cpu_to_be32(JBD2_KNOWN_INCOMPAT_FEATURES)) {
        printk(KERN_ERR "JBD2: unrecognised features on journal\n");
        err = -EINVAL;                    /* 不支持的特性 */
        goto out;                         /* 跳出 */
    }
    
    /* 4. 执行恢复过程 */
    err = do_one_pass(journal, &info, PASS_SCAN);  /* 第一遍:扫描 */
    if (!err)
        err = do_one_pass(journal, &info, PASS_REVOKE);  /* 第二遍:撤销 */
    if (!err)
        err = do_one_pass(journal, &info, PASS_REPLAY);  /* 第三遍:重放 */
    
    /* 5. 清理恢复信息 */
    jbd2_journal_clear_revoke(journal);   /* 清除撤销信息 */
    
out:
    return err;                           /* 返回错误码 */
}

/* 单遍恢复处理 */
static int do_one_pass(journal_t *journal,
                      struct recovery_info *info, enum passtype pass)
{
    unsigned int first_commit_ID;         /* 第一个提交ID */
    unsigned long next_log_block;         /* 下一个日志块 */
    int err;                              /* 错误码 */
    int success;                          /* 成功标志 */
    
    /* 1. 初始化恢复信息 */
    memset(info, 0, sizeof(*info));       /* 清空恢复信息 */
    
    /* 2. 查找日志的结尾 */
    err = jbd2_find_journal_tail(journal, &first_commit_ID, &next_log_block);  /* 查找日志尾 */
    if (err)
        return err;                       /* 查找失败 */
    
    /* 3. 遍历日志块 */
    while (1) {
        int flags;                        /* 标志 */
        char *tagp;                       /* 标签指针 */
        journal_block_tag_t tag;          /* 块标签 */
        struct buffer_head *bh;           /* 缓冲区头 */
        unsigned long io_block;           /* IO块号 */
        
        /* 4. 读取下一个日志块 */
        bh = jbd2_journal_bread(journal, next_log_block);  /* 读取日志块 */
        if (!bh) {
            /* 读取失败 */
            err = -EIO;
            break;
        }
        
        /* 5. 解析块类型 */
        if (!jbd2_descriptor_block_csum_verify(journal, bh->b_data)) {
            /* 校验和验证失败 */
            brelse(bh);                   /* 释放缓冲区 */
            err = -EFSBADCRC;
            break;
        }
        
        /* 6. 根据块类型处理 */
        switch (be32_to_cpu(((__be32 *)bh->b_data)[0])) {
        case JBD2_DESCRIPTOR_BLOCK:
            /* 描述符块 */
            if (pass != PASS_REPLAY) {
                /* 不是重放阶段,跳过 */
                brelse(bh);
                next_log_block++;
                continue;
            }
            
            /* 处理描述符块 */
            err = process_descriptor_buffer(journal, bh, info);  /* 处理描述符缓冲区 */
            brelse(bh);                   /* 释放缓冲区 */
            if (err)
                goto failed;              /* 处理失败 */
            break;
            
        case JBD2_COMMIT_BLOCK:
            /* 提交块 */
            if (pass == PASS_SCAN) {
                /* 扫描阶段,记录提交信息 */
                info->end_transaction = be32_to_cpu(
                    ((__be32 *)bh->b_data)[1]);  /* 结束事务 */
            }
            brelse(bh);                   /* 释放缓冲区 */
            break;
            
        case JBD2_REVOKE_BLOCK:
            /* 撤销块 */
            if (pass == PASS_REVOKE) {
                /* 撤销阶段,处理撤销信息 */
                err = scan_revoke_records(journal, bh, 
                                         info->end_transaction);  /* 扫描撤销记录 */
            }
            brelse(bh);                   /* 释放缓冲区 */
            break;
            
        default:
            /* 数据块或其他块 */
            if (pass == PASS_REPLAY) {
                /* 重放阶段,重放数据块 */
                err = replay_data_block(journal, bh, info);  /* 重放数据块 */
            }
            brelse(bh);                   /* 释放缓冲区 */
            break;
        }
        
        if (err) {
            /* 处理错误 */
            goto failed;                  /* 跳转到失败处理 */
        }
        
        next_log_block++;                 /* 下一个日志块 */
    }
    
failed:
    return err;                           /* 返回错误码 */
}

/* 重放数据块 */
static int replay_data_block(journal_t *journal, struct buffer_head *bh,
                            struct recovery_info *info)
{
    struct buffer_head *obh;              /* 原始缓冲区 */
    struct journal_head *jh;              /* 日志头 */
    unsigned long blocknr;                /* 块号 */
    int err;                              /* 错误码 */
    
    /* 1. 获取块号 */
    blocknr = be32_to_cpu(((__be32 *)bh->b_data)[0]);  /* 从数据中获取块号 */
    
    /* 2. 读取原始数据块 */
    obh = __getblk(journal->j_fs_dev, blocknr, journal->j_blocksize);  /* 获取原始块 */
    if (!obh) {
        printk(KERN_ERR "JBD2: replay failed to get block %lu\n",
               blocknr);
        return -EIO;                      /* 获取块失败 */
    }
    
    /* 3. 锁定原始块 */
    lock_buffer(obh);                     /* 锁定缓冲区 */
    
    /* 4. 拷贝日志数据到原始块 */
    memcpy(obh->b_data, bh->b_data, journal->j_blocksize);  /* 拷贝数据 */
    
    /* 5. 如果支持校验和,验证并设置 */
    if (jbd2_has_feature_csum2(journal) ||
        jbd2_has_feature_csum3(journal)) {
        /* 设置校验和 */
        obh->b_data[obh->b_size - 4] = bh->b_data[bh->b_size - 4];
        obh->b_data[obh->b_size - 3] = bh->b_data[bh->b_size - 3];
        obh->b_data[obh->b_size - 2] = bh->b_data[bh->b_size - 2];
        obh->b_data[obh->b_size - 1] = bh->b_data[bh->b_size - 1];
    }
    
    /* 6. 标记缓冲区脏 */
    set_buffer_uptodate(obh);             /* 设置更新完成 */
    mark_buffer_dirty(obh);               /* 标记缓冲区脏 */
    
    /* 7. 创建日志头 */
    jh = jbd2_journal_add_journal_head(obh);  /* 添加日志头 */
    jbd2_journal_file_buffer(jh, info->transaction, BJ_Metadata);  /* 文件缓冲区 */
    jbd2_journal_put_journal_head(jh);    /* 释放日志头 */
    
    /* 8. 解锁并释放缓冲区 */
    unlock_buffer(obh);                   /* 解锁缓冲区 */
    __brelse(obh);                        /* 释放缓冲区引用 */
    
    return 0;                             /* 成功返回 */
}

9.5 Ext4 特定的日志操作

/* fs/ext4/ext4_jbd2.c */
/*
 * Ext4 特定日志操作
 * 桥接模式:连接Ext4文件系统和JBD2日志系统
 */

/* Ext4 有序模式写操作 */
static int ext4_ordered_write_end(struct file *file,
                                 struct address_space *mapping,
                                 loff_t pos, unsigned len, unsigned copied,
                                 struct page *page, void *fsdata)
{
    handle_t *handle = ext4_journal_current_handle();  /* 当前句柄 */
    struct inode *inode = mapping->host;  /* inode */
    int ret = 0, ret2;                    /* 返回值 */
    
    /* 1. 跟踪数据块 */
    ret = ext4_handle_dirty_metadata(handle, inode, page);  /* 处理脏元数据 */
    if (ret) {
        unlock_page(page);                /* 解锁页面 */
        put_page(page);                   /* 释放页面 */
        goto out;                         /* 跳出 */
    }
    
    /* 2. 调用通用写结束 */
    ret = generic_write_end(file, mapping, pos, len, copied, page, fsdata);  /* 通用写结束 */
    
    /* 3. 如果写入了数据,需要记录数据块 */
    if (pos + ret > inode->i_size) {
        /* 文件大小增加 */
        i_size_write(inode, pos + ret);   /* 更新文件大小 */
        ret2 = ext4_mark_inode_dirty(handle, inode);  /* 标记inode脏 */
        if (unlikely(ret2 && !ret))
            ret = ret2;                   /* 更新错误 */
    }
    
out:
    /* 4. 如果发生错误,中止句柄 */
    if (ret)
        ext4_journal_stop(handle);        /* 停止日志句柄 */
    
    return ret;                           /* 返回结果 */
}

/* 处理脏元数据 */
int ext4_handle_dirty_metadata(handle_t *handle, struct inode *inode,
                              struct buffer_head *bh)
{
    int err = 0;                          /* 错误码 */
    
    /* 1. 标记缓冲区脏 */
    mark_buffer_dirty(bh);                /* 标记缓冲区脏 */
    
    /* 2. 如果是元数据块,需要记录到日志 */
    if (ext4_handle_valid(handle)) {
        /* 有效句柄,记录到日志 */
        err = jbd2_journal_dirty_metadata(handle, bh);  /* 日志脏元数据 */
        
        /* 3. 如果是撤销块,特殊处理 */
        if (is_handle_aborted(handle)) {
            /* 句柄已中止 */
            ext4_std_error(inode->i_sb, err);  /* 标准错误处理 */
        }
    } else {
        /* 无日志模式,直接写回 */
        if (inode && inode_needs_sync(inode)) {
            sync_dirty_buffer(bh);        /* 同步脏缓冲区 */
            if (buffer_req(bh) && !buffer_uptodate(bh)) {
                /* 缓冲区错误 */
                ext4_error_inode_block(inode, bh->b_blocknr, EIO,
                                      "IO error syncing itable block");
                err = -EIO;               /* IO错误 */
            }
        }
    }
    
    return err;                           /* 返回错误码 */
}

/* JBD2 脏元数据记录 */
int jbd2_journal_dirty_metadata(handle_t *handle, struct buffer_head *bh)
{
    transaction_t *transaction = handle->h_transaction;  /* 事务 */
    journal_t *journal = transaction->t_journal;  /* 日志 */
    struct journal_head *jh;              /* 日志头 */
    int ret = 0;                          /* 返回值 */
    
    /* 1. 获取或创建日志头 */
    jh = jbd2_journal_add_journal_head(bh);  /* 添加日志头 */
    J_ASSERT_JH(jh, handle->h_buffer_credits > 0);  /* 断言信用足够 */
    
    /* 2. 检查缓冲区状态 */
    if (jh->b_transaction == transaction && jh->b_jlist == BJ_Metadata) {
        /* 已经在当前事务中 */
        goto out;                         /* 跳出 */
    }
    
    if (jh->b_transaction != transaction) {
        /* 不在当前事务中,需要移动 */
        jbd2_journal_file_buffer(jh, transaction, BJ_Metadata);  /* 文件缓冲区 */
    }
    
    /* 3. 如果缓冲区是新的,需要设置原始数据 */
    if (!jh->b_modified) {
        /* 首次修改 */
        jh->b_modified = 1;               /* 设置修改标志 */
        if (handle->h_sync) {
            /* 同步句柄 */
            jbd2_journal_set_buffer_sync_io(jh);  /* 设置同步IO */
        }
    }
    
out:
    /* 4. 减少句柄信用 */
    handle->h_buffer_credits--;           /* 减少缓冲区信用 */
    
    /* 5. 释放日志头 */
    jbd2_journal_put_journal_head(jh);    /* 释放日志头 */
    
    return ret;                           /* 返回结果 */
}

更多推荐