专栏首页LINUX阅码场黄东升: mount namespace和共享子树

黄东升: mount namespace和共享子树

没有一个mount namespace是一座孤岛

2

本文翻译自lwn上一个namespace系列文章的一篇,原文链接会在文末给出,各位看官开始进入mount namespace的世界吧!

Mount namespaces用来创建一个用户或者容器独立的文件系统树,是一个强大且灵活的工具。同时,mount namespaces也有一些比较复杂的特点。本文,作为一系列的namespace文章延续,将对这些复杂性进行解释。我们将更细致的说明一下共享子树(shared subtrees) 这个特征,通过共享子树,挂载(mount)和卸载(unmount)事件可以通过一种自动,可控的方式在不同的命名空间之间传递。

导论

Mount namespaces 是第一个进入Linux内核的namespace,首次出现在Linux 2.4.19版本。它们隔离了每个进程可以看到的挂载点列表,或者换句话说,每个Mount namespace都有它们自己的挂载点列表,意味着在不同namespace中的进程都可以看到且控制不同的目录层次结构(目录树)。

当系统启动时,只有一个“ 初始命名空间”。新的mount命名空间通过设置clone()系统调用中的CLONE_NEWNS来创建,或者通过unshare()系统调用将调用者迁移到一个新的命名空间。当一个新的命名空间被创建时,它将继承调用clone()和unshare()的进程的命名空间的全部挂载点列表(mount point list)。

调用clone()或者unshare()之后,通过mount()或者unmount()函数,每个namespace中的挂载点可以被动态的删除或者增加。默认情况下,对挂载点列表的修改只对处于该命名空间的进程是可见的,对于其他命名空间中的进程则是无效的。

Mount namespaces 有多种用途。比如说,它们(指代命名空间)可以给每个用户提供一个独立的文件系统视图。其他的用途包括为新创建的pid namespaces挂载一个/proc文件系统,或者实现类似chroot() 的目录层次结构的隔离。在一些应用中,mount namespaces是和 bind mounts联合使用的。

共享子树(Shared subtrees)

在使用mount namespaces的过程中,用户空间的程序员可能会遇到一个难题:mount namespaces提供了过度的隔离。设想一下,当我们将一个光盘插入到光驱中,在初始的mount namespace中,令光盘在所有的namespace中可见的唯一方法是,将光盘挂载到每一个namespace中。在许多情况下,如果能够通过一次挂载就使得光盘对所有的namespace都是可见的,那么这将是一件十分绝佳的事情。

由于以上所描述的难题,共享子树机制被引进了Linux 2.6.15(2006年早期,大约是Mount namespace第一次发布后的三年)。共享子树最核心的特征是允许挂载和卸载事件以一种自动的,可控的方式在不同的namespaces间传递(propagation)。这就意味着,在一个命名空间中挂载光盘的同时也会触发对于其他namespace对同一张光盘的挂载。

在共享子树中,每个挂载点都都存在一个名为传递类型(propagation type)的标记,该标记决定了一个namespace中创建或者删除的挂载点是否会传递到其他的namespaces。

有四种传递类型:

  • MS_SHARED:该挂载点和它的 “对等组”(peer group,接下来会详细解释它)共享挂载和卸载事件。当一个挂载点被删除或者添加到namespace中,这些事件会被传递到它的对等组,这样挂载和卸载事件会发生在所有对等挂载点。传递也会发生在相反的方向。也就是说,事件的传递是双向的。
  • MS_PRIVATE: 和共享挂载相反,标记为private的事件不会传递到任何的对等组,挂载操作默认使用该标志。
  • MS_SLAVE: 这个传递类型介于shared 和 slave之间,一个slave mount拥有一个master(一个共享的对等组),该对等组中的成员可以将事件传递到他的slave mount。但是slave mount不能将事件传递给master mount。
  • MS_UNBINDABLE: 该挂载点是不可绑定的。

有几点值得展开说一下。第一点是传递类型是一个针对挂载点的设置(per-mount-point setting)。在一个命名空间中,一些挂载点被标记为 shared,其他的挂载点可以被标记为Private(或者slave, unbindable)。

