Linux文件系统注册、安装与卸载 - STEMHA's Blog

Linux文件系统注册、安装与卸载

概述

当内核被编译时,就已经确定了可以支持哪些文件系统,这些文件系统在系统引导时,在 VFS 中进行注册。如果文件系统是作为内核可装载的模块,则在实际安装时进行注册,并在模块卸载时注销。

  • VFS的初始化函数用来向VFS注册,即填写文件注册表file_system_type数据结构。每一个文件系统类型在注册表中有一个登记项,记录该文件系统的类型名、文件系统特性、指向对应的VFS超级块读取函数的地址及已注册项的链指针等。
  • 函数register_filesystem()用于注册文件系统类型,函数unregister_filesystem()用于注销一个文件系统类型。

文件系统的注册

VFS以链表的形式管理已经注册的文件系统。文件系统的注册有两种途径:

  • 第一种是编译操作系统内核的时候确定了可以支持那性文件系统,在文件系统被引导是,在VFS中进行注册。
  • 第二种是文件系统被当作可装载模块,通过insmod/rmmod命令在装入该文件系统模块时向VFS注册。

文件系统的初始化例程:每个文件系统都有一个初始化例程,它的作用就是在VFS中进行注册,即填写一个叫 file_system_type 的数据结构。

file_system_type数据结构

  • file_system_type结构包含了文件系统的名称以及一个指向对应的 VFS 超级块读取例程的地址。
  • 所有已注册的文件系统的file_system_type 结构形成一个链表,为区别后面将要说到的已安装的文件系统形成的另一个链表,我们把这个链表称为注册链表
  • 这个注册链表是一个临界资源,受file_systems_lock 自旋读写锁的保护。

图1所示就是内核中的 file_system_type 链表,链表头由file_systems变量指定。图1仅示意性地说明系统中已安装的3个文件系统Ext2、proc、iso9660 其file_system_type 结构所形成的链表。

图1. 已注册的文件系统形成的链表.PNG

file_system_type数据结构在include/linux/fs.h中定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
struct file_system_type {
const char *name; // 文件系统的类型名,以字符串的形式出现
int fs_flags; //指明具体文件系统的一些特性。
/* public flags for file_system_type */
#define FS_REQUIRES_DEV 1
#define FS_BINARY_MOUNTDATA 2
#define FS_HAS_SUBTYPE 4
#define FS_USERNS_MOUNT 8 /* Can be mounted by userns root */
#define FS_USERNS_DEV_MOUNT 16 /* A userns mount does not imply MNT_NODEV */
#define FS_RENAME_DOES_D_MOVE 32768 /* FS will handle d_move() during rename() internally. */
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; //把所有的file_system_type 结构链接成单项链表的链接指针
struct hlist_head fs_supers; //具有相同文件系统类型的超级块对象链表的头

struct lock_class_key s_lock_key;
struct lock_class_key s_umount_key;
struct lock_class_key s_vfs_rename_key;
struct lock_class_key s_writers_key[SB_FREEZE_LEVELS];

struct lock_class_key i_lock_key;
struct lock_class_key i_mutex_key;
struct lock_class_key i_mutex_dir_key;
};

说明:

  • fs_flags字段存放几个标志,的说明如下:
    • FS_REQUIRES_DEV:这种类型的任何文件系统必须位于物理磁盘设备上
    • FS_BINARY_MOUNTDATA:文件系统使用的二进制安装数据
    • 一般的文件系统类型要求有物理的设备作为其物质基础,其fs_flags 中的FS_REQUIRES_DEV 标志位为1,这些文件系统如Ext2、Minix、ufs 等。
  • next:把所有的file_system_type 结构链接成单项链表的链接指针, 变量file_systems 指向这个链表。这个链表是一个临界资源,受file_systems_lock 自旋读写锁的保护。
  • owner:如果file_system_type 所代表的文件系统是通过可安装模块实现的,则该指针指向代表着具体模块的module 结构。如果文件系统是静态地链接到内核,则这个域为NULL。实际上,你只需要把这个域置为THIS_MODLUE (这是个一个宏),它就能自动地完成上述工作。
  • fs_supers:一个双向链表。链表中的元素是超级块结构。每个文件系统都有一个超级块,但有些文件系统可能被安装在不同的设备上,而且每个具体的设备都有一个超级块,这些超级块就形成一个双向链表。链表元素的向前和向后链接存放在超级块的s_instances字段中。
  • kill_sb字段指向删除超级块的函数。

