06 ext2文件系统IO流程:创建目录mkdir

前一篇介绍了在目录下创建文件,我接下来们继续ext2主IO流程:创建目录。不论是创建文件还是创建目录,都是基于父目录,两者有很多相似之处,都要新建inode和目录项,然后建立inode与dentry的关系。但是创建目录操作,在写入目录项时,要多写两个目录项.(当前目录)和..(上层目录),详细流程如下:

  • 新建inode,并标记dirty,待内核同步
  • 设置操作表:i_op、i_fop、i_mapping->a_ops
  • 新建.和..两个目录项,inode指向新建的inode,并持久化
  • 在父目录的页缓存中,寻找一个空间,新建目录的目录项,inode指向新建的inode,并持久化(ext2_add_link)
  • 建立缓存dentry与inode关系
ext2创建目录
ext2创建目录
// namei.c

static int ext2_mkdir(struct user_namespace * mnt_userns,
    struct inode * dir, struct dentry * dentry, umode_t mode)
{
    struct inode * inode;
    int err;
    ......
    inode = ext2_new_inode(dir, S_IFDIR | mode, &dentry->d_name);
    err = PTR_ERR(inode);
    if (IS_ERR(inode))
        goto out_dir;

    inode->i_op = &ext2_dir_inode_operations;
    inode->i_fop = &ext2_dir_operations;
    if (test_opt(inode->i_sb, NOBH))
        inode->i_mapping->a_ops = &ext2_nobh_aops;
    else
        inode->i_mapping->a_ops = &ext2_aops;

    inode_inc_link_count(inode);

    err = ext2_make_empty(inode, dir);
    if (err)
        goto out_fail;

    err = ext2_add_link(dentry, inode);
    if (err)
        goto out_fail;

    d_instantiate_new(dentry, inode);
out:
    return err;
    ......
}

一、新建inode和设置操作表(略)

新建目录的inode,mode参数S_IFDIR,具体的流程与新建文件Inode一致,详细参考上一篇博文:

05 ext2文件系统IO流程:在目录下创建文件

二、创建.和..目录项

这两个是特殊目录项,指向第一步中新建的inode,这两个目录项使用单独的page缓存页,代码中没有跟父目录放到一起,具体步骤如下:

  • ① 根据的inode的地址映射,获取page页缓存空间(grab_cache_page)
  • ② 根据page缓存,分配具体一个块大小空间(ext2_prepare_chunk)
  • ③ 获取页缓存的虚拟地址(kmap_atomic)
  • ④ 填充两个目录项信息
  • ⑤ 提交写入两个目录项(ext2_commit_chunk)
// dir.c

/*
 * Set the first fragment of directory.
 */
int ext2_make_empty(struct inode *inode, struct inode *parent)
{
    struct page *page = grab_cache_page(inode->i_mapping, 0);
    unsigned chunk_size = ext2_chunk_size(inode);
    struct ext2_dir_entry_2 * de;
    int err;
    void *kaddr;

    if (!page)
        return -ENOMEM;

    err = ext2_prepare_chunk(page, 0, chunk_size);
    if (err) {
        unlock_page(page);
        goto fail;
    }
    kaddr = kmap_atomic(page);
    memset(kaddr, 0, chunk_size);
    de = (struct ext2_dir_entry_2 *)kaddr;
    de->name_len = 1;
    de->rec_len = ext2_rec_len_to_disk(EXT2_DIR_REC_LEN(1));
    memcpy (de->name, ".\0\0", 4);
    de->inode = cpu_to_le32(inode->i_ino);
    ext2_set_de_type (de, inode);

    de = (struct ext2_dir_entry_2 *)(kaddr + EXT2_DIR_REC_LEN(1));
    de->name_len = 2;
    de->rec_len = ext2_rec_len_to_disk(chunk_size - EXT2_DIR_REC_LEN(1));
    de->inode = cpu_to_le32(parent->i_ino);
    memcpy (de->name, "..\0", 4);
    ext2_set_de_type (de, inode);
    kunmap_atomic(kaddr);
    err = ext2_commit_chunk(page, 0, chunk_size);
fail:
    put_page(page);
    return err;
}

1.grab_cache_page()

grab_cache_page()这个函数作用是从页缓存中,获取Inode相关的一个页面,如果不存在就创建一个,并插入到页缓存中。函数主要参数就是inode的i_mapping,就是地址映射,其中就包含了inode。(pagemap.h)

2.__block_write_begin()

在ext2_prepare_chunk()中调用一个函数__block_write_begin(),传递了块大小,并且提供一个函数指针ext2_get_block。

__block_write_begin()这个函数是内核用于处理块设备写操作的一个重要函数,函数实现在buffer.c中。该函数主要功能是为写操作准备内存页面,并将其映射到磁盘块,磁盘块的分配方法是调用函数指针获取。

具体来说,它会在内存中为需要写入的数据分配一个页面,并将该页面与对应的磁盘块关联起来。这样,当实际写入操作发生时,数据可以直接写入到这个页面所对应的磁盘块中‌。在使用这个函数之前,一般都要申请一个page页面。

3.kmap_atomic()/kunmap_atomic()

前者主要作用是将一个page映射到内核地址空间,它适用于高端内存映射,通常用于紧急的、短时间的映射需求。kmap_atomic没有使用任何锁,依靠一个公式来确保不会冲突。

后者主要是撤销地址映射,一旦撤销之后,之前获取的映射地址将不能使用。

4.block_write_end()

在ext2_commit_chunk()函数中,调用了block_write_end()函数,作用就是把页缓存回写到磁盘上。

三、创建目录项,建立dentry与inode关系(略)

此部分与创建文件一致,可以参考上一篇博文,不再赘述。

内核版本:5.16.7

参考资料:https://docs.kernel.org/filesystems/index.html

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注