linux内核mount过程复杂的do_loopback()、attach_recursive_mnt()、propagate_mnt()函数详解

本文对mount过程流程做了较详细的解释。首先以mount /dev/sda3 /home/为例,对普通的内核mount流程做了解释。接着以mount --bind /home/ /home/test为例,对mount bind的内核流程做了详细解释,尤其是mount bind过程的涉及的几个复杂函数做了详细解释。
我觉得attach_recursive_mnt是mount过程最复杂的一个环节,特别烧脑。尤其是容器场景下,大量使用mount bind建立容器和物理机的目录共享,而共享属性有private、share、slave。多种搭配特别容易出现重复mount问题,而如果对attach_recursive_mnt函数源码熟悉,就能从原理上理解mount bind建立的目录共享原理。

涉及的内核源码注释其实更详细,详细看
https://github.com/dongzhiyan-stack/kernel-code-comment

内核版本3.10.96

mount的核心数据结构

struct mount {
    /*mount靠mnt_hash链入mount hash链表,__lookup_mnt()是从该mount hash链表
    搜索mount结构。commit_tree()和attach_mnt()中靠mnt_hash把mount链入mount hash
    链表,并且链入hash表的键值是(父mount结构的vfsmount成员+该mount的挂载点dentry)*/
	struct list_head mnt_hash;
	
     /*父mount,attach_recursive_mnt->mnt_set_mountpoint(),竟然设置为挂点目录所
     在文件系统的mount,也说也是,挂载源的mount的父mount是挂载点目录所在的文件系统的mount结构*/
	struct mount *mnt_parent;
	
     /*挂载点dentry,attach_recursive_mnt->mnt_set_mountpoint()设置为挂载点目录dentry*/
	struct dentry *mnt_mountpoint;
	
    //包含块设备的根目录dentry,每个mount结构对应一个唯一的vfsmount。
	struct vfsmount mnt;
	
    /*commit_tree()靠mnt_child把mount结构添加到mount的parent mount的
    mnt_mounts链表,所以这个看着是mount的子mount结构保存的链表*/
	struct list_head mnt_mounts;	
	
    /*next_mnt()里根据mnt_child返回其mount结构,commit_tree()和attach_mnt()靠
    mnt_child把mount结构添加到mount的mnt_parent的mnt_mounts链表*/
	struct list_head mnt_child;	
	
    //copy_tree()创建的新mount并靠mnt_list添加到该链表,搞不懂有什么用?
	struct list_head mnt_list;
	
    //clone_mnt()把本次挂载的source mount通过其mnt_share链接到克隆母体的mnt_share链表
	struct list_head mnt_share;	
	
    /*clone_mnt()中,把本次挂载slave属性的source mount结构链接到克隆母体mount
    的mnt_slave_list链表。mount结构的mnt_slave_list链表是保存子slave mount的,凡
    是照着一个mount结构克隆生成的mount,都添加到克隆母体的mnt_slave_list链表,克
    隆的mount是母体子slave mount*/
	struct list_head mnt_slave_list;
	
    /* 1 clone_mnt()中,把本次挂载source slave属性的mount结构链接到克隆母体mount
    的mnt_slave_list链表2 clone_mnt()中,克隆母体是slave属性而本次source mount没
    有指定属性,则source mount被添加到与克隆母体同一个mount salve组链表具体添加形
    式是,source mount结构靠其mnt_slave添加到克隆母体的mnt_slave链表。source mount
    和克隆母体靠各自的mnt_slave构成链表,二者是同一个mount slave组成员。如果source
     mount靠其mnt_slave添加到克隆母体的mnt_slave_list链表,则二者是父子关系,不是同组关系。*/
	struct list_head mnt_slave;	
	
    /* 1 clone_mnt()中,本次挂载是slave属性,克隆生成的source mount,即mnt,其
    mnt_master指向克隆母体的mount结构。2 clone_mnt()中,本次挂载没有指定mount属性,
    而克隆母体又是slave属性,则souece mount的mnt_master就是克隆母体的
    mount->mnt_master,二者属于同一个mount slave组。3 正常mount /dev/sda3 /home这
    样生成的mount其mnt_master是NULL,mount bind的share属性的mount其mnt_master是NULL*/
	struct mount *mnt_master;	
	
    //mount所属命名空间,commit_tree()中把mount结构添加到父mount的mnt_ns的list链表
	struct mnt_namespace *mnt_ns;
    //挂载点结构,包含挂载点dentry,attach_recursive_mnt->mnt_set_mountpoint()中设置
	struct mountpoint *mnt_mp;	
	
     //mount group id,一个mount组里,所有的mount结构的mnt_group_id一样.就是靠这个判断两个mount是否属于同一个peer group
	int mnt_group_id;		
};

重点说几个成员

1 每一次mount挂载块设备或者tmpfs都要生成一个mount结构体。mount结构每次mount挂载都生成一个,作为本次挂载的source mount,dest mount是挂载点目录的所在文件系统的mount结构。source mount和dest mount是相对的,现在的source mount说不定下次就成了dest mount。比如”mount -t ext4 /dev/sda1 /” 挂载到根目录,假设本次挂载sda1的source mount结构是 mount1,可以理解成根文件系统对应的mount结构是mount1。接着”mount -t ext4 /dev/sda3 /home/”, 假设本次挂载sda3生成source mount是mount2,由于挂载点目录”/home”是根文件系统下的目录,则dest mount是上次的source mount,即mount1。如果再接着有”mount -t ext4 /dev/sda5 /home/test”, 假设本次挂载sda5的source mount结构是 mount3,由于挂载点目录”/home/test”是sda3 ext4文件系统下的”test”目录,所以本次的dest mount是上次的source mount,即mount2。

2 mount结构的成员struct mount *mnt_parent指向其父mount结构,父子mount该怎么定义呢?就是本次的挂载的source mount和dest mount构成父子关系,即source mount->mnt_parent=dest mount。

3 mount结构的成员struct dentry * mnt_mountpoint指向本次挂载的挂载点目录dentry

4 mount结构的成员struct list_head mnt_child和struct list_head mnt_mounts,commit_tree()函数中,把本次挂载生成的souce mount添加到父mount结构(就是dest mount)的mnt_mounts链表。代码是list_add_tail(&source mount->mnt_child, &parent mount->mnt_mounts)。

