挂载文件系统

news2024/9/29 13:28:10

文章目录

  • 注册文件系统类型
  • 挂载文件系统
  • 系统调用mount
  • 绑定挂载
  • 挂载命名空间
    • 1.标准的挂载命名空间
    • 2.共享子树
      • (1)共享挂载
      • (2)从属挂载
      • (3)私有挂载
      • (4)不可绑定挂载
  • 挂载跟文件系统
    • 1.根文件系统rootfs
    • 2.用户指定的文件系统

注册文件系统类型

因为每种文件系统的超级块的格式不同,所以每种文件系统需要向虚拟文件系统注册文件系统类型 file_system_type,实现 mount 方法用来读取和解析超级块。

函数 register_filesystem 用来注册文件系统类型:

int register_filesystem(struct file_system_type *fs); 

函数 unregister_filesystem 用来注销文件系统类型:

int unregister_filesystem(struct file_system_type *fs);

管理员可以执行命令“cat /proc/filesystems”来查看已经注册的文件系统类型。

挂载文件系统

虚拟文件系统在内存中把目录组织为一棵树。一个文件系统,只有挂载到内存中目录树的一个目录下,进程才能访问这个文件系统。

管理员可以执行命令“mount -t fstype [-o options] device dir”,把存储设备 device 上类型为 fstype 的文件系统挂载到目录 dir 下。例如:命令“mount -t ext4 /dev/sda1 /a”把 SATA硬盘 a 的第一个分区上的 EXT4 文件系统挂载到目录“/a”下。

管理员可以执行命令“umount dir”来卸载在目录 dir 下挂载的文件系统。

glibc 库封装了挂载文件系统的函数 mount:

int mount(const char *dev_name, const char *dir_name, const char *type, unsigned long flags,  const void *data); 

参数 dev_name 是设备名称,参数 dir_name 是目录名称,参数 type 是文件系统类型的名称,参数 flags 是挂载标志位,参数 data 是挂载选项。这个函数调用内核的系统调用 mount。

glibc 库封装了两个卸载文件系统的函数。

(1)函数 umount,对应内核的系统调用 oldumount。

int umount(const char *target);

(2)函数 umount2,对应内核的系统调用 umount。

int umount2(const char *target, int flags); 

每次挂载文件系统,虚拟文件系统就会创建一个挂载描述符:mount 结构体。挂载描述符用来描述文件系统的一个挂载实例,同一个存储设备上的文件系统可以多次挂载,每次挂载到不同的目录下。

假设我们把文件系统 2 挂载到目录“/a”下,目录 a 属于文件系统 1,挂载描述符的数据结构如图所示。

在这里插入图片描述

为了能够快速找到目录 a 下挂载的文件系统,把文件系统 2 的挂载描述符加入全局散列表 mount_hashtable,关键字是{父挂载描述符,挂载点},根据文件系统 1 的挂载描述符和目录 a 可以在散列表中找到文件系统 2 的挂载描述符。

如图所示,在文件系统1中,目录a下可能有子目录和文件。在目录a下挂载文件系统2以后,当进程访问目录“/a”的时候,虚拟文件系统发现目录a是挂载点,就会跳转到文件系统 2 的根目录。所以进程访问目录“/a”,实际上是访问目录a下挂载的文件系统2的根目录,进程看不到文件系统1中目录a下的子目录和文件。只有从目录a卸载文件系统2以后,进程才能重新看到文件系统1中目录a下的子目录和文件。

在这里插入图片描述

假设在文件系统1中,在目录a下挂载文件系统2,在目录b下挂载文件系统3,在目录c下挂载文件系统4。假设文件系统1的挂载描述符是m1,文件系统2的挂载描述符是m2,文件系统3的挂载描述符是m3,文件系统4的挂载描述符是m4,如图所示,这些挂载描述符组成一棵挂载树。

在这里插入图片描述

系统调用mount

系统调用 mount 用来挂载文件系统,其定义如下:

fs/namespace.c 
SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name, char __user *, type, unsigned long, flags, void __user *, data) 

使用命令“mount -t fstype [-o options] device dir”执行一个标准的挂载操作时,系统调用 mount 的执行流程如图所示。

在这里插入图片描述

(1)调用函数 user_path,根据目录名称找到挂载描述符和 dentry 实例。
(2)调用函数 get_fs_type,根据文件系统类型的名称查找 file_system_type 实例。
(3)调用函数 alloc_vfsmnt,分配挂载描述符。
(4)调用文件系统类型的挂载方法,读取并且解析超级块。
(5)把挂载描述符添加到超级块的挂载实例链表中。
(6)把挂载描述符加入散列表。
(7)把挂载描述符加入父亲的孩子链表。