第二点要说明的是,传递类型决定了挂载和卸载的传递。因此,在一个共享挂载X中创建一个子挂载 Y(表示挂载点Y是挂载点X的子目录),子挂载会传递到对等组的其他挂载中。然而,X的传递类型不会影响到在Y中创建或者删除的挂载点。Y中的事件是否会传递到其他的对等组中取决于Y的挂载类型。类似的,当X本身被卸载,该卸载事件是否会被传递,也同样取决于X的传递类型。

另外,有一点值得阐明的是,本文中的“事件(event)”是一个相对抽象的名词,意思是“有一些事情发生”。事件传递的概念并不意味着不同的挂载点之间的消息传递。恰恰相反,事件传递应该被阐述为当一个挂载点的挂载或者卸载事件发生时会触发其他挂载点的相同的事件。

Peer groups(对等组)

对等组是一组挂载点,并且一个对等组可以将挂载和卸载事件传递给另一个对等组。

举例来说,假设一个运行在初始mount namespace的shell,我们使得根挂载为private,并且创建两个共享的挂载点:

建挂载点和改变传递类型需要root权限。

然后,在第二个窗口中,我们使用ushare()系统调用创建一个新的mount namespace,并在新的namespace中运行一个shell:

-m 表示创建一个新的namespace,–propagation unchanged的意义我们随后解释)

返回到第一个窗口,我们使用绑定挂载将/X挂载到/Z。

完成这些步骤之后,命名空间和对等组的情况如下所示:

这个场景中,有两个对等组:

  • 在第一个对等组中包含挂载点 X, X’( X的挂载点的拷贝在第二个命名空间被创建时),Z(由挂载点X绑定挂载得到)。
  • 第二个对等组包含挂载点Y和Y’(挂载点Y的复制品,当第二个命名空间被创建时)。

注意绑定挂载的挂载点Z,该挂载点在第一个命名空间中,当第二个命名空间被创建时创建,挂载点Z不会在第二个挂载点内被复制,因为父挂载点被标记为private。

译者记:可以看到即便是不同的namespace中的挂载点也是可能处于同一个对等组的。同时,只要一个挂载点是shared类型的,它就会传递到所有namespace中的对等组。 另外Peer group的翻译不是很恰当,希望读者能够提出自己的意见。

通过/proc/PID/mountinfo查看传递类型和对等组

/proc/PID/mountinfo显示的是进程PID所在的mount namespace 的挂载点信息。所有位于同一命名空间的进程都将看到相同的视图。该文件比/proc/PID/mounts能够提供更加丰富的信息。文件中的每一条记录都存在一个可选字段,该字段显示了挂载点的传递类型和对等组(共享挂载)该字段可以为空。

对于共享挂载,/proc/PID/mountinfo中的每条记录的可选字段都包含一个格式为 shared: N类型的标记。Shared标记表示该挂载点和同一对等组共享传递事件。对等组由整数N来标识。这些ID从1开始分配,如果一个对等组内的成员数为0,闲置的ID可能会被回收。

举例来说,如果我们在第一个shell中打印/proc/self/mountinfo,我们可以看到如下的输出:

输出可以看到,在可选字段的shared标志为空,表明根挂载点是private。我们也可以看到挂载点/X和/Z属于同一个对等组(组ID = 1), 意味着挂载和卸载事件会在这两个挂载点之间传递。挂载点/Y属于另一个对等组(组ID = 2)。

/proc/PID/mountinfo文件让我们可以得到不同挂载点的亲属关系。每条记录的第一个字段是一个挂载点特有的ID。第二个字段是父挂载点的ID。从上述输出我们可以看到,挂载点/X /Y /Z的父挂载点ID是61。

在第二个窗口中(第二个namespace)打印/proc/PID/mountinfo, 我们可以看到:

样的根目录是私有挂载点。/X属于第一个对等组,初始命名空间的/X和/Z同样属于第一个对等组。挂载点/Y则属于另一个对等组。最后一点需要说明的是,第二个namespace中的被复制过来的挂载点和它的另一个实体拥有不同的ID。

