01 ext2注册/解注册文件系统

前面章节介绍了文件系统数据结构和基本流程,接下来以ext2文件系统为例,详细说明如何从头构建一个文件系统,主要包括:文件系统注册/解注册、文件系统挂载、超级块管理、inode管理等等。今天我们先来说一下注册和解注册文件系统。

文件系统属于内核模块,内核在加载这些模块时,会调用初始化init和退出exit函数,一般在这两个函数中完成注册和解注册动作,参考如下示意图。

ext2注册/解注册
ext2注册/解注册

一、注册ext2文件系统

ext2文件系统注册时,要完成以下几个动作:

  • 准备内核模块初始化入口函数
  • 初始化inode缓存
  • 构造struct file_system_type文件系统类型结构体
  • 调用VFS的register_filesystem()函数完成注册
// fs/ext2/super.c

static int __init init_ext2_fs(void)
{
	int err;

	err = init_inodecache();
	if (err)
		return err;
        err = register_filesystem(&ext2_fs_type);
	if (err)
		goto out;
	return 0;
out:
	destroy_inodecache();
	return err;
}

static void __exit exit_ext2_fs(void)
{
	unregister_filesystem(&ext2_fs_type);
	destroy_inodecache();
}

MODULE_AUTHOR("Remy Card and others");
MODULE_DESCRIPTION("Second Extended Filesystem");
MODULE_LICENSE("GPL");
module_init(init_ext2_fs)
module_exit(exit_ext2_fs)

1.准备模块初始化入口

在ext2代码中,初始化入口函数是init_ext2_fs(void),所以只需要在文件底部,使用module_init()指定初始化的函数即可,这样在内核加载模块时,会先调用这个入口函数。

2.初始化inode缓存

在初始化函数中,首先调用了init_inodecache()函数构造了inode缓存,这里的inode就是文件系统自己维护,用户快速查找目的,也就是前面章节介绍的内存态inode,数据结构是struct ext2_inode_info,先来看一下数据结构,然后看处理逻辑。

1)内存态inode数据结构

ext2_inode_info这个结构体里有文件的属性信息:标志位flag、文件地址faddr、分片号frag_no、ACL信息、块组号、XATTR信号量、属性锁、未链接的孤儿inode、磁盘配额i_dquot等。另外,这个结构体里还包装了VFS的索引节点对象vfs_inode,也就是说VFS的inode,是具体文件系统inode的子集,所以在inode初始化时,这些都要处理。

/*
 * second extended file system inode data in memory
 */
struct ext2_inode_info {
	__le32	i_data[15];
	__u32	i_flags;
	__u32	i_faddr;
	__u8	i_frag_no;
	__u8	i_frag_size;
	__u16	i_state;
	__u32	i_file_acl;
	__u32	i_dir_acl;
	__u32	i_dtime;
	__u32	i_block_group;

	/* block reservation info */
	struct ext2_block_alloc_info *i_block_alloc_info;

	__u32	i_dir_start_lookup;
#ifdef CONFIG_EXT2_FS_XATTR
	struct rw_semaphore xattr_sem;
#endif
	rwlock_t i_meta_lock;

	struct mutex truncate_mutex;
	struct inode	vfs_inode;
	struct list_head i_orphan;	/* unlinked but open inodes */
#ifdef CONFIG_QUOTA
	struct dquot *i_dquot[MAXQUOTAS];
#endif
};

2)inode缓存初始化

inode缓存初始化,主要完成三个动作:

  • 调用kmem_cache_create_usercopy函数从内核申请缓存
  • 提供空间对应的构造函数,也就是inode初始化函数
  • 提供VFS的inode的初始化函数
a.申请内核空间

使用Slab缓存分配器的kmem_cache_create_usercopy()函数来分配缓存,这种分配方式,主要用于业务中会存在很多相同对象。调用时指定了名称、结构体大小、对齐方式0、标志位,同时指定了单块空间的构造函数是init_once()。

kmem_cache_create_usercopy这个函数,允许部分内存拷贝到用户空间,也就是ext2_inode_info结构体的i_data域可以拷贝到用户空间。