绑定挂载

绑定挂载(bind mount)用来把目录树的一棵子树挂载到其他地方。执行绑定挂载的命令如下所示:

mount --bind olddir newdir

把以目录 olddir 为根的子树挂载到目录 newdir,以后从目录 newdir 和目录 olddir 可以看到相同的内容。

可以把一个文件绑定到另一个文件,访问这两个文件时看到的数据完全相同。例如:执行命令“mount --bind /a/c.txt /b/d.txt”,把文件“/a/c.txt”绑定挂载到文件“/b/d.txt”。

命令“mount --bind olddir newdir”只会挂载一个文件系统(即目录 olddir 所属的文件系统)或其中的一部分。如果需要绑定挂载目录 olddir 所属的文件系统及其所有子挂载,应该执行下面的命令:

mount --rbind olddir newdir

rbind 中的 r 是递归(recursively)的意思。

如果需要在程序中执行绑定挂载,方法是:调用系统调用 mount,把参数flags 设置为 MS_BIND。

举例说明:假设执行命令“mount --bind /a/b /c”,把目录“/a/b”绑定挂载到目录“/c”,目录a和b属于文件系统1,目录c属于文件系统2,实际上是把文件系统1中以目录b为根的子树挂载到文件系统2的目录“/c”,数据结构如图所示。注意:只挂载了文件系统1的一部分,文件系统1的 mount实例的成员mnt.mnt_root指向文件系统1的目录b,而不是指向文件系统1的根目录。

在这里插入图片描述

挂载命名空间

和虚拟机相比,容器是一种轻量级的虚拟化技术,直接使用宿主机的内核,使用命名空间隔离资源,其中挂载命名空间用来隔离挂载点。

每个进程属于一个挂载命名空间,数据结构如图所示。

在这里插入图片描述

可以使用以下两种方法创建新的挂载命名空间。
(1)调用 clone 创建子进程时,如果指定标志位 CLONE_NEWNS,那么子进程将会从父进程的挂载命名空间复制生成一个新的挂载命名空间;如果没有指定标志位 CLONE_ NEWNS,那么子进程将会和父进程属于同一个挂载命名空间。
(2)调用 unshare(CLONE_NEWNS)以设置不再和父进程共享挂载命名空间,从父进程的挂载命名空间复制生成一个新的挂载命名空间。复制生成的挂载命名空间的级别和旧的挂载命名空间是平等的,不存在父子关系。

调用系统调用 clone 创建子进程,如果指定标志位 CLONE_NEWNS,执行流程如图所示。

在这里插入图片描述

(1)调用函数 alloc_mnt_ns 以分配挂载命名空间。
(2)调用函数 copy_tree 以复制挂载树。
(3)把子进程的根目录的挂载描述符(task_struct.fs->root.mnt)设置为复制生成的挂载描述符。如果父进程的根目录的挂载描述符是 m1,复制挂载树时从挂载描述符 m1 复制生成挂载描述符 m1-1,那么子进程的根目录的挂载描述符是 m1-1。
(4)把子进程的当前工作目录的挂载描述符(task_struct.fs->pwd.mnt)设置为复制生成的挂载描述符。如果父进程的当前工作目录的挂载描述符是 m2,复制挂载树时从挂载描述符 m2 复制生成挂载描述符 m2-1,那么子进程的当前工作目录的挂载描述符是 m2-1。

假设在文件系统 1 中,在目录 a 下挂载文件系统 2,在目录 b 下挂载文件系统 3,在目录 c 下挂载文件系统 4。假设文件系统 1 的挂载描述符是 m1,文件系统 2 的挂载描述符是m2,文件系统 3 的挂载描述符是 m3,文件系统 4 的挂载描述符是 m4,那么这些挂载描述符组成一棵挂载树,假设这棵挂载树属于挂载命名空间 1,挂载命名空间 1 的成员 root 指向挂载树的根。

如图所示,从挂载命名空间 1 复制生成挂载命名空间 2 的时候,把挂载命名空间 1 的挂载树复制一份,也就是把挂载树中的每个挂载描述符复制一份:“从 m1 复制生成 m1-1,从 m2 复制生成 m2-1,从 m3 复制生成 m3-1,从 m4 复制生成 m4-1”,实际上是在挂载命名空间 2 中把挂载命名空间 1 的所有文件系统重新挂载一遍。m1 和 m1-1 是文件系统 1 的两个挂载描述符,m2 和 m2-1 是文件系统 2 的两个挂载描述符,m2 和 m2-1 的挂载