5 mount结构的成员struct list_head mnt_share,同一个mount share组的mount结构靠mnt_share构成一个链表。

6 mount结构的成员struct list_head mnt_slave_list和struct list_head mnt_slave。当mount时指slave属性,clone_mnt()中,把本次挂载slave属性的source mount结构通过其mnt_slave成员链接到克隆母体mount的mnt_slave_list链表。list_add(&source mount->mnt_slave, &克隆母体 mount->mnt_slave_list);。mount结构的mnt_slave_list链表是保存子slave mount的,凡是照着一个mount结构克隆生成的mount,都添加到克隆母体的mnt_slave_list链表,克隆的mount是母体的子slave mount。

7 mount结构的成员struct list_head mnt_slave,这里又是mnt_salve成员。如果克隆母体slave属性而本次mount没有指定属性,则本次mount会被添加到与克隆母体同一个mount salve组链表。即list_add(&source mount ->mnt_slave, &克隆母体 mount->mnt_slave)

8 mount结构的成员struct mount *mnt_master。本次挂载的mount属于slave 属性,mnt_master指向其克隆母体的mount结构。没错,slave属性的mount,其mnt_slave指向它克隆母体mount。

1 mount –t ext4 /dev/sda3 /home/内核流程

在这里插入图片描述

首先需要大体说明一下,内核mount有三个要素:挂载源的source mount结构、挂载点目录的dest mount结构、挂载点目录mountpoint结构。比如在mount后期的关键函数do_mount->do_new_mount->do_add_mount->graft_tree->attach_recursive_mnt,参数参数是

static int attach_recursive_mnt(struct mount *source_mnt,
			struct mount *dest_mnt,
			struct mountpoint *dest_mp,
			struct path *parent_path)
{………}

这三个数据在哪些函数中生成?上边的流程图已经标注出来。

我们执行mount –t ext4 /dev/sda3 /home/,/dev/sda3就是挂载源,内核针对它生成一个struct mount结构,作为source mount。该source mount结构就代表了本次mount挂载,因为每一次mount挂载内核都要针对挂载源生成一个struct mount结构。/home是挂载点目录,它本是根文件系统目录下的一个目录,则dest mount就是”/home目录所在的根文件系统挂载时生成的mount结构”。简单总结,source mount是针对挂载源生成的mount结构,代表了本次mount挂载;dest mount是挂载点目录所在的文件系统挂载时生成的mount结构。 挂载点目录mountpoint结构其实就保存了挂载点目录的dentry结构,其他没啥。

1.1 sys_mount ->do_mount ->kern_path过程

  struct path path;
  kern_path(dir_name, LOOKUP_FOLLOW, &path)

dest mount就是在该函数中分配生成。dir_name就是本次挂载点目录”/home”字符串。kern_path获取挂载点目录”/home”的dentry和所在文件系统vfsmount保存到struct path.dentry和path.mnt。如果”/home”目录已经有其他文件系统(块设备或者tmpfs等)挂载,则要一直遍历直至找到最后一次挂载到该目录的文件系统(块设备或者tmpfs等)的根目录dentry存于path.dentry,该块设备挂载时生成的vfsmount结构保存到path.mnt。内核流程可参考:
do_mount ->kern_path->do_path_lookup->filename_lookup->lookup_last->walk_component->lookup_fast->__follow_mount_rcu->__lookup_mnt

源码不再贴了,详细看源码注释
https://github.com/dongzhiyan-stack/kernel-code-comment/tree/master/linux-3.10.96/fs/namespace.c

这点怎么理解,假设本次mount /dev/sda3 /home/命令执行前,先执行了如下命令
mount –t ext4 /dev/sda1 /home 该mount过程生成mount结构mount1(就是前文提到的source mount)。之后ls /home,实际是查看的是sda1 ext4文件系统根目录下的信息

在这里插入图片描述

现在执行kern_path探测”/home”目录,探测过程是:

根文件系统根目录 -> home目录 -> 发现/home目录有文件系统挂载,并且是sda1挂载到了/home目录 -> 找到sda1 ext4文件系统的根目录dentry和sda1挂载到/home目录时生成的mount结构mount1 -> path.dentry=dentry和path.mnt=&mount1.mnt,返回path。这个过程可以简单按照下边描述:

在这里插入图片描述

如果之前有多个块设备挂载到了/home目录,则要一直遍历,直到找到最后一次挂载的块设备
比如
mount –t ext4 /dev/sda1 /home 该mount过程生成mount结构mount1
mount –t ext4 /dev/sda2 /home 该mount过程生成mount结构mount2
mount –t ext4 /dev/sda5 /home 该mount过程生成mount结构mount5

之后ls /home,实际是查看的是sda5 ext4文件系统根目录下的信息

在这里插入图片描述

此时kern_path探测”/home”目录,简单来说是
根文件系统根目录 -> home目录->sda1 ext4文件系统根目录 -> sda2 ext4文件系统根目录 -> sda5 ext4文件系统根目录 -> 找到sda5 ext4文件系统根目录dentry和sda5挂载到/home目录生成的mount结构mount5 -> path.dentry=dentry和path.mnt=&mount5.mnt,返回path。这个过程可以简单按照下边描述:
在这里插入图片描述

1.2 sys_mount ->do_mount->do_new_mount -> vfs_kern_mount 过程

执行细节如下:

struct vfsmount *mnt;
mnt = vfs_kern_mount(type, flags, name, data);

vfs_kern_mount()查找到/dev/sda3的dentry、mnt、inode结构,并由inode得到块设备的bdev,创建superblock结构并初始化其成员,然后读取ext4文件系统磁盘中的超块数据,赋值给superbolck。接着生成本次挂载的source mount结构。

1.3 sys_mount ->do_mount->do_new_mount->do_add_mount->lock_mount过程

struct mountpoint *mp;
mp = lock_mount(path)

验证下来,发现该函数仅仅只是分配生成一个struct mountpoint *mp结构,然后mp->m_dentry=path.dentry设置一下挂载点目录dentry而已。之前错误以为该函数会遍历挂载点目录的所有挂载的文件系统(块设备或者tmpfs等),得到最后一次挂载的文件系统的mount和根目录dentry,然后mp->m_dentry=dentry,想复杂了。这个过程在do_mount ->kern_path已经做过了。

1.4 sys_mount->do_mount->do_new_mount ->do_add_mount->graft_tree->attach_recursive_mnt

