在上一期命名空间系列的文章中,我们研究了挂载命名空间和共享子树的基本概念,包括挂载传播类型和对等组的概念。在这篇文章中,我们提供了各种传播类型操作的一些实际演示:MS_SHARED,MS_PRIVATE,MS_SLAVE 和 MS_UNBINDABLE。
正如在上一篇文章中看到的,MS_SHARED 和 MS_PRIVATE 传播类型大致相反。共享挂载点是对等组的成员。对等组中的挂载点之间互相传递挂载和卸载事件。相比之下,私有挂载点不属于对等组;它既不向对等方传播事件,也不从对等方接收事件。在下面的 shell 会话中,我们将演示这两种传播类型的不同之处。
假设在最初的挂载命名空间中,我们已经有两个挂载点,/mntS 和 /mntP。在命名空间中的 shell 中,我们将 /mntS 标记为共享,将 /mntP 标记为私有,并在 /proc/self/mountinfo 中查看这些挂载:
sh1# mount --make-shared /mntS
sh1# mount --make-private /mntP
sh1# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//'
77 61 8:17 / /mntS rw,relatime shared:1
83 61 8:15 / /mntP rw,relatime
从输出中,我们看到 /mntS 是对等组 1 中的共享挂载,而 /mntP 没有标记,这表明它是私有挂载。(如前一篇文章所述,大多数挂载和卸载操作都要求用户具有特权,如“#”提示符所示。)
在第二个终端中,我们创建一个新的挂载命名空间,在其中运行第二个 shell 并检查挂载:
sh2# unshare -m --propagation unchanged sh
sh2# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//'
222 145 8:17 / /mntS rw,relatime shared:1
225 145 8:15 / /mntP rw,relatime
新的挂载命名空间得到了最初挂载命名空间的挂载点的拷贝。这些新的挂载点保持相同的传播类型,但具有唯一的挂载 ID(记录中的第一个字段)。
在第二个终端中,我们在 /mntS 和 /mntP 下创建挂载并检查结果:
sh2# mkdir /mntS/a
sh2# mount /dev/sdb6 /mntS/a
sh2# mkdir /mntP/b
sh2# mount /dev/sdb7 /mntP/b
sh2# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//'
222 145 8:17 / /mntS rw,relatime shared:1
225 145 8:15 / /mntP rw,relatime
178 222 8:22 / /mntS/a rw,relatime shared:2
230 225 8:23 / /mntP/b rw,relatime
从上面可以看出,/mntS/a 为共享的(从其父挂载继承此设置),而 /mntP/b 为私有挂载。
返回到第一个终端并检查设置,我们看到在共享挂载点 /mntS 下创建的新挂载传播到其对等挂载(位于最初挂载命名空间中),但在私有挂载点 /mntP 下创建的新挂载没有传播:
sh1# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//'
77 61 8:17 / /mntS rw,relatime shared:1
83 61 8:15 / /mntP rw,relatime
179 77 8:22 / /mntS/a rw,relatime shared:2
将挂载点设为从节点,可让它收到主对等组的挂载和卸载事件,同时防止它将事件传播到该主节点。如果我们希望(比方说)在主对等组(在另一个装载命名空间中)中挂载光盘时接收挂载事件,但希望防止从属挂载下的挂载和卸载事件在其他命名空间中产生副作用,则这非常有用。
通过将最初挂载命名空间中的两个(现有)挂载点标记为共享来演示从属行为的效果:
sh1# mount --make-shared /mntX
sh1# mount --make-shared /mntY
sh1# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//'
132 83 8:23 / /mntX rw,relatime shared:1
133 83 8:22 / /mntY rw,relatime shared:2
在第二个终端上,我们创建一个新的挂命名空间并检查复制后的挂载点:
sh2# unshare -m --propagation unchanged sh
sh2# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//'
168 167 8:23 / /mntX rw,relatime shared:1
169 167 8:22 / /mntY rw,relatime shared:2
在新的挂载命名空间中,我们将其中一个挂载点标记为从属。将共享挂载更改为从属挂载的效果是,使其成为其以前所属对等组的从属。
sh2# mount --make-slave /mntY
sh2# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//'
168 167 8:23 / /mntX rw,relatime shared:1
169 167 8:22 / /mntY rw,relatime master:2
在上述输出中,/mntY 挂载点用 master:2 标记。标记名可能有悖常理:它表示挂载点是从挂载,从 ID 为 2 的主对等组接收传播事件。如果一个挂载既是另一个对等组的从属,又与它自己的对等组共享事件,那么 /proc/pid/mountinfo 记录中的可选字段将同时显示一个 master:M 标记和一个 shared:N 标记。
继续停在新命名空间中,我们在 /mntX 和 /mntY 下创建挂载:
sh2# mkdir /mntX/a
sh2# mount /dev/sda3 /mntX/a
sh2# mkdir /mntY/b
sh2# mount /dev/sda5 /mntY/b
当我们检查新挂载命名空间中挂载点的状态时,我们看到 /mntX/a 被创建为新的共享挂载(从其父挂载继承“共享”设置),/mntY/b 被创建为私有挂载(即,在可选字段中没有显示标记):
sh2# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//'
168 167 8:23 / /mntX rw,relatime shared:1
169 167 8:22 / /mntY rw,relatime master:2
173 168 8:3 / /mntX/a rw,relatime shared:3
175 169 8:5 / /mntY/b rw,relatime
回到第一个终端,我们看到 mount/mntX/a 传播了到最初命名空间中的对等方 /mntX ,但是 mount/mntY/b 没有传播:
sh1# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//'
132 83 8:23 / /mntX rw,relatime shared:1
133 83 8:22 / /mntY rw,relatime shared:2
174 132 8:3 / /mntX/a rw,relatime shared:3
接下来,我们在最初挂载命名空间中的 /mntY 下创建一个新的挂载点:
sh1# mkdir /mntY/c
sh1# mount /dev/sda1 /mntY/c
sh1# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//'
132 83 8:23 / /mntX rw,relatime shared:1
133 83 8:22 / /mntY rw,relatime shared:2
174 132 8:3 / /mntX/a rw,relatime shared:3
178 133 8:1 / /mntY/c rw,relatime shared:4
当我们检查第二个挂载命名空间中的挂载点时,看到在这种情况下,新挂载已传播到从属挂载点,并且新挂载是从属挂载(到对等组4):
sh2# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//'
168 167 8:23 / /mntX rw,relatime shared:1
169 167 8:22 / /mntY rw,relatime master:2
173 168 8:3 / /mntX/a rw,relatime shared:3
175 169 8:5 / /mntY/b rw,relatime
179 169 8:1 / /mntY/c rw,relatime master:4
稍后,我们将讨论 MS_UNBINDABLE 传播类型的使用。但是,先简要对绑定挂载的概念进行描述是很有用的,这是 Linux 2.4 中首次出现的特性。
绑定挂载可使得文件或目录子树在单个目录层次结构中的其他位置可见。在某些方面,绑定挂载类似于硬链接,但又在一些重要方面有所不同:
可以通过带 MS_BIND 标志的 mount() 创建绑定挂载,也可以在命令行上使用 mount--bind 创建绑定装载。在下面的示例中,我们首先创建一个包含文件的目录,然后在新位置对该目录进行绑定挂载:
# mkdir dir1 # Create source directory
# touch dir1/x # Populate the directory
# mkdir dir2 # Create target for bind mount
# mount --bind dir1 dir2 # Create bind mount
# ls dir2 # Bind mount has same content
x
然后,我们在新挂载点下创建一个文件,并观察到该新文件在原始目录下也是可见的,这表明绑定挂载引用了同一个目录对象:
# touch dir2/y
# ls dir1
x y
默认情况下,在创建目录的绑定挂载时,只有该目录会被挂载到新位置;该目录树下的任何挂载都不会被复制到挂载目标。可以通过使用 带 MS_BIND 和 MS_REC 标志的 mount(),或者在命令行中使用 mount--rbind 选项,递归地绑定挂载。在这种情况下,源树下的每个挂载都将复制到目标树中的相应位置。
共享、私有和从属传播类型是用来管理对等挂载点(通常位于不同命名空间中)之间挂载事件的传播的。不可挂载点用来解决不同的问题,即挂载命名空间出现前的问题。这个问题就是所谓的“挂载点爆炸”,当在低级别挂载点重复执行高级别子树的递归绑定挂载时发生。我们通过一个 shell 会话演示该问题,然后看看不可绑定的挂载是如何解决该问题的。
首先,假设我们有一个有两个挂载点的系统,如下所示:
# mount | awk '{print $1, $2, $3}'
/dev/sda1 on /
/dev/sdb6 on /mntX
现在假设我们要递归地将根目录绑定挂载到几个用户主目录下。我们将为第一个用户执行此操作并检查挂装点。首先创建一个新的命名空间,在该命名空间中,我们递归地将所有挂载点标记为从属,以防止对其它挂载命名空间产生副作用:
# unshare -m sh
# mount --make-rslave /
# mount --rbind / /home/cecilia
# mount | awk '{print $1, $2, $3}'
/dev/sda1 on /
/dev/sdb6 on /mntX
/dev/sda1 on /home/cecilia
/dev/sdb6 on /home/cecilia/mntX
当我们对第二个用户重复递归绑定操作时,可看到爆炸问题:
# mount --rbind / /home/henry
# mount | awk '{print $1, $2, $3}'
/dev/sda1 on /
/dev/sdb6 on /mntX
/dev/sda1 on /home/cecilia
/dev/sdb6 on /home/cecilia/mntX
/dev/sda1 on /home/henry
/dev/sdb6 on /home/henry/mntX
/dev/sda1 on /home/henry/home/cecilia
/dev/sdb6 on /home/henry/home/cecilia/mntX
在 /home/henry 下,我们不仅递归地添加了/mntX 挂载,还添加了在上一步中创建的 /home/cecilia 的递归挂载。在为第三个用户重复该步骤并简单地计算挂载量后,很明显,爆炸是指数级的:
# mount --rbind / /home/otto
# mount | awk '{print $1, $2, $3}' | wc -l
16
通过使每个新的挂载不可绑定来避免该绑定爆炸问题。这样做的效果是,根目录的递归绑定挂载不会复制不可绑定挂载。回到最初的场景,我们为第一个用户创建一个不可绑定挂载,并通过 /proc/self/mountinfo 检查挂载:
# mount --rbind --make-unbindable / /home/cecilia
# cat /proc/self/mountinfo | grep /home/cecilia | sed 's/ - .*//'
108 83 8:2 / /home/cecilia rw,relatime unbindable
...
在 /proc/self/mountinfo 记录的可选字段中,显示了一个带有不可绑定标记的不可绑定挂载。
现在,我们为其他两个用户创建 unbindable 递归绑定挂载:
# mount --rbind --make-unbindable / /home/henry
# mount --rbind --make-unbindable / /home/otto
在检查挂载点列表时,我们看到没有挂载点爆炸,因为不可绑定挂载没有被复制到用户的目录下:
# mount | awk '{print $1, $2, $3}'
/dev/sda1 on /
/dev/sdb6 on /mntX
/dev/sda1 on /home/cecilia
/dev/sdb6 on /home/cecilia/mntX
/dev/sda1 on /home/henry
/dev/sdb6 on /home/henry/mntX
/dev/sda1 on /home/otto
/dev/sdb6 on /home/otto/mntX
挂载命名空间与共享子树特征结合,是创建每-用户和每-容器文件系统树的强大而灵活的工具。它们也是一个令人惊讶的复杂特性,我们已经试图在本文中解开一些复杂点。然而,实际上还有几个问题没有考虑。例如,有详细的规则描述在执行绑定挂载和移动(mount--move)操作时所得到的传播类型,也有规则描述改变挂载的传播类型时的结果。其中许多细节可以在内核源文件Documentation/filesystems/sharedsubtree.txt 中找到。
原文:https://lwn.net/Articles/690679/
公众号:Geek乐园
博客:https://blog.csdn.net/u012319493/article/details/102887094
本文系外文翻译,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系外文翻译,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。