点都是文件系统 1 的目录 a,同一个挂载点下有两个挂载描述符。

在这里插入图片描述

1.标准的挂载命名空间

标准的挂载命名空间是完全隔离的,在一个挂载命名空间中挂载或卸载一个文件系统,不会影响其他挂载命名空间。

如下图所示,如果在挂载命名空间 1 的 m2 的一个目录下挂载文件系统 5,挂载描述符是 m5;在挂载命名空间 2 的 m2-1 的一个目录下挂载文件系统 6,挂载描述符是 m6,那么出现的结果是:挂载命名空间 2 看不到 m5 对应的文件系统 5,挂载命名空间 1 看不到 m6 对应的文件系统 6。

在这里插入图片描述

如下图所示,如果在挂载命名空间 1 中卸载 m2 对应的文件系统,不会影响挂载命名空间 2。在挂载命名空间 2 中,文件系统 1 的目录 a 仍然挂载文件系统 2。

在这里插入图片描述

2.共享子树

在一个标准的挂载命名空间中挂载或卸载一个文件系统,不会影响其他挂载命名空间。在某些情况下,隔离程度太重了。例如:用户插入一个移动硬盘,为了使移动硬盘在所有的挂载命名空间中可用,必须在每个挂载命名空间中执行挂载操作,非常麻烦。用户的需求是:只执行一次挂载操作,所有挂载命名空间都可以访问移动硬盘。为了满足这种用户需求,Linux 2.6.15 版本引入了共享子树。

共享子树提供了 4 种挂载类型。
 共享挂载(shared mount)。
 从属挂载(slave mount)。
 私有挂载(private mount)。
 不可绑定挂载(unbindable mount)。
默认的挂载类型是私有挂载。

(1)共享挂载

共享挂载的特点是:同一个挂载点下面的所有共享挂载共享挂载/卸载事件。如果我们在一个共享挂载下面挂载或卸载文件系统,那么会自动传播到所有其他共享挂载,即自动在所有其他共享挂载下面执行挂载或卸载操作。如果需要把一个挂载设置为共享挂载,可以执行下面的命令:

mount --make-shared mountpoint

同一个挂载点下面的所有共享挂载组成一个对等体组(peer group),内核自动给每个对等体组分配一个唯一的标识符。执行命令“cat /proc/[pid]/mountinfo”以查看挂载信息的时候,共享挂载会显示标记“shared:X”,X 是对等体组的标识符。

# mount --make-shared /mntS 
# cat /proc/self/mountinfo 
77 61 8:17 / /mntS rw,relatime shared:1

如果需要在程序中把一个挂载设置为共享挂载,方法是:调用系统调用 mount,把参数 flags 设置为MS_SHARED。

如图下所示,假设我们把 m2 和 m2-1 设置为共享挂载,当我们在挂载命名空间 1 的 m2 下面挂载文件系统 5 的时候,会自动把挂载事件传播到挂载命名空间 2 的 m2-1,即自动在挂载命名空间 2 的 m2-1 下面挂载文件系统 5,最终的结果是:在 m2 下面生成子挂载 m5,在 m2-1 下面生成子挂载 m5-1。

在这里插入图片描述

当我们在挂载命名空间 1 的 m2 下面卸载文件系统 5 的时候,会自动把卸载事件传播到挂载命名空间 2 的 m2-1,即自动在挂载命名空间 2 的 m2-1 下面卸载文件系统 5。

(2)从属挂载

从属挂载的特点是:假设在同一个挂载点下面同时有共享挂载和从属挂载,所有共享挂载组成一个共享对等体组,如果我们在共享对等体组中的任何一个共享挂载下面挂载或卸载文件系统,会自动传播到所有从属挂载;如果我们在任何一个从属挂载下面挂载或卸载文件系统,则不会传播到所有共享挂载。可以看出,传播是单向的,只能从共享挂载传播到从属挂载,不能从从属挂载传播到共享挂载。

如果需要把一个挂载设置为从属挂载,可以执行下面的命令:

mount --make-slave mountpoint

执行命令“cat /proc/[pid]/mountinfo”以查看挂载信息的时候,从属挂载会显示标记“master:X”,表示这个挂载是共享对等体组 X 的从属;如果从属挂载是从共享挂载传播过来的,会显示标记“propagate_from:X”,表示这个挂载是从属挂载,是从共享对等体组 X 传播过来的。

# mount --make-slave /mntY 
# cat /proc/self/mountinfo 
169 167 8:22 / /mntY rw,relatime master:2

