共享子树

1) 概述

考虑以下情况

一个进程想要克隆它自己的命名空间,但是仍然想要访问最近挂载的 CD。共享子树语义提供了完成上述操作的必要机制。

它为诸如每个用户命名空间和版本化文件系统等功能提供了必要的构建块。

2) 功能

共享子树提供了四种不同类型的挂载;更准确地说,是 struct vfsmount

  1. 共享挂载

  2. 从属挂载

  3. 私有挂载

  4. 不可绑定挂载

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 上的挂载。

即使在 /mnt/a 上挂载 /dev/sd0 也是如此。内容在 /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) 用例

  1. 一个进程想要克隆它自己的命名空间,但是仍然想要访问最近挂载的 CD。

    解决方案

    系统管理员可以使 /cdrom 上的挂载共享

    mount --bind /cdrom /cdrom
    mount --make-shared /cdrom
    

    现在,任何克隆出新命名空间的进程都将在 /cdrom 上有一个挂载,它是父命名空间中相同挂载的副本。

    因此,当插入 CD 并挂载到 /cdrom 时,该挂载会传播到所有其他克隆命名空间中 /cdrom 上的其他挂载。

B) 一个进程想要使其挂载对任何其他进程不可见,但仍然能够看到其他系统挂载。

解决方案

首先,管理员可以将整个挂载树标记为可共享

mount --make-rshared /

新进程可以克隆出一个新的命名空间。并将它的命名空间的一部分标记为从属

mount --make-rslave /myprivatetree

因此,进程在 /myprivatetree 中进行的任何挂载都不会出现在任何其他命名空间中。但是,在父命名空间中 /myprivatetree 下进行的挂载仍然会出现在进程的命名空间中。

除了上述语义之外,此功能还提供了解决以下问题的构建块

  1. 每个用户的命名空间

    上述语义提供了一种跨命名空间共享挂载的方法。但是命名空间与进程相关联。如果命名空间成为头等对象,并具有用户 API 来将命名空间与 userid 关联/分离,那么每个用户都可以拥有他/她自己的命名空间,并根据他/她的要求进行定制。这需要在 PAM 中支持。

  2. 版本化文件

    如果整个挂载树在多个位置可见,那么底层版本控制文件系统可以根据用于访问该文件的路径返回该文件的不同版本。

    一个例子是

    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、move、mount、umount 和 clone-namespace 操作的详细语义。

注意:在整个文档中,单词“vfsmount”和名词“mount”已被用来表示相同的事物。

5a) 挂载状态

给定的挂载可以处于以下状态之一

  1. 共享

  2. 从属

  3. 共享和从属

  4. 私有

  5. 不可绑定

“传播事件”定义为在 vfsmount 上生成的事件,该事件导致其他 vfsmount 中挂载或卸载操作。

“对等组”定义为彼此传播事件的 vfsmount 组。

  1. 共享挂载

    “共享挂载”定义为属于“对等组”的 vfsmount。

    例如

    mount --make-shared /mnt
    mount --bind /mnt /tmp
    

    在 /mnt 上的挂载和在 /tmp 上的挂载都是共享的,并且属于同一个对等组。在 /mnt 或 /tmp 下挂载或卸载的任何内容都会反映在其对等组的所有其他挂载中。

  2. 从属挂载

    “从属挂载”定义为接收传播事件但不转发传播事件的 vfsmount。

    顾名思义,从属挂载具有一个主挂载,从中接收挂载/卸载事件。事件不会从从属挂载传播到主挂载。只有共享挂载可以通过执行以下命令来制作成从属挂载

    mount --make-slave mount
    

    被制作成从属的共享挂载不再共享,除非修改为共享。

  3. 共享和从属

    vfsmount 可以既是共享的又是从属的。此状态表示挂载是从某个 vfsmount 的从属,并且也具有其自己的对等组。此 vfsmount 从其主 vfsmount 接收传播事件,并且还将其传播事件转发到其“对等组”和其从属 vfsmount。

    严格来说,vfsmount 是共享的,具有其自己的对等组,并且该对等组是其他某个对等组的从属。

    只有从属 vfsmount 可以通过执行以下命令之一来制作成“共享和从属”

    mount --make-shared mount
    

    或者通过将从属 vfsmount 移动到共享 vfsmount 下。

  4. 私有挂载

    “私有挂载”定义为不接收或转发任何传播事件的 vfsmount。

  5. 不可绑定挂载

    “不可绑定挂载”定义为不接收或转发任何传播事件并且无法绑定挂载的 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.

