【linux内核分析-存储】EXT4源码分析之“创建文件”原理

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关键流程

  1. 用户态发起:
    用户进程通过 open("testfile", O_CREAT, 0644) 发起文件创建请求 → 进入内核 sys_open() / sys_openat()
  2. 内核系统调用:
    sys_open() / sys_openat() 会整理参数并最终调用 do_filp_open()
    do_filp_open() 核心逻辑包括路径解析、分配 file 结构、选择合适的 VFS 操作等,最后会调用 vfs_create() 来尝试在文件系统上创建文件。
  3. VFS 层 vfs_create()
    该函数依据父目录的 inode->i_op->create 指针去调用具体文件系统的回调函数,ext4 对应 ext4_dir_inode_operations.create = ext4_create
  4. EXT4 的 ext4_create()
    • 调用 ext4_new_inode() 分配一个新的 inode 号并初始化 inode(权限、uid/gid、时间戳等),并将这次操作放入 jbd2 (journaling) 事务中,以保证元数据一致性。
    • 接着调用 ext4_add_nondir(),在父目录中写入一个新的目录项 (dentry),让这个 inode 与文件名建立关联,并更新父目录的元数据。
  5. 返回到 VFS:
    ext4_create() 完成后,返回到 vfs_create()
    vfs_create() 告知 do_filp_open() 文件创建已完成,并能生成相应的文件描述符。
  6. 返回给用户态:
    • 系统调用返回到 sys_open(),并将分配好的文件描述符(fd)返回给用户进程。
    • 最终 open() 函数在用户态得到返回值 fd,即新创建文件的句柄。

1.2 关键步骤说明

  1. 路径解析与 VFS
    系统调用处理时,会先根据传入路径解析目录结构,找到父目录 inode,再调用 vfs_create()。
    若路径有符号链接或挂载点,需要额外解析,但在此简化。
  2. EXT4 inode 分配
    ext4_new_inode() 在超级块管理结构中找到空闲 inode 位图位置,分配给新文件,并做必要的初始化(如设置 i_mode、权限、时间戳等)。
  3. 目录项写入
    ext4_add_nondir() 会修改父目录的数据区(或 htree 索引区),写入 { 文件名, 新 inode号 }。父目录的链接数、ctime/mtime 也可能更新。
  4. 日志保护 (JBD2)
    在 EXT4 中,这些元数据修改(inode 位图、目录项、超级块等)往往要写进 EXT4 的日志 (JBD2) 系统,以确保原子性和一致性。
  5. 完成
    返回文件描述符给用户态,创建流程至此结束,空文件(大小=0,尚未写数据块)已存在磁盘元数据中。

2.源码分析

接下来分析下在EXT4中,创建文件关键入口处的核心源码。

2.1 入口函数ext4_create

入口函数是位于fs/ext4/namei.cext4_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;
}

创建文件的主要流程

  1. 初始化配额:
    • 调用dquot_initialize函数初始化目录的配额,如果失败,则返回错误,终止创建过程。
  2. 计算事务块数:
    • 根据当前目录的超级块信息,计算创建文件所需的事务块数(Credits),用于文件系统的事务管理。
  3. 创建新的inode:
    • 调用ext4_new_inode_start_handle函数尝试分配并初始化一个新的inode。
    • 如果分配成功,设置inode的操作函数(i_op)和文件操作函数(i_fop),并调用ext4_add_nondir将新inode添加到目录中。
    • 如果分配失败,处理相应的错误(如重试或返回错误)。
  4. 将inode添加到目录:
    • 在ext4_add_nondir函数中,通过调用ext4_add_entry将新inode对应的目录项添加到目录中。
    • 如果添加成功,标记inode为脏(需要写回磁盘),并实例化新的dentry(目录项)。
  5. 错误处理与清理:
    • 如果在任何步骤中遇到错误(如配额初始化失败、inode分配失败、目录项添加失败等),相应地释放资源(如释放inode引用、将inode加入孤儿链表等),并返回错误代码。
  6. 完成创建:
    • 如果所有步骤均成功,停止当前的journal handle,并释放inode引用,返回成功。