如果需要在程序中把一个挂载设置为从属挂载,方法是:调用系统调用 mount,把参数 flags 设置为MS_SLAVE。

如下图所示,假设我们把 m2 设置为共享挂载,把 m2-1 设置为从属挂载。当我们在挂载命名空间 1 的 m2 下面挂载文件系统 5 的时候,会自动把挂载事件传播到挂载命名空间 2 的 m2-1,即自动在挂载命名空间 2 的 m2-1 下面挂载文件系统 5,最终的结果是:在 m2 下面生成子挂载 m5,在 m2-1 下面生成子挂载 m5-1。当我们在挂载命名空间 2 的 m2-1 下面挂载文件系统 6 的时候,不会把挂载事件传播到挂载命名空间 1 的 m2,即不会在挂载命名空间 1 的 m2 下面挂载文件系统 6,最终的结果是:在 m2-1 下面生成子挂载 m6。当我们在挂载命名空间 1 的 m2 下面卸载文件系统 5 的时候,会自动把卸载事件传播到挂载命名空间 2 的 m2-1,即自动在挂载命名空间 2 的 m2-1 下面卸载文件系统 5。

在这里插入图片描述

(3)私有挂载

私有挂载和同一个挂载点下面的所有其他挂载是完全隔离的:如果我们在一个私有挂载下面挂载或卸载文件系统,不会传播到同一个挂载点下面的所有其他挂载;在同一个挂载点的其他挂载下面挂载或卸载文件系统,也不会传播到私有挂载。如果需要把一个挂载设置为私有挂载,可以执行下面的命令:

mount --make-private mountpoint

默认的挂载类型是私有挂载,当执行命令“mount -t fstype [-o options] device dir”,把存储设备 device 上类型为 fstype 的文件系统挂载到目录 dir 的时候,挂载类型是私有挂载。如果需要在程序中把一个挂载设置为私有挂载,方法是:调用系统调用 mount,把参数 flags 设置为 MS_PRIVATE。

(4)不可绑定挂载

不可绑定挂载是私有挂载,并且不允许被绑定挂载。如果需要把一个挂载设置为不可绑定挂载,可以执行下面的命令:

mount --make-unbindable mountpoint

执行命令“cat /proc/[pid]/mountinfo”以查看挂载信息的时候,不可绑定挂载会显示标记“unbindable”。如果需要在程序中把一个挂载设置为不可绑定挂载,方法是:调用系统调用 mount,把参数 flags 设置为 MS_UNBINDABLE。

挂载跟文件系统

一个文件系统,只有挂载到内存中目录树的一个目录下,进程才能访问这个文件系统。问题是:怎么挂载第一个文件系统呢?第一个文件系统称为根文件系统,没法执行 mount 命令来挂载根文件系统,也不能通过系统调用 mount 挂载根文件系统。
内核有两个根文件系统。

(1)一个是隐藏的根文件系统,文件系统类型的名称是“rootfs”。
(2)另一个是用户指定的根文件系统,引导内核时通过内核参数指定,内核把这个根
文件系统挂载到 rootfs 文件系统的根目录下。

1.根文件系统rootfs

内核初始化的时候最先挂载的根文件系统是 rootfs 文件系统,它是一个内存文件系统,对用户隐藏。虽然我们看不见这个根文件系统,但是我们每天都在使用,每个进程使用的标准输入、标准输出和标准错误,对应文件描述符 0、1 和 2,这 3 个文件描述符都对应控制台的字符设备文件“/dev/console”,这个文件属于 rootfs 文件系统。

如图所示,内核初始化的时候,调用函数 init_ rootfs 以注册 rootfs 文件系统,然后调用函数 init_mount_tree 以挂载 rootfs 文件系统。

在这里插入图片描述

(1)函数init_rootfs。
函数 init_rootfs 负责注册 rootfs 文件系统,其代码如下:

init/do_mounts.c 
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);}

(2)函数 init_mount_tree。
函数 init_mount_tree 负责挂载 rootfs 文件系统,其代码如下:

fs/namespace.c
1 static void __init init_mount_tree(void) 
2 { 
3 	struct vfsmount *mnt; 
4 	struct mnt_namespace *ns; 
5 	struct path root; 
6 	struct file_system_type *type; 
7 
8 	type = get_fs_type("rootfs"); 
9 	if (!type) 
10 		panic("Can't find rootfs type"); 
11 	mnt = vfs_kern_mount(type, 0, "rootfs", NULL); 
12 	put_filesystem(type); 
13 	if (IS_ERR(mnt)) 
14 		panic("Can't create rootfs"); 
15 
16 	ns = create_mnt_ns(mnt); 
17 	if (IS_ERR(ns)) 
18 		panic("Can't allocate initial namespace"); 
19 
20 	init_task.nsproxy->mnt_ns = ns; 
21 	get_mnt_ns(ns); 
22 
23 	root.mnt = mnt; 
24 	root.dentry = mnt->mnt_root; 
25 	mnt->mnt_flags |= MNT_LOCKED; 
26 
27 	set_fs_pwd(current->fs, &root); 
28 	set_fs_root(current->fs, &root); 
29 } 