译者注,看了这么多,那么究竟一个对等组都包含那种类型的挂载点呢?可以看到,只要一个挂载点是MS_SHREAD类型的,那么系统中所有namespace中的相等挂载点都属于同一个对等组。另外,通过bind mount得到的挂载点也属于同一个对等组。

关于默认值的争议

因为情况有一点复杂,目前为止,我们都避免讨论传递类型的默认值。对于内核来说,一个新的挂载点被创建时的情况如下:

  • 如果一个挂载点有一个父挂载点,并且父挂载点的类型时MS_SHARED,那么新的挂载点的传递类型也是MS_SHARED。
  • 否则,新的挂载点的类型时MS_PRIVATE。

根据这些规则,根挂载点应该时MS_PRIVATE,这样它的子挂载点默认都是MS_PRIVATE。然而,由于MS_SHARED是更为常用的一种传递类型,所以默认值设为MS_SHARED也更为合理。这样,systemd将所有挂载点的传递类型设为MS_SHARED。 因此,在大多数现代Linux发行版中,默认传递类型实际上是MS_SHARED。但是,还不能就此作结论。当使用ushare()函数创建一个新的命名空间时,会将命名空间内的所有新创建的挂载点的默认值设置为MS_PRIVATE, 如下命令所示,递归的将根目录下的所有挂载点设置为MS_PRIVATE。

了防止unshare将默认值设置为MS_PRIVATE,我们可以使用如下命令创建新的命名空间:

THE END

本文中,我们讲解了mount namespace和共享子树,大家也可以阅读其他lwn上其他关于namespace的文章。

namespace系列链接:https://lwn.net/Articles/531114/#series_index

原文链接:

https://lwn.net/Articles/689856/

本文来源:Linux内核之旅


本文分享自微信公众号 - Linux阅码场(LinuxDev),作者:黄东升

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-10-25

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • copy_{to, from}_user()的思考

    我们对copy_{to,from}_user()接口的使用应该是再熟悉不过吧。基本Linux书籍都会介绍它的作用。毕竟它是kernel space和user s...

    Linux阅码场
  • linux内核写时复制机制源代码解读

    韩传华,就职于国内一家半导体公司,主要从事linux相关系统软件开发工作,负责Soc芯片BringUp及系统软件开发,乐于分享喜欢学习,喜欢专研Linux内核源...

    Linux阅码场
  • CPU高速缓存与反置页表&调度的科普

    虽然我喜欢分级页表,但是反置页表才是更加自然的方式。之所以叫做 反置 页表,大概是因为它颠倒我们常规理解的寻址:

    Linux阅码场
  • 命名空间介绍之八:挂载命名空间和共享子树

    挂载命名空间是创建每-用户和每-容器文件系统树的强大而灵活的工具。本文中,我们将仔细研究共享子树特性,它可通过自动、可控的方式在挂载命名空间之间传播挂载和卸载事...

    谛听
  • 将磁盘误挂载到根分区下的问题处理记录

    需求说明: 因云线上服务器的根目录/空间不足,所以想购买一块磁盘挂载到服务器的/data下,将根分区下占用空间的打目录软链接到/data下。 但是在成功输入mo...

    洗尽了浮华
  • emlog插件挂载点使用和自定义名称方法

    对于初学的emlog插件开发者来说,插件挂载点定义真是一个麻烦事!官方的插件定义不是很详细。所以小白给大家提供点思路。     其实挂载点主要就是两个函数...

    奶糖味的代言
  • CentOS7挂载新数据盘的完整步骤

    刚刚买了一台新的VPS,新买的VPS的数据盘默认没有挂载到系统上,需要我们自己来挂载的。我们给服务器添加新的硬盘的时候都需要进行挂载操作,本文简要记录挂载操作过...

    砸漏
  • 云服务器装了 linux 面板挂载磁盘后不显示数据盘

    魏艾斯博客www.vpsss.net
  • CentOS 7.x 系统新数据盘分区挂载教程

    好了,控制台层面的挂载就结束了;但是硬盘要能使用,还得到服务器内在系统层面进行挂载;

    夏日萤火
  • 轻松挂载远程目录 | sshfs

    AlicFeng

扫码关注云+社区

领取腾讯云代金券