前一篇介绍了在目录下创建文件,我接下来们继续ext2主IO流程:创建目录。不论是创建文件还是创建目录,都是基于父目录,两者有很多相似之处,都要新建inode和目录项,然后建立inode与dentry的关系。但是创建目录操作,在写入目录项时,要多写两个目录项.(当前目录)和..(上层目录),详细流程如下:
- 新建inode,并标记dirty,待内核同步
- 设置操作表:i_op、i_fop、i_mapping->a_ops
- 新建.和..两个目录项,inode指向新建的inode,并持久化
- 在父目录的页缓存中,寻找一个空间,新建目录的目录项,inode指向新建的inode,并持久化(ext2_add_link)
- 建立缓存dentry与inode关系
// 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一致,详细参考上一篇博文:
二、创建.和..目录项
这两个是特殊目录项,指向第一步中新建的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