挂载一个新的块设备时,内核会根据文件系统的类型,找到它的file_system_type对象,并调用该文件系统的mount函数,mount函数初始化超级块对象,设置s_op函数指针,这样VFS通过这个指针就知道如何操作这个块设备了。同时mount函数也会设置根目录的inode对象,并设置i_op和i_fop函数指针。
文件系统的挂载
Linux中只有一颗目录树,新的块设备必须mount到现有的目录下,称为安装点,每一个安装点对应一个vfsmount对象:
struct vfsmount {
struct dentry *mnt_root; /* root of the mounted tree */
struct super_block *mnt_sb; /* pointer to superblock */
int mnt_flags;
};
mount里面包含vfsmount对象:
struct mount {
struct hlist_node mnt_hash;
struct mount *mnt_parent;
struct dentry *mnt_mountpoint;
struct vfsmount mnt;
union {
struct rcu_head mnt_rcu;
struct llist_node mnt_llist;
};
#ifdef CONFIG_SMP
struct mnt_pcp __percpu *mnt_pcp;
#else
int mnt_count;
int mnt_writers;
#endif
struct list_head mnt_mounts; /* list of children, anchored here */
struct list_head mnt_child; /* and going through their mnt_child */
struct list_head mnt_instance; /* mount instance on sb->s_mounts */
const char *mnt_devname; /* Name of device e.g. /dev/dsk/hda1 */
struct list_head mnt_list;
struct list_head mnt_expire; /* link in fs-specific expiry list */
struct list_head mnt_share; /* circular list of shared mounts */
struct list_head mnt_slave_list;/* list of slave mounts */
struct list_head mnt_slave; /* slave list entry */
struct mount *mnt_master; /* slave is on master->mnt_slave_list */
struct mnt_namespace *mnt_ns; /* containing namespace */
struct mountpoint *mnt_mp; /* where is it mounted */
struct hlist_node mnt_mp_list; /* list mounts with the same mountpoint */
#ifdef CONFIG_FSNOTIFY
struct hlist_head mnt_fsnotify_marks;
__u32 mnt_fsnotify_mask;
#endif
int mnt_id; /* mount identifier */
int mnt_group_id; /* peer group identifier */
int mnt_expiry_mark; /* true if marked for expiry */
struct hlist_head mnt_pins;
struct fs_pin mnt_umount;
struct dentry *mnt_ex_mountpoint;
};
所有的挂载命令都会指定源和目标目录路径,那么这些路径是怎么来的呢?这就要追溯到内核启动完成后会先挂载一个临时的根目录,这个临时的根目录文件系统类型是rootfs,不是磁盘文件而是基于内存的ramfs文件系统。
下面来探究一下rootfs挂载流程:
rootfs on / type rootfs (rw)
/dev/root on / type jffs2 (rw,relatime)
sysfs on /sys type sysfs (rw,relatime)
ramfs on /tmp type ramfs (rw,nosuid,nodev,relatime,mode=1777)
proc on /proc type proc (rw,relatime)
临时根文件系统rootfs
init/main.c
start_kernel
vfs_caches_init
mnt_init
mnt_init首先对相关缓存和sysfs初始化,然后调用 init_rootfs注册rootfs文件系统:
void __init mnt_init(void)
{
sysfs_init(); 注册sysfs
init_rootfs();
init_mount_tree();
}
init_rootfs只是调用register_filesystem注册rootfs文件系统。而挂载工作是由init_mount_tree来完成。
static struct file_system_type rootfs_fs_type = {
.name = "rootfs",
.mount = rootfs_mount,
.kill_sb = kill_litter_super,
};
int __init init_rootfs(void)
{
int err = register_filesystem(&rootfs_fs_type);
if (err)
return err;
err = init_ramfs_fs(); 注册ramfs
if (err)
unregister_filesystem(&rootfs_fs_type);
return err;
}
挂载rootfs函数:
static void __init init_mount_tree(void)
{
struct vfsmount *mnt;
struct mnt_namespace *ns;
struct path root;
struct file_system_type *type;
type = get_fs_type("rootfs"); 获取指定文件系统对象
if (!type)
panic("Can't find rootfs type");
mnt = vfs_kern_mount(type, 0, "rootfs", NULL); 挂载rootfs
put_filesystem(type);
if (IS_ERR(mnt))
panic("Can't create rootfs");
ns = create_mnt_ns(mnt);
if (IS_ERR(ns))
panic("Can't allocate initial namespace");
init_task.nsproxy->mnt_ns = ns;
get_mnt_ns(ns);
root.mnt = mnt;
root.dentry = mnt->mnt_root;
mnt->mnt_flags |= MNT_LOCKED;
set_fs_pwd(current->fs, &root);
set_fs_root(current->fs, &root);
}
struct vfsmount *
vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void *data)
{
struct mount *mnt;
struct dentry *root;
if (!type)
return ERR_PTR(-ENODEV);
mnt = alloc_vfsmnt(name); 分配vfsmnt对象
if (!mnt)
return ERR_PTR(-ENOMEM);
if (flags & MS_KERNMOUNT)
mnt->mnt.mnt_flags = MNT_INTERNAL;
root = mount_fs(type, flags, name, data);
if (IS_ERR(root)) {
mnt_free_id(mnt);
free_vfsmnt(mnt);
return ERR_CAST(root);
}
mnt->mnt.mnt_root = root;
mnt->mnt.mnt_sb = root->d_sb;
mnt->mnt_mountpoint = mnt->mnt.mnt_root;
mnt->mnt_parent = mnt;
lock_mount_hash();
list_add_tail(&mnt->mnt_instance, &root->d_sb->s_mounts);
unlock_mount_hash();
return &mnt->mnt;
}
EXPORT_SYMBOL_GPL(vfs_kern_mount);
调用mount_fs里面的type->mount,也就是.mount = rootfs_mount函数:
struct dentry *
mount_fs(struct file_system_type *type, int flags, const char *name, void *data)
{
root = type->mount(type, flags, name, data);
rootfs_mount函数分析:
static bool is_tmpfs;
static struct dentry *rootfs_mount(struct file_system_type *fs_type,
int flags, const char *dev_name, void *data)
{
static unsigned long once;
void *fill = ramfs_fill_super;
if (test_and_set_bit(0, &once))
return ERR_PTR(-ENODEV);
if (IS_ENABLED(CONFIG_TMPFS) && is_tmpfs)
fill = shmem_fill_super;
return mount_nodev(fs_type, flags, data, fill);
}
mount_nodev函数为哪些不对应于任何设备文件的文件系统设置超级块,参数中传递了ramfs_fill_super。
struct dentry *mount_nodev(struct file_system_type *fs_type,
int flags, void *data,
int (*fill_super)(struct super_block *, void *, int))
{
int error;
struct super_block *s = sget(fs_type, NULL, set_anon_super, flags, NULL);
if (IS_ERR(s))
return ERR_CAST(s);
error = fill_super(s, data, flags & MS_SILENT ? 1 : 0);
if (error) {
deactivate_locked_super(s);
return ERR_PTR(error);
}
s->s_flags |= MS_ACTIVE;
return dget(s->s_root);
}
首先sget获取一个super_block对象,sget里面会先检查是否存在,不存在就分配一个,这里是不存在的;然后抵用fill_super就是ramfs_fill_super函数:
int ramfs_fill_super(struct super_block *sb, void *data, int silent)
{
struct ramfs_fs_info *fsi;
struct inode *inode;
int err;
save_mount_options(sb, data);
fsi = kzalloc(sizeof(struct ramfs_fs_info), GFP_KERNEL);
sb->s_fs_info = fsi;
if (!fsi)
return -ENOMEM;
err = ramfs_parse_options(data, &fsi->mount_opts);
if (err)
return err;
sb->s_maxbytes = MAX_LFS_FILESIZE;
sb->s_blocksize = PAGE_CACHE_SIZE;
sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
sb->s_magic = RAMFS_MAGIC;
sb->s_op = &ramfs_ops;
sb->s_time_gran = 1;
inode = ramfs_get_inode(sb, NULL, S_IFDIR | fsi->mount_opts.mode, 0);
sb->s_root = d_make_root(inode);
if (!sb->s_root)
return -ENOMEM;
return 0;
}
主要是分配并初始化根目录的inode和dentry结构s_root,并把超级块的s_op设置为ramfs_ops:
static const struct super_operations ramfs_ops = {
.statfs = simple_statfs,
.drop_inode = generic_delete_inode,
.show_options = generic_show_options,
};
继续分析ramfs_fill_super里面的ramfs_get_inode函数:
struct inode *ramfs_get_inode(struct super_block *sb,
const struct inode *dir, umode_t mode, dev_t dev)
{
struct inode * inode = new_inode(sb);
if (inode) {
inode->i_ino = get_next_ino();
inode_init_owner(inode, dir, mode);
inode->i_mapping->a_ops = &ramfs_aops;
mapping_set_gfp_mask(inode->i_mapping, GFP_HIGHUSER);
mapping_set_unevictable(inode->i_mapping);
inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
switch (mode & S_IFMT) {
default:
init_special_inode(inode, mode, dev);
break;
case S_IFREG:
inode->i_op = &ramfs_file_inode_operations;
inode->i_fop = &ramfs_file_operations;
break;
case S_IFDIR:
inode->i_op = &ramfs_dir_inode_operations;
inode->i_fop = &simple_dir_operations;
/* directory inodes start off with i_nlink == 2 (for "." entry) */
inc_nlink(inode);
break;
case S_IFLNK:
inode->i_op = &page_symlink_inode_operations;
break;
}
}
return inode;
}
首先申请一个inode对象,然后根据文件类型S_IFDIR设置inode的i_op和i_fop,将来file对象的f_op也取自i_fop。
dentry由__d_alloc分配:
struct dentry *d_make_root(struct inode *root_inode)
{
struct dentry *res = NULL;
if (root_inode) {
static const struct qstr name = QSTR_INIT("/", 1);
res = __d_alloc(root_inode->i_sb, &name);
if (res)
d_instantiate(res, root_inode);
else
iput(root_inode);
}
return res;
}
到这里,VFS需要的管理结构(super_block,inode,dentry)都建立起来,rootfs是完全的虚拟文件系统,不基于磁盘文件,是一个特殊的文件系统,类似的有sysfs,ramfs,proc,temfs,只需要安装规则建立起inode,dentry结构。
mount
当用户使用mount 命令挂载设备到目录时,会调用mount的系统调用,如下:
fs/namespace.c SYSCALL_DEFINE5(mount,
SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name,
char __user *, type, unsigned long, flags, void __user *, data)
{
int ret;
char *kernel_type;
struct filename *kernel_dir;
char *kernel_dev;
unsigned long data_page;
ret = copy_mount_string(type, &kernel_type); 获取文件系统类型
if (ret < 0)
goto out_type;
kernel_dir = getname(dir_name); 获取目的目录路径
if (IS_ERR(kernel_dir)) {
ret = PTR_ERR(kernel_dir);
goto out_dir;
}
ret = copy_mount_string(dev_name, &kernel_dev); 获取设备路径
if (ret < 0)
goto out_dev;
ret = copy_mount_options(data, &data_page); 获取挂载选项
if (ret < 0)
goto out_data;
ret = do_mount(kernel_dev, kernel_dir->name, kernel_type, flags,
(void *) data_page);
一系列检查和获取数据就调用do_mount开始挂载,do_mount函数中会检查挂载参数,可以进行修改重新挂载参数之类的,这里看do_new_mount。
static int do_new_mount(struct path *path, const char *fstype, int flags,
int mnt_flags, const char *name, void *data)
{
type = get_fs_type(fstype);
mnt = vfs_kern_mount(type, flags, name, data);
err = do_add_mount(real_mount(mnt), path, mnt_flags);
if (err)
mntput(mnt);
return err;
}
可以看到调用了前面分析的vfs_kern_mount函数,这里根据获取文件系统类型调用对应的挂载函数,
fs/ext4/super.c
static struct file_system_type ext4_fs_type = {
.owner = THIS_MODULE,
.name = "ext4",
.mount = ext4_mount,
.kill_sb = kill_block_super,
.fs_flags = FS_REQUIRES_DEV,
};
MODULE_ALIAS_FS("ext4");
最后会调用到ext4_fill_super函数,ext4_fill_super里面会初始化VFS和FS和磁盘一系列的结构体,读取磁盘的超级块来设置super_block信息,详细见EXT4文件系统学习(13)VFS之VFS超级块。