register_filesystem()注册函数

搞清楚这个数据结构的各个域以后, 就很容易理解下面的注册函数register_filesystem(),该函数定义于fs/filesystems.c

点击展开代码 >folded
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* register_filesystem - register a new filesystem
* @fs: the file system structure
*
* Adds the file system passed to the list of file systems the kernel
* is aware of for mount and other syscalls. Returns 0 on success,
* or a negative errno code on an error.
*
* The &struct file_system_type that is passed is linked into the kernel
* structures and must not be freed until the file system has been
* unregistered.
*/

int register_filesystem(struct file_system_type * fs)
{
int res = 0;
struct file_system_type ** p;

BUG_ON(strchr(fs->name, '.'));
if (fs->next)
return -EBUSY;
write_lock(&file_systems_lock); //对该链表的查找加了写锁write_lock
p = find_filesystem(fs->name, strlen(fs->name));
if (*p)
res = -EBUSY;
else
*p = fs;
write_unlock(&file_systems_lock);
return res;
}

EXPORT_SYMBOL(register_filesystem);

find_filesystem()函数在同一个文件中定义如下:

1
2
3
4
5
6
7
8
9
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 (strlen((*p)->name) == len &&
strncmp((*p)->name, name, len) == 0)
break;
return p;
}

注意,对注册链表的操作必须互斥地进行,因此,对该链表的查找加了写锁write_lock。

unregister_filesystem()撤销注册

文件系统注册后,还可以撤消这个注册,即从注册链表中删除一个file_system_type结构,此后系统不再支持该种文件系统。
fs/filesystems.c中的unregister_filesystem()函数就是起这个作用的,它在执行成功后返回0,如果注册链表中本来就没有指定的要删除的结构,则返回-1,其代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* unregister_filesystem - unregister a file system
* @fs: filesystem to unregister
*
* Remove a file system that was previously successfully registered
* with the kernel. An error is returned if the file system is not found.
* Zero is returned on a success.
*
* Once this function has returned the &struct file_system_type structure
* may be freed or reused.
*/

int unregister_filesystem(struct file_system_type * fs)
{
struct file_system_type ** tmp;

write_lock(&file_systems_lock);
tmp = &file_systems;
while (*tmp) {
if (fs == *tmp) {
*tmp = fs->next;
fs->next = NULL;
write_unlock(&file_systems_lock);
synchronize_rcu();
return 0;
}
tmp = &(*tmp)->next;
}
write_unlock(&file_systems_lock);

return -EINVAL;
}

EXPORT_SYMBOL(unregister_filesystem);

文件系统的安装

安装一个文件系统时,内核首先要检查参数的合法性,VFS通过查找由file_systems(file_system_type的首结点)指向的注册表,寻找匹配的file_system_type,就可获得读取文件系统超级块函数的地址,接着查找作为新文件系统安装点的VFS inode,VFS安装程序必须分配一个VFS超级块,然后读入安装文件系统的超级块,并进行填充,再申请一个vfsmount数据结构(其中包含了文件系统所在的块设备的标识、安装点及指向VFS超级块的指针等),使它的指针指向所分配的VFS超级块。当文件系统安装以后,它的根inode便常驻在inode高速缓存中。
总的来说,安装过程的主要工作是:创建安装点对象、将其挂接到根文件系统的指定安装点下、初始化超级块对象从而获得文件系统的基本信息和相关的操作。

