跳转至

Mount命名空间

挂载命名空间

挂载命名空间是第一个添加到 Linux 的命名空间类型,出现在 2002 年的 Linux 2.4.19 中

它们可隔离命名空间中的进程所看到的挂载点列表,换言之,每个挂载命名空间都有自己的挂载点列表,这意味着不同命名空间中的进程可以看到并操作单个目录层次结构的不同视图

image-20230311151324467

使用

  • 初始命名空间

当系统首次启动时,有一个单一的挂载命名空间,即所谓的“初始命名空间”

  • 创建新的挂载命名空间

CLONE_NEWNS 标志的 clone()(在新命名空间中创建新子进程)或 unshare()(将调用方移到新命名空间中)可创建新的挂载命名空间

当新的装挂载名空间被创建时,它将接收 clone()unshare() 的调用者的命名空间的挂载点列表的拷贝

  • 在新的挂载空间独立挂载与卸载

clone()unshare() 之后,可以在每个命名空间中独立地添加和删除挂载点(通过 mount()umount()

对挂载点列表的更改(默认情况下)仅对进程所在的挂载命名空间中的进程可见;这些更改在其他挂载命名空间中不可见

用途

挂载命名空间有多种用途,例如:

  • 可以提供文件系统的每个用户视图
  • 可以为新的 PID 命名空间挂载 /proc 文件系统,而不会对其它进程造成副作用
  • 还可通过 chroot() 将进程隔离到单个目录层次结构中

在某些用例中,挂载命名空间与绑定挂载一起使用

共享子树

解决的问题

挂载命名空间实现后,用户空间的程序员就遇到了一个可用性问题:挂载命名空间在命名空间之间提供了太多的隔离

例如:假设一个新磁盘加载到一个光盘驱动器中;在原来实现中,使该磁盘在所有挂载命名空间中可见的唯一方法是在每个命名空间中分别挂载该磁盘;但在许多情况下,最好仅执行一个挂载操作,就可使磁盘在系统上的所有挂载命名空间(或某些子集)中可见

因此,共享子树特性被添加到 Linux 2.6.15 中(在 2006 年初,即大约挂载命名空间实现了三年后)

共享子树的主要优点是:

  • 允许在命名空间之间自动
  • 可控地传播挂载和卸载事件

这意味着,例如,在一个挂载命名空间中挂载一个光盘可能会使得所有其他命名空间中都挂载该光盘

传播类型

在共享子树特性下,每个挂载点都用“传播类型”标记,该类型决定在此挂载点下创建和删除的挂载点是否传播到其他挂载点

有四种不同的传播类型:

  • MS_SHARED

此挂载点与同一“对等组”中的其它挂载点共享挂载和卸载事件

当在此挂载点下添加或删除挂载点时,此更改将传播到对等组,因此挂载或卸载也会发生在每个对等挂载点

传播也会反向进行,因此对等挂载上的挂载和卸载事件也会传播到此挂载点。

  • MS_PRIVATE

与共享挂载点相反。挂载点不会将事件传播到任何对等方,也不会从任何对等方接收传播事件

  • MS_SLAVE

这种传播类型介于共享挂载和私有挂载之间

从挂载有一个主挂载 --- 一个共享对等组,其成员将挂载和卸载事件传播到从属挂载

但是,从属挂载不会将事件传播到主挂载对等组

  • MS_UNBINDABLE

该挂载点不可绑定

与私有挂载点一样,此挂载点不会将事件传播到对等方或接收来自对等方传播的事件

此外,此挂载点不能作为绑定挂载操作的源

传播类型补充

  • 传播类型是一个每个挂载点单独配置

在一个命名空间中,某些挂载点可能标记为共享,而其它挂载点则标记为私有(或从属或不可绑定)

  • 传播类型决定了在挂载点下挂载和卸载事件的传播

因此,如果在共享挂载 X 下创建了子挂载 Y,则该子挂载将传播到对等组中的其它挂载点

但是,X 的传播类型不会影响在 Y 下创建和删除的挂载点;Y 下的事件的传播与否取决于对 Y 传播类型的定义

类似地,当 X 被卸载时,卸载事件是否会传播取决于其父挂载点为其定义的传播类型

对等组

对等组是一组挂载点,它们互相传播挂载和卸载事件

在这两种情况下,新挂载点都会成为现有挂载点的对等组的成员:当传播类型是共享的挂载点在创建新命名空间时被复制或作为绑定挂载的源时,对等组会获得新成员(对于绑定挂载,其细节比我们这里描述的要复杂得多;具体可查看内核源文件 Documentation/filesystems/sharedsubtree.txt

相反,挂载点在卸载时不再是对等组的成员,无论是显式的还是隐式的,如当挂载命名空间的最后一个成员进程终止或移动到另一个命名空间

假设在运行于最初挂载命名空间中的 shell 中,将根挂载点设为私有,并创建两个共享挂载点:

mount --make-private /

mount --make-shared /dev/vdb1 /X

mount --make-shared /dev/vdb2 /Y

在第二个终端上,使用 unshare 命令创建一个新的挂载命名空间,在其中运行 shell:

# -m 选项创建一个新的挂载命名空间
unshare -m --propagation unchanged sh

返回到第一个终端,创建一个对 /X 挂载点的绑定挂载:

mkdir /Z

mount --bind /X /Z

将看到下图所示的情况:

img

在这种情况下,有两个对等组:

  • 第一个对等组包含挂载点 XX'(挂载点 X 的副本)和 Z(对最初命名空间中源挂载点 X 的绑定挂载)
  • 第二个对等组包含挂载点 YY'(挂载点 Y 的副本)

在创建第二个命名空间后才在最初命名空间中创建的绑定挂载 Z,并没有被复制到第二个命名空间,因为父挂载(/)被标记为私有

通过 /proc/pid/mountinfo 检查传播类型和对等组

/proc/pid/mountinfo 文件(记录在 proc(5) 手册页中)显示了有关进程 PID 所在挂载命名空间中的挂载点的信息

位于同一挂载命名空间中的所有进程都将在此文件中看到相同的视图

此文件旨在提供比旧的、不可扩展的 /proc/pid/mounts 文件更多的挂载点信息,此文件中的每个记录中都包含一组(可能为空)“可选字段”,这些字段显示每个挂载的传播类型和对等组(用于共享挂载)信息

对于共享装载,/proc/pid/mountinfo 中相应记录中的可选字段将包含 shared:N 形式的标记

  • shared 标记表示挂载正与对等组共享传播事件
  • 对等组由 N 标识,N 是唯一标识对等组的整数值,这些 ID 从 1 开始编号,当一个对等组不存在后还可循环使用
  • 同一对等组的所有挂载点在 /proc/pid/mountinfo 文件中的 shared:N 标记相同

在上面示例中的第一个 shell 中列出 /proc/self/mountinfo 的内容,将看到以下内容(使用 sed 过滤掉一些不相关的信息):

> cat /proc/self/mountinfo | sed 's/ - .*//'

61 0 8:2 / / rw,relatime
81 61 8:3 / /X rw,relatime shared:1
124 61 8:5 / /Y rw,relatime shared:2
228 61 8:3 / /Z rw,relatime shared:1

从该输出中:

  • 首先看到根挂载点是私有的,因为可选字段中没有任何标记
  • 挂载点 /X/Z 位于同一对等组(ID 为1),这意味着这两个挂载下的挂载和卸载事件将互相传播
  • /Y 是另一个对等组(ID 2)中的共享装载,根据定义,它不会与对等组 1 中的挂载点相互传播事件

还可通过 proc/pid/mountinfo 文件查看挂载点之间的父子关系

  • 每个记录中的第一个字段是挂载点的 ID
  • 第二个字段是父挂载的 ID

从上面的输出中,我们可以看到挂载点 /X/Y/Z 都是根挂载的子项,因为它们的父 ID 都是 61

在第二个 shell(在第二个命名空间)中运行相同的命令,看到:

> cat /proc/self/mountinfo | sed 's/ - .*//'
147 146 8:2 / / rw,relatime
221 147 8:3 / /X rw,relatime shared:1
224 147 8:5 / /Y rw,relatime shared:2

可以看到:

  • 根挂载点是私有的
  • /X 是对等组 1 中的共享挂载,与最初挂载命名空间中的挂载 /X/Z 相同
  • /Y 是对等组 2 中的共享装载,与最初挂载名空间中的挂载 /Y 相同

要注意的是,在第二个命名空间中复制的挂载点有自己的 ID,与最初命名空间中相应挂载的 ID 不同

默认值

由于情况有点复杂,到目前为止,避免讨论新挂载点的默认传播类型

从内核的角度来看,新挂载的默认值如下:

  • 如果挂载点有父亲(即非根挂载点),并且父亲的传播类型是 MS_SHARED,则新挂载点的传播类型也是 MS_SHARED
  • 否则,新挂载的传播类型为 MS_PRIVATE

根据这些规则,如果根挂载是 MS_PRIVATE,默认情况下,所有子挂载也将是 MS_PRIVATE

不过,MS_SHARED 是一个更好的默认值,是更常用的传播类型;因此,systemd 将所有挂载点的传播类型设置为 MS_SHARED;所以,在大多数现代 Linux 发行版中,默认的传播类型是 MS_SHARED

不过,util-linux 的 unshare 特性却不这样认为;在创建新的挂载命名空间时,unshare 假定用户需要完全隔离的命名空间,并通过执行以下命令(该命令递归地将根目录下的所有挂载标记为私有)将所有挂载点设置为私有:

mount --make-rprivate /

为了防止出现这种情况,我们可以在创建新命名空间时使用其它选项:

unshare -m --propagation unchanged <cmd>

挂载传播和不可绑定挂载

https://cloud.tencent.com/developer/article/1532014