Linux Kernel:mount 流程(从 VFS 到实际文件系统 exfat)

1 初始

  • 一个文件系统首先要挂载到系统中才能被用户所访问,第一个操作就是 mount
  • 在用户空间可是由mount函数来挂载文件系统,该函数的函数原型如下
int mount(const char *dev_name, const char *dir_name, const char *fstype, unsigned long flags, const void *data);

/* 调用示例 */
mount("sysfs", "/sys", "sysfs", 0, NULL);
mount("/dev/sda1", "a", "ext4", 0, NULL);
  • mount 是一个系统调用,其入口为 sys_mountsys_mount 调用 ksys_mount 将参数从用户空间复制到内核空间,之后调用 do_mount

2 调用流程

  • 整体调用流程如下
mount()
   ->SYSCALL_DEFINE5
     copy_mount_string
     copy_mount_string
     copy_mount_options
     do_mount
        ->user_path_at
          path_mount
             ->do_new_mount
               get_fs_type
               fs_context_for_mount
                  ->alloc_fs_context
                     ->kzalloc
                       init_fs_context = legacy_init_fs_context	
                       # 指明相关操作方法但是没有调用,文件系统没实现则使用内核提供的提供方法去初始化挂载上下文
                          ->fc->ops = &legacy_fs_context_ops
                            get_tree		= legacy_get_tree
               parse_monolithic_mount_data
               vfs_get_tree		# 此处就具体调用了上面所初始化的挂载上下文的mount方法去读取超级块
                  ->error = fc->ops->get_tree(fc);
                    legacy_get_tree
                       ->fc->fs_type->mount
                          ->f2fs_mount
               do_new_mount_fc

重点数据结构如下:

首先是 path 结构,包含了 vfsmount (关联 dentry 和 superblock,即内存中的文件映射数据和超级块的关联)

struct path {
	struct vfsmount *mnt;
	struct dentry *dentry;
} __randomize_layout;
  • dentry,是一个类
  • 描述系统目录树中的一个节点,用于在内存中描述路径到 inode 的映射
  • 其主要信息有该 dentry 的父子 dentry,文件名,指向的 inode,dentry 方法集合等
  • 磁盘中没有直接关联的数据

dentry

struct dentry {
	struct hlist_bl_node d_hash;	/* lookup hash list */
	struct dentry *d_parent;	/* parent directory */
	struct qstr d_name;
	struct inode *d_inode;		/* Where the name belongs to - NULL is
					 * negative */
	unsigned char d_iname[DNAME_INLINE_LEN];	/* small names */

	const struct dentry_operations *d_op;
	struct super_block *d_sb;	/* The root of the dentry tree */
	unsigned long d_time;		/* used by d_revalidate */
	void *d_fsdata;			/* fs-specific data */
} __randomize_layout;
  • 有指向父目录的 d_parent

  • 有 qstr。其结构如下:

    可以看出是记录了路径名,和字符串的 hash 值。

    struct qstr {
    	union {
    		struct {
    			HASH_LEN_DECLARE;
    		};
    		u64 hash_len;
    	};
    	const unsigned char *name;
    };
    
  • inodesuper_block 的指针

vfsmount

struct vfsmount {
	struct dentry *mnt_root;	/* root of the mounted tree */
	struct super_block *mnt_sb;	/* pointer to superblock */
	int mnt_flags;
	struct mnt_idmap *mnt_idmap;
} __randomize_layout;
  • dentrysuper_block 。但 dentry 结构中也有对 super_block 结构的指针。意义何在?
  • mnt_idmap 是指向 user_namespace 的指针,还有一个 refcount。应该不用管

3 VFS 挂载

  • 在使用 mount 挂载文件系统的时候会发起一个 mount 系统调用,其对应函数如下