要使用一个文件系统,仅仅注册是不行的,还必须安装这个文件系统。

  • Linux使用系统的根文件系统(system's root filesystem),它由内核在引导阶段直接安装,并拥有系统初始化脚本以及最基本的系统程序。在安装Linux 时,硬盘上已经有一个分区安装了ext3文件系统(也可能是其他种类的系统),它是作为根文件系统的,根文件系统在启动时自动安装。其实,在系统启动后你所看到的文件系统,都是在启动时安装的。
  • 其他文件系统要么由初始化脚本安装,要么由用户直接安装在已安装文件系统的目录上。

作为一个目录树,每个文件系统都拥有自己的根目录(root directory)。安装文件系统的这个目录称之为安装点(mount point)。已安装文件系统属于安装点目录的一个子文件系统。

  • 例如,/proc虚拟文件系统是系统的根文件系统的孩子(且系统的根文件系统是/proc的父亲)。
  • 已安装文件系统的根目录隐藏了父文件系统的安装点目录原来的内容,而且父文件系统的整个子树位于安装点之下。

mount命令

如果需要自己(一般是超级用户)安装文件系统,则需要指定3 种信息:文件系统的名称、包含文件系统的物理块设备、文件系统在已有文件系统中的安装点

1
mount -t ext3 /dev/sda5 /mnt
  • ext3 就是文件系统的名称
  • /dev/sda5 是包含文件系统的物理块设备,
  • /mnt 是将要安装到的目录,即安装点。
  • 从这个例子可以看出,安装一个文件系统实际上是安装一个物理设备。

vfsmount数据结构

把一个文件系统(或设备)安装到一个目录点时要用到的主要数据结构为vfsmountstruct mount,定义于include/linux/mount.h 中:

1
2
3
4
5
struct vfsmount {
struct dentry *mnt_root; /* root of the mounted tree */
struct super_block *mnt_sb; /* pointer to superblock */
int mnt_flags;
};

说明:

  • mnt_sb 指向所安装设备的超级块结构super_block。

文件系统的安装选项,也就是vfsmount 结构中的安装标志mnt_flags在include/uapi/linux/fs.h 中,定义如下:

点击展开代码 >folded
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/*
* These are the fs-independent mount-flags: up to 32 flags are supported
*/
#define MS_RDONLY 1 /* Mount read-only */
#define MS_NOSUID 2 /* Ignore suid and sgid bits */
#define MS_NODEV 4 /* Disallow access to device special files */
#define MS_NOEXEC 8 /* Disallow program execution */
#define MS_SYNCHRONOUS 16 /* Writes are synced at once */
#define MS_REMOUNT 32 /* Alter flags of a mounted FS */
#define MS_MANDLOCK 64 /* Allow mandatory locks on an FS */
#define MS_DIRSYNC 128 /* Directory modifications are synchronous */
#define MS_NOATIME 1024 /* Do not update access times. */
#define MS_NODIRATIME 2048 /* Do not update directory access times */
#define MS_BIND 4096
#define MS_MOVE 8192
#define MS_REC 16384
#define MS_VERBOSE 32768 /* War is peace. Verbosity is silence.
MS_VERBOSE is deprecated. */
#define MS_SILENT 32768
#define MS_POSIXACL (1<<16) /* VFS does not apply the umask */
#define MS_UNBINDABLE (1<<17) /* change to unbindable */
#define MS_PRIVATE (1<<18) /* change to private */
#define MS_SLAVE (1<<19) /* change to slave */
#define MS_SHARED (1<<20) /* change to shared */
#define MS_RELATIME (1<<21) /* Update atime relative to mtime/ctime. */
#define MS_KERNMOUNT (1<<22) /* this is a kern_mount call */
#define MS_I_VERSION (1<<23) /* Update inode I_version field */
#define MS_STRICTATIME (1<<24) /* Always perform atime updates */

/* These sb flags are internal to the kernel */
#define MS_NOSEC (1<<28)
#define MS_BORN (1<<29)
#define MS_ACTIVE (1<<30)
#define MS_NOUSER (1<<31)

说明:

  • 从定义可以看出,每个标志对应32 位中的一位。
  • 安装标志是针对整个文件系统中的所有文件的。例如,如果MS_NOSUID 标志为1,则整个文件系统中所有可执行文件的suid 标志位都不起作用了。

struct mount结构

struct mount结构在linux/fs/mount.h

点击展开代码 >folded
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
struct mount {
struct list_head mnt_hash; //安装点的哈希表
struct mount *mnt_parent; //是指向上一层安装点的指针
struct dentry *mnt_mountpoint; //指向安装点dentry 结构的指针
struct vfsmount mnt;
#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 */
#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 */
int mnt_pinned;
int mnt_ghosts;
};

说明:

  • mnt_hash:为了对系统中的所有安装点进行快速查找,内核把它们按哈希表来组织,mnt_hash就是形成哈希表的队列指针。
  • mnt_mountpoint: 是指向安装点dentry 结构的指针。而dentry 指针指向安装点所在目录树中根目录的dentry 结构。?
  • mnt_parent: 是指向上一层安装点的指针。如果当前的安装点没有上一层安装点(如根设备),则这个指针为NULL。
  • 同时,vfsmount 结构中还有mnt_mounts 和mnt_child 两个队列头
    • 只要上一层vfsmount 结构存在,就把当前vfsmount 结构中mnt_child 链入上一层vfsmount 结构的mnt_mounts 队列中。这样就形成一个设备安装的树结构,从一个vfsmount结构的mnt_mounts 队列开始,可以找到所有直接或间接安装在这个安装点上的其他设备。
  • mnt_list:是指向vfsmount结构所形成链表的头指针。

另外,系统还定义了vfsmntlist 变量,指向mnt_list 队列。//在Linux kernel3.10 中没有vfsmntlist

安装根文件系统

每个文件系统都有它自己的根目录,如果某个文件系统(如Ext3)的根目录是系统目录树的根目录,那么该文件系统称为根文件系统。而其他文件系统可以安装在系统的目录树上,把这些文件系统要插入的那些目录就称为安装点

  • 当系统启动时,就要在变量ROOT_DEV 中寻找包含根文件系统的磁盘主码。
  • 当编译内核或向最初的启动装入程序传递一个合适的选项时,根文件系统可以被指定为/dev 目录下的一个设备文件。
  • 类似地,根文件系统的安装标志存放在root_mountflags 变量中。用户可以指定这些标志,这是通过对已编译的内核映像执行/sbin/rdev 外部程序,或者向最初的启动装入程序传递一个合适的选项来达到的。
  • 根文件系统的安装函数为mount_root()

安装一个常规文件系统

一旦在系统中安装了根文件系统,就可以安装其他的文件系统。每个文件系统都可以安装在系统目录树中的一个目录上。

安装文件系统的两个系统的两种方式:

  • 以命令方式来安装文件系统,例如mount命令。
  • 在用户程序中要安装一个文件系统则可以调用mount()系统调用。

sys_mount()函数

mount()系统调用在内核的实现函数为sys_mount(),其函数定义使用SYSCALL_DEFINE定义,其代码在fs/namespace.c 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/*sys_mount系统调用*/
/* dev_name为待安装设备的路径名;
dir_name为安装点的路径名;
type是表示文件系统类型的字符串;
*/
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);

