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链表。