EXT4源码分析之“文件创建”原理,详细的介绍文件创建的核心流程,并对EXT4中关于文件创建的关键函数进行了分析。
文章目录
- 0.前言
- 1.创建文件概览
- 1.1关键流程
- 1.2 关键步骤说明
- 2.源码分析
- 2.1 入口函数ext4_create
- 2.2 分配inode关键函数 ext4_new_inode_start_handle
- 2.3 添至加目录关键函数ext4_add_nondir
- 3.总结
- 4.参考
0.前言
分析块分配算法的时候,有不少小伙伴和我讨论了EXT4文件系统的相关细节,经过一些时间的讨论,我发现一个比较重要的问题是:创建文件的时候底层到底发生了什么?
如果不理解这个问题,直接去看一些EXT4源码,可能根本根本无法理解这段代码会在什么时候被调用。
所以我决定专门写一篇文章,以EXT4为例,分析常见文件的相关源码。
在开始本文之前,先讲一个非常容易误解的常识:
创建文件只分配 inode 和目录项,不含实际数据!!!
1.创建文件概览
先从用户的角度出发,大概看看,用户创建了一个文件,底层发生了哪些事情:
1.1关键流程
- 用户态发起:
用户进程通过open("testfile", O_CREAT, 0644)
发起文件创建请求 → 进入内核sys_open()
/sys_openat()
。 - 内核系统调用:
•sys_open()
/sys_openat()
会整理参数并最终调用do_filp_open()
。
•do_filp_open()
核心逻辑包括路径解析、分配 file 结构、选择合适的 VFS 操作等,最后会调用vfs_create()
来尝试在文件系统上创建文件。 - VFS 层
vfs_create()
:
该函数依据父目录的inode->i_op->create
指针去调用具体文件系统的回调函数,ext4 对应ext4_dir_inode_operations.create = ext4_create
。 - EXT4 的
ext4_create()
:
• 调用ext4_new_inode()
分配一个新的 inode 号并初始化 inode(权限、uid/gid、时间戳等),并将这次操作放入 jbd2 (journaling) 事务中,以保证元数据一致性。
• 接着调用ext4_add_nondir()
,在父目录中写入一个新的目录项 (dentry),让这个 inode 与文件名建立关联,并更新父目录的元数据。 - 返回到 VFS:
•ext4_create()
完成后,返回到vfs_create()
。
•vfs_create()
告知do_filp_open()
文件创建已完成,并能生成相应的文件描述符。 - 返回给用户态:
• 系统调用返回到sys_open()
,并将分配好的文件描述符(fd)返回给用户进程。
• 最终open()
函数在用户态得到返回值 fd,即新创建文件的句柄。
1.2 关键步骤说明
- 路径解析与 VFS
系统调用处理时,会先根据传入路径解析目录结构,找到父目录 inode,再调用 vfs_create()。
若路径有符号链接或挂载点,需要额外解析,但在此简化。 - EXT4 inode 分配
ext4_new_inode() 在超级块管理结构中找到空闲 inode 位图位置,分配给新文件,并做必要的初始化(如设置 i_mode、权限、时间戳等)。 - 目录项写入
ext4_add_nondir() 会修改父目录的数据区(或 htree 索引区),写入 { 文件名, 新 inode号 }。父目录的链接数、ctime/mtime 也可能更新。 - 日志保护 (JBD2)
在 EXT4 中,这些元数据修改(inode 位图、目录项、超级块等)往往要写进 EXT4 的日志 (JBD2) 系统,以确保原子性和一致性。 - 完成
返回文件描述符给用户态,创建流程至此结束,空文件(大小=0,尚未写数据块)已存在磁盘元数据中。
2.源码分析
接下来分析下在EXT4中,创建文件关键入口处的核心源码。
2.1 入口函数ext4_create
入口函数是位于fs/ext4/namei.c
的ext4_create
函数:
static int ext4_create(struct user_namespace *mnt_userns, struct inode *dir,
struct dentry *dentry, umode_t mode, bool excl)
{
handle_t *handle;
struct inode *inode;
int err, credits, retries = 0;
// 初始化配额(Quotas),如果失败则返回错误
err = dquot_initialize(dir);
if (err)
return err;
// 计算事务所需的块数(Credits)
credits = (EXT4_DATA_TRANS_BLOCKS(dir->i_sb) +
EXT4_INDEX_EXTRA_TRANS_BLOCKS + 3);
retry:
// 尝试创建新的inode,返回新的inode指针或错误指针
inode = ext4_new_inode_start_handle(mnt_userns, dir, mode, &dentry->d_name,
0, NULL, EXT4_HT_DIR, credits);
handle = ext4_journal_current_handle();
err = PTR_ERR(inode);
if (!IS_ERR(inode)) {
// 设置inode的操作函数和文件操作函数
inode->i_op = &ext4_file_inode_operations;
inode->i_fop = &ext4_file_operations;
ext4_set_aops(inode);
// 将新inode添加到目录中
err = ext4_add_nondir(handle, dentry, &inode);
if (!err)
ext4_fc_track_create(handle, dentry);
}
// 停止当前的journal handle
if (handle)
ext4_journal_stop(handle);
// 如果inode创建成功且无错误,则释放inode引用
if (!IS_ERR_OR_NULL(inode))
iput(inode);
// 如果遇到空间不足错误且重试次数未达上限,则重试
if (err == -ENOSPC && ext4_should_retry_alloc(dir->i_sb, &retries))
goto retry;
return err;
}
创建文件的主要流程
- 初始化配额:
• 调用dquot_initialize
函数初始化目录的配额,如果失败,则返回错误,终止创建过程。 - 计算事务块数:
• 根据当前目录的超级块信息,计算创建文件所需的事务块数(Credits),用于文件系统的事务管理。 - 创建新的inode:
• 调用ext4_new_inode_start_handle
函数尝试分配并初始化一个新的inode。
• 如果分配成功,设置inode的操作函数(i_op)和文件操作函数(i_fop),并调用ext4_add_nondir
将新inode添加到目录中。
• 如果分配失败,处理相应的错误(如重试或返回错误)。 - 将inode添加到目录:
• 在ext4_add_nondir
函数中,通过调用ext4_add_entry
将新inode对应的目录项添加到目录中。
• 如果添加成功,标记inode为脏(需要写回磁盘),并实例化新的dentry(目录项)。 - 错误处理与清理:
• 如果在任何步骤中遇到错误(如配额初始化失败、inode分配失败、目录项添加失败等),相应地释放资源(如释放inode引用、将inode加入孤儿链表等),并返回错误代码。 - 完成创建:
• 如果所有步骤均成功,停止当前的journal handle,并释放inode引用,返回成功。
2.2 分配inode关键函数 ext4_new_inode_start_handle
ext4_new_inode_start_handle
在fs/ext4/ext4.h
中定义:
其关键函数为*__ext4_new_inode
:
struct inode *__ext4_new_inode(struct user_namespace *mnt_userns,
handle_t *handle, struct inode *dir,
umode_t mode, const struct qstr *qstr,
__u32 goal, uid_t *owner, __u32 i_flags,
int handle_type, unsigned int line_no,
int nblocks)
{
struct super_block *sb;
struct buffer_head *inode_bitmap_bh = NULL;
struct buffer_head *group_desc_bh;
ext4_group_t ngroups, group = 0;
unsigned long ino = 0;
struct inode *inode;
struct ext4_group_desc *gdp = NULL;
struct ext4_inode_info *ei;
struct ext4_sb_info *sbi;
int ret2, err;
struct inode *ret;
ext4_group_t i;
ext4_group_t flex_group;
struct ext4_group_info *grp = NULL;
bool encrypt = false;
// 检查目录是否存在且链接数大于0,否则返回权限错误
if (!dir || !dir->i_nlink)
return ERR_PTR(-EPERM);
// 获取超级块和ext4特有的超级块信息
sb = dir->i_sb;
sbi = EXT4_SB(sb);
// 检查文件系统是否被强制关闭,如果是,则返回输入/输出错误
if (unlikely(ext4_forced_shutdown(sbi)))
return ERR_PTR(-EIO);
// 获取文件系统中的组数量
ngroups = ext4_get_groups_count(sb);
trace_ext4_request_inode(dir, mode);
// 分配一个新的inode结构
inode = new_inode(sb);
if (!inode)
return ERR_PTR(-ENOMEM);
ei = EXT4_I(inode);
/*
* 初始化inode的所有者和配额
* 如果提供了owner指针,则使用提供的UID和GID
* 否则,根据挂载选项和目录的GID进行初始化
*/
if (owner) {
inode->i_mode = mode;
i_uid_write(inode, owner[0]);
i_gid_write(inode, owner[1]);
} else if (test_opt(sb, GRPID)) {
inode->i_mode = mode;
inode_fsuid_set(inode, mnt_userns);
inode->i_gid = dir->i_gid;
} else
inode_init_owner(mnt_userns, inode, dir, mode);
// 如果启用了项目ID特性且目录继承项目ID,则设置新inode的项目ID
if (ext4_has_feature_project(sb) &&
ext4_test_inode_flag(dir, EXT4_INODE_PROJINHERIT))
ei->i_projid = EXT4_I(dir)->i_projid;
else
ei->i_projid = make_kprojid(&init_user_ns, EXT4_DEF_PROJID);
// 如果不使用扩展属性inode标志,则准备加密上下文
if (!(i_flags & EXT4_EA_INODE_FL)) {
err = fscrypt_prepare_new_inode(dir, inode, &encrypt);
if (err)
goto out;
}
// 初始化配额,如果失败则跳转到错误处理
err = dquot_initialize(inode);
if (err)
goto out;
/*
* 如果没有提供handle且有日志(journal)且不使用扩展属性inode标志,
* 则计算为新inode所需的xattr信用块数,并将其加入nblocks
*/
if (!handle && sbi->s_journal && !(i_flags & EXT4_EA_INODE_FL)) {
ret2 = ext4_xattr_credits_for_new_inode(dir, mode, encrypt);
if (ret2 < 0) {
err = ret2;
goto out;
}
nblocks += ret2;
}
// 如果goal为0,则使用超级块中的默认inode目标
if (!goal)
goal = sbi->s_inode_goal;
// 如果提供了goal且在有效范围内,则计算所属的组和组内的inode编号
if (goal && goal <= le32_to_cpu(sbi->s_es->s_inodes_count)) {
group = (goal - 1) / EXT4_INODES_PER_GROUP(sb);
ino = (goal - 1) % EXT4_INODES_PER_GROUP(sb);
ret2 = 0;
goto got_group;
}
// 根据是否是目录,选择使用Orlov算法或其他算法查找组
if (S_ISDIR(mode))
ret2 = find_group_orlov(sb, dir, &group, mode, qstr);
else
ret2 = find_group_other(sb, dir, &group, mode);
got_group:
// 记录父目录的最后分配组
EXT4_I(dir)->i_last_alloc_group = group;
err = -ENOSPC;
if (ret2 == -1)
goto out;
/*
* 循环遍历组,尝试找到一个有空闲inode的组
* 通常只会经过一次循环,除非选定的组已被其他进程占用
*/
for (i = 0; i < ngroups; i++, ino = 0) {
err = -EIO;
// 获取组描述符,如果失败则跳转到下一个组
gdp = ext4_get_group_desc(sb, group, &group_desc_bh);
if (!gdp)
goto out;
// 检查组中是否有空闲inode,如果没有则跳转到下一个组
if (ext4_free_inodes_count(sb, gdp) == 0)
goto next_group;
// 如果没有处于回放状态,检查组的inode bitmap是否损坏
if (!(sbi->s_mount_state & EXT4_FC_REPLAY)) {
grp = ext4_get_group_info(sb, group);
// 跳过已知的损坏组
if (EXT4_MB_GRP_IBITMAP_CORRUPT(grp))
goto next_group;
}
// 释放之前的inode bitmap buffer
brelse(inode_bitmap_bh);
// 读取当前组的inode bitmap
inode_bitmap_bh = ext4_read_inode_bitmap(sb, group);
// 如果组的inode bitmap已损坏或读取失败,则跳转到下一个组
if (((!(sbi->s_mount_state & EXT4_FC_REPLAY))
&& EXT4_MB_GRP_IBITMAP_CORRUPT(grp)) ||
IS_ERR(inode_bitmap_bh)) {
inode_bitmap_bh = NULL;
goto next_group;
}
repeat_in_this_group:
// 在当前组中查找一个空闲的inode位
ret2 = find_inode_bit(sb, group, inode_bitmap_bh, &ino);
if (!ret2)
goto next_group;
// 检查组0的保留inode是否被错误地清除
if (group == 0 && (ino + 1) < EXT4_FIRST_INO(sb)) {
ext4_error(sb, "reserved inode found cleared - inode=%lu", ino + 1);
ext4_mark_group_bitmap_corrupted(sb, group, EXT4_GROUP_INFO_IBITMAP_CORRUPT);
goto next_group;
}
// 如果没有handle且不处于回放状态,则启动journal事务
if ((!(sbi->s_mount_state & EXT4_FC_REPLAY)) && !handle) {
BUG_ON(nblocks <= 0);
handle = __ext4_journal_start_sb(sb, line_no,
handle_type, nblocks, 0,
ext4_trans_default_revoke_credits(sb));
if (IS_ERR(handle)) {
err = PTR_ERR(handle);
ext4_std_error(sb, err);
goto out;
}
}
// 获取对inode bitmap的写权限
BUFFER_TRACE(inode_bitmap_bh, "get_write_access");
err = ext4_journal_get_write_access(handle, sb, inode_bitmap_bh,
EXT4_JTR_NONE);
if (err) {
ext4_std_error(sb, err);
goto out;
}
// 锁定当前组
ext4_lock_group(sb, group);
// 测试并设置inode位,如果已经被设置则尝试下一个位
ret2 = ext4_test_and_set_bit(ino, inode_bitmap_bh->b_data);
if (ret2) {
// 如果位已经被设置,则重新查找
ret2 = find_inode_bit(sb, group, inode_bitmap_bh, &ino);
if (ret2) {
ext4_set_bit(ino, inode_bitmap_bh->b_data);
ret2 = 0;
} else {
ret2 = 1; // 没有成功分配inode
}
}
// 解锁组
ext4_unlock_group(sb, group);
ino++; // inode编号是从0开始的
// 如果成功分配inode,则跳转到got
if (!ret2)
goto got;
// 如果组内还有未检查的位,则继续查找
if (ino < EXT4_INODES_PER_GROUP(sb))
goto repeat_in_this_group;
next_group:
// 移动到下一个组,循环往复
if (++group == ngroups)
group = 0;
}
// 如果所有组都没有空闲inode,则返回空间不足错误
err = -ENOSPC;
goto out;
got:
// 标记inode bitmap为脏,需要写回磁盘
BUFFER_TRACE(inode_bitmap_bh, "call ext4_handle_dirty_metadata");
err = ext4_handle_dirty_metadata(handle, NULL, inode_bitmap_bh);
if (err) {
ext4_std_error(sb, err);
goto out;
}
// 获取组描述符的写权限
BUFFER_TRACE(group_desc_bh, "get_write_access");
err = ext4_journal_get_write_access(handle, sb, group_desc_bh,
EXT4_JTR_NONE);
if (err) {
ext4_std_error(sb, err);
goto out;
}
/*
* 如果组描述符标记块bitmap未初始化,则初始化块bitmap
*/
if (ext4_has_group_desc_csum(sb) &&
gdp->bg_flags & cpu_to_le16(EXT4_BG_BLOCK_UNINIT)) {
struct buffer_head *block_bitmap_bh;
// 读取块bitmap
block_bitmap_bh = ext4_read_block_bitmap(sb, group);
if (IS_ERR(block_bitmap_bh)) {
err = PTR_ERR(block_bitmap_bh);
goto out;
}
BUFFER_TRACE(block_bitmap_bh, "get block bitmap access");
// 获取块bitmap的写权限
err = ext4_journal_get_write_access(handle, sb, block_bitmap_bh,
EXT4_JTR_NONE);
if (err) {
brelse(block_bitmap_bh);
ext4_std_error(sb, err);
goto out;
}
BUFFER_TRACE(block_bitmap_bh, "dirty block bitmap");
// 处理脏的块bitmap
err = ext4_handle_dirty_metadata(handle, NULL, block_bitmap_bh);
/*
* 在锁定组的情况下,清除块bitmap未初始化标志
*/
ext4_lock_group(sb, group);
if (ext4_has_group_desc_csum(sb) &&
(gdp->bg_flags & cpu_to_le16(EXT4_BG_BLOCK_UNINIT))) {
gdp->bg_flags &= cpu_to_le16(~EXT4_BG_BLOCK_UNINIT);
ext4_free_group_clusters_set(sb, gdp,
ext4_free_clusters_after_init(sb, group, gdp));
ext4_block_bitmap_csum_set(sb, group, gdp, block_bitmap_bh);
ext4_group_desc_csum_set(sb, group, gdp);
}
// 解锁组
ext4_unlock_group(sb, group);
// 释放块bitmap的buffer
brelse(block_bitmap_bh);
if (err) {
ext4_std_error(sb, err);
goto out;
}
}
/*
* 更新组描述符中的空闲inode和目录计数
*/
if (ext4_has_group_desc_csum(sb)) {
int free;
struct ext4_group_info *grp = NULL;
if (!(sbi->s_mount_state & EXT4_FC_REPLAY)) {
grp = ext4_get_group_info(sb, group);
down_read(&grp->alloc_sem); // 保护与itable lazyinit
}
ext4_lock_group(sb, group); // 锁定组以修改组描述符
free = EXT4_INODES_PER_GROUP(sb) -
ext4_itable_unused_count(sb, gdp);
if (gdp->bg_flags & cpu_to_le16(EXT4_BG_INODE_UNINIT)) {
gdp->bg_flags &= cpu_to_le16(~EXT4_BG_INODE_UNINIT);
free = 0;
}
// 如果当前inode编号大于组内未使用的inode数,则更新组内未使用的inode数
if (ino > free)
ext4_itable_unused_set(sb, gdp,
(EXT4_INODES_PER_GROUP(sb) - ino));
if (!(sbi->s_mount_state & EXT4_FC_REPLAY))
up_read(&grp->alloc_sem);
} else {
ext4_lock_group(sb, group);
}
// 更新组内空闲inode数
ext4_free_inodes_set(sb, gdp, ext4_free_inodes_count(sb, gdp) - 1);
if (S_ISDIR(mode)) {
// 如果是目录,增加组内目录数
ext4_used_dirs_set(sb, gdp, ext4_used_dirs_count(sb, gdp) + 1);
if (sbi->s_log_groups_per_flex) {
ext4_group_t f = ext4_flex_group(sbi, group);
atomic_inc(&sbi_array_rcu_deref(sbi, s_flex_groups,
f)->used_dirs);
}
}
// 设置组描述符的校验和
if (ext4_has_group_desc_csum(sb)) {
ext4_inode_bitmap_csum_set(sb, group, gdp, inode_bitmap_bh,
EXT4_INODES_PER_GROUP(sb) / 8);
ext4_group_desc_csum_set(sb, group, gdp);
}
// 解锁组
ext4_unlock_group(sb, group);
// 处理脏的组描述符
BUFFER_TRACE(group_desc_bh, "call ext4_handle_dirty_metadata");
err = ext4_handle_dirty_metadata(handle, NULL, group_desc_bh);
if (err) {
ext4_std_error(sb, err);
goto out;
}
// 减少全局空闲inode计数器,如果是目录则增加全局目录计数器
percpu_counter_dec(&sbi->s_freeinodes_counter);
if (S_ISDIR(mode))
percpu_counter_inc(&sbi->s_dirs_counter);
if (sbi->s_log_groups_per_flex) {
flex_group = ext4_flex_group(sbi, group);
atomic_dec(&sbi_array_rcu_deref(sbi, s_flex_groups,
flex_group)->free_inodes);
}
// 设置新inode的inode编号
inode->i_ino = ino + group * EXT4_INODES_PER_GROUP(sb);
// 初始化inode的块数和时间戳
inode->i_blocks = 0;
inode->i_mtime = inode->i_atime = inode->i_ctime = current_time(inode);
ei->i_crtime = inode->i_mtime;
// 清空inode的数据块指针
memset(ei->i_data, 0, sizeof(ei->i_data));
ei->i_dir_start_lookup = 0;
ei->i_disksize = 0;
// 设置inode的标志,清除继承的标志,并添加新的标志
ei->i_flags =
ext4_mask_flags(mode, EXT4_I(dir)->i_flags & EXT4_FL_INHERITED);
ei->i_flags |= i_flags;
ei->i_file_acl = 0;
ei->i_dtime = 0;
ei->i_block_group = group;
ei->i_last_alloc_group = ~0;
// 设置inode标志和同步处理
ext4_set_inode_flags(inode, true);
if (IS_DIRSYNC(inode))
ext4_handle_sync(handle);
// 将inode插入到inode缓存中,如果失败则标记组bitmap损坏
if (insert_inode_locked(inode) < 0) {
/*
* 可能是bitmap损坏导致inode被分配两次
*/
err = -EIO;
ext4_error(sb, "failed to insert inode %lu: doubly allocated?",
inode->i_ino);
ext4_mark_group_bitmap_corrupted(sb, group,
EXT4_GROUP_INFO_IBITMAP_CORRUPT);
goto out;
}
// 为inode生成一个随机的生成号
inode->i_generation = prandom_u32();
/*
* 如果启用了元数据校验,则预计算inode元数据的校验和
*/
if (ext4_has_metadata_csum(sb)) {
__u32 csum;
__le32 inum = cpu_to_le32(inode->i_ino);
__le32 gen = cpu_to_le32(inode->i_generation);
csum = ext4_chksum(sbi, sbi->s_csum_seed, (__u8 *)&inum,
sizeof(inum));
ei->i_csum_seed = ext4_chksum(sbi, csum, (__u8 *)&gen,
sizeof(gen));
}
// 清除inode的状态标志
ext4_clear_state_flags(ei); /* Only relevant on 32-bit archs */
// 设置inode的状态为新的
ext4_set_inode_state(inode, EXT4_STATE_NEW);
// 设置inode的额外空间大小和内联偏移
ei->i_extra_isize = sbi->s_want_extra_isize;
ei->i_inline_off = 0;
// 如果支持内联数据并且不是DAX inode或是目录,则设置内联数据状态
if (ext4_has_feature_inline_data(sb) &&
(!(ei->i_flags & EXT4_DAX_FL) || S_ISDIR(mode)))
ext4_set_inode_state(inode, EXT4_STATE_MAY_INLINE_DATA);
ret = inode;
// 分配配额
err = dquot_alloc_inode(inode);
if (err)
goto fail_drop;
/*
* 如果启用了加密,则设置加密上下文
*/
if (encrypt) {
err = fscrypt_set_context(inode, handle);
if (err)
goto fail_free_drop;
}
// 如果不使用扩展属性inode标志,则初始化ACL和安全属性
if (!(ei->i_flags & EXT4_EA_INODE_FL)) {
err = ext4_init_acl(handle, inode, dir);
if (err)
goto fail_free_drop;
err = ext4_init_security(handle, inode, dir, qstr);
if (err)
goto fail_free_drop;
}
/*
* 如果支持扩展,则为目录、常规文件和符号链接设置扩展
*/
if (ext4_has_feature_extents(sb)) {
/* set extent flag only for directory, file and normal symlink*/
if (S_ISDIR(mode) || S_ISREG(mode) || S_ISLNK(mode)) {
ext4_set_inode_flag(inode, EXT4_INODE_EXTENTS);
ext4_ext_tree_init(handle, inode);
}
}
// 如果handle有效,则记录事务ID
if (ext4_handle_valid(handle)) {
ei->i_sync_tid = handle->h_transaction->t_tid;
ei->i_datasync_tid = handle->h_transaction->t_tid;
}
// 标记inode为脏,需要写回磁盘
err = ext4_mark_inode_dirty(handle, inode);
if (err) {
ext4_std_error(sb, err);
goto fail_free_drop;
}
// 调试输出,记录分配的inode编号
ext4_debug("allocating inode %lu\n", inode->i_ino);
trace_ext4_allocate_inode(inode, dir, mode);
// 释放inode bitmap的buffer
brelse(inode_bitmap_bh);
// 返回新分配的inode
return ret;
fail_free_drop:
// 释放配额
dquot_free_inode(inode);
fail_drop:
// 清除inode的链接数并解锁新inode
clear_nlink(inode);
unlock_new_inode(inode);
out:
// 释放配额和inode引用,释放inode bitmap的buffer
dquot_drop(inode);
inode->i_flags |= S_NOQUOTA;
iput(inode);
brelse(inode_bitmap_bh);
// 返回错误指针
return ERR_PTR(err);
}
*__ext4_new_inode
函数的主要流程分析:
- 参数检查与初始化:
• 检查目标目录是否存在且链接数大于0,确保可以在该目录下创建新文件。
• 获取超级块和ext4特有的超级块信息,检查文件系统是否被强制关闭。 - 分配新inode:
• 使用new_inode函数分配一个新的inode结构,并初始化与所有者相关的信息(UID、GID等)。
• 如果启用了项目ID特性且目录继承项目ID,则为新inode设置项目ID。 - 配额与加密准备:
• 初始化配额,确保新inode的磁盘空间使用受到配额限制。
• 如果不使用扩展属性inode标志,则准备加密上下文。 - 确定分配组:
• 如果提供了goal参数且在有效范围内,则直接计算所属的组和组内的inode编号。
• 如果没有提供goal,则根据文件类型(目录或普通文件)使用不同的算法查找合适的组:
• 目录:使用Orlov算法寻找一个具有低目录数量和足够空闲inode的组。
• 普通文件:使用其他算法(如与父目录相同组或其他策略)查找组。 - 分配inode位:
• 在选定的组中查找一个空闲的inode位。
• 如果找到空闲inode,则设置该位并标记inode bitmap为脏,需要写回磁盘。
• 如果未找到空闲inode,则尝试下一个组,直到所有组都检查完毕。 - 初始化inode描述符:
• 设置inode的基本属性,如时间戳、块数、项目ID、标志等。
• 如果支持扩展,则为目录、常规文件和符号链接设置扩展属性。
• 如果启用了日志系统(journal),则记录事务ID。 - 处理错误与资源清理:
• 在过程中如果遇到任何错误,确保释放已分配的资源(如配额、inode引用等),并返回相应的错误码。 - 最终返回:
• 如果所有步骤成功完成,返回新分配的inode结构。
• 否则,返回错误指针,指示创建inode失败。
2.3 添至加目录关键函数ext4_add_nondir
在ext4_add_nondir
函数中,通过调用ext4_add_entry
将新inode对应的目录项添加到目录中。如果添加成功,标记inode为脏(需要写回磁盘),并实例化新的dentry(目录项)。
int ext4_add_nondir(handle_t *handle,
struct dentry *dentry, struct inode **inodep)
{
struct inode *dir = d_inode(dentry->d_parent);
struct inode *inode = *inodep;
// 将inode添加到目录中,添加目录项
int err = ext4_add_entry(handle, dentry, inode);
if (!err) {
// 标记inode为脏,需要写回磁盘
err = ext4_mark_inode_dirty(handle, inode);
// 如果目录是同步目录,则同步处理
if (IS_DIRSYNC(dir))
ext4_handle_sync(handle);
// 实例化新的dentry(目录项)
d_instantiate_new(dentry, inode);
// 清空inode指针,表示所有权已转移
*inodep = NULL;
return err;
}
// 如果添加目录项失败,则减少inode的链接数
drop_nlink(inode);
// 将inode添加到孤儿链表,以便后续清理
ext4_orphan_add(handle, inode);
// 解锁新的inode
unlock_new_inode(inode);
return err;
}
static int ext4_add_entry(handle_t *handle, struct ext4_filename *fname,
struct inode *dir,
struct inode *inode, struct ext4_dir_entry_2 *de,
struct buffer_head *bh)
{
unsigned int blocksize = dir->i_sb->s_blocksize;
int csum_size = 0;
int err, err2;
// 如果目录启用了元数据校验,则需要处理校验尾部
if (ext4_has_metadata_csum(dir->i_sb))
csum_size = sizeof(struct ext4_dir_entry_tail);
// 如果没有提供具体的目录项位置,则查找合适的位置
if (!de) {
err = ext4_find_dest_de(dir, inode, bh, bh->b_data,
blocksize - csum_size, fname, &de);
if (err)
return err;
}
// 获取对目录块的写权限,以便修改目录项
BUFFER_TRACE(bh, "get_write_access");
err = ext4_journal_get_write_access(handle, dir->i_sb, bh,
EXT4_JTR_NONE);
if (err) {
// 处理错误(记录错误信息)
ext4_std_error(dir->i_sb, err);
return err;
}
// 插入新的目录项
ext4_insert_dentry(dir, inode, de, blocksize, fname);
// 更新目录的修改时间
dir->i_mtime = dir->i_ctime = current_time(dir);
// 更新目录的哈希标志(如果启用了哈希索引)
ext4_update_dx_flag(dir);
// 增加目录的版本号,用于一致性检查
inode_inc_iversion(dir);
// 标记目录为脏,需要写回磁盘
err2 = ext4_mark_inode_dirty(handle, dir);
// 处理目录块的脏状态
BUFFER_TRACE(bh, "call ext4_handle_dirty_metadata");
err = ext4_handle_dirty_dirblock(handle, dir, bh);
if (err)
// 处理错误
ext4_std_error(dir->i_sb, err);
// 返回最终的错误状态
return err ? err : err2;
}
3.总结
在ext4文件创建过程中,主要涉及以下几个关键步骤和函数:
- 配额初始化:
• 通过dquot_initialize
函数初始化目录的配额,确保用户和组的磁盘使用情况得到正确管理。 - inode的分配与初始化:
• 使用ext4_new_inode_start_handle
函数分配并初始化新的inode,根据文件类型(如普通文件、目录、符号链接等)设置相应的操作函数和属性。 - 将inode添加到目录中:
• 通过ext4_add_nondir
函数调用ext4_add_entry
函数,将新inode对应的目录项添加到目标目录中。
• 在添加目录项时,如果当前目录块空间不足,通过ext4_append
函数分配新的目录块,确保目录结构的扩展。 - 错误处理与资源管理:
• 在整个创建过程中,ext4通过检查各个函数的返回值,确保在遇到错误时能够正确释放资源,保持文件系统的一致性和稳定性。
• 对于可恢复的错误(如-ENOSPC),通过重试机制尝试解决问题;对于不可恢复的错误,及时返回错误码并进行必要的清理。
__ext4_new_inode
函数是ext4文件系统中用于分配和初始化新inode的核心函数。其主要流程包括:
- 检查与初始化:确保目标目录有效,初始化配额和加密上下文。
- 分配组与inode:根据goal参数或文件类型,选择合适的组并分配一个空闲的inode位。
- 更新文件系统元数据:标记inode bitmap和组描述符为脏,更新空闲inode和目录计数。
- 初始化inode结构:设置inode的各种属性、标志和时间戳,初始化扩展属性。
- 错误处理与资源管理:在过程中遇到错误时,确保资源的正确释放,维护文件系统的一致性。
4.参考
- https://github.com/torvalds/linux/blob/v5.19/fs/ext4/iname.c(内核源代码)
- https://github.com/torvalds/linux/blob/v5.19/fs/ext4/inode.c(内核源代码)
ATFWUS 2024-12-30