/* in fs/namespace.c */
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;
	char *kernel_dev;
	void *options;
	
    /* 拷贝用户传入的文件系统类型字符串 */
	kernel_type = copy_mount_string(type);
	/* 拷贝用户传入的块设备文件名 */
	kernel_dev = copy_mount_string(dev_name);
	/* 拷贝挂载选项到内核空间 */
	options = copy_mount_options(data);
	
    /* 调用do_mount函数继续下面的操作 */
	ret = do_mount(kernel_dev, dir_name, kernel_type, flags, options);
}
  • 在系统调用过程最后会调用 do_mount 继续执行相关操作,其先根据 const char* dir_name 找到用户想要挂载的目录,该目录以 path 对象返回,其包括目标目录对应的 dentry 与它的 mount 信息
  • 此处重点关注 mount 逻辑,至于如何查找后续会详细说明
/* in fs/namespace.c */
long do_mount(const char *dev_name, const char __user *dir_name,
		const char *type_page, unsigned long flags, void *data_page)
{
	struct path path;
	int ret;
	/* 寻找用户传入的挂载点的挂载描述符和dentry */
	ret = user_path_at(AT_FDCWD, dir_name, LOOKUP_FOLLOW, &path);
	if (ret)
        /* 找不到就退出 */
		return ret;
    /* 调用path_mount函数继续下面的操作 */
	ret = path_mount(dev_name, &path, type_page, flags, data_page);
	path_put(&path);
	return ret;
}
  • path_mount,根据挂载标志
/* in fs/namespace.c */
/* 
	Flags是一个32位的值,允许最多31个非fs相关的标志。(即:只读、无设备、无权限等)
	
	data是一个(void *),可以指向PAGE_SIZE-1字节以内的任何结构,其中可以包含任意的与fs相关的 信息(或为NULL)
	
	0.97以前的mount()版本没有flags标志字,当标志字被引入时,它的上半部分被要求具有魔数0xC0ED,直到2.4.0-test9都是如此。因此,如果这个魔数出现了,它就不带有任何信息且必须被丢弃。
*/
int path_mount(const char *dev_name, struct path *path,
		const char *type_page, unsigned long flags, void *data_page)
{
	unsigned int mnt_flags = 0, sb_flags;
	int ret;
	/* 处理挂载标志,此处省略 */
    ...;
    /* 根据挂载标志调用不同处理函数 */
	if ((flags & (MS_REMOUNT | MS_BIND)) == (MS_REMOUNT | MS_BIND))
		return do_reconfigure_mnt(path, mnt_flags);
	if (flags & MS_REMOUNT)
		return do_remount(path, flags, sb_flags, mnt_flags, data_page);
	if (flags & MS_BIND)
		return do_loopback(path, dev_name, flags & MS_REC);
	if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE))
		return do_change_type(path, flags);
	if (flags & MS_MOVE)
		return do_move_mount_old(path, dev_name);
	/* 默认调用do_new_mount */
	return do_new_mount(path, type_page, sb_flags, mnt_flags, dev_name,
			    data_page);
}
  • 找到挂载点的目录后就执行do_new_mount,其首先根据 fstype 参数找到对应的 file_system_type 对象,之后就是创建文件系统上下文
static int do_new_mount(struct path *path, const char *fstype, int sb_flags,
			int mnt_flags, const char *name, void *data)
{
	struct file_system_type *type;
	struct fs_context *fc;
	const char *subtype = NULL;
	int err = 0;

	/* 根据用户传入的文件系统名称去查找内核中已经注册的文件系统类型(此处就包含了由具体文件系统所实现的mount实现方法) */
	type = get_fs_type(fstype);
	/* ----------------------------------step1:根据用户传入的参数去构造并填充fc结构体---------------------------------- */
    /* 分配并初始化struct fs_context对象 */
	fc = fs_context_for_mount(type, sb_flags);
	put_filesystem(type);
	if (IS_ERR(fc))
		return PTR_ERR(fc);

	if (subtype)
		err = vfs_parse_fs_string(fc, "subtype",
					  subtype, strlen(subtype));
	if (!err && name)
		err = vfs_parse_fs_string(fc, "source", name, strlen(name));
	if (!err)
        /* 调用fc->ops->parse_monolithic来解析用户传入的挂载选项 */
		err = parse_monolithic_mount_data(fc, data);
    
    /* 检查是否有挂载权限 */
	if (!err && !mount_capable(fc))
		err = -EPERM;
	if (!err)
        /* 挂载重点调用fc->ops->get_tree(fc) 读取创建超级块实例,在内存中创建与磁盘对应的超级块对象与s_root */
		err = vfs_get_tree(fc);
    
    /* ----------------------------------step2:真正执行挂载操作---------------------------------- */
	if (!err)
        /* 创建mount实例、关联挂载点和超级块、添加到命名空间的挂载树中 */
		err = do_new_mount_fc(fc, path, mnt_flags);

	put_fs_context(fc);
	return err;
}

