static int simplefs_init(void)
{
int ret;
sfs_inode_cachep = kmem_cache_create("sfs_inode_cache",
sizeof(struct simplefs_inode),
0,
(SLAB_RECLAIM_ACCOUNT| SLAB_MEM_SPREAD),
NULL);
if (!sfs_inode_cachep) {
return -ENOMEM;
}
ret = register_filesystem(&simplefs_fs_type);
if (likely(ret == 0))
printk(KERN_INFO "Sucessfully registered simplefs\n");
else
printk(KERN_ERR "Failed to register simplefs. Error:[%d]", ret);
return ret;
}
上述代码:
第一步建立了用于vfs的inode cache:
关于inode和dentry的cache可以看下面的blob。
inode和dentry
第二步注册当前的文件系统:
struct file_system_type simplefs_fs_type = {
.owner = THIS_MODULE,
.name = "simplefs",
.mount = simplefs_mount,
.kill_sb = simplefs_kill_superblock,
.fs_flags = FS_REQUIRES_DEV,
};
当前文件系统的类型是"simplefs",mount指针指向的simplefs_mount会在文件系统挂载的时候被调用,kill_sb指针则会在umount文件系统的时候被调用。
第三步挂载的时候获取超级块
simplefs_mount->mount_bdev
看看mount_bdev的传入参数:
fs_type:文件系统类型,当前是simplefs
flags:
dev_name:块设备的名称:例如:/dev/sda,当前我们是/dev/loop0
data:传入指针
fill_super:函数指针,当前指向的是simplefs_fill_super,该函数主要完成的是super block的填充。
struct dentry *mount_bdev(struct file_system_type *fs_type,
int flags, const char *dev_name, void *data,
int (*fill_super)(struct super_block *, void *, int))
{
...
// 通过dev_name设备名(如/dev/loop0)得到对应的block_device结构
// 首先是一个路径查找的过程,调用kern_path()得到struct path
// 然后以path.dentry->d_inode为参数调用bd_acquire得到block_device结构
// 对于路径查找和块设备的问题以后再叙述
bdev = blkdev_get_by_path(dev_name, mode, fs_type);
...
// sget现在现存fs_type->fs_supers链表中查找已经存在的对应的超级块实例(因为一个设备可能已经被挂载过了),fs_supers是file_system_type的成员,它指向一个特定文件系统下的所有超级块实例的链表表头。比较的过程就是遍历fs_supers链表,用每一个super_block->s_bdev和sget的bdev参数做比较,比较他们是不是同一个设备,test_bdev_super就是为了比较bdev而传入的函数参数。
// 如果没能找到已经存在的超级块实例,那就只能创建一个新的了。此时set_bdev_super函数就是用来把bdev参数设置到新创建的super_block的s_bdev域中。然后设置一下s_type和s_id(s_id这里先初始化为文件系统名,之后如果发现是磁盘设备再改为磁盘设备名),并把这个新的sb加入到全局super_blocks链表,以及此file_system_type的fs_supers链表中。
// 到此就得到了一个已知的或新的super_block实例,后面的工作都是为了填充这个super_block的内容,并把它加入到各种链表中。
s = sget(fs_type, test_bdev_super, set_bdev_super, flags | MS_NOSEC,bdev);
// 这个if是判断得到的sb是一个已经存在的还是一个新的sb,已经存在的sb的s_root已经被初始化了,新的sb的s_root还是空
if (s->s_root) {
// 处理已经存在的sb
// 判断此次的挂载flag是否和之前的挂载有读/写冲突,如果有冲突则返回错误
if ((flags ^ s->s_flags) & MS_RDONLY) {
deactivate_locked_super(s);
error = -EBUSY;
goto error_bdev;
}
/*
* s_umount nests inside bd_mutex during
* __invalidate_device(). blkdev_put() acquires
* bd_mutex and can't be called under s_umount. Drop
* s_umount temporarily. This is safe as we're
* holding an active reference.
*/
up_write(&s->s_umount);
// 因为已经有了之前存在的sb,也就是block_dev之前也分配过了,所以这个新的bdev就可以释放了。这里对应前面的blkdev_get_by_path
blkdev_put(bdev, mode);
down_write(&s->s_umount);
} else {
// 处理新的sb
char b[BDEVNAME_SIZE];
s->s_mode = mode;
strlcpy(s->s_id, bdevname(bdev, b), sizeof(s->s_id));
sb_set_blocksize(s, block_size(bdev));
// 设置了sb的mode, id, blocksize后,就到了fill_super的时候了。fill_super是一个函数参数,它由具体文件系统自己实现,如xfs就实现了xfs_fs_fill_super。
error = fill_super(s, data, flags & MS_SILENT ? 1 : 0);
if (error) {
deactivate_locked_super(s);
goto error;
}
s->s_flags |= MS_ACTIVE;
bdev->bd_super = s;
}
return dget(s->s_root);
}
mount_bdev函数的主要逻辑就是这样的
blkdev_get_by_path根据设备名得到block_device结构
sget得到已经存在或者新分配的super_block结构
如果是已经存在的sb,就释放第一步得到的bdev结构
如果是新的sb,就调用文件系统个别实现的fill_super函数继续处理新的sb,并创建根inode, dentry
返回得到的s_root
可以看到fill_super函数将完成mount接下来重要的工作,我们来看一下simplefs是如何做fill_super处理的。
第四步填充超级块
int simplefs_fill_super(struct super_block *sb, void *data, int silent)
{
struct inode *root_inode;
struct buffer_head *bh;
struct simplefs_super_block *sb_disk;
int ret = -EPERM;
bh = sb_bread(sb, SIMPLEFS_SUPERBLOCK_BLOCK_NUMBER);
BUG_ON(!bh);
//获取磁盘中存放的super block的真实内容
sb_disk = (struct simplefs_super_block *)bh->b_data;
printk(KERN_INFO "The magic number obtained in disk is: [%llu]\n",
sb_disk->magic);
if (unlikely(sb_disk->magic != SIMPLEFS_MAGIC)) {
printk(KERN_ERR
"The filesystem that you try to mount is not of type simplefs. Magicnumber mismatch.");
goto release;
}
if (unlikely(sb_disk->block_size != SIMPLEFS_DEFAULT_BLOCK_SIZE)) {
printk(KERN_ERR
"simplefs seem to be formatted using a non-standard block size.");
goto release;
}
printk(KERN_INFO
"simplefs filesystem of version [%llu] formatted with a block size of [%llu] detected in the device.\n",
sb_disk->version, sb_disk->block_size);
/* A magic number that uniquely identifies our filesystem type */
//sb中的魔数和磁盘中的一致
sb->s_magic = SIMPLEFS_MAGIC;
/* For all practical purposes, we will be using this s_fs_info as the super block */
//指向文件系统信息的数据结构,
sb->s_fs_info = sb_disk;
//表面当前文件系统最大数据块为4K
sb->s_maxbytes = SIMPLEFS_DEFAULT_BLOCK_SIZE;
//实现超级块的指针,不是必须实现的
sb->s_op = &simplefs_sops;
//为我们的根节点分配一个Inode
root_inode = new_inode(sb);
//设置根节点的Inode编号
root_inode->i_ino = SIMPLEFS_ROOTDIR_INODE_NUMBER;
//声明这个Inode是一个目录,因为是根dentry所以它的所在目录为NULL
inode_init_owner(root_inode, NULL, S_IFDIR);
//指向所在文件系统的超级块
root_inode->i_sb = sb;
//因为需要在根节点下进行操作,因此需要实现节点的操作指针
/*
* 1.创建一个普通文件,调用create指针;
* 2.创建一个普通文件之前,还需要先调用lookup指针;
* 3.创建一个目录,调用mkdir;
*/
root_inode->i_op = &simplefs_inode_ops;
//Provide Io Operation For UserSpace,eg:readdir
//是否需要支持文件的操作,比如读写,mmap等
root_inode->i_fop = &simplefs_dir_operations;
//Inode的创建时间戳
root_inode->i_atime = root_inode->i_mtime = root_inode->i_ctime =
CURRENT_TIME;
//既然是根节点,自然是需要获取根节点的内容,这里的i_private就是指向根节点信息的
/*
信息如下:
1.根节点号
2.根节点中数据内容存放的位置-> DATA_BLOCK_BUMBER
3.根节点下面的子节点个数
*/
root_inode->i_private =
simplefs_get_inode(sb, SIMPLEFS_ROOTDIR_INODE_NUMBER);
/* TODO: move such stuff into separate header. */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 3, 0)
//Super Block需要告知当前文件系统的根dentry,而这个dentry本质上也是来自于一个inode
sb->s_root = d_make_root(root_inode);
#else
sb->s_root = d_alloc_root(root_inode);
if (!sb->s_root)
iput(root_inode);
#endif
if (!sb->s_root) {
ret = -ENOMEM;
goto release;
}
ret = 0;
release:
brelse(bh);
return ret;
}