第 11 行代码,挂载 rootfs 文件系统。
第 16 行代码,创建第一个挂载命名空间。
第 20 行代码,设置 0 号线程的挂载命名空间。
第 27 行代码,把 0 号线程的当前工作目录设置为 rootfs 文件系统的根目录。
第 28 行代码,把 0 号线程的根目录设置为 rootfs 文件系统的根目录。

(3)函数 default_rootfs。
接下来,函数 default_rootfs 在 rootfs 文件系统中创建必需的目录和文件。
1)创建目录“/dev”。
2)创建控制台的字符设备文件“/dev/console”,主设备号是 5,从设备号是 1。
3)创建目录“/root”。

init/noinitramfs.c 
static int __init default_rootfs(void) 
{ 
 	int err; 
 	err = sys_mkdir((const char __user __force *) "/dev", 0755); 
 	if (err < 0) 
 		goto out; 
 	err = sys_mknod((const char __user __force *) "/dev/console", 
 		S_IFCHR | S_IRUSR | S_IWUSR, 
 		new_encode_dev(MKDEV(5, 1))); 
 	if (err < 0) 
 		goto out; 
 	err = sys_mkdir((const char __user __force *) "/root", 0700); 
 	if (err < 0) 
 		goto out; 
 return 0; 
out: 
 	printk(KERN_WARNING "Failed to create a rootfs\n"); 
 	return err; 
} 
rootfs_initcall(default_rootfs); 

(4)打开文件描述符 0、1 和 2。
然后 1 号线程打开控制台的字符设备文件“/dev/console”,得到文件描述符 0,接着两
次复制文件描述符 0,得到文件描述符 1 和 2。

kernel_init -> kernel_init_freeable 
init/main.c 
static noinline void __init kernel_init_freeable(void) 
{ 
 	… 
 	/* 打开rootfs文件系统的字符设备文件“/dev/console” */ 
 	if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0) 
 		pr_err("Warning: unable to open an initial console.\n"); 
 	(void) sys_dup(0); 
 	(void) sys_dup(0);} 

最后 1 号线程在函数 kernel_init 中装载用户程序,转换成用户空间的 1 号进程,分叉生成子进程,子进程从 1 号进程继承打开文件表,继承文件描述符 0、1 和 2。

2.用户指定的文件系统

引导内核的时候,可以使用内核参数“root”指定存储设备的名称,使用内核参数“rootfstype”指定根文件系统的类型。

假设使用SATA硬盘作为存储设备,根文件系统是SATA硬盘a的第一个分区上的EXT4文件系统,那么指定根文件系统的方法如下:

root=/dev/sda1 rootfstype=ext4

假设使用 NAND 闪存作为存储设备,根文件系统是 UBI 设备 1 的卷 rootfs 上的 UBIFS 文件系统,那么指定根文件系统的方法如下:

root=ubi1:rootfs rootfstype=ubifs 

UBIFS 文件系统基于 UBI 设备,UBI 设备是虚拟设备,用户可以在 NAND 闪存的一个分区上创建一个 UBI 设备,然后对 UBI 设备分区,UBI 把分区称为卷。UBI 设备负责如下。
(1)管理 NAND 闪存的坏块。
(2)实现损耗均衡,保证所有擦除块的擦除次数均衡,UBI 使用逻辑擦除块,把逻辑擦除块映射到 NAND 闪存的物理擦除块。内核实现了 UBI 层,位于 UBIFS 文件系统和 MTD 层之间。

(1)解析参数“root”和“rootfstype”。
内核初始化的时候,调用函数 parse_args 解析参数,调用参数的解析函数。

init/main.c 
asmlinkage __visible void __init start_kernel(void) 
{ 
 	… 
 	after_dashes = parse_args("Booting kernel", 
 		static_command_line, __start___param, 
 		__stop___param - __start___param, 
 		-1, -1, NULL, &unknown_bootoption);} 

参数“root”用来指定根文件系统所在的存储设备,解析函数是 root_dev_setup,把设备名称保存在静态变量 saved_root_name 中。

