Linux 文件系统(FS)之Ext4 文件系统磁盘布局与数据结构
·
第五部分 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; /* 返回结果 */
}
更多推荐


所有评论(0)