static int __init init_inodecache(void)
{
	ext2_inode_cachep = kmem_cache_create_usercopy("ext2_inode_cache",
				sizeof(struct ext2_inode_info), 0,
				(SLAB_RECLAIM_ACCOUNT|SLAB_MEM_SPREAD|
					SLAB_ACCOUNT),
				offsetof(struct ext2_inode_info, i_data),
				sizeof_field(struct ext2_inode_info, i_data),
				init_once);
	if (ext2_inode_cachep == NULL)
		return -ENOMEM;
	return 0;
}
b.单块缓存构造函数

在单块缓存构造函数里,做了四个动作:初始化属性锁、初始化XAttr信号量、初始化truncate操作互斥锁、调用vfs索引节点的初始化函数。

static void init_once(void *foo)
{
	struct ext2_inode_info *ei = (struct ext2_inode_info *) foo;

	rwlock_init(&ei->i_meta_lock);
#ifdef CONFIG_EXT2_FS_XATTR
	init_rwsem(&ei->xattr_sem);
#endif
	mutex_init(&ei->truncate_mutex);
	inode_init_once(&ei->vfs_inode);
}
c.初始化VFS的inode

初始化VFS的inode时,主要做了如下动作:

  • 结构体清0
  • 初始化链表节点i_hash(inode哈希表)
  • 初始化表头i_devices
  • 初始化表头i_io_list(正在做IO操作的inode列表)
  • 初始化表头i_wb_list(回写列表)
  • 初始化表头i_lru
  • 初始化地址空间映射i_data
  • 如果是32位处理器,初始化i_size_seqcount
/*
 * These are initializations that only need to be done
 * once, because the fields are idempotent across use
 * of the inode, so let the slab aware of that.
 */
void inode_init_once(struct inode *inode)
{
	memset(inode, 0, sizeof(*inode));
	INIT_HLIST_NODE(&inode->i_hash);
	INIT_LIST_HEAD(&inode->i_devices);
	INIT_LIST_HEAD(&inode->i_io_list);
	INIT_LIST_HEAD(&inode->i_wb_list);
	INIT_LIST_HEAD(&inode->i_lru);
	__address_space_init_once(&inode->i_data);
	i_size_ordered_init(inode);
}

3.构造文件系统类型结构体

在注册文件系统时,需要给VFS传递文件系统类型信息,所以要事先构造好文件系统类型结构体。在这个结构体中,包含了多个信息:

  • owner是当前模块
  • 文件系统名称ext2
  • 挂载函数指针:也就是在挂载文件系统时,调用的函数入口,指定的是VFS自带的mount_bdev()也就是挂载块设备函数
  • 销毁超级块函数指针:去注册时需要调用kill_sb,指定的是VFS自带的kill_block_super()函数
  • 文件系统标志位:FS_REQUIRES_DEV,表示必须有实际的物理设备
static struct file_system_type ext2_fs_type = {
	.owner		= THIS_MODULE,
	.name		= "ext2",
	.mount		= ext2_mount,
	.kill_sb	= kill_block_super,
	.fs_flags	= FS_REQUIRES_DEV,
};

4.文件系统注册

经过起前面的准备,最后在初始化函数里,调用VFS的register_filesystem()函数,完成注册。

static int __init init_ext2_fs(void)
{
	int err;

	err = init_inodecache();
	if (err)
		return err;
        err = register_filesystem(&ext2_fs_type);
	if (err)
		goto out;
	return 0;
out:
	destroy_inodecache();
	return err;
}

二、解注册文件系统

在文件系统模块从内核删除时,会调用模块退出函数,一般情况在模块退出函数中,调用解注册文件系统。

解注册文件系统时,首先要指定模块退出,一般情况完成两个动作,一个是调用VFS解除注册文件系统,第二个是释放注册时申请的资源,详细代码如下:

static void __exit exit_ext2_fs(void)
{
	unregister_filesystem(&ext2_fs_type);
	destroy_inodecache();
}

static void destroy_inodecache(void)
{
	/*
	 * Make sure all delayed rcu free inodes are flushed before we
	 * destroy cache.
	 */
	rcu_barrier();
	kmem_cache_destroy(ext2_inode_cachep);
}

前面文章:虚拟文件系统VFS基本数据结构

《01 ext2注册/解注册文件系统》有一个想法

发表回复

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