free_page(data_page);
out_data:
kfree(kernel_dev);
out_dev:
putname(kernel_dir);
out_dir:
kfree(kernel_type);
out_type:
return ret;
}

说明:

  • dev_name为待安装文件系统所在设备的路径名,如果不需要就为空(例如,当待安装的是基于网络的文件系统时);
  • dir_name 则是安装点(空闲目录)的路径名;
  • type 是文件系统的类型,必须是已注册文件系统的字符串名(如“Ext3”)
  • flags是安装模式,如前面所述。
  • data 指向一个与文件系统相关的数据结构(可以为NULL)。
  • copy_mount_options()和getname()函数将结构形式或字符串形式的参数值从用户空间拷贝到内核空间。
    • 这些参数值的长度均以一个页面为限,但是getname()在复制时遇到字符串结尾符“\0”就停止,并返回指向该字符串的指针;
    • 而copy_mount_options()则拷贝整个页面,并返回该页面的起始地址。
  • 该函数调用的主要函数为do_mount(),do_mount()执行期间要加内核锁,不过这个锁是针对SMP,我们暂不考虑。

do_mount()的实现代码在fs/namespace.c中:

点击展开代码 >folded
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/*
* Flags is a 32-bit value that allows up to 31 non-fs dependent flags to
* be given to the mount() call (ie: read-only, no-dev, no-suid etc).
*
* data is a (void *) that can point to any structure up to
* PAGE_SIZE-1 bytes, which can contain arbitrary fs-dependent
* information (or be NULL).
*
* Pre-0.97 versions of mount() didn't have a flags word.
* When the flags word was introduced its top half was required
* to have the magic value 0xC0ED, and this remained so until 2.4.0-test9.
* Therefore, if this magic number is present, it carries no information
* and must be discarded.
*/
long do_mount(const char *dev_name, const char *dir_name,
const char *type_page, unsigned long flags, void *data_page)
{
struct path path;
int retval = 0;
int mnt_flags = 0;

/* Discard magic */
if ((flags & MS_MGC_MSK) == MS_MGC_VAL)
flags &= ~MS_MGC_MSK;

/* Basic sanity checks */

if (!dir_name || !*dir_name || !memchr(dir_name, 0, PAGE_SIZE))
return -EINVAL;

if (data_page)
((char *)data_page)[PAGE_SIZE - 1] = 0;

/* ... and get the mountpoint */
retval = kern_path(dir_name, LOOKUP_FOLLOW, &path);
if (retval)
return retval;

retval = security_sb_mount(dev_name, &path,
type_page, flags, data_page);
if (!retval && !may_mount())
retval = -EPERM;
if (retval)
goto dput_out;

/* Default to relatime unless overriden */
if (!(flags & MS_NOATIME))
mnt_flags |= MNT_RELATIME;

/* Separate the per-mountpoint flags */
if (flags & MS_NOSUID)
mnt_flags |= MNT_NOSUID;
if (flags & MS_NODEV)
mnt_flags |= MNT_NODEV;
if (flags & MS_NOEXEC)
mnt_flags |= MNT_NOEXEC;
if (flags & MS_NOATIME)
mnt_flags |= MNT_NOATIME;
if (flags & MS_NODIRATIME)
mnt_flags |= MNT_NODIRATIME;
if (flags & MS_STRICTATIME)
mnt_flags &= ~(MNT_RELATIME | MNT_NOATIME);
if (flags & MS_RDONLY)
mnt_flags |= MNT_READONLY;

flags &= ~(MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_ACTIVE | MS_BORN |
MS_NOATIME | MS_NODIRATIME | MS_RELATIME| MS_KERNMOUNT |
MS_STRICTATIME);

if (flags & MS_REMOUNT)
retval = do_remount(&path, flags & ~MS_REMOUNT, mnt_flags,
data_page);
else if (flags & MS_BIND)
retval = do_loopback(&path, dev_name, flags & MS_REC);
else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE))
retval = do_change_type(&path, flags);
else if (flags & MS_MOVE)
retval = do_move_mount(&path, dev_name);
else
retval = do_new_mount(&path, type_page, flags, mnt_flags,
dev_name, data_page);
dput_out:
path_put(&path);
return retval;
}