是文件系统挂载的后期核心,稍后一起讲。

2 mount bind过程实例演示

前文是个开胃菜,远没有涉及到内核mount过程最复杂的地方,下一节会介绍mount bind命令的内核实现过程,这个就复杂多了。为了便于下一节展开,这一节先举多个例子做个铺垫。

2.1 mount bind基础知识介绍

为什么要有mount bind?mount bind操作有什么影响?

[root@localhost ~]# mount  –t ext4  /dev/sdb  /mnt/ext4/
[root@localhost ~]# mkdir /mnt/ext4/test1
[root@localhost ~]# mkdir /mnt/ext4/test2
[root@localhost ~]# touch  /mnt/ext4/test1/file1
[root@localhost ~]# touch  /mnt/ext4/test2/file2
[root@localhost ~]# mount --bind  /mnt/ext4/test1/  /mnt/ext4/test2/
[root@localhost ~]# ls /mnt/ext4/test1
file1
[root@localhost ~]# ls /mnt/ext4/test2
file1

现在应该可以看到mount bind的初步效果了吧!简单说是把”/mnt/ext4/test1/”目录挂载到”/mnt/ext4/test2/”目录,这样/mnt/ext4/test2原始目录就被隐藏了。ls /mnt/ext4/test2/内核的大体过程是,当内核遍历到”test2”目录dentry时,发现该dentry有挂载属性,因为”/mnt/ext4/test1/”已经挂载到了”/mnt/ext4/test2/”,所以最终得到的是”test1”目录dentry。所以ls /mnt/ext4/test2实际看到的是”/mnt/ext4/test1/”目录下的文件。容器启动时docker –v指定容器中的一个目录mount bind到物理机的一个目录,原理是一样的,之后容器中访问这个目录,实际是访问物理机的目录,实现了目录共享。

mount –-bind可以指定本次挂载的属性
–make-shared:mount属于share mount组(内核叫peer mount),默认属性。mount传播在一个share mount组的mount都会发生。
–make-slave: mount属于slave mount组。mount传播只能由master到slave mount。
–make-private:mount私有,不与其他mount有关系。容器启动时-v指定容器和物理机目录共享,mount时就是这个private属性。

该怎么使用这些属性?还是以上方的例子

[root@localhost ~]#mount  --bind  /mnt/ext4/test1/  /mnt/ext4/test2/
[root@localhost ~]# mkdir /mnt/ext4/test3
[root@localhost ~]# mkdir /mnt/ext4/test4
[root@localhost ~]# mkdir /mnt/ext4/test5
[root@localhost ~]# mount  --make-shared  --bind  /mnt/ext4/test3/  /mnt/ext4/test2/
[root@localhost ~]# mount  --make-slave  --bind  /mnt/ext4/test4/  /mnt/ext4/test2/
[root@localhost ~]# mount  --make-private  --bind  /mnt/ext4/test5/  /mnt/ext4/test2/

该怎么查看mount指定了什么属性呢?
执行cat /proc/self/mountinfo命令即可。常用的mount或者cat /proc/mounts命令查看的mount信息太有限。

[root@localhost ~]# cat /proc/self/mountinfo | grep  /mnt/ext4
53  40  8:16  /       /mnt/ext4       rw,relatime  shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
56  53  8:16  /test1   /mnt/ext4/test2  rw,relatime  shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
65  59  8:16  /test4   /mnt/ext4/test2  rw,relatime  master:31 - ext4 /dev/sdb rw,seclabel,data=ordered
74  65  8:16  /test5   /mnt/ext4/test2  rw,relatime - ext4 /dev/sdb rw,seclabel,data=ordered

首先介绍一下cat /proc/self/mountinfo 命令看到的信息格式

第1是代表mount id,第2列是父mount id,第3列是挂载源块设备的主次设备号,第4列代表挂载源目录,第5列代表挂载点目录,之后的都是mount有关的属性。重点介绍的是第7列的”shared”、”master”,这就是前文提到的mount挂载时指定的“–make-slave”、“–make-private”属性,如果没看到”shared”、”master”,那mount挂载时指定的是”–make-private”属性。还有一点,”shared:31”这个数字31是什么意思?它表示当前的mount属于编号是31的”mount share共享组”,内核叫做mount peer group,不过我觉得mount share共享组更容易理解。”mount share共享组”又是什么意思?mount bind命令挂载的source mount和dest mount就属于一个mount共享组。而”master:31”与”shared:31”有相似之处,但是有区别,下一小节有解释。还有一列信息”/dev/sdb”,这表示挂载源目录所处的文件系统的块设备。

第1列的mount id是怎么回事?我们每次执行mount挂载,比如mount –t ext4 /dev/sdb /mnt/ext4/ ,在内核就会生成一个struct mount结构,即source mount,mount id可以理解成该mount的编号,本例是53,请看前文cat /proc/self/mountinfo的第一行打印,它就是与mount –t ext4 /dev/sdb /mnt/ext4/ 命令对应的详细挂载信息。

第2列的父 mount id又是怎么回事?还是举例,mount –t ext4 /dev/sdb /mnt/ext4/ ,按照第1节最开头的内容,针对挂载源的块设备分配生成了source mount,代表了本次mount挂载生成mount结构,这个source mount的mount id是53。之后/mnt/ext4目录不再是根文件系统下的目录,而是sdb ext4文件系统的根目录。接着mount --bind /mnt/ext4/test1/ /mnt/ext4/test2/,这是把/mnt/ext4/test1目录挂载到/mnt/ext4/test2目录,一个是挂载源目录,一个是挂载点目录。同样的,要挂载源目录生成source mount,即本次mount挂载的mount结构,mount id是56。然后找到挂载点目录/mnt/ext4/test2/所在文件系统挂载时生成的mount结构作为dest mount,即前边mount –t ext4 /dev/sdb /mnt/ext4/是生成的souce mount结构,mount id是53。53号mount上一部还是source mount,这一步就变成dest mount。而每次mount挂载dest mount就是source mount的父mount。

