共享子树¶
1) 概述¶
考虑以下情况
一个进程想要克隆自己的命名空间,但仍然希望访问最近挂载的 CD。共享子树语义提供了完成上述操作的必要机制。
它为诸如每个用户命名空间和版本化文件系统等功能提供了必要的构建模块。
2) 功能¶
共享子树提供了四种不同的挂载类型;更准确地说,是 struct vfsmount。
共享挂载
从属挂载
私有挂载
不可绑定挂载
2a) 共享挂载可以复制到任意多个挂载点,并且所有副本都保持完全相同。
这是一个例子
假设 /mnt 有一个共享的挂载
mount --make-shared /mnt注意:mount(8) 命令现在支持 --make-shared 标志,因此不再需要示例 ‘smount’ 程序,并且已将其删除。
# mount --bind /mnt /tmp上面的命令将 /mnt 的挂载复制到 /tmp 挂载点,并且两个挂载的内容保持相同。
#ls /mnt a b c #ls /tmp a b c现在假设我们在 /tmp/a 挂载一个设备
# mount /dev/sd0 /tmp/a #ls /tmp/a t1 t2 t3 #ls /mnt/a t1 t2 t3请注意,该挂载也已传播到 /mnt 的挂载。
即使 /dev/sd0 挂载在 /mnt/a 上也是如此。内容也将在 /tmp/a 下可见。
- 2b) 从属挂载类似于共享挂载,只是挂载和卸载事件
仅向其传播。
所有从属挂载都有一个主挂载,该主挂载是共享的。
这是一个例子
假设 /mnt 有一个共享的挂载。# mount --make-shared /mnt
让我们将 /mnt 绑定挂载到 /tmp # mount --bind /mnt /tmp
/tmp 的新挂载将变为共享挂载,它是 /mnt 挂载的副本。
现在让我们将 /tmp 的挂载设为 /mnt 的从属挂载 # mount --make-slave /tmp
让我们在 /mnt/a 上挂载 /dev/sd0 # mount /dev/sd0 /mnt/a
#ls /mnt/a t1 t2 t3
#ls /tmp/a t1 t2 t3
请注意,挂载事件已传播到 /tmp 的挂载。
但是,让我们看看如果我们在 /tmp 的挂载上挂载某些内容会发生什么
# mount /dev/sd1 /tmp/b
#ls /tmp/b s1 s2 s3
#ls /mnt/b
请注意,挂载事件如何未传播到 /mnt 的挂载
2c) 私有挂载不会转发或接收传播。
这是我们熟悉的挂载。它是默认类型。
2d) 不可绑定挂载是不可绑定的私有挂载
假设我们在 /mnt 有一个挂载,我们将其设为不可绑定
# mount --make-unbindable /mnt Let's try to bind mount this mount somewhere else:: # mount --bind /mnt /tmp mount: wrong fs type, bad option, bad superblock on /mnt, or too many mounted file systems绑定不可绑定挂载是无效的操作。
3) 设置挂载状态¶
可以使用 mount 命令(util-linux 包)设置挂载状态
mount --make-shared mountpoint mount --make-slave mountpoint mount --make-private mountpoint mount --make-unbindable mountpoint
4) 用例¶
一个进程想要克隆自己的命名空间,但仍然希望访问最近挂载的 CD。
解决方案
系统管理员可以将 /cdrom 的挂载设为共享
mount --bind /cdrom /cdrom mount --make-shared /cdrom现在,任何克隆出新命名空间的进程都将在 /cdrom 处有一个挂载,该挂载是父命名空间中同一挂载的副本。
因此,当插入 CD 并将其挂载到 /cdrom 时,该挂载会传播到所有其他克隆命名空间中 /cdrom 的其他挂载。
B) 一个进程希望其挂载对任何其他进程不可见,但仍然能够看到其他系统挂载。
解决方案
首先,管理员可以将整个挂载树标记为可共享
mount --make-rshared /新进程可以克隆出新命名空间。并将命名空间的某些部分标记为从属
mount --make-rslave /myprivatetree因此,该进程在 /myprivatetree 中完成的任何挂载都不会在任何其他命名空间中显示。但是,在父命名空间中 /myprivatetree 下完成的挂载仍然会显示在该进程的命名空间中。
除了上述语义之外,此功能还为解决以下问题提供了构建模块
每个用户的命名空间
上述语义允许跨命名空间共享挂载的方法。但是命名空间与进程关联。如果将命名空间设为具有用户 API 的第一类对象以将命名空间与 userid 关联/取消关联,则每个用户都可以拥有自己的命名空间并根据他/她的要求进行定制。这需要在 PAM 中支持。
版本化文件
如果整个挂载树在多个位置可见,则底层版本控制文件系统可以根据用于访问该文件的路径返回文件的不同版本。
一个例子是
mount --make-shared / mount --rbind / /view/v1 mount --rbind / /view/v2 mount --rbind / /view/v3 mount --rbind / /view/v4如果 /usr 挂载了版本控制文件系统,则该挂载也会出现在 /view/v1/usr、/view/v2/usr、/view/v3/usr 和 /view/v4/usr 中
用户可以通过访问 /view/v3/usr/fs/namespace.c 来请求文件 /usr/fs/namespace.c 的 v3 版本。然后,底层的版本控制文件系统可以解密正在请求文件系统的 v3 版本并返回相应的 inode。
5) 详细语义¶
以下部分解释了绑定、rbind、移动、挂载、卸载和克隆命名空间操作的详细语义。
注意:在本文档中,单词 “vfsmount” 和名词 “挂载” 用于表示相同的事物。
5a) 挂载状态
给定的挂载可以处于以下状态之一
共享
从属
共享和从属
私有
不可绑定
“传播事件” 定义为在 vfsmount 上生成的事件,该事件导致其他 vfsmount 中的挂载或卸载操作。
“对等组” 定义为相互传播事件的 vfsmount 组。
共享挂载
“共享挂载” 定义为属于 “对等组” 的 vfsmount。
例如
mount --make-shared /mnt mount --bind /mnt /tmp/mnt 的挂载和 /tmp 的挂载都是共享的,并且属于同一个对等组。在 /mnt 或 /tmp 下挂载或卸载的任何内容都会反映在其对等组的所有其他挂载中。
从属挂载
“从属挂载” 定义为接收传播事件但不转发传播事件的 vfsmount。
顾名思义,从属挂载有一个主挂载,从中接收挂载/卸载事件。事件不会从从属挂载传播到主挂载。只有共享挂载才能通过执行以下命令成为从属挂载
mount --make-slave mount除非修改为共享,否则作为从属的共享挂载不再共享。
共享和从属
vfsmount 可以同时是共享和从属。此状态表示挂载是某个 vfsmount 的从属,并且也有自己的对等组。此 vfsmount 接收来自其主 vfsmount 的传播事件,并且还将传播事件转发到其 “对等组” 和其从属 vfsmount。
严格来说,vfsmount 是共享的,具有自己的对等组,并且此对等组是某个其他对等组的从属。
只有从属 vfsmount 才能通过执行以下命令之一来成为 “共享和从属”
mount --make-shared mount或通过将从属 vfsmount 移动到共享 vfsmount 下。
私有挂载
“私有挂载” 定义为不接收或转发任何传播事件的 vfsmount。
不可绑定挂载
“不可绑定挂载” 定义为不接收或转发任何传播事件且无法绑定挂载的 vfsmount。
状态图
下面的状态图解释了挂载响应各种命令的状态转换
----------------------------------------------------------------------- | |make-shared | make-slave | make-private |make-unbindab| --------------|------------|--------------|--------------|-------------| |shared |shared |*slave/private| private | unbindable | | | | | | | |-------------|------------|--------------|--------------|-------------| |slave |shared | **slave | private | unbindable | | |and slave | | | | |-------------|------------|--------------|--------------|-------------| |shared |shared | slave | private | unbindable | |and slave |and slave | | | | |-------------|------------|--------------|--------------|-------------| |private |shared | **private | private | unbindable | |-------------|------------|--------------|--------------|-------------| |unbindable |shared |**unbindable | private | unbindable | ------------------------------------------------------------------------ * if the shared mount is the only mount in its peer group, making it slave, makes it private automatically. Note that there is no master to which it can be slaved to. ** slaving a non-shared mount has no effect on the mount.除了下面列出的命令外,“移动” 操作还会根据目标挂载的类型更改挂载的状态。这在 5d 节中进行了说明。
5b) 绑定语义
考虑以下命令
mount --bind A/a B/b其中 “A” 是源挂载,“a” 是挂载 “A” 中的 dentry,“B” 是目标挂载,“b” 是目标挂载中的 dentry。
结果取决于 “A” 和 “B” 的挂载类型。下表包含快速参考
-------------------------------------------------------------------------- | BIND MOUNT OPERATION | |************************************************************************| |source(A)->| shared | private | slave | unbindable | | dest(B) | | | | | | | | | | | | | v | | | | | |************************************************************************| | shared | shared | shared | shared & slave | invalid | | | | | | | |non-shared| shared | private | slave | invalid | **************************************************************************详细信息
- “A” 是共享挂载,“B” 是共享挂载。创建新的挂载 “C”
它是 “A” 的克隆。它的根 dentry 是 “a”。“C” 在挂载 “B” 上的 dentry “b” 处挂载。此外,创建新的挂载 “C1”、“C2”、“C3”...,并在 “B” 传播到的所有挂载上的 dentry “b” 处挂载。创建包含 “C1”...,“Cn” 的新传播树。此传播树与 “B” 的传播树相同。最后,将 “C” 的对等组与 “A” 的对等组合并。
- “A” 是私有挂载,“B” 是共享挂载。创建新的挂载 “C”
它是 “A” 的克隆。它的根 dentry 是 “a”。“C” 在挂载 “B” 上的 dentry “b” 处挂载。此外,创建新的挂载 “C1”、“C2”、“C3”...,并在 “B” 传播到的所有挂载上的 dentry “b” 处挂载。设置一个包含所有新挂载 “C”、“C1”...、“Cn” 的新传播树,其配置与 “B” 的传播树完全相同。
- “A” 是挂载 “Z” 的从属挂载,“B” 是共享挂载。一个新的
创建挂载点 ‘C’,它是 ‘A’ 的克隆。其根目录项是 ‘a’。 ‘C’ 被挂载到挂载点 ‘B’ 的目录项 ‘b’ 上。同时,创建新的挂载点 ‘C1’、‘C2’、‘C3’ ... 并将它们挂载到所有 ‘B’ 传播到的挂载点的目录项 ‘b’ 上。创建一个新的传播树,其中包含新的挂载点 ‘C’、‘C1’、...、‘Cn’。这个传播树与 ‘B’ 的传播树相同。最后,挂载点 ‘C’ 及其对等组被设置为挂载点 ‘Z’ 的从属。换句话说,挂载点 ‘C’ 处于 ‘从属且共享’ 状态。
- ‘A’ 是一个不可绑定挂载点,而 ‘B’ 是一个共享挂载点。这是一个
无效操作。
- ‘A’ 是一个私有挂载点,而 ‘B’ 是一个非共享(私有、从属或
不可绑定)挂载点。创建一个新的挂载点 ‘C’,它是 ‘A’ 的克隆。其根目录项是 ‘a’。 ‘C’ 被挂载到挂载点 ‘B’ 的目录项 ‘b’ 上。
- ‘A’ 是一个共享挂载点,而 ‘B’ 是一个非共享挂载点。创建一个新的挂载点 ‘C’
,它是 ‘A’ 的克隆。其根目录项是 ‘a’。 ‘C’ 被挂载到挂载点 ‘B’ 的目录项 ‘b’ 上。 ‘C’ 被设置为 ‘A’ 的对等组的成员。
- ‘A’ 是挂载点 ‘Z’ 的从属挂载点,而 ‘B’ 是一个非共享挂载点。一个
新的挂载点 ‘C’ 被创建,它是 ‘A’ 的克隆。其根目录项是 ‘a’。 ‘C’ 被挂载到挂载点 ‘B’ 的目录项 ‘b’ 上。此外,‘C’ 被设置为 ‘Z’ 的从属挂载点。换句话说,‘A’ 和 ‘C’ 都是 ‘Z’ 的从属挂载点。 ‘Z’ 上的所有挂载/卸载事件都会传播到 ‘A’ 和 ‘C’。但是 ‘A’ 上的挂载/卸载不会传播到任何其他地方。同样,‘C’ 上的挂载/卸载也不会传播到任何其他地方。
- ‘A’ 是一个不可绑定挂载点,而 ‘B’ 是一个非共享挂载点。这是一个
无效操作。不可绑定挂载点不能被绑定挂载。
5c) Rbind 语义
rbind 与 bind 相同。Bind 复制指定的挂载点。Rbind 复制属于指定挂载点的树中的所有挂载点。Rbind 挂载是应用于树中所有挂载点的绑定挂载。
如果 rbind 的源树包含一些不可绑定挂载点,那么在新的位置,不可绑定挂载点下的子树将被修剪掉。
例如
假设我们有以下挂载树
A / \ B C / \ / \ D E F G假设树中除挂载点 C 之外的所有挂载点都是非不可绑定类型。
如果此树被 rbind 到比如 Z
我们将在新位置获得以下树
Z | A' / B' Note how the tree under C is pruned / \ in the new location. D' E'
5d) 移动语义
考虑以下命令
mount --move A B/b
其中 ‘A’ 是源挂载点,‘B’ 是目标挂载点,而 ‘b’ 是目标挂载点中的目录项。
结果取决于 ‘A’ 和 ‘B’ 的挂载点类型。下表是一个快速参考
--------------------------------------------------------------------------- | MOVE MOUNT OPERATION | |************************************************************************** | source(A)->| shared | private | slave | unbindable | | dest(B) | | | | | | | | | | | | | v | | | | | |************************************************************************** | shared | shared | shared |shared and slave| invalid | | | | | | | |non-shared| shared | private | slave | unbindable | ***************************************************************************注意
移动共享挂载点下的挂载点是无效的。
详情如下
- ‘A’ 是一个共享挂载点,而 ‘B’ 是一个共享挂载点。挂载点 ‘A’ 被
挂载到挂载点 ‘B’ 的目录项 ‘b’ 上。同时,创建新的挂载点 ‘A1’、‘A2’...‘An’ 并将其挂载到所有接收来自挂载点 ‘B’ 的传播的挂载点的目录项 ‘b’ 上。创建一个新的传播树,其配置与 ‘B’ 的完全相同。这个新的传播树包含所有新的挂载点 ‘A1’、‘A2’... ‘An’。并且这个新的传播树被附加到 ‘A’ 的已存在传播树上。
- ‘A’ 是一个私有挂载点,而 ‘B’ 是一个共享挂载点。挂载点 ‘A’ 被
挂载到挂载点 ‘B’ 的目录项 ‘b’ 上。同时,创建新的挂载点 ‘A1’、‘A2’... ‘An’ 并将其挂载到所有接收来自挂载点 ‘B’ 的传播的挂载点的目录项 ‘b’ 上。挂载点 ‘A’ 变为共享挂载点,并创建一个与 ‘B’ 相同的传播树。这个新的传播树包含所有新的挂载点 ‘A1’、‘A2’... ‘An’。
- ‘A’ 是挂载点 ‘Z’ 的从属挂载点,而 ‘B’ 是一个共享挂载点。该
挂载点 ‘A’ 被挂载到挂载点 ‘B’ 的目录项 ‘b’ 上。同时,创建新的挂载点 ‘A1’、‘A2’... ‘An’ 并将其挂载到所有接收来自挂载点 ‘B’ 的传播的挂载点的目录项 ‘b’ 上。创建一个新的传播树,其配置与 ‘B’ 的完全相同。这个新的传播树包含所有新的挂载点 ‘A1’、‘A2’... ‘An’。并且这个新的传播树被附加到 ‘A’ 的已存在传播树上。挂载点 ‘A’ 继续是 ‘Z’ 的从属挂载点,但它也变为 ‘共享’。
- ‘A’ 是一个不可绑定挂载点,而 ‘B’ 是一个共享挂载点。此操作
无效。因为在共享挂载点 ‘B’ 上挂载任何东西都会创建新的挂载点,这些挂载点会挂载到接收来自 ‘B’ 的传播的挂载点上。由于挂载点 ‘A’ 是不可绑定的,因此无法将其克隆以挂载在其他挂载点上。
- ‘A’ 是一个私有挂载点,而 ‘B’ 是一个非共享(私有、从属或
不可绑定)挂载点。挂载点 ‘A’ 被挂载到挂载点 ‘B’ 的目录项 ‘b’ 上。
- ‘A’ 是一个共享挂载点,而 ‘B’ 是一个非共享挂载点。挂载点 ‘A’
被挂载到挂载点 ‘B’ 的目录项 ‘b’ 上。挂载点 ‘A’ 继续是共享挂载点。
- ‘A’ 是挂载点 ‘Z’ 的从属挂载点,而 ‘B’ 是一个非共享挂载点。
挂载点 ‘A’ 被挂载到挂载点 ‘B’ 的目录项 ‘b’ 上。挂载点 ‘A’ 继续是挂载点 ‘Z’ 的从属挂载点。
- ‘A’ 是一个不可绑定挂载点,而 ‘B’ 是一个非共享挂载点。挂载点
‘A’ 被挂载到挂载点 ‘B’ 的目录项 ‘b’ 上。挂载点 ‘A’ 继续是不可绑定挂载点。
5e) 挂载语义
考虑以下命令
mount device B/b‘B’ 是目标挂载点,而 ‘b’ 是目标挂载点中的目录项。
上述操作与绑定操作相同,但源挂载点始终是私有挂载点。
5f) 卸载语义
考虑以下命令
umount A其中 ‘A’ 是挂载到挂载点 ‘B’ 的目录项 ‘b’ 上的挂载点。
如果挂载点 ‘B’ 是共享的,那么在接收来自挂载点 ‘B’ 的传播且内部没有子挂载点的挂载点上的目录项 ‘b’ 处,所有最近挂载的挂载点都将被卸载。
示例:假设 ‘B1’、‘B2’、‘B3’ 是彼此传播的共享挂载点。
假设 ‘A1’、‘A2’、‘A3’ 分别首先挂载在挂载点 ‘B1’、‘B2’ 和 ‘B3’ 的目录项 ‘b’ 上。
假设 ‘C1’、‘C2’、‘C3’ 接下来分别挂载在挂载点 ‘B1’、‘B2’ 和 ‘B3’ 的相同目录项 ‘b’ 上。
如果 ‘C1’ 被卸载,则在 ‘B1’ 以及 ‘B1’ 传播到的挂载点上,所有最近挂载的挂载点都将被卸载。
‘B1’ 传播到 ‘B2’ 和 ‘B3’。在 ‘B2’ 上目录项 ‘b’ 处最近挂载的挂载点是 ‘C2’,而 ‘B3’ 上的挂载点是 ‘C3’。
因此,所有 ‘C1’、‘C2’ 和 ‘C3’ 都应该被卸载。
如果 ‘C2’ 或 ‘C3’ 中的任何一个有子挂载点,则不会卸载该挂载点,但会卸载所有其他挂载点。但是,如果 ‘C1’ 被指示卸载且 ‘C1’ 有子挂载点,则 umount 操作将完全失败。
5g) 克隆命名空间
克隆的命名空间包含与父命名空间相同的所有挂载点。
假设 ‘A’ 和 ‘B’ 是父命名空间和子命名空间中对应的挂载点。
如果 ‘A’ 是共享的,那么 ‘B’ 也是共享的,并且 ‘A’ 和 ‘B’ 会彼此传播。
如果 ‘A’ 是 ‘Z’ 的从属挂载点,那么 ‘B’ 也是 ‘Z’ 的从属挂载点。
如果 ‘A’ 是一个私有挂载点,那么 ‘B’ 也是一个私有挂载点。
如果 ‘A’ 是一个不可绑定挂载点,那么 ‘B’ 也是一个不可绑定挂载点。
6) 测验¶
以下命令序列的结果是什么?
mount --bind /mnt /mnt mount --make-shared /mnt mount --bind /mnt /tmp mount --move /tmp /mnt/1/mnt /mnt/1 /mnt/1/1 的内容应该是什么? 它们应该都相同吗?还是只有 /mnt 和 /mnt/1 相同?
以下命令序列的结果是什么?
mount --make-rshared / mkdir -p /v/1 mount --rbind / /v/1/v/1/v/1 的内容应该是什么?
以下命令序列的结果是什么?
mount --bind /mnt /mnt mount --make-shared /mnt mkdir -p /mnt/1/2/3 /mnt/1/test mount --bind /mnt/1 /tmp mount --make-slave /mnt mount --make-shared /mnt mount --bind /mnt/1/2 /tmp1 mount --make-slave /mnt此时,我们在 /tmp 有第一个挂载点,其根目录项是 1。我们称这个挂载点为 ‘A’。然后我们在 /tmp1 有第二个挂载点,其根目录项是 2。我们称这个挂载点为 ‘B’。接下来,我们在 /mnt 有第三个挂载点,其根目录项为 mnt。我们称这个挂载点为 ‘C’。
‘B’ 是 ‘A’ 的从属,而 ‘C’ 是 ‘B’ 的从属。 A -> B -> C
此时,如果我们执行以下命令
mount --bind /bin /tmp/test
挂载尝试在 ‘A’ 上进行。
挂载会传播到 ‘B’ 和 ‘C’ 吗?
/mnt/1/test 的内容应该是什么?
7) 常见问题¶
- Q1. 为什么需要绑定挂载?它与符号链接有何不同?
如果目标挂载点被卸载或移动,符号链接可能会失效。即使其他挂载点被卸载或移动,绑定挂载仍然存在。
Q2. 为什么不能使用 exportfs 来实现共享子树?
exportfs 是一种实现共享子树部分功能的重量级方法。我无法想象如何使用 exportfs 来实现从属挂载的语义?
Q3 为什么需要不可绑定挂载?
假设我们想在同一子树中的多个位置复制挂载树。
如果一个 rbind 将一个树在同一子树内挂载 ‘n’ 次,则创建的挂载点数量是 ‘n’ 的指数函数。拥有不可绑定挂载点可以帮助修剪不需要的绑定挂载点。这是一个示例。
- 步骤 1
假设根树只有两个目录,其中一个 vfsmount
root / \ tmp usr And we want to replicate the tree at multiple mountpoints under /root/tmp- 步骤 2
mount --make-shared /root mkdir -p /tmp/m1 mount --rbind /root /tmp/m1新树现在看起来像这样
root / \ tmp usr / m1 / \ tmp usr / m1 it has two vfsmounts- 步骤 3
mkdir -p /tmp/m2 mount --rbind /root /tmp/m2 the new tree now looks like this:: root / \ tmp usr / \ m1 m2 / \ / \ tmp usr tmp usr / \ / m1 m2 m1 / \ / \ tmp usr tmp usr / / \ m1 m1 m2 / \ tmp usr / \ m1 m2 it has 6 vfsmounts- 步骤 4
- ::
mkdir -p /tmp/m3 mount --rbind /root /tmp/m3
我不会绘制树...但它有 24 个 vfsmounts
在步骤 i,vfsmounts 的数量是 V[i] = i*V[i-1]。这是一个指数函数。而且这棵树的挂载点比我们最初真正需要的要多得多。
可以在每个步骤使用一系列 umount 来修剪不需要的挂载点。但有一个更好的解决方案。不可克隆的挂载点在这里派上用场。
- 步骤 1
假设根树只有两个目录,其中一个 vfsmount
root / \ tmp usr How do we set up the same tree at multiple locations under /root/tmp- 步骤 2
mount --bind /root/tmp /root/tmp mount --make-rshared /root mount --make-unbindable /root/tmp mkdir -p /tmp/m1 mount --rbind /root /tmp/m1新树现在看起来像这样
root / \ tmp usr / m1 / \ tmp usr- 步骤 3
mkdir -p /tmp/m2 mount --rbind /root /tmp/m2新树现在看起来像这样
root / \ tmp usr / \ m1 m2 / \ / \ tmp usr tmp usr- 步骤 4
mkdir -p /tmp/m3 mount --rbind /root /tmp/m3新树现在看起来像这样
root / \ tmp usr / \ \ m1 m2 m3 / \ / \ / \ tmp usr tmp usr tmp usr
8) 实现¶
8A) 数据结构
在 struct vfsmount 中引入了 4 个新字段
->mnt_share
->mnt_slave_list
->mnt_slave
->mnt_master
- ->mnt_share
将所有此 vfsmount 发送/接收传播事件的挂载点链接在一起。
- ->mnt_slave_list
链接此 vfsmount 传播到的所有挂载点。
- ->mnt_slave
链接其主 vfsmount 传播到的所有从属。
- ->mnt_master
指向此 vfsmount 从中接收传播的主 vfsmount。
- ->mnt_flags
采用另外两个标志来指示 vfsmount 的传播状态。MNT_SHARE 指示 vfsmount 是共享的 vfsmount。MNT_UNCLONABLE 指示 vfsmount 不能被复制。
对等组中的所有共享 vfsmount 通过 ->mnt_share 形成一个循环列表。
所有具有相同 ->mnt_master 的 vfsmount 在 ->mnt_master->mnt_slave_list 中形成一个循环列表,并通过 ->mnt_slave 进行。
->mnt_master 可以指向主对等组的任意(可能不同的)成员。要找到对等组的所有直接从属,您需要遍历其成员的 _所有_ ->mnt_slave_list。从概念上讲,它只是一个集合 - 各个列表之间的分布不会影响传播或操作修改传播树的方式。
对等组中的所有 vfsmount 都具有相同的 ->mnt_master。如果它不是 NULL,则它们形成从属列表的连续(有序)段。
示例传播树如下图所示。[注意:虽然它看起来像一个森林,但如果我们将所有共享挂载点视为一个称为 ‘pnode’ 的概念实体,它就变成了一棵树]
A <--> B <--> C <---> D /|\ /| |\ / F G J K H I / E<-->K /|\ M L N在上图中,A、B、C 和 D 都是共享的,并且彼此传播。‘A’ 有 3 个从属挂载点 ‘E’、‘F’ 和 ‘G’,‘C’ 有 2 个从属挂载点 ‘J’ 和 ‘K’,而 ‘D’ 有 2 个从属挂载点 ‘H’ 和 ‘I’。‘E’ 也与 ‘K’ 共享,并且它们彼此传播。而 ‘K’ 有 3 个从属 ‘M’、‘L’ 和 ‘N’。
A 的 ->mnt_share 链接到 ‘B’、‘C’ 和 ‘D’ 的 ->mnt_share
A 的 ->mnt_slave_list 链接到 ‘E’、‘K’、‘F’ 和 ‘G’ 的 ->mnt_slave
E 的 ->mnt_share 链接到 K 的 ->mnt_share
‘E’、‘K’、‘F’、‘G’ 的 ->mnt_master 指向 ‘A’ 的 struct vfsmount
‘M’、‘L’、‘N’ 的 ->mnt_master 指向 ‘K’ 的 struct vfsmount
K 的 ->mnt_slave_list 链接到 ‘M’、‘L’ 和 ‘N’ 的 ->mnt_slave
C 的 ->mnt_slave_list 链接到 ‘J’ 和 ‘K’ 的 ->mnt_slave
J 和 K 的 ->mnt_master 指向 C 的 struct vfsmount
最后,D 的 ->mnt_slave_list 链接到 ‘H’ 和 ‘I’ 的 ->mnt_slave
‘H’ 和 ‘I’ 的 ->mnt_master 指向 ‘D’ 的 struct vfsmount。
注意:传播树与挂载树是正交的。
8B 锁定
->mnt_share、 ->mnt_slave、 ->mnt_slave_list、 ->mnt_master 由 namespace_sem 保护(修改时为独占,读取时为共享)。
通常,我们通过 vfsmount_lock 序列化 ->mnt_flags 的修改。有两个例外:do_add_mount() 和 clone_mnt()。前者修改的 vfsmount 尚未在任何共享数据结构中可见。后者持有 namespace_sem,并且对 vfsmount 的唯一引用位于没有 namespace_sem 就无法遍历的列表中。
8C 算法
实现的关键在于 rbind/move 操作。
总体算法将操作分为 3 个阶段:(查看 attach_recursive_mnt() 和 propagate_mnt())
准备阶段。
提交阶段。
中止阶段。
准备阶段
对于源树中的每个挂载点
- 创建必要数量的挂载树,以便
附加到从目标挂载点接收传播的每个挂载点。
不要将任何树附加到其目标。但是记下其 ->mnt_parent 和 ->mnt_mountpoint
链接所有新的挂载点,形成一个与目标挂载点的传播树相同的传播树。
如果此阶段成功,则应有 ‘n’ 个新的传播树;其中 ‘n’ 是源树中挂载点的数量。转到提交阶段
还应该有 ‘m’ 个新的挂载树,其中 ‘m’ 是目标挂载点传播到的挂载点数量。
如果任何内存分配失败,则转到中止阶段。
- 提交阶段
将每个挂载树附加到其相应的目标挂载点。
- 中止阶段
删除所有新创建的树。
注意
所有与传播相关的功能都位于文件 pnode.c 中
版本 0.1(创建初始文档,Ram Pai linuxram@us.ibm.com)
版本 0.2(整合了 Al Viro 的评论)