2.2 分配inode关键函数 ext4_new_inode_start_handle

ext4_new_inode_start_handlefs/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函数的主要流程分析:

  1. 参数检查与初始化:
    • 检查目标目录是否存在且链接数大于0,确保可以在该目录下创建新文件。
    • 获取超级块和ext4特有的超级块信息,检查文件系统是否被强制关闭。
  2. 分配新inode:
    • 使用new_inode函数分配一个新的inode结构,并初始化与所有者相关的信息(UID、GID等)。
    • 如果启用了项目ID特性且目录继承项目ID,则为新inode设置项目ID。
  3. 配额与加密准备:
    • 初始化配额,确保新inode的磁盘空间使用受到配额限制。
    • 如果不使用扩展属性inode标志,则准备加密上下文。
  4. 确定分配组:
    • 如果提供了goal参数且在有效范围内,则直接计算所属的组和组内的inode编号。
    • 如果没有提供goal,则根据文件类型(目录或普通文件)使用不同的算法查找合适的组:
    目录:使用Orlov算法寻找一个具有低目录数量和足够空闲inode的组。
    普通文件:使用其他算法(如与父目录相同组或其他策略)查找组。
  5. 分配inode位:
    • 在选定的组中查找一个空闲的inode位。
    • 如果找到空闲inode,则设置该位并标记inode bitmap为脏,需要写回磁盘。
    • 如果未找到空闲inode,则尝试下一个组,直到所有组都检查完毕。
  6. 初始化inode描述符:
    • 设置inode的基本属性,如时间戳、块数、项目ID、标志等。
    • 如果支持扩展,则为目录、常规文件和符号链接设置扩展属性。
    • 如果启用了日志系统(journal),则记录事务ID。
  7. 处理错误与资源清理:
    • 在过程中如果遇到任何错误,确保释放已分配的资源(如配额、inode引用等),并返回相应的错误码。
  8. 最终返回:
    • 如果所有步骤成功完成,返回新分配的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文件创建过程中,主要涉及以下几个关键步骤和函数:

  1. 配额初始化:
    • 通过dquot_initialize函数初始化目录的配额,确保用户和组的磁盘使用情况得到正确管理。
  2. inode的分配与初始化:
    • 使用ext4_new_inode_start_handle函数分配并初始化新的inode,根据文件类型(如普通文件、目录、符号链接等)设置相应的操作函数和属性。
  3. 将inode添加到目录中:
    • 通过ext4_add_nondir函数调用ext4_add_entry函数,将新inode对应的目录项添加到目标目录中。
    • 在添加目录项时,如果当前目录块空间不足,通过ext4_append函数分配新的目录块,确保目录结构的扩展。
  4. 错误处理与资源管理:
    • 在整个创建过程中,ext4通过检查各个函数的返回值,确保在遇到错误时能够正确释放资源,保持文件系统的一致性和稳定性。
    • 对于可恢复的错误(如-ENOSPC),通过重试机制尝试解决问题;对于不可恢复的错误,及时返回错误码并进行必要的清理。

__ext4_new_inode函数是ext4文件系统中用于分配和初始化新inode的核心函数。其主要流程包括:

  1. 检查与初始化:确保目标目录有效,初始化配额和加密上下文。
  2. 分配组与inode:根据goal参数或文件类型,选择合适的组并分配一个空闲的inode位。
  3. 更新文件系统元数据:标记inode bitmap和组描述符为脏,更新空闲inode和目录计数。
  4. 初始化inode结构:设置inode的各种属性、标志和时间戳,初始化扩展属性。
  5. 错误处理与资源管理:在过程中遇到错误时,确保资源的正确释放,维护文件系统的一致性。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/945445.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