对函数中的主要代码给予说明:

  • 对参数dir_name 和dev_name 进行基本检查,注意“!dir_name ” 和“!*dir_name”的不同,前者指指向字符串的指针为不为空,而后者指字符串不为空。memchr()函数在指定长度的字符串中寻找指定的字符,如果字符串中没有结尾符“\0”,也是一种错误。前面已说过,对于基于网络的文件系统dev_name 可以为空。
  • 把安装标志为MS_NOSUID、MS_NOEXEC 和MS_NODEV 的3个标志位从flags 分离出来,放在局部安装标志变量mnt_flags 中。
  • 如果flags 中的MS_REMOUNT 标志位为1,就表示所要求的只是改变一个原已安装设备的安装方式,例如从“只读“安装方式改为“可写”安装方式,这是通过调用do_remount()函数完成的。
  • 如果flags 中的MS_BIND 标志位为1,就表示把一个“回接”设备捆绑到另一个对象上。
    • 回接设备是一种特殊的设备(虚拟设备),而实际上并不是一种真正设备,而是一种机制,这种机制提供了把回接设备回接到某个可访问的常规文件或块设备的手段。通常在/dev目录中有/dev/loop0 和/dev/loop1 两个回接设备文件。调用do_loopback()来实现回接设备的安装。
  • 如果flags 中的MS_MOVE 标志位为1,就表示把一个已安装的设备可以移到另一个安装点,这是通过调用do_move_mount()函数来实现的。
  • 如果不是以上3 种情况,那就是一般的安装请求,于是把安装点加入到目录树中,这是通过调用do_new_mount()函数来实现的,而do_new_mount()首先调用vfs_kern_mount函数形成一个安装点。do_new_mount()函数也调用了

do_new_mount()函数的代码在fs/namespace.c 中:

点击展开代码 >folded
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/*
* create a new mount for userspace and request it to be added into the
* namespace's tree
*/
static int do_new_mount(struct path *path, const char *fstype, int flags,
int mnt_flags, const char *name, void *data)
{
struct file_system_type *type;
struct user_namespace *user_ns = current->nsproxy->mnt_ns->user_ns;
struct vfsmount *mnt;
int err;

if (!fstype)
return -EINVAL;

type = get_fs_type(fstype);
if (!type)
return -ENODEV;

if (user_ns != &init_user_ns) {
if (!(type->fs_flags & FS_USERNS_MOUNT)) {
put_filesystem(type);
return -EPERM;
}
/* Only in special cases allow devices from mounts
* created outside the initial user namespace.
*/
if (!(type->fs_flags & FS_USERNS_DEV_MOUNT)) {
flags |= MS_NODEV;
mnt_flags |= MNT_NODEV;
}
}

mnt = vfs_kern_mount(type, flags, name, data);
if (!IS_ERR(mnt) && (type->fs_flags & FS_HAS_SUBTYPE) &&
!mnt->mnt_sb->s_subtype)
mnt = fs_set_subtype(mnt, fstype);

put_filesystem(type);
if (IS_ERR(mnt))
return PTR_ERR(mnt);

err = do_add_mount(real_mount(mnt), path, mnt_flags);
if (err)
mntput(mnt);
return err;
}