init/do_mounts.c 
static char __initdata saved_root_name[64]; 
static int __init root_dev_setup(char *line) 
{ 
 	strlcpy(saved_root_name, line, sizeof(saved_root_name)); 
 	return 1; 
} 
__setup("root=", root_dev_setup);

参数“rootfstype”用来指定根文件系统的类型,解析函数是 fs_names_setup,把根文件系统的类型保存在静态变量 root_fs_names 中。

init/do_mounts.c 
static char * __initdata root_fs_names; 
static int __init fs_names_setup(char *str) 
{
	root_fs_names = str; 
 	return 1; 
} 
__setup("rootfstype=", fs_names_setup); 

(2)函数 prepare_namespace。
接下来 1 号线程调用函数 prepare_namespace 以挂载根文件系统,主要代码如下:

kernel_init -> kernel_init_freeable -> prepare_namespace 
init/do_mounts.c
1 	void __init prepare_namespace(void) 
2 	{ 
34 		if (saved_root_name[0]) { 
5 			root_device_name = saved_root_name; 
6 			if (!strncmp(root_device_name, "mtd", 3) || 
7				!strncmp(root_device_name, "ubi", 3)) { 
8 					mount_block_root(root_device_name, root_mountflags); 
9 					goto out; 
10 			} 
11 			ROOT_DEV = name_to_dev_t(root_device_name); 
12 			if (strncmp(root_device_name, "/dev/", 5) == 0) 
13 				root_device_name += 5; 
14 	} 
15 
1617 	mount_root(); 
18 	out: 
19 		devtmpfs_mount("dev"); 
20 		sys_mount(".", "/", NULL, MS_MOVE, NULL); 
21 		sys_chroot("."); 
22 	}

第 6~10 行代码,如果存储设备是闪存分区(设备名称以“mtd”开头)或是在闪存分区的基础上封装的 UBI 设备(设备名称以“ubi”开头),那么调用函数 mount_block_root,把根文件系统挂载到 rootfs 文件系统的目录“/root”下。第 17 行代码,如果存储设备是其他设备,例如机械硬盘或固态硬盘,那么调用函数 mount_root,把根文件系统挂载到 rootfs 文件系统的目录“/root”下。第 20 行代码,把根文件系统从目录“/root”移动到根目录下,换言之,以前挂载到目录“/root”下,现在挂载到根目录下。在执行这一步之前,1 号线程的当前工作目录已经是目录“/root”,也就是刚刚挂载的根文件系统的根目录。第 21 行代码,把 1 号线程的根目录设置为根文件系统的根目录。函数 mount_root 的主要代码如下:

init/do_mounts.c 
void __init mount_root(void) 
{#ifdef CONFIG_BLOCK 
 	{ 
 		int err = create_dev("/dev/root", ROOT_DEV); 
 		if (err < 0) 
 			pr_emerg("Failed to create /dev/root: %d\n", err); 
 		mount_block_root("/dev/root", root_mountflags);
 	} 
#endif 
} 

如果根设备是块设备,执行过程如下。
1)创建块设备文件“/dev/root”,使用根设备的设备号。
2)调用函数 mount_block_root,把根文件系统挂载到目录“/root”下。假设根文件系统的类型是 EXT4,存储设备是“/dev/sda1”,挂载根文件系统相当于执行命令“mount –t ext4 /dev/sda1 /root”。现在内核创建块设备文件“/dev/root”,设备号和块设备文件“/dev/sda1”的设备号相同,挂载根文件系统相当于执行命令“mount –t ext4 /dev/root /root”。
(3)函数 mount_block_root。
参数“rootfstype”可能指定多个文件系统类型,使用逗号分隔,函数 mount_block_root依次使用每种文件系统类型尝试挂载根文件系统,直到挂载成功为止,主要代码如下:

init/do_mounts.c
1 	void __init mount_block_root(char *name, int flags) 
2 	{ 
3 		struct page *page = alloc_page(GFP_KERNEL | 
4 			__GFP_NOTRACK_FALSE_POSITIVE); 
5 		char *fs_names = page_address(page); 
6 		char *p; 
7 		#ifdef CONFIG_BLOCK 
8 		char b[BDEVNAME_SIZE]; 
9 		#else 
10 		const char *b = name; 
11 		#endif 
12 
13 		get_fs_names(fs_names); 
14 	retry: 
15 		for (p = fs_names; *p; p += strlen(p)+1) { 
16 			int err = do_mount_root(name, p, flags, root_mount_data); 
17 			switch (err) { 
18 				case 0: 
19 					goto out; 
20 				case -EACCES: 
21 				case -EINVAL: 
22 					continue; 
23 			} 
24 		printk("VFS: Cannot open root device \"%s\" or %s: error %d\n", 
25 			root_device_name, b, err); 
26 		printk("Please append a correct \"root=\" boot option; here are the available partitions:\n"); 
2728 		panic("VFS: Unable to mount root fs on %s", b); 
29 		} 
3031 		panic("VFS: Unable to mount root fs on %s", b); 
32 	out: 
33 		put_page(page); 
34 	} 