自学记录鸿蒙 API 13:骨骼点检测应用Core Vision Skeleton Detection

骨骼点检测技术是一项强大的AI能力&#xff0c;能够从图片中识别出人体的关键骨骼点位置&#xff0c;如头部、肩部、手肘等。这些信息在人体姿态分析、动作捕捉、健身指导等场景中有着广泛应用。我决定深入学习HarmonyOS Next最新版本API 13中的Skeleton Detection API&#xf…

使用ArcGIS Pro自带的Notebook计算多个遥感指数

在之前的分享中&#xff0c;我们介绍了如何使用ArcPy将GEE下载的遥感影像转为单波段文件。基于前面创建的单波段文件&#xff0c;我们可以一次性计算多种遥感指数&#xff0c;例如NDVI、EVI、NDSI等。我这里直接在ArcGIS Pro中自带的Notebook进行的运行。如下图所示&#xff0c…

XGPT用户帮助手册

文章目录 2024 更新日志2024.12.272024.12.29 摘要 本文详细介绍了XGPT软件的功能及发展历程。XGPT是一款融合了当前最先进人工智能技术的多模态智能软件&#xff0c;专为国内用户优化设计。除了强大的智能问答功能外&#xff0c;XGPT还结合日常办公和科学研究的需求&#xff0…

面试提问:Redis为什么快?

Redis为什么快&#xff1f; 引言 Redis是一个高性能的开源内存数据库&#xff0c;以其快速的读写速度和丰富的数据结构支持而闻名。本文将探讨Redis快速处理数据的原因&#xff0c;帮助大家更好地理解Redis的内部机制和性能优化技术。 目录 完全基于内存高效的内存数据结构…

强化学习——贝尔曼公式

文章目录 前言一、Return的重要性二、State Value三、贝尔曼公式总结 前言 一、Return的重要性 在不同策略下&#xff0c;最终得到的return都会有所不同。因此&#xff0c;return可以用来评估策略。 return的计算公式在基础概念中已经给出&#xff0c;通过包含 γ {\gamma} γ与…

使用MFC编写一个paddleclas预测软件

目录 写作目的 环境准备 下载编译环境 解压预编译库 准备训练文件 模型文件 图像文件 路径整理 准备预测代码 创建预测应用 新建mfc应用 拷贝文档 配置环境 界面布局 添加回cpp文件 修改函数 报错1解决 报错2未解决 修改infer代码 修改MFCPaddleClasDlg.cp…

CSS特效032:2025庆新春,孔明灯向上旋转飘移效果

CSS常用示例100专栏目录 本专栏记录的是经常使用的CSS示例与技巧&#xff0c;主要包含CSS布局&#xff0c;CSS特效&#xff0c;CSS花边信息三部分内容。其中CSS布局主要是列出一些常用的CSS布局信息点&#xff0c;CSS特效主要是一些动画示例&#xff0c;CSS花边是描述了一些CSS…

3D云展厅对文物保护有什么意义?

在文化遗产保护领域&#xff0c;3D云展厅技术的应用正成为一股新兴力量&#xff0c;它不仅改变了文物展示的方式&#xff0c;也为文物保护工作带来了深远的影响。 下面&#xff0c;由【圆桌3D云展厅平台】为大家介绍一下3D云展厅对文物保护意义的详细探讨。 1. 减少物理接触&a…

spring入门程序

安装eclipse https://blog.csdn.net/qq_36437991/article/details/131644570 新建maven项目 安装依赖包 pom.xml <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation&quo…

vue 修改vant样式NoticeBar中的图标,不用插槽可以直接用图片

使用文档中是可以直接使用图片链接的 :left-icon"require(../../assets/newImages/noticeImg.png)" <html> .... <NoticeBarmode""color"#C6C6C6"background""v-if"global_info.site_bulletin":left-icon"r…

MySQL数据导出导出的三种办法(1316)

