前面章节介绍了文件系统数据结构和基本流程,接下来以ext2文件系统为例,详细说明如何从头构建一个文件系统,主要包括:文件系统注册/解注册、文件系统挂载、超级块管理、inode管理等等。今天我们先来说一下注册和解注册文件系统。
文件系统属于内核模块,内核在加载这些模块时,会调用初始化init和退出exit函数,一般在这两个函数中完成注册和解注册动作,参考如下示意图。
一、注册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注册/解注册文件系统》有一个想法