第3列的挂载源目录有没有觉得很奇怪?以mount id是56的mount为例,为什么是”/test1”,而不是”/mnt/ext4/test1”?这是因为,这个mount信息是执行mount --bind /mnt/ext4/test1/ /mnt/ext4/test2/命令生成,这是把”/mnt/ext4/test1”挂载到”/mnt/ext4/test2”,而”/mnt/ext4/test1”是sdb块设备ext4 文件系统下根目录下的”test1”目录。所以转换成”/test1”,表示这是sdb块设备ext4 文件系统下根目录下的”test1”目录。这是内核的规则,记住即可。

2.2 mount bind 的mount share共享组的传播

这个一个更重要的知识点,还是通过例子说明。2.1节的mount挂载全部umount,本节从零开始演示。

[root@localhost ~]# mount -t ext4 /dev/sdb /mnt/ext4/
[root@localhost ~]# cat /proc/self/mountinfo  | grep /mnt/ext4
49 40 8:16 / /mnt/ext4 rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered

这个mount生成了mount id是49的mount

[root@localhost ~]# mount  --bind  /mnt/ext4/test1/  /mnt/ext4/test2/
[root@localhost ~]# cat /proc/self/mountinfo  | grep /mnt/ext4
49 40 8:16 / /mnt/ext4 rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
52 49 8:16 /test1 /mnt/ext4/test2 rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered

这一步生成mount id是52的mount。发现没,它和mount id是49的mount是父子关系。因为它的mount信息的第2列,即父mount id是49。再啰嗦一遍mount --bind /mnt/ext4/test1/ /mnt/ext4/test2/的执行过程:以挂载源”/mnt/ext4/test1”生成本次的source mount,mount id是52,以挂载点目录“/mnt/ext4/test2”所在文件系统挂载时生成的mount为dest mount,该mount id正是49。“/mnt/ext4/test2”所在的文件系统不就是mount -t ext4 /dev/sdb /mnt/ext4/命令执行后生成sdb ext4文件系统,该挂载命令生成的mount就是mount id为49的mount。

[root@localhost ~]# mount  --bind  /mnt/ext4/test3/  /mnt/ext4/test2/
[root@localhost ~]# cat /proc/self/mountinfo  | grep /mnt/ext4
49  40  8:16   /      /mnt/ext4       rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
52  49  8:16   /test1  /mnt/ext4/test2  rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
55  52  8:16   /test3  /mnt/ext4/test2  rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
56  49  8:16   /test3  /mnt/ext4/test1  rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered

这一步生成了mount id是55和56的两个mount,父mount id分别是52和49。

有没有发现一个神奇的地方?mount --bind /mnt/ext4/test3/ /mnt/ext4/test2/命令执行前,有2个mount挂载,但是执行后却有4个mount。一个mount挂载命令不是只会生成一个mount,但是现在却多了一个。这正是mount share共享组传播mount的体现!
首先观察,这4个mount属性都有”shared:31”,说明这4个mount都属于mount share共享组31,并且彼此有父子mount关系。我们就详细说下mount --bind /mnt/ext4/test3/ /mnt/ext4/test2/ 命令执行后发生了什么?

1该命令执行前,已经执行了mount --bind /mnt/ext4/test1/ /mnt/ext4/test2/,把/mnt/ext4/test1目录挂载到了/mnt/ext4/test2。所以执行mount --bind /mnt/ext4/test3/ /mnt/ext4/test2/ 命令,实际是mount --bind /mnt/ext4/test3/ /mnt/ext4/test1/,为什么挂载点目录要从“/mnt/ext4/test2/”转换成”/mnt/ext4/test1/”?因为/mnt/ext4/test1目录挂载到了/mnt/ext4/test2,在遍历/mnt/ext4/test2目录时,发现test2目录有挂载属性,就要转换成最后挂载到该目录的目录,即test1。为什么要这样操作,理解成这是内核规则吧,在访问一个目录A时,如果有多个目录或者块设备挂载到目录A,要转换成最后一次挂载到目录A的目录或者块设备文件系统根目录,这点在1.1节有详细解释,可以回头看看。

2 继续挂载过程,挂载源目录是”/mnt/ext4/test3/”,挂载点目录是” /mnt/ext4/test1/”!针对挂载源目录,生成本次mount bind挂载的source mount,代表了本次的mount,它的mount id是56。针对挂载点目录,同理,找到它所处文件系统挂载时生成的mount结构,作为dest mount,显然mount id是49,即mount -t ext4 /dev/sdb /mnt/ext4/ 命令生成的mount结构。dest mount和source mount 是父子关系,dest mount是父。需要补充一点,在访问”/mnt/ext4/test3/”目录时,也要转换成最后挂载到该目录的目录或者块设备文件系统,本次不需要。

3 问题来了,mount id是55的那个mount哪里冒出来的?它是针对mount id是52的mount传播生成的。过程是这样,本次的mount bind命令,是以49号mount为dest mount,生成了source mount,mount id是56。因为52号mount和49号mount是同一个mount shared组,即31那个mount shared组,则也要以52号mount为dest mount^,以56号mount作为样板克隆生成一个新的source mount^,即mount id是55的那个mount,然后55号mount和52号mount是父子关系,52号mount是父,55号mount的挂载点目录是本次原始mount bind命令的挂载点目录“/mnt/ext4/test2/”。注意,这一步的source mount^和dest mount都加了符号,表示是mount share共享组传播生成的source mount和与之有关的dest mount,不是本次mount bind针对挂载源和挂载点目录的source mount和dest mount。可以简单总结下,mount bind命令执行过程,要遍历与dest mount属于同一个mount share共享组的其他mount,也作为dest mount^,然后以本次mount挂载生成source mount作为克隆样板克隆生成一个source mount^,接着设置source mount^和dest mount^的父子关系。简单来说,本次的mount bind命令的挂载源目录也要挂载到与挂载点目录的dest mount同一个mount share共享组的其他mount。

有一点需要注意,mount id是55的这个mount,cat /proc/self/mountinfo看到的挂载点目录是” /mnt/ext4/test2/”,因为” /mnt/ext4/test1”已经挂载到了” /mnt/ext4/test2/”,所以实际内核访问时是” /mnt/ext4/test1/”目录,看到的mount信息是虚的。而56号mount的挂载点目录原本是“/mnt/ext4/test2/”,但是cat /proc/self/mountinfo看到的挂载点目录却是” /mnt/ext4/test1/”,个人推测原因可能是,这是本次mount bind命令原始mount挂载,在前期执行kern_path()函数时已经探测到” /mnt/ext4/test1/”挂载到了” /mnt/ext4/test2/”,所以就将挂载点目录直接转换成” /mnt/ext4/test1/”了。