数据导入导出 基本概述 目前常用的有3中数据导入与导出方法&#xff1a; 使用mysqldump工具&#xff1a; 优点&#xff1a; 简单易用&#xff0c;只需一条命令即可完成数据导出。可以导出表结构和数据&#xff0c;方便完整备份。支持过滤条件&#xff0c;可以选择导出部分数据…

【亲测有效】k8s分布式集群安装部署

1.实验环境准备 准备三台centos7虚拟机&#xff0c;用来部署k8s集群&#xff1a; master&#xff08;hadoop1&#xff0c;192.168.229.111&#xff09;配置&#xff1a; 操作系统&#xff1a;centos7.3以及更高版本都可以配置&#xff1a;4核cpu&#xff0c;4G内存&#xff…

点进CSS选择器

CSS 1.直接在标签的style属性进行设置(行内式) //写在数据单元格td标签内的stytle&#xff0c;设置color颜色和font-size字体大小&#xff1b; <td rowspan"3" style"color: red;font-size: 12px;">Web技术与应用</td> 2.写在head标签中的…

【C#特性整理】C#特性及语法基础

1. C#特性 1.1 统一的类型系统 C#中, 所有类型都共享一个公共的基类型. 例如&#xff0c;任何类型的实例都可以通过调用ToString方法将自身转换为一个字符串 1.2 类和接口 接口: 用于将标准与实现隔离, 仅仅定义行为,不做实现. 1.3 属性、方法、事件 属性: 封装了一部分对…

Flutter DragTarget拖拽控件详解

文章目录 1. DragTarget 控件的构造函数主要参数&#xff1a; 2. DragTarget 的工作原理3. 常见用法示例 1&#xff1a;实现一个简单的拖拽目标解释&#xff1a;示例 2&#xff1a;与 Draggable 结合使用解释&#xff1a; 4. DragTarget 的回调详解5. 总结 DragTarget 是 Flutt…

因系统默认 而未注意过的 create UTF-8 files: with no BOM导致的问题

简单记录一次 开发问题 因为一次编码问题&#xff0c;同事帮忙改了 File Encodings的配置。 没有想到 一个随意的改动with no BOM ------ with BOM &#xff08;自言自语 这个选啥&#xff09;&#xff0c;让一个开发 投入了三四个小时 来排查这个问题。尽其所有思路和方法&am…

前端正在被“锈”化

jeff Atwood 在 2007 年说&#xff1a;"any application that can be writen in JavaScript , willeventually be written in JavaScript"&#xff0c;翻译过来就是&#xff1a;“任何可以使用 JavaScript 来编写的应用&#xff0c;并最终也会由 JavaScript 编写”&a…

【Ubuntu】Ubuntu server 18.04 搭建Slurm并行计算环境(包含NFS)

Ubuntu server 18.04 搭建Slurm并行计算环境&#xff08;包含NFS&#xff09; 一、Munge 认证模块 1.1、安装 munge 主节点和子节点都安装munge #安装 sudo apt update && sudo apt install munge libmunge-dev#设置开机启动 sudo systemctl enable munge sudo syste…

SELECT 语句用法大全:数据库查询的核心力量

在数据库的世界中&#xff0c;SELECT 语句犹如一把万能钥匙&#xff0c;开启了数据检索的大门&#xff0c;让我们能够从海量的数据中精准地获取所需的信息。它的用法丰富多样&#xff0c;涵盖了从简单的数据查看&#xff0c;到复杂的数据统计和关联查询等多个方面&#xff0c;为…

小程序配置文件 —— 14 全局配置 - tabbar配置

全局配置 - tabBar配置 tabBar 字段&#xff1a;定义小程序顶部、底部 tab 栏&#xff0c;用以实现页面之间的快速切换&#xff1b;可以通过 tabBar 配置项指定 tab 栏的表现&#xff0c;以及 tab 切换时显示的对应页面&#xff1b; 在上面图中&#xff0c;标注了一些 tabBar …