第 13 行代码,调用函数 get_fs_names:如果使用参数“rootfstype”指定多个文件系统类型,使用逗号分隔,那么函数 get_fs_names 把逗号替换为字符串的结束符号;如果没有使用参数“rootfstype”指定文件系统类型,那么函数 get_fs_names 把所有注册的文件系统类型添加进来。

第 15~29 行代码,针对指定的每种文件系统类型,调用函数 do_mount_root 尝试挂载。如果指定的文件系统类型和存储设备上的文件系统类型一致,那么挂载成功。

(4)函数 do_mount_root。
函数 do_mount_root 把根文件系统挂载到 rootfs 文件系统的“/root”目录下,代码如下:

init/do_mounts.c
1 	static int __init do_mount_root(char *name, char *fs, int flags, void *data) 
2 	{ 
3 		struct super_block *s; 
4 		int err = sys_mount(name, "/root", fs, flags, data); 
5 		if (err) 
6 			return err; 
7 
8 		sys_chdir("/root"); 
9 		s = current->fs->pwd.dentry->d_sb; 
10 		ROOT_DEV = s->s_dev; 
1112 		return 0; 
13 	} 

第 4 行代码,在目录“/root”下挂载根文件系统。
第 8 行代码,把 1 号线程的当前工作目录设置为目录“/root”,也就是刚刚挂载的根文件系统的根目录。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/789366.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

YOLOv5改进RepViT结构:清华 ICCV 2023,原创Bottleneck设计

RepViT: Revisiting Mobile CNN From ViT Perspective 论文方法从块设计改进YOLOv5结构核心代码1核心代码2核心代码3yaml1yaml2yaml3论文:https://arxiv.org/pdf/2307.09283.pdf   代码:https://github.com/THU-MIG/RepViT 最近,轻量级视觉 Trans

Android App 持续集成性能测试:启动流量

目录 前言&#xff1a; get app UID 获取流量数据 获得启动流量数据 总结 前言&#xff1a; Jenkins 是一种开源的持续集成工具&#xff0c;可以帮助我们更加方便地进行软件开发和测试工作。通过 API 远程管理 Jenkins 可以帮助我们更加方便地进行 Jenkins 的配置和管理工…

临时文档3

值传递 当一个对象被当作参数传递到一个方法后&#xff0c;此方法可改变这个对象的属性&#xff0c;并可返回变化后的结果&#xff0c;那么这里到底是值传递还是引用传递 是值传递。Java 语言的方法调用只支持参数的值传递。当一个对象实例作为一个参数被传递到方法中时&…

什么是Java中的JVM(Java虚拟机)?

JVM&#xff08;Java虚拟机&#xff09;是Java平台的核心组件之一&#xff0c;是一个用于执行Java字节码的虚拟计算机。Java源代码经过编译器编译&#xff0c;生成字节码文件&#xff08;.class文件&#xff09;&#xff0c;然后由JVM来解释和执行这些字节码。JVM负责将字节码翻…

比较6组迭代次数的大小顺序

( A, B )---3*30*2---( 1, 0 )( 0, 1 ) 让网络的输入只有3个节点&#xff0c;AB训练集各由6张二值化的图片组成&#xff0c;让A&#xff0c;B中各有5个点&#xff0c;并且让这10个点的位置没有重合。比较迭代次数。 其中有6组数据 差值结构 A-B 迭代次数 差值结构 构造平均…

tabs-pane锚点

目录 隐藏滚动条 锚点 <div class"right-wrap"><mds-tabs position"right" v-model"active" change"scroll(active, $event)"><mds-tabs-pane v-for"i in moduleNames" :key"i.val" :tab"i…

Android Service启动ANR原理

一、前言 在Service组件StartService()方式启动流程分析文章中&#xff0c;针对Context#startService()启动Service流程分析了源码&#xff0c;其实关于Service启动还有一个比较重要的点是Service启动的ANR&#xff0c;因为因为线上出现了上百例的"executing service &quo…

力扣 435. 无重叠区间