接着再来一个复杂的,继续执行如下命令

[root@localhost ~]# mount  --bind  /mnt/ext4/test4/  /mnt/ext4/test2/ 
[root@localhost ~]# cat /proc/self/mountinfo  | grep /mnt/ext4       
49  40  8:16  /      /mnt/ext4 rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
52  49  8:16  /test1  /mnt/ext4/test2 rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
55  52  8:16  /test3  /mnt/ext4/test2 rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
56  49  8:16  /test3  /mnt/ext4/test1 rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
61  55  8:16  /test4  /mnt/ext4/test2 rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
63  49  8:16  /test4  /mnt/ext4/test3 rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
62  56  8:16  /test4  /mnt/ext4/test1 rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered

这次一个mount bind命令生成了mount id是61、63、62的三个mount

63号mount是针对本次的mount bind命令生成source mount,代表了本次的挂载,它的父mount id是49,就是本次的dest mount。挂载点目录是“/mnt/ext4/test3”,因为“/mnt/ext4/test3”挂载到了本次的挂载点目录“/mnt/ext4/test2”。61和62号mount在mount share共享组传播生成mount,父mount id分别是55和56。再啰嗦一遍,二者产生过程是:遍历与dest mount(mount id是49)属于同一个mount share共享组的其他mount(mount id是55和56),也作为dest mount^,然后以本次mount挂载生成source mount(mount id 63)作为克隆样板克隆生成一个source mount^(mount id是61和62)。在mount共享组传播克隆生成的mount,挂载点目录与父mount一样,看cat /proc/self/mountinfo打印可以证明。并且,cat /proc/self/mountinfo查看挂载源和挂载点目录有玄机,这点不再深究。

有个疑问,52号mount命令也与dest mount处于同一个mount share共享组,为什么没有以它为dest mount^,传播克隆生成一个source mount^呢?这是因为52号mount的挂载源目录是” /mnt/ext4/test1”,本次的挂载点目录是” /mnt/ext4/test3”,而挂载点目录不是挂载源目录下的一个目录,这是规则,内核propagate_mnt()中的if (is_subdir(dest_mp->m_dentry, m->mnt.mnt_root))就是这个判断!想了解细节的可以看列出的源码注释。

2.3 mount bind 的mount slave共享组的传播

上一节一直是mount –bind没有带参数,因为默认是“–make-shared”属性,这一节就简单介绍一下“–make-slave”的影响。还是举例子,先把上一节的mount全umount,重新开始。

[root@localhost ~]# mount  --bind  /mnt/ext4/test1/  /mnt/ext4/test2/ 
[root@localhost ~]# cat /proc/self/mountinfo  | grep /mnt/ext4       
49 40 8:16 / /mnt/ext4 rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
52 49 8:16 /test1 /mnt/ext4/test2 rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
[root@localhost ~]# mount  --bind  /mnt/ext4/test3/  /mnt/ext4/test2/ 
[root@localhost ~]# cat /proc/self/mountinfo  | grep /mnt/ext4       
49 40 8:16 / /mnt/ext4 rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
52 49 8:16 /test1 /mnt/ext4/test2 rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
55 52 8:16 /test3 /mnt/ext4/test2 rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
56 49 8:16 /test3 /mnt/ext4/test1 rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered    

好的,现在mount信息是恢复到最初的状态,现在正片开始,开启烧脑时刻:

[root@localhost ~]# mount  --make-slave --bind  /mnt/ext4/test4/  /mnt/ext4/test5
[root@localhost ~]# cat /proc/self/mountinfo  | grep /mnt/ext4
49 40 8:16 / /mnt/ext4 rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
52 49 8:16 /test1 /mnt/ext4/test2 rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
55 52 8:16 /test3 /mnt/ext4/test2 rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
56 49 8:16 /test3 /mnt/ext4/test1 rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
61 49 8:16 /test4 /mnt/ext4/test5 rw,relatime master:31 - ext4 /dev/sdb rw,seclabel,data=ordered

本次mount --make-slave –bind…….生成了一个mount id是61的mount,它的属性有一个”master”,这就表示它有“-- make-slave”属性。接着烧脑

[root@localhost ~]# mount  --bind  /mnt/ext4/test5  /mnt/ext4/test6
[root@localhost ~]# cat /proc/self/mountinfo  | grep /mnt/ext4     
49 40 8:16 / /mnt/ext4 rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
52 49 8:16 /test1 /mnt/ext4/test2 rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
55 52 8:16 /test3 /mnt/ext4/test2 rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
56 49 8:16 /test3 /mnt/ext4/test1 rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
61 49 8:16 /test4 /mnt/ext4/test5 rw,relatime master:31 - ext4 /dev/sdb rw,seclabel,data=ordered
64 49 8:16 /test4 /mnt/ext4/test6 rw,relatime shared:42 master:31 - ext4 /dev/sdb rw,seclabel,data=ordered

本次产生了一个mount id是64的mount,它的属性有“shared:42 master:31”,这表示它不属于mount share 31了,但是它是61号mount的slave。反过来说,61号mount是64号mount的master。

[root@localhost ~]# mount  --bind  /mnt/ext4/test5  /mnt/ext4/test7
[root@localhost ~]# cat /proc/self/mountinfo  | grep /mnt/ext4     
49 40 8:16 / /mnt/ext4 rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
52 49 8:16 /test1 /mnt/ext4/test2 rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
55 52 8:16 /test3 /mnt/ext4/test2 rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
56 49 8:16 /test3 /mnt/ext4/test1 rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
61 49 8:16 /test4 /mnt/ext4/test5 rw,relatime master:31 - ext4 /dev/sdb rw,seclabel,data=ordered
64 49 8:16 /test4 /mnt/ext4/test6 rw,relatime shared:42 master:31 - ext4 /dev/sdb rw,seclabel,data=ordered
67 49 8:16 /test4 /mnt/ext4/test7 rw,relatime shared:45 master:31 - ext4 /dev/sdb rw,seclabel,data=ordered

本次产生了一个mount id是67的mount,它的属性有“shared:45 master:31”,这表示它也不属于mount share 31了,但是它是61号mount的slave。反过来说,61号mount是67号mount的master。