对函数中的主要代码给予说明:

  • 只有系统管理员才具有安装一个设备的权力,因此首先要检查当前进程是否具有这种权限。
  • get_fs_type()函数根据具体文件系统的类型名在file_system_file 链表中找到相应的结构。

vfs_kern_mount()函数代码在fs/namespace.c 中:

点击展开代码 >folded
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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);
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)) {
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;
br_write_lock(&vfsmount_lock);
list_add_tail(&mnt->mnt_instance, &root->d_sb->s_mounts);
br_write_unlock(&vfsmount_lock);
return &mnt->mnt;
}
EXPORT_SYMBOL_GPL(vfs_kern_mount);
  • alloc_vfsmnt()函数调用slab 分配器给类型为vfsmount 结构的局部变量mnt 分配空间,并进行相应的初始化。

do_add_mount()函数代码,fs/namespace.c 中:

点击展开代码 >folded
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/*
* add a mount into a namespace's mount tree
*/
static int do_add_mount(struct mount *newmnt, struct path *path, int mnt_flags)
{
struct mountpoint *mp;
struct mount *parent;
int err;

mnt_flags &= ~(MNT_SHARED | MNT_WRITE_HOLD | MNT_INTERNAL);

mp = lock_mount(path);
if (IS_ERR(mp))
return PTR_ERR(mp);

parent = real_mount(path->mnt);
err = -EINVAL;
if (unlikely(!check_mnt(parent))) {
/* that's acceptable only for automounts done in private ns */
if (!(mnt_flags & MNT_SHRINKABLE))
goto unlock;
/* ... and for those we'd better have mountpoint still alive */
if (!parent->mnt_ns)
goto unlock;
}

/* Refuse the same filesystem on the same mount point */
err = -EBUSY;
if (path->mnt->mnt_sb == newmnt->mnt.mnt_sb &&
path->mnt->mnt_root == path->dentry)
goto unlock;

err = -EINVAL;
if (S_ISLNK(newmnt->mnt.mnt_root->d_inode->i_mode))
goto unlock;

newmnt->mnt.mnt_flags = mnt_flags;
err = graft_tree(newmnt, parent, mp);

unlock:
unlock_mount(mp);
return err;
}

文件系统的卸载

如果文件系统中的文件当前正在使用,该文件系统是不能被卸载的。
如果文件系统中的文件或目录正在使用,则 VFS 索引节点高速缓存中可能包含相应的 VFS 索引节点。
根据文件系统所在设备的标识符,检查在索引节点高速缓存中是否有来自该文件系统的 VFS 索引节点,如果有且使用计数大于0,则说明该文件系统正在被使用,因此,该文件系统不能被卸
载。否则,查看对应的 VFS 超级块,如果该文件系统的 VFS 超级块标志为“脏”,则必须将超级块信息写回磁盘。
上述过程结束之后,对应的 VFS 超级块被释放,vfsmount 数据结构将从vfsmntlist链表中断开并被释放。
具体的实现代码为fs/super.c 中的sys_umount()函数。

参考资料

《深入分析Linux内核源代码》 //书籍内核版本有些旧
控制文件系统的安装和卸载
文件系统的注册与注销、安装与卸载
文件系统怎么让Linux内核认识自己
mount系统调用初探

注解

uapi文件夹:(the user space API of the kernel,Then upon kernel installation, the uapi include files become the top level /usr/include/linux/ files.)Linux Kernel 中新增的这些 uapi 头文件,其实都是来自于各个模块原先的头文件,最先是由 David Howells 提出来的。uapi 只是把内核用到的头文件和用户态用到的头文件分开。
Linux的系统调用都改为SYSCALL_DEFINE定义,原因参照:Linux系统调用之SYSCALL_DEFINE

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×