题目来源&#xff1a;https://leetcode.cn/problems/non-overlapping-intervals/description/ C题解1&#xff1a;对区间进行排序&#xff0c;根据区间的末端进行排序&#xff0c;小的在前&#xff0c;大的在后&#xff1b;由于有重复区间&#xff0c;我们拿后面的区间去看是否…

产品设计思考:如何平衡用户习惯和用户体验

在产品设计领域&#xff0c;平衡用户习惯与用户体验之间的关系是一个重要而复杂的任务。 用户习惯是指用户在长期使用产品过程中逐渐形成的一种行为模式&#xff0c;而用户体验则是用户在与产品交互时所感受到的整体感受。 在追求良好的用户体验的同时&#xff0c;还需要考虑用…

【广州华锐互动】AR智慧机房设备巡检系统

AR智慧机房设备巡检系统是一种新型的机房巡检方式&#xff0c;它通过使用增强现实技术将机房设备、环境等信息实时呈现在用户面前&#xff0c;让巡检人员可以更加高效地完成巡检任务。 首先&#xff0c;AR智慧机房设备巡检系统具有极高的智能化程度。该系统可以根据用户设定的…

C语言每天练习----输出乘法口诀表

题目&#xff1a;乘法口诀表 内容&#xff1a;在屏幕上输出9*9乘法口诀表 #define _CRT_SECURE_NO_WARNINGS 1#include <stdio.h>int main() {int a 0;int b 0;for (a 1; a < 9; a) {for (b 1; b < 9; b) {printf("%d*%d%2d\t", a, b, a*b);}}retu…

Docker Compose容器的快速编排

Docker Compose容器的快速编排 一、Docker Compose简介1、Docker Compose是什么2、Docker Compose三大概念 二、Docker Compose 安装与操作1、环境安装2、YAML文件格式及编写注意事项3、Docker Compose配置常用字段4、Docker Compose常用命令5、Docker Compose文件结构6、删除创…

AI智能化技术对项目管理的挑战与应对︱腾讯CSIG能源行业总经理王磊

腾讯CSIG能源行业总经理王磊先生受邀为由PMO评论主办的2023第十二届中国PMO大会演讲嘉宾&#xff0c;演讲议题&#xff1a;AI智能化技术对项目管理的挑战与应对。大会将于8月12-13日在北京举办&#xff0c;敬请关注&#xff01; 议题简要&#xff1a; 自从DALLE、Midjourney、…

chatGPT 学习分享:内含PPT分享下载

InstructGPT论文地址&#xff1a; Training language models to follow instructions with human feedbackchatGPT地址&#xff1a;openAI个人整理的PPT&#xff08;可编辑&#xff09;&#xff0c;下载地址&#xff1a;chatGPT学习分享PPT

二十三种设计模式第十七篇--迭代子模式

迭代子模式是一种行为型设计模式&#xff0c;它允许你按照特定方式访问一个集合对象的元素&#xff0c;而又不暴露该对象的内部结构。迭代子模式提供了一种统一的方式来遍历容器中的元素&#xff0c;而不需要关心容器的底层实现。 该模式包含以下几个关键角色&#xff1a; 迭…

【UGUI基础】Toggle Group组件

Toggle组件&#xff1a;控制一个复选框&#xff0c;打开或关闭选项。 主要属性&#xff1a; isOn&#xff1a;为true时显示&#xff0c;为false隐藏。 onValueChanged&#xff1a;isOn变化时&#xff0c;如果监听了事件会执行对应事件。 Group&#xff1a;做单选时需要添加T…

Acwing.898 数字三角形(动态规划)

题目 给定一个如下图所示的数字三角形&#xff0c;从顶部出发&#xff0c;在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点&#xff0c;一直走到底层&#xff0c;要求找出─条路径&#xff0c;使路径上的数字的和最大。 输入格式 第一行包含整数n&#xff0…

helm部署rabbitmq

1.添加rabbitmq仓库并下载包 helm repo add bitnami https://charts.bitnami.com/bitnami helm pull bitnami/rabbitmq --version 10.1.4 tar -zxvf rabbitmq-10.1.4.tgz mv values.yaml values.yaml.back grep -v "#" values.yaml.back > values.yaml2.helm部署…

Ubuntu18.04配置PX4开发环境

源文件下载 读者可以参考PX4中文维基百科&#xff0c;或者使用下面命令↓ git clone https://github.com/PX4/PX4-Autopilot.git --recursive 下载完成之后&#xff0c;执行脚本安装命令&#xff0c;PX4给我们提供了脚本安装模式 bash ./PX4-Autopilot/Tools/setup/ubuntu.sh …

【雕爷学编程】Arduino动手做(81)--- 8位LED数码管模块2

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…