[root@localhost ~]# mount  --bind  /mnt/ext4/test5  /mnt/ext4/test8
[root@localhost ~]# cat /proc/self/mountinfo  | grep /mnt/ext4     
49 40 8:16 / /mnt/ext4 rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
52 49 8:16 /test1 /mnt/ext4/test2 rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
55 52 8:16 /test3 /mnt/ext4/test2 rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
56 49 8:16 /test3 /mnt/ext4/test1 rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
61 49 8:16 /test4 /mnt/ext4/test5 rw,relatime master:31 - ext4 /dev/sdb rw,seclabel,data=ordered
64 49 8:16 /test4 /mnt/ext4/test6 rw,relatime shared:42 master:31 - ext4 /dev/sdb rw,seclabel,data=ordered
67 49 8:16 /test4 /mnt/ext4/test7 rw,relatime shared:45 master:31 - ext4 /dev/sdb rw,seclabel,data=ordered
74 49 8:16 /test4 /mnt/ext4/test8 rw,relatime shared:52 master:31 - ext4 /dev/sdb rw,seclabel,data=ordered

本次产生了一个mount id是74的mount,它的属性有“shared:52 master:31”,这表示它也不属于mount share 31了,但是它是61号mount的slave。反过来说,61号mount是74号mount的master。

下边来个更神奇的

[root@localhost ~]# mkdir /mnt/ext4/test4/test4_1
[root@localhost ~]# mount  --bind  /mnt/ext4/test9  /mnt/ext4/test4/test4_1
[root@localhost ~]# cat /proc/self/mountinfo  | grep /mnt/ext4             
49 40 8:16 / /mnt/ext4 rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
52 49 8:16 /test1 /mnt/ext4/test2 rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
55 52 8:16 /test3 /mnt/ext4/test2 rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
56 49 8:16 /test3 /mnt/ext4/test1 rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
61 49 8:16 /test4 /mnt/ext4/test5 rw,relatime master:31 - ext4 /dev/sdb rw,seclabel,data=ordered
64 49 8:16 /test4 /mnt/ext4/test6 rw,relatime shared:42 master:31 - ext4 /dev/sdb rw,seclabel,data=ordered
67 49 8:16 /test4 /mnt/ext4/test7 rw,relatime shared:45 master:31 - ext4 /dev/sdb rw,seclabel,data=ordered
74 49 8:16 /test4 /mnt/ext4/test8 rw,relatime shared:52 master:31 - ext4 /dev/sdb rw,seclabel,data=ordered
77 49 8:16 /test9 /mnt/ext4/test4/test4_1 rw,relatime shared:31 - ext4 /dev/sdb rw,seclabel,data=ordered
87 64 8:16 /test9 /mnt/ext4/test6/test4_1 rw,relatime shared:63 master:31 - ext4 /dev/sdb rw,seclabel,data=ordered
84 67 8:16 /test9 /mnt/ext4/test7/test4_1 rw,relatime shared:60 master:31 - ext4 /dev/sdb rw,seclabel,data=ordered
81 74 8:16 /test9 /mnt/ext4/test8/test4_1 rw,relatime shared:57 master:31 - ext4 /dev/sdb rw,seclabel,data=ordered
80 61 8:16 /test9 /mnt/ext4/test5/test4_1 rw,relatime master:31 - ext4 /dev/sdb rw,seclabel,data=ordered

本次一个mount命令生成了5个mount。其中77号mount是本次mount命令原始生成的。其余4个是在mount slave组传播生成的,可以发现这4个mount的父mount id依次是64、67、74、61。这里出现了新名字” mount slave组”, 64、67、74、61这4个mount就处于一个mount slave组,61号mount是剩余3个mount的master。本次的挂载命令是mount --bind /mnt/ext4/test9 /mnt/ext4/test4/test4_1,挂载点目录是” /mnt/ext4/test4/test4_1”,该目录没有被挂载过,所以dest mount是49号mount。然后针对挂载源目录生成source mount,即77号mount。接着是在mount share或者slave 共享组里传播生成新的mount,这个过程和上一节是一样的。这里需要特别说明的是由于挂载点目录是” /mnt/ext4/test4/test4_1”,不是52、55、56号mount挂载源目录下的目录,所以没有针对这三个mount生成新的mount。而是61、64、67、74这4个mount挂载源目录下的目录,所以针对这4个mount传播生成了87、84、81、80这4个mount。并且,61、64、67、74这4个mount正好还是一个mount slave 组,61号mount是其他3个mount的master。

87、84、81、80这4个mount的挂载点目录,看着有点奇怪。以87号mount为例,挂载点目录是” /mnt/ext4/test6/test4_1”,不是“/mnt/ext4/test4/test4_1”,这是因为它的父mount显示,” /mnt/ext4/test4/”已经挂载到了” /mnt/ext4/test6/”,所以访问 ”/mnt/ext4/test6/”目录就是访问” /mnt/ext4/test4/” 目录

如果dest mount处于mount share组,那该组的所有mount 都可能会传播mount。但是dest mount处于mount slave组,只能由master mount向其下方的slave mount传播mount,不能反过来。

用下边的流程图表示mount传播过程

mount +mount id

在这里插入图片描述

当执行mount --bind /mnt/ext4/test9 /mnt/ext4/test4/test4_1命令后,在mount share或者slave组传播mount的过程:从dest mount,即mount49开始遍历,一个个开始,mount52、mount55、mount56都不符合条件,下一个是mount61,以它dest mount^,照着source mount克隆生成source mount^,即mount80。然后寻找mount61的下一个mount,但是发现mount61是mount slave组的master,就进入该slave mount组,依次以mount64、mount67、mount74为dest mount^,照着他们master(mount61)对应source mount,即mount80,克隆生成dest mount^,依次是mount87、mount84、mount81。同时,mount80、mount81、mount84、mount87这4个mount又形成一个新的mount slave组,并且mount80是另外三个mount的master。

第3节基本可以看成是对2节的例子的源码解释,更加烧脑。内核mount原理研究起来真的复杂!

3 mount --bind /home/ /home/test内核流程

在这里插入图片描述

3.1 sys_mount ->do_mount->kern_path过程

struct path path;
kern_path(dir_name, LOOKUP_FOLLOW, &path)
前文已经介绍过,再说一次。同样得到挂载点目录”/home/test”的mount和dentry结构,保存到path。如果该目录之前已经有块设备挂载,当然,也是要遍历到最后一次挂载到”/home/test”目录的文件系统(块设备或者tmpfs)的mount和dentry结构,再保存到path。