file_system_type 结构如下:

struct file_system_type {
	const char *name;
	int fs_flags;
	int (*init_fs_context)(struct fs_context *);
	const struct fs_parameter_spec *parameters;
	struct dentry *(*mount) (struct file_system_type *, int,
		       const char *, void *);
	void (*kill_sb) (struct super_block *);
	struct module *owner;
	struct file_system_type * next;
	struct hlist_head fs_supers;
};
  • 内核中维护了一个 file_system_type 类型的链表

  • 通过 get_fs_type() 从链表中找到这个文件系统类 (file_system_type)。具体来说,执行流程如下:

    // fs/namespace.c/do_new_mount.c
    static int do_new_mount(struct path *path, const char *fstype, int sb_flags, int mnt_flags, const char *name, void *data)
    {
    	struct file_system_type *type;
    	struct fs_context *fc;
    	const char *subtype = NULL;
    	int err = 0;
    
    	if (!fstype)
    		return -EINVAL;
        
    	/* 从链表中获取文件系统类 */
    	type = get_fs_type(fstype);
    
    // fs/filesystem.c
    struct file_system_type *get_fs_type(const char *name)
    {
    	struct file_system_type *fs;
    	const char *dot = strchr(name, '.');
    	int len = dot ? dot - name : strlen(name);
    	/* 进一步解析 */ 
    	fs = __get_fs_type(name, len);
        
    	...
            
    	if (dot && fs && !(fs->fs_flags & FS_HAS_SUBTYPE)) {
    		put_filesystem(fs);
    		fs = NULL;
    	}
    	return fs;
    }
    
    // fs/filesystem.c
    static struct file_system_type *__get_fs_type(const char *name, int len)
    {
    	struct file_system_type *fs;
    
    	read_lock(&file_systems_lock);
    	fs = *(find_filesystem(name, len));
    	if (fs && !try_module_get(fs->owner))
    		fs = NULL;
    	read_unlock(&file_systems_lock);
    	return fs;
    }
    
    // fs/filesystem.c
    static struct file_system_type **find_filesystem(const char *name, unsigned len)
    {
        // 从内核链表里找到相应的文件系统,返回
    	struct file_system_type **p;
    	for (p = &file_systems; *p; p = &(*p)->next)
    		if (strncmp((*p)->name, name, len) == 0 &&
    		    !(*p)->name[len])
    			break;
    	return p;
    }
    

再看 static int do_new_mount(),继续往下,有:

// fs/namespace.c/do_new_mount.c
static int do_new_mount(struct path *path, const char *fstype, int sb_flags, int mnt_flags, const char *name, void *data)
{
	struct file_system_type *type;
	struct fs_context *fc;
	const char *subtype = NULL;
	int err = 0;

	if (!fstype)
		return -EINVAL;
	/* 从链表中获取文件系统类 */
	type = get_fs_type(fstype);

	...

    /* 获取 fs_context */
	fc = fs_context_for_mount(type, sb_flags);
  • fs_context_for_mount 函数用于创建文件系统上下文,其具体就是为 fs_context 对象分配空间并初始化其中的参数
struct fs_context *fs_context_for_mount(struct file_system_type *fs_type,
					unsigned int sb_flags)
{
	return alloc_fs_context(fs_type, NULL, sb_flags, 0,
					FS_CONTEXT_FOR_MOUNT);
}
/**
 * alloc_fs_context - 创建文件系统挂载上下文.
 * @fs_type: 文件系统类型.
 * @reference: The dentry from which this one derives (or NULL)
 * @sb_flags: Filesystem/superblock flags (SB_*)
 * @sb_flags_mask: Applicable members of @sb_flags
 * @purpose: The purpose that this configuration shall be used for.
 */
static struct fs_context *alloc_fs_context(struct file_system_type *fs_type,
				      struct dentry *reference,
				      unsigned int sb_flags,
				      unsigned int sb_flags_mask,
				      enum fs_context_purpose purpose)
{
	int (*init_fs_context)(struct fs_context *);
	struct fs_context *fc;
	int ret = -ENOMEM;

    /* 为fs_context分配空间 */
	fc = kzalloc(sizeof(struct fs_context), GFP_KERNEL);
	if (!fc)
		return ERR_PTR(-ENOMEM);

    /* 初始化上下文部分信息 */
	fc->purpose	= purpose;
	fc->sb_flags	= sb_flags;
	fc->sb_flags_mask = sb_flags_mask;
	fc->fs_type	= get_filesystem(fs_type);
	fc->cred	= get_current_cred();
	fc->net_ns	= get_net(current->nsproxy->net_ns);
	fc->log.prefix	= fs_type->name;

	mutex_init(&fc->uapi_mutex);

	switch (purpose) {
	case FS_CONTEXT_FOR_MOUNT:
		fc->user_ns = get_user_ns(fc->cred->user_ns);
		break;
	case FS_CONTEXT_FOR_SUBMOUNT:
		fc->user_ns = get_user_ns(reference->d_sb->s_user_ns);
		break;
	case FS_CONTEXT_FOR_RECONFIGURE:
		atomic_inc(&reference->d_sb->s_active);
		fc->user_ns = get_user_ns(reference->d_sb->s_user_ns);
		fc->root = dget(reference);
		break;
	}

    /* 实现初始化文件系统上下文的抽象方法 */
    // 这里会调用 exfat_init_fs_context 的 exfat 函数
	init_fs_context = fc->fs_type->init_fs_context;
    /* 如果某文件系统没有实现该初始化方法就给其指定一个默认的初始化方法,即legacy_init_fs_context */
	if (!init_fs_context)
		init_fs_context = legacy_init_fs_context;

    /* 初始化文件系统挂载上下文,其由具体文件系统实现,没实现就使用默认方法 */
    /* 注意:此处仅仅是指明方法并没有调用这些方法,例如指明了读取超级块内容的方法但是并没有调用这些方法去读取超级块内容 */
	ret = init_fs_context(fc);
	if (ret < 0)
		goto err_fc;
	fc->need_free = true;
	return fc;

err_fc:
	put_fs_context(fc);
	return ERR_PTR(ret);
}
  • fs/exfat/super.c 中,有如下内容:
static struct file_system_type exfat_fs_type = {
	.owner			= THIS_MODULE,
	.name			= "exfat",
	.init_fs_context	= exfat_init_fs_context,
	.parameters		= exfat_parameters,
	.kill_sb		= kill_block_super,
	.fs_flags		= FS_REQUIRES_DEV,
};

可以看到这里读取了 exfat_init_fs_context 函数。

static int exfat_init_fs_context(struct fs_context *fc)
{
	struct exfat_sb_info *sbi;
	
    // 申请一块物理内存,存放 exfat_sb_info. 这个是 superblock 在内存中的数据
	sbi = kzalloc(sizeof(struct exfat_sb_info), GFP_KERNEL);
	if (!sbi)
		return -ENOMEM;

	mutex_init(&sbi->s_lock);
	mutex_init(&sbi->bitmap_lock);
	ratelimit_state_init(&sbi->ratelimit, DEFAULT_RATELIMIT_INTERVAL,
			DEFAULT_RATELIMIT_BURST);

	sbi->options.fs_uid = current_uid();
	sbi->options.fs_gid = current_gid();
	sbi->options.fs_fmask = current->fs->umask;
	sbi->options.fs_dmask = current->fs->umask;
	sbi->options.allow_utime = -1;
	sbi->options.iocharset = exfat_default_iocharset;
	sbi->options.errors = EXFAT_ERRORS_RO;

	fc->s_fs_info = sbi;
	fc->ops = &exfat_context_ops;
	return 0;
}
  • 然后再回过头看 static int do_new_mount(),最后一步是 vfs_get_tree()
static int do_new_mount(struct path *path, const char *fstype, int sb_flags,
			int mnt_flags, const char *name, void *data)
{
	struct file_system_type *type;
	struct fs_context *fc;
	const char *subtype = NULL;
	int err = 0;

	if (!fstype)
		return -EINVAL;

	/* 从链表中获取文件系统类 */
	type = get_fs_type(fstype);

	...

    /* 获取 fs_context */
	fc = fs_context_for_mount(type, sb_flags);


	...
    
	if (!err)
		err = vfs_get_tree(fc);
	if (!err)
		err = do_new_mount_fc(fc, path, mnt_flags);

	put_fs_context(fc);
	return err;
}
  • vfs_get_tree(),该函数读取文件系统的 superblock 信息,并创建一个超级块对象,然后执行具体的挂载方法。
/**
 * vfs_get_tree - 获取可挂载的根目录
 * @fc: The superblock configuration context.
 *
 * The filesystem is invoked to get or create a superblock which can then later
 * be used for mounting.  The filesystem places a pointer to the root to be
 * used for mounting in @fc->root.
 */
int vfs_get_tree(struct fs_context *fc)
{
	struct super_block *sb;
    
	int error;
	/* 调用文件系统类型的mount方法来读取并创建超级块 */
	error = fc->ops->get_tree(fc);

	sb = fc->root->d_sb;

	return 0;
}
  • exfat 中,vfs_get_tree() 调用的是 exfat_get_tree(struct fs_context* fc)
// fs/exfat/super.c
static int exfat_get_tree(struct fs_context *fc)
{
	return get_tree_bdev(fc, exfat_fill_super);
}
// fs/super.c
/**
 * get_tree_bdev - Get a superblock based on a single block device
 * @fc: The filesystem context holding the parameters
 * @fill_super: Helper to initialise a new superblock
 */
int get_tree_bdev(struct fs_context *fc,
		int (*fill_super)(struct super_block *,
				  struct fs_context *))
{
	struct block_device *bdev;
	struct super_block *s;
	int error = 0;

	/* 获取 superblock 磁盘块 */
	bdev = blkdev_get_by_path(fc->source, sb_open_mode(fc->sb_flags),
				  fc->fs_type, &fs_holder_ops);


	/* 从 fs_context 中拿到 superblock */
	s = sget_fc(fc, test_bdev_super_fc, set_bdev_super_fc);

    /* 这个 if 没看懂。这是表达已经有挂载了,所以不处理的意思吗? */
	if (s->s_root) {
		/* Don't summarily change the RO/RW state. */
		if ((fc->sb_flags ^ s->s_flags) & SB_RDONLY) {
			warnf(fc, "%pg: Can't mount, would change RO state", bdev);
			deactivate_locked_super(s);
			blkdev_put(bdev, fc->fs_type);
			return -EBUSY;
		}

		/*
		 * s_umount nests inside open_mutex during
		 * __invalidate_device().  blkdev_put() acquires
		 * open_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);
		blkdev_put(bdev, fc->fs_type);
		down_write(&s->s_umount);
	} else {
		
		sb_set_blocksize(s, block_size(bdev));
        /* fill_super 调用 exfat_fill_super 填充超级块 */
		error = fill_super(s, fc);

		s->s_flags |= SB_ACTIVE;
		bdev->bd_super = s;
	}

	fc->root = dget(s->s_root);
	return 0;
}

作用如下:

  • 获取 super_block 磁盘块
  • 调用传入的 fill_super 函数,执行文件系统自定义的操作。 这里一般做的是去解析文件系统的元数据,即,从磁盘上读取超级块数据,并填充到文件系统的内存私有结构体中。

最后,回到 do_new_mount() 函数中,最后是do_new_mount_fc() 函数:

static int do_new_mount(struct path *path, const char *fstype, int sb_flags, int mnt_flags, const char *name, void *data)
{
	...
	if (!err)
		err = do_new_mount_fc(fc, path, mnt_flags);

	put_fs_context(fc);
	return err;
}
static int do_new_mount_fc(struct fs_context *fc, struct path *mountpoint, unsigned int mnt_flags)
{
	struct vfsmount *mnt;
	struct mountpoint *mp;
    /* 获得vfs的超级块 (之前已经构建好) */
	struct super_block *sb = fc->root->d_sb;
	int error;

	up_write(&sb->s_umount);
	/* 为一个已配置的超级块分配mount实例 */
	mnt = vfs_create_mount(fc);

	mnt_warn_timestamp_expiry(mountpoint, mnt);
	/* Step1:寻找挂载点 
	 * 如果已经有文件系统挂载在挂载点上,则将最后一次挂载的文件系统根目录作为挂载点
     */
	mp = lock_mount(mountpoint);
	/* Step2:关联挂载点,加入全局文件系统树 */
	error = do_add_mount(real_mount(mnt), mp, mountpoint, mnt_flags);
	unlock_mount(mp);
	return error;
}
  • do_new_mount_fc 函数,创建mount实例、关联挂载点和超级块、添加到命名空间的挂载树中,以便于用户可通过路径来访问所挂载的文件系统
  • 简言之就干了两件事情:首先找到挂载点、然后将挂载点与 mount 对象关联
  • lock_mount 函数,此函数不仅仅是完成加锁功能,更重要的是完成了寻找挂载点的任务。其可通过传来的挂载点的 path(包含vfsmout, dentry),来查找最后一次挂载的文件系统的根 dentry 作为即将挂载文件系统的挂载点

  • 首先在内核中维护了一个哈希链表,由 mount_hashtable 变量指向,所有的mount对象都会插入到该链表中

  • 此处逻辑就是一个循环,此处会不断调用以 path 为参数的 lookup_mnt 方法,此方法判断是否存在挂载到(path->mnt,path->dentry)上的文件系统,如果找到就返回这个 vfsmount 信息,然后继续查找是否有挂载到它身上的文件系统,如此反复直到找不到为止

  • 举个例子:

    static struct mountpoint *lock_mount(struct path *path)
    {
    	struct vfsmount *mnt;
    	struct dentry *dentry = path->dentry;
    retry:
    	inode_lock(dentry->d_inode);
        /* 判断挂载目录能否被挂载 */
    	if (unlikely(cant_mount(dentry))) {
    		inode_unlock(dentry->d_inode);
    		return ERR_PTR(-ENOENT);
    	}
    	namespace_lock();
        /* 查找挂载在path上的第一个子mount */
    	mnt = lookup_mnt(path);
    	if (likely(!mnt)) {
            /* mnt为空 说明没有文件系统挂载在这个path上 */
            /* 如果挂载点的path是正常的目录,原来不是挂载点,则直接返回这个目录的dentry作为挂载点 */
    		struct mountpoint *mp = get_mountpoint(dentry);
    		if (IS_ERR(mp)) {
    			namespace_unlock();
    			inode_unlock(dentry->d_inode);
    			return mp;
    		}
    		return mp;
    	}
        /* 如果挂载点的path不是正常的目录,其原来就是挂载点
         * 说明这个目录已经有其他的文件系统挂载,那么它会查找最后一个挂载到这个目录的文件系统的根dentry,
         * 将其作为真正的挂载点,即覆盖上一个文件系统 */
    	namespace_unlock();
    	inode_unlock(path->dentry->d_inode);
    	path_put(path);
    	path->mnt = mnt;
    	dentry = path->dentry = dget(mnt->mnt_root);
    	goto retry;
    }
    
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值