除了下面列出的命令之外,“move”操作还会根据目标挂载的类型更改挂载的状态。它在第 5d 节中进行了解释。

5b) 绑定语义

考虑以下命令

mount --bind A/a  B/b

其中“A”是源挂载,“a”是挂载“A”中的目录项,“B”是目标挂载,“b”是目标挂载中的目录项。

结果取决于“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   |
**************************************************************************

详细信息

  1. “A”是共享挂载,“B”是共享挂载。一个新的挂载“C”

    是“A”的克隆,被创建。它的根目录项是“a”。“C”被挂载在目录项“b”上的挂载“B”上。还创建了新的挂载“C1”、“C2”、“C3” ... 并且被挂载在目录项“b”上,在“B”传播到的所有挂载上。创建了一个包含“C1”,..,“Cn”的新传播树。此传播树与“B”的传播树相同。最后,“C”的对等组与“A”的对等组合并。

  2. “A”是私有挂载,“B”是共享挂载。一个新的挂载“C”

    是“A”的克隆,被创建。它的根目录项是“a”。“C”被挂载在目录项“b”上的挂载“B”上。还创建了新的挂载“C1”、“C2”、“C3” ... 并且被挂载在目录项“b”上,在“B”传播到的所有挂载上。设置了一个新的传播树,其中包含所有新的挂载“C”、“C1”、..、“Cn”,其配置与“B”的传播树完全相同。

  3. “A”是挂载“Z”的从属挂载,“B”是共享挂载。一个新的

    是“A”的克隆,被创建。它的根目录项是“a”。“C”被挂载在目录项“b”上的挂载“B”上。还创建了新的挂载“C1”、“C2”、“C3” ... 并且被挂载在目录项“b”上,在“B”传播到的所有挂载上。创建了一个包含新挂载“C”,'C1',.. 'Cn' 的新传播树。此传播树与“B”的传播树相同。最后,挂载“C”及其对等组被设置为挂载“Z”的从属。换句话说,挂载“C”处于“从属和共享”状态。

  4. “A”是不可绑定挂载,“B”是共享挂载。这是一个

    无效的操作。

  5. “A”是私有挂载,“B”是非共享(私有、从属或

    不可绑定)挂载。一个新的挂载“C”是“A”的克隆,被创建。它的根目录项是“a”。“C”被挂载在目录项“b”上的挂载“B”上。

  6. “A”是共享挂载,“B”是非共享挂载。一个新的挂载“C”

    是“A”的克隆,被创建。它的根目录项是“a”。“C”被挂载在目录项“b”上的挂载“B”上。“C”被设置为“A”的对等组的成员。

  7. “A”是挂载“Z”的从属挂载,“B”是非共享挂载。一个

    是“A”的克隆,被创建。它的根目录项是“a”。“C”被挂载在目录项“b”上的挂载“B”上。此外,“C”被设置为“Z”的从属挂载。换句话说,“A”和“C”都是“Z”的从属挂载。“Z”上的所有挂载/卸载事件都会传播到“A”和“C”。但是“A”上的挂载/卸载不会传播到任何地方。同样,“C”上的挂载/卸载也不会传播到任何地方。

  8. “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 |
***************************************************************************

注意

移动位于共享挂载下的挂载是无效的。