3.2 sys_mount ->do_loopback -> kern_path 过程

struct path old_path;
kern_path(old_name, LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT, &old_path)

又来了一次kern_path,不过这里有区别。探测/home/挂载源,得到该目录的所在文件系统的vfsmount和目录dentry信息保存到old_path。如果/home/目录已经有块设备挂载了。则要把vfsmount和dentry转成挂载的块设备文件系统的vfsmount和根目录dentry保存到old_path。vfsmount和mount其实很接近,可以理解二者可以相互替代吧。

在mount bind挂载模式下,不仅要kern_path(”/home/test”),获取该目录的mount和dentry,还要得kern_path(”/home/”) 获取该目录的mount和dentry。这里要再详细强调一下,只要执行kern_path()类似函数,在遍历这个路径每一层目录时,都要判断该目录是否有文件系统(块设备或者tmpfs等)挂载到该目录,如果有的话,则要跳转到挂载的文件系统,获取该文件系统的mount结构、根目录dentry,接着继续遍历下一级目录。比如遍历”/home/test”,如果之前已经有mount –t ext4 /dev/sda3 /home。则探测”/home”目录时,发现有sda3的ext4文件系统已经挂载到了”/home”目录,先获取”/home”目录的mount和dentry结构(mount是根文件系统挂载时生成的mount结构),但是发现dentry有挂载属性,则通过固定算法找到sda3这个ext4文件系统的mount结构和根目录dentry。继续遍历sad3 这个ext4文件系统根目录下的test目录,获取该目录dentry,返回mount和dentry。这个过程见lookup_fast->__follow_mount_rcu->__lookup_mnt。

3.3 sys_mount ->do_loopback ->lock_mount

struct mountpoint *mp;
mp = lock_mount(path)
本质只是生成一个struct mountpoint *mp结构,然后mp->m_dentry=path.dentry设置一下挂载点目录dentry而已。

3.4 sys_mount ->do_loopback -> clone_mnt

struct mount *mnt = NULL, *old, *parent;
struct path old_path;
mnt = clone_mnt(old, old_path.dentry, 0);

本次的mount命令是mount --bind /home/ /home/test

该函数的作用是克隆创建本次挂载的mount结构,即本次挂载的source mount,大部分成员复制了old的,old是mount bind源目录/home/所在文件系统的mount结构。也就是说,mount bind操作的source mount是克隆挂载源目录mount克隆生成的。正常的mount操作是do_new_mount -> vfs_kern_mount时根据本次挂载的块设备或者tmpfs等,分配本次挂载的mount结构。
old_path.dentry是/home挂载源目录dentry。如果/home/目录已经有文件系统(块设备或者tmpfs等)挂载了,则要转成最后一次挂载的文件系统的mount结构,赋予old.mnt,然后将它的根目录dentry保存到old_path.dentry。

详细过程还是看源码注释
https://github.com/dongzhiyan-stack/kernel-code-comment/tree/master/linux-3.10.96/fs/namespace.c

3.5 sys_mount ->do_loopback ->graft_tree-> attach_recursive_mnt

正常的mount操作也要执行graft_tree-> attach_recursive_mnt,这里就一起讲了。容器场景下,大量使用mount bind建立容器和物理机的目录共享,而共享属性有private、share、slave。多种搭配特别容易出现重复mount问题,而如果对attach_recursive_mnt函数源码熟悉,就能从原理上理解mount bind建立的目录共享原理。

详细过程还是看源码注释
https://github.com/dongzhiyan-stack/kernel-code-comment/tree/master/linux-3.10.96/fs/namespace.c

函数调用流程是
在这里插入图片描述

该函数关键代码

static int attach_recursive_mnt(struct mount *source_mnt,
			struct mount *dest_mnt,
			struct mountpoint *dest_mp,
			struct path *parent_path)
{
struct mount *child, *p;

propagate_mnt(dest_mnt, dest_mp, source_mnt, &tree_list);
	mnt_set_mountpoint(dest_mnt, dest_mp, source_mnt);
commit_tree(source_mnt);

	list_for_each_entry_safe(child, p, &tree_list, mnt_hash) {
		list_del_init(&child->mnt_hash);
		commit_tree(child);
	}
}

1 执行propagate_mnt(dest_mnt, dest_mp, source_mnt, &tree_list)遍历dest mount下的slave mount组或者share mount组的所有mount结构,每个这种mount都克隆生成一个mount,即所谓的传播mount。克隆生成的mount添加到tree_list链表。
2 执行mnt_set_mountpoint(dest_mnt, dest_mp, source_mnt)设置本次挂载的source mount和dest mount父子关系,设置source mount的挂载点目录。
3 执行commit_tree(source_mnt) 把source mount这个mount结构添加到各个链表,设置mount的文件系统命名空间为父mount的命名空间
4 执行commit_tree(child)把tree_list链表上保存的克隆生成的mount添加到各个mount结构有关的链表。

3.5.1 attach_recursive_mnt-> propagate_mnt 过程

该函数关键代码

int propagate_mnt(struct mount *dest_mnt, struct mountpoint *dest_mp,
		    struct mount *source_mnt, struct list_head *tree_list)
{
   	struct mount *m, *child;
	struct mount *prev_dest_mnt = dest_mnt;
	struct mount *prev_src_mnt  = source_mnt;

    for (m = propagation_next(dest_mnt, dest_mnt); m;
			m = propagation_next(m, dest_mnt)) {
         struct mount *source;

         source =  get_source(m, prev_dest_mnt, prev_src_mnt, &type);
         child = copy_tree(source, source->mnt.mnt_root, type);
         mnt_set_mountpoint(m, dest_mp, child);

         prev_dest_mnt = m;
         prev_src_mnt  = child;
    }
}

1 m = propagation_next(m, dest_mnt) :以dest mount为源头,遍历遍历share mount或者slave mount组的mount并返回给m。
2 source = get_source(m, prev_dest_mnt, prev_src_mnt, &type):得到m对应的”source mount”并返回给soucre。注意,这里的”soucrce mount”加了双引号,这不是本次mount bind操作的source mount,而只是遍历得到share mount或者slave mount组mount,作为”dest mount”,这些”dest mount”对应的” source mount”。
3 child = copy_tree(source, source->mnt.mnt_root, type) :实际是执行clone_mnt()照着source克隆一个mount,即child mount。然后设置child mount的mnt_mountpoint挂载点目录为克隆母体的mnt_mountpoint。还有一个要点,设置child mount的child->mnt.mnt_root是克隆母体的source->mnt.mnt_root。最后返回克隆mount于child。

