接前一篇文章:Linux内核有什么之块设备驱动有什么第六回 —— 邂逅的三个文件系统之二:实际文件系统(3)
本文内容参考:
《Linux设备驱动开发详解 —— 基于最新的Linux4.0内核》 宋宝华,机械工业出版社
34 | 块设备(上):如何建立代理商销售模式?-趣谈Linux操作系统-极客时间
特此致谢!
上回书以F2FS文件系统为例,讲解了块设备邂逅的第二个文件系统 —— 实际文件系统的挂载流程中的mount_bdev函数调用的第1个函数lookup_bdev。再来看一下调用流程:
struct file_system_type f2fs_fs_type --->
f2fs_fs_type->mount --->
f2fs_mount --->
mount_bdev --->
lookup_bdev
为了便于理解和回顾,再次贴出mount_bdev函数的源码,在Linux内核源码根目录/fs/super.c中,代码如下:
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))
{
struct super_block *s;
int error;
dev_t dev;
error = lookup_bdev(dev_name, &dev);
if (error)
return ERR_PTR(error);
flags |= SB_NOSEC;
s = sget(fs_type, test_bdev_super, set_bdev_super, flags, &dev);
if (IS_ERR(s))
return ERR_CAST(s);
if (s->s_root) {
if ((flags ^ s->s_flags) & SB_RDONLY) {
deactivate_locked_super(s);
return ERR_PTR(-EBUSY);
}
} else {
/*
* We drop s_umount here because we need to open the bdev and
* bdev->open_mutex ranks above s_umount (blkdev_put() ->
* bdev_mark_dead()). It is safe because we have active sb
* reference and SB_BORN is not set yet.
*/
super_unlock_excl(s);
error = setup_bdev_super(s, flags, NULL);
__super_lock_excl(s);
if (!error)
error = fill_super(s, data, flags & SB_SILENT ? 1 : 0);
if (error) {
deactivate_locked_super(s);
return ERR_PTR(error);
}
s->s_flags |= SB_ACTIVE;
}
return dget(s->s_root);
}
EXPORT_SYMBOL(mount_bdev);
接下来来看mount_bdev()调用的第2个函数sget。代码片段如下:
flags |= SB_NOSEC;
s = sget(fs_type, test_bdev_super, set_bdev_super, flags, &dev);
if (IS_ERR(s))
return ERR_CAST(s);
这里注意老版本和新版本的区别,老版本这一部分代码如下:
这两者最大的区别就是函数的最后一个参数。老版本中是前一步得到的struct block_device *bdev;而新版本中则是dev_t *bdev(bdev实际上就是一个u32类型的变量,中间类型为__kernel_dev_t)。
sget函数也在Linux内核源码根目录/fs/super.c中,代码如下:
/**
* sget - find or create a superblock
* @type: filesystem type superblock should belong to
* @test: comparison callback
* @set: setup callback
* @flags: mount flags
* @data: argument to each of them
*/
struct super_block *sget(struct file_system_type *type,
int (*test)(struct super_block *,void *),
int (*set)(struct super_block *,void *),
int flags,
void *data)
{
struct user_namespace *user_ns = current_user_ns();
struct super_block *s = NULL;
struct super_block *old;
int err;
/* We don't yet pass the user namespace of the parent
* mount through to here so always use &init_user_ns
* until that changes.
*/
if (flags & SB_SUBMOUNT)
user_ns = &init_user_ns;
retry:
spin_lock(&sb_lock);
if (test) {
hlist_for_each_entry(old, &type->fs_supers, s_instances) {
if (!test(old, data))
continue;
if (user_ns != old->s_user_ns) {
spin_unlock(&sb_lock);
destroy_unused_super(s);
return ERR_PTR(-EBUSY);
}
if (!grab_super_dead(old))
goto retry;
destroy_unused_super(s);
return old;
}
}
if (!s) {
spin_unlock(&sb_lock);
s = alloc_super(type, (flags & ~SB_SUBMOUNT), user_ns);
if (!s)
return ERR_PTR(-ENOMEM);
goto retry;
}
err = set(s, data);
if (err) {
spin_unlock(&sb_lock);
destroy_unused_super(s);
return ERR_PTR(err);
}
s->s_type = type;
strscpy(s->s_id, type->name, sizeof(s->s_id));
list_add_tail(&s->s_list, &super_blocks);
hlist_add_head(&s->s_instances, &type->fs_supers);
spin_unlock(&sb_lock);
get_filesystem(type);
shrinker_register(s->s_shrink);
return s;
}
EXPORT_SYMBOL(sget);
sget这个函数也是新老内核版本不一样了。老版本代码是这样:
static int set_bdev_super(struct super_block *s, void *data)
{
s->s_bdev = data;
s->s_dev = s->s_bdev->bd_dev;
s->s_bdi = bdi_get(s->s_bdev->bd_bdi);
return 0;
}
/**
* sget - find or create a superblock
* @type: filesystem type superblock should belong to
* @test: comparison callback
* @set: setup callback
* @flags: mount flags
* @data: argument to each of them
*/
struct super_block *sget(struct file_system_type *type, int (*test)(struct super_block *,void *), int (*set)(struct super_block *,void *), int flags, void *data)
{
......
return sget_userns(type, test, set, flags, user_ns, data);
}
/**
* sget_userns - find or create a superblock
* @type: filesystem type superblock should belong to
* @test: comparison callback
* @set: setup callback
* @flags: mount flags
* @user_ns: User namespace for the super_block
* @data: argument to each of them
*/
struct super_block *sget_userns(struct file_system_type *type, int (*test)(struct super_block *,void *), int (*set)(struct super_block *,void *), int flags, struct user_namespace *user_ns, void *data)
{
struct super_block *s = NULL;
struct super_block *old;
int err;
......
if (!s)
{
s = alloc_super(type, (flags & ~MS_SUBMOUNT), user_ns);
......
}
err = set(s, data);
......
s->s_type = type;
strlcpy(s->s_id, type->name, sizeof(s->s_id)); list_add_tail(&s->s_list, &super_blocks);
hlist_add_head(&s->s_instances, &type->fs_supers);
spin_unlock(&sb_lock);
get_filesystem(type);
register_shrinker(&s->s_shrink);
return s;
}
通过新老版本内核代码可以看到,sget这个函数确实有比较大的改动了。
老版本中的机制是:将 block_device塞进superblock里面(在set_bdev_super函数中)。而sget要做的,就是分配一个 super_block,然后调用set_bdev_super这个回调函数。
新版本中的机制和老版本差不多,大体也是这个流程:
if (!s)
{
s = alloc_super(type, (flags & ~MS_SUBMOUNT), user_ns);
......
}
err = set(s, data);
只不过新版本直接放在了sget函数中,而不像老版本中还多了一层函数调用(sget_userns)。
这里实际看一下新版本内核代码中的set_bdev_super函数的源码,在同文件(Linux内核源码根目录/fs/super.c)中,如下:
static int set_bdev_super(struct super_block *s, void *data)
{
s->s_dev = *(dev_t *)data;
return 0;
}
对比一下老版本的代码:
static int set_bdev_super(struct super_block *s, void *data)
{
s->s_bdev = data;
s->s_dev = s->s_bdev->bd_dev;
s->s_bdi = bdi_get(s->s_bdev->bd_bdi);
return 0;
}
可见,新版本内核代码在这一块更为精简了。
这里也给出test_bdev_super函数的代码,也在同文件中,如下:
static int test_bdev_super(struct super_block *s, void *data)
{
return !(s->s_iflags & SB_I_RETIRED) && s->s_dev == *(dev_t *)data;
}
到此为止,mount 中一个块设备的过程就结束了。设备打开了,查找得到了dev_t dev,并且塞到了super_block结构(实例)中。
至此,mount_bdev函数就解析完了。欲知后事如何,且看下回分解。