前言

之所以有这篇文章,是因为在写别的内容时一不小心写多了,那就作为独立的文章发出来罢。

本文仅记录了一些我认为有趣的内容,想要了解详情可以参考 比较官方的文档 或者是网上各路大佬的解析。
本文中存在一些未经验证的直觉与推断,如有不准确的地方还望包容和指正。

简介

绑定挂载(bind mount),简单来说,就是替换一个文件夹及其内容(A)为另一个文件夹(B)。(也可以替换文件为文件)
但是,这里的替换是“软”的,文件夹 A 的内容本身并没有从磁盘上消失——bind mount 通过在 kernel 的 vfs 层上做手脚,在 A 的 dentry 上覆盖 B 的,于是就顺理成章的实现了 A 文件夹 与 B 文件夹共享一个 inode。那个被覆盖的 A ,在此就被称为挂载点(mount point)。

从这个角度看,绑定挂载和硬连接(hard link)倒是有几分相似:从用户的视角看,它们都使得多个东西指向了相同的 inode,只不过硬连接不支持文件夹罢了。但它们又有些不同:硬连接可是实打实的使多个 dentry 指向相同的 inode,而 bind mount 只不过是在 vfs 层进行了一个“虚”的实现——它在重启后就会消失。

分辨

硬连接具有“公平”的特点。这里的“公平”是指,对于硬连接来说,连接完后的两个文件是完全等价的,你完全无法分辨谁是谁,也无法分辨到底哪个才是原来的文件。并且,即使将原来的文件删除,新文件由于自身就持有对应 inode 的引用,因此完全不会受到影响。而绑定挂载也具有类似的特点。

我们能够通过查看 /proc/<pid>/mountinfo/proc/<pid>/mounts(legacy) 中的挂载信息来区分挂载点和普通文件夹,
那么,我们能分辨 bind mount 和普通挂载吗?

情况一:挂载点 到 挂载点

假如 /A 本身就是一个挂载点,然后将 /A 绑定挂载到了 /B 。那么 /A 和 /B 将会是完全等价的,在挂载完成后,不通过查看历史记录,你完全无法区分是 /A 被 bind mount 到了 /B 还是 /B 被 bind mount 到了 A ,又或者是它们对应的 filesystem 被用正常的方式挂载了两次。即使 umount 了 /A , /B 也丝毫不会受到影响。同样的,这是因为它们都独立的持有着对相关 inode 的引用。

验证一下:

# 我的数据盘一开始挂载在 /mnt/data 目录下
➜  /home/libxzr cat /proc/self/mountinfo | grep /mnt
93 28 8:1 / /mnt/data rw,relatime shared:48 - ext4 /dev/sda1 rw
# 然后我们将 /mnt/data 绑定挂载到 /mnt/data2
➜  /home/libxzr sudo mkdir /mnt/data2
➜  /home/libxzr sudo mount --bind /mnt/data /mnt/data2
# 然后再来看一看挂载信息
➜  /home/libxzr cat /proc/self/mountinfo | grep /mnt
93 28 8:1 / /mnt/data rw,relatime shared:48 - ext4 /dev/sda1 rw
361 28 8:1 / /mnt/data2 rw,relatime shared:48 - ext4 /dev/sda1 rw

先来科普一下挂载点信息前的这一串数字,以 93 28 8:1 为例:

  • 第一个数字 93 代表唯一的挂载 id 。
  • 第二个数字 28 代表其父挂载点的挂载 id (假如其为根挂载点,则父挂载 id 等于自身挂载 id )。
  • 第三组数字 8:1 则等于这个挂载中的文件系统的主次设备号。(一般可以通过这组数字来判断是否是同一物理或虚拟设备)

接下来看看上面的挂载信息,怎么样,除了挂载号外完全一致。
该如何区分谁是祖宗,又该如何区分这是一个 bind mount 还是一个一般的挂载?

嗯,就像硬连接一样,它是难以区分的。

情况二:非挂载点 到 挂载点

但是还有另一种情况,那就是被挂载者并不是一个挂载点:假如 /A 是一个挂载点, /A/B 是 /A 下面的一个文件夹,然后我们将 /A/B bind mount 到了 /C ,这下总不能说 /A 与 /C 完全相同了吧。没错,在这种情况下, /C 与 /A/B 指向了相同的 inode ,但是 /A 和 /C 并不相同。那么在这种情况下 umount 掉 /A ,会发生啥?答案是 /C 仍然能正常工作不会受到任何影响,因为它本身独立的持有对相关 inode 的引用。于是这里就可以产生一个 HACK:假如我们只想要挂载一个文件系统中的一个文件夹,那么我们可以先挂载整个文件系统(A),然后将其中的目标文件夹绑定挂载出来(B),然后将 A umount 掉即可得到对目标文件夹的挂载

所以,在这种情况下,能够看出这是一个 bind mount 吗?
答案是可以的:

# 这一次,挂载一个文件夹
➜  /home/libxzr sudo mount --bind /mnt/data/test /mnt/data2
# 看看挂载信息
➜  /home/libxzr cat /proc/self/mountinfo | grep /mnt
93 28 8:1 / /mnt/data rw,relatime shared:48 - ext4 /dev/sda1 rw
361 28 8:1 /test /mnt/data2 rw,relatime shared:48 - ext4 /dev/sda1 rw

可以看到,我们挂载的文件夹 test 赫然出现在了挂载信息中。包含它的这部分信息(/test)代表了被挂载对象相对于其文件系统根目录的位置。
在情况一中,由于我们挂载的本身就是挂载点,因此这部分信息就是 /

于是,我们就可以利用这部分信息来区分这是不是一个绑定挂载——因为一个正常挂载是没法指定子目录的(除非 fs 支持)。

这便是为什么 stackexchange 上有人认为使用 findmnt | grep "\[" 可以列出 bind mount 的原因。这个命令本质上就是对 /proc/self/mountinfo 的解析,因此它仅能列出本情况描述的 bind mount 类型,对于情况一仍然是无能为力的。

但是,我们所能够知道的也只有它是一个绑定挂载,我们仍然无法判断它来自哪里