4 mnt_set_mountpoint(m, dest_mp, child) :克隆生成的child mount作为”source mount”,m作为”dest mount”,设置父子关系,设置child mount的挂载点目录等等。同样吗,这里的”source mount”和”dest mount”也加了引号,并不是本次mount bind操作的source mount和dest mount。而只是遍历得到share mount或者slave mount组mount,作为”dest mount”,这些”dest mount”对应的” source mount”。

下边重点介绍propagation_next()和get_source()两个过程

3.5.1.1 propagate_mnt-> propagation_next过程

先举一个例子,该例子是照着第二节最后演示的示意图实现的,原理都一样。

假设本次操作是 mount --bind /home/ /home/test
mount1是dest mount,自身属于share mount组。mount1_1和mount1_2和mount_1_3属于一个slave mount组,他们的mnt_master都指向mount1。下边没有画全,正常slave mount组的mount->mnt_master都指向其克隆母体。mount1是mount1_1~mount1_3这三个slave mount组1的mount的克隆母体,mount1_1~mount1_3这三个mount都是slave 属性的mount,都是照着mount1克隆生成的。同理,mount1_2又是mount1_2_1~ mount1_2_3这三个slave mount组3的mount的克隆母体。mount2又是mount2_1~ mount2_5这5个slave mount组1的mount的克隆母体。mount1~ mount5这5个mount都属于同一个mount share组。

在这里插入图片描述

struct mount *m
m = propagation_next(m, dest_mnt)

propagation_next函数的作用是:以dest mount为源头,遍历遍历shared mount或者slave mount组的mount并返回。一般情况,dest mount是在share mount组,所以遍历这个share mount组的一个个成员并返回。但是,这个share mount的mount可能是slave mount组的母体。这种情况,就进入这个slave mount组遍历slave mount并返回,遍历完则返回slave mount母体,继续遍历母体所在的那个mount组。注意,slave mount组的mount也可能是另一个slave mount组的母体,这种就继续深入这一层的slave mount遍历mount,遍历完全部的slave mount再返回母体mount那一层。如果没有slave mount,遍历过程非常简单,一个个遍历share mount组的mount就行了。如果中途碰到有个mount是slave mount组的母体,那就遍历这个slave mount组的mount,遍历完返回母体那一层的mount继续遍历。

详细过程看源码注释
https://github.com/dongzhiyan-stack/kernel-code-comment/tree/master/linux-3.10.96/fs/ pnode.c

3.5.1.2 propagate_mnt-> get_source 过程

还是举一个例子

假设本次操作时 mount --bind /home/ /home/test,mount1是dest mount

在这里插入图片描述

struct mount *source;
m = propagation_next(m, dest_mnt
source =  get_source(m, prev_dest_mnt, prev_src_mnt, &type)

get_source()得到propagation_next()返回的这个与dest mount同一个slave mount或者share mount组的mount对应的source mount。规则是,如果是dest是slave mount,则get_source()返回的永远是最近一步的last_src那个mount,last_src初值是source mount,之后一直一直变。如果m为slave mount,则get_source()返回它的克隆母体对应的那个source mount吧。是的,mount1的slave mount组成员mount1_1和mount1_2是m时,get_source返回的就是本次挂载的原始source mount,而这个原始souce mount和mount1就是本次mount bind的原始source mount和原始dest mount。如果propagation_next返回的是share组mount成员,get_source()返回的last_src,永远是propagate_mnt->child = copy_tree(source…) 这个克隆生成child(第一次除外,last_src是本次原始dest mount的source mount),实时在变。

针对shard mount,特别再补充一下,get_soucrce()返回的是dest在shared mount组前一个mount的对应的source mount,即last_src。share mount组的每个mountx (是slave mount组成员克隆母体的除外,如mount1),都要照着get_soucrce()返回的last_src克隆生成一个child。last_src和child的关系是,二者是同一个share mount组的,child和mountx是父子关系,即child->mnt_parent=mountx。child的挂载点目录dentry是本次mount bind操作的挂载点目录dentry。再总结一下,share mount组传播克隆生成mount,父mount是每次propagation_next()返回同一个share mount组的mount;挂载点目录是本次mount bind操作的挂载点目录dentry;克隆mount->mnt.mnt_root是克隆母体的source->mnt.mnt_root,mount bind模式下,可不是块设备文件系统的根目录,而是挂载源目录dentry。

3.5.2 attach_recursive_mnt函数的后半段处理

再列一下attach_recursive_mnt()函数的整理流程
1 attach_recursive_mnt-> propagate_mnt(dest_mnt, dest_mp, source_mnt, &tree_list) 遍历dest mount树下的slave mount组或者share mount组的所有mount,每个这种mount都克隆生成一个mount,即传播mount。克隆生成的mount添加到tree_list链表,下边执行commit_tree()再把这些mount链表添加到各个mount结构有关的链表。
2 attach_recursive_mnt-> mnt_set_mountpoint(dest_mnt, dest_mp, source_mnt)
3 attach_recursive_mnt-> commit_tree(source_mnt)
4 attach_recursive_mnt-> commit_tree(child)

后三步的mnt_set_mountpoint、commit_tree(source_mnt)、commit_tree(child)作用如下。

. mnt_set_mountpoint(dest_mnt, dest_mp, source_mnt) 是设置本次原始source mount和dest mount的父子关系,设置source mount的挂载点目录

. commit_tree(source_mnt)设置source mount的各个mount链表关系、设置命名空间、执行list_add_tail(&mnt->mnt_child, &parent->mnt_mounts) 靠mnt_child把mount结构添加到mount的mnt_parent的mnt_mounts链表。

. commit_tree(child)把本次mount操作过程,在share mount组或者slave mount组传播生成的克隆mount,一一执行commit_tree,设置mount链表关系,执行list_add_tail(&mnt->mnt_child, &parent->mnt_mounts) 靠mnt_child把mount结构添加到mount的mnt_parent的mnt_mounts链表。

Logo

更多推荐