详细信息如下

  1. “A”是共享挂载,“B”是共享挂载。挂载“A”是

    挂载在目录项“b”上的挂载“B”上。还创建了新的挂载“A1”、“A2”...“An”,并挂载在目录项“b”上,在从挂载“B”接收传播的所有挂载上。创建一个新的传播树,其配置与“B”的配置完全相同。这个新的传播树包含所有新的挂载“A1”、“A2”...“An”。并且这个新的传播树被附加到“A”的已经存在的传播树上。

  2. “A”是私有挂载,“B”是共享挂载。挂载“A”是

    挂载在目录项“b”上的挂载“B”上。还创建了新的挂载“A1”、“A2”...“An”,并挂载在目录项“b”上,在从挂载“B”接收传播的所有挂载上。挂载“A”变成共享挂载,并创建一个与“B”相同的传播树。这个新的传播树包含所有新的挂载“A1”、“A2”...“An”。

  3. “A”是挂载“Z”的从属挂载,“B”是共享挂载。该

    挂载“A”挂载在目录项“b”上的挂载“B”上。还创建了新的挂载“A1”、“A2”...“An”,并挂载在目录项“b”上,在从挂载“B”接收传播的所有挂载上。创建一个新的传播树,其配置与“B”的配置完全相同。这个新的传播树包含所有新的挂载“A1”、“A2”...“An”。并且这个新的传播树被附加到“A”的已经存在的传播树上。挂载“A”继续是“Z”的从属挂载,但它也变为“共享”。

  4. “A”是不可绑定挂载,“B”是共享挂载。操作

    是无效的。因为在共享挂载“B”上挂载任何东西都可以创建新的挂载,这些挂载会被挂载在从“B”接收传播的挂载上。并且由于挂载“A”是不可绑定的,因此无法克隆它以挂载在其他挂载点。

  5. “A”是私有挂载,“B”是非共享(私有、从属或

    不可绑定)挂载。挂载“A”挂载在目录项“b”上的挂载“B”上。

  6. “A”是共享挂载,“B”是非共享挂载。挂载“A”

    挂载在目录项“b”上的挂载“B”上。挂载“A”继续是共享挂载。

  7. “A”是挂载“Z”的从属挂载,“B”是非共享挂载。

    挂载“A”挂载在目录项“b”上的挂载“B”上。挂载“A”继续是挂载“Z”的从属挂载。

  8. “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”。目录项“b”上“B2”上最近挂载的挂载是“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) 测验

  1. 以下命令序列的结果是什么?

    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 相同?

  2. 以下命令序列的结果是什么?

    mount --make-rshared /
    mkdir -p /v/1
    mount --rbind / /v/1
    

    /v/1/v/1 的内容应该是什么?

  3. 以下命令序列的结果是什么?

    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) 常见问题解答

问题 1. 为什么需要绑定挂载?它与符号链接有什么不同?

如果目标挂载被卸载或移动,符号链接可能会失效。即使其他挂载被卸载或移动,绑定挂载仍然存在。

问题 2. 为什么不能使用 exportfs 实现共享子树?

exportfs 是一种实现共享子树可以完成的部分功能的一种重量级方式。我无法想象如何使用 exportfs 实现从属挂载的语义?

问题 3 为什么需要不可绑定挂载?

假设我们想要在同一子树中的多个位置复制挂载树。

如果一个 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,vfsmount 的数量是 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 可以指向主对等组的任意(并且可能不同)成员。要查找对等组的所有直接从属,您需要遍历其成员的 _all_ ->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())

  1. 准备阶段。

  2. 提交阶段。

  3. 中止阶段。

准备阶段

对于源树中的每个挂载

  1. 创建所需数量的挂载树,以便

    附加到从目标挂载接收传播的每个挂载。

  2. 不要将任何树附加到其目标。但记下其 ->mnt_parent 和 ->mnt_mountpoint

  3. 链接所有新的挂载,以形成一个与目标挂载的传播树相同的传播树。

如果此阶段成功,则应有 ‘n’ 个新的传播树;其中 ‘n’ 是源树中挂载的数量。转到提交阶段

还应该有 ‘m’ 个新的挂载树,其中 ‘m’ 是目标挂载传播到的挂载数量。

如果任何内存分配失败,则转到中止阶段。

提交阶段

将每个挂载树附加到其对应的目标挂载。

中止阶段

删除所有新创建的树。

注意

所有与传播相关的功能都位于文件 pnode.c 中


版本 0.1 (创建初始文档, Ram Pai linuxram@us.ibm.com)

版本 0.2 (合并来自 Al Viro 的评论)