Mount --bind 简介

mound –bind

mount --bind 把一个目录(或文件)绑定到另一个位置,让两个路径指向同一份数据。它不创建副本,不产生额外存储开销,修改任意一个路径都能看到另一个路径的变化。

1
mount --bind /源目录 /目标目录

核心特点:被挂载目录的目录项块被屏蔽,读取请求被重定向到挂载目录的 inode。对内核而言,访问目标路径等同于访问源路径。


为什么需要 --bind?先看一个真实场景

场景:把容器数据卷挂载到指定目录

容器运行时(Docker / Podman)常用 bind mount 把宿主机目录映射进容器:

1
2
# 把宿主机的日志目录绑定到容器内的 /app/logs
docker run -v /var/log/myapp:/app/logs myimage

这个 -v 底层就是 mount --bind(或 mount --rbind)。


示例

ls -i 列出文件的 inode 号。inode 是 Linux 文件的"身份证",同一 inode = 同一个文件实体。这是验证 bind mount 最直接的方法。

准备环境

1
2
3
# 创建两个空目录
mkdir -p ~/bind_src ~/bind_dst
echo "hello bind" > ~/bind_src/a.txt

绑定前的状态

1
2
3
4
5
$ ls -i ~/bind_src/ ~/bind_dst/
/home/user/bind_src/:
131078 a.txt

/home/user/bind_dst/:   # 目标目录为空

执行 bind mount

1
sudo mount --bind ~/bind_src ~/bind_dst

绑定后的状态 —— 关键证据

1
2
3
4
5
6
$ ls -i ~/bind_src/ ~/bind_dst/
/home/user/bind_src/:
131078 a.txt

/home/user/bind_dst/:
131078 a.txt      # ← inode 完全一样!

两个目录下的 a.txt inode 都是 131078。这说明它们根本就是同一个文件

双向同步验证

1
2
3
4
5
6
7
# 通过目标路径写文件
echo "from dst" > ~/bind_dst/b.txt

# 在源路径立刻能看到
$ ls -i ~/bind_src/
131078 a.txt
131085 b.txt    # ← 刚才写入的文件,两个路径都能看到
1
2
3
4
5
6
# 通过源路径修改文件
echo "modified" > ~/bind_src/a.txt

# 从目标路径读取
$ cat ~/bind_dst/a.txt
modified        # ← 立即生效

与符号链接的区别

mount --bind 并非简单地在两个路径之间建立链接,而是通过 Linux VFS(虚拟文件系统)层实现的一种重定向。以 mount --bind test1 test2 为例:

当 mount –bind 命令执行后,Linux 将会把被挂载目录的目录项(也就是该目录文件的 block,记录了下级目录的信息)屏蔽,即 test2 的下级路径被隐藏起来了(注意,只是隐藏不是删除,数据都没有改变,只是访问不到了)。同时,内核将挂载目录(test1)的目录项提取出来,在 mount 命令执行时,VFS 会创建一个 vfsmount 对象,这个对象里包含了本次挂载的相关信息,包括源目录(test1)和目标挂载点(test2)的目录项指针。随后,内核会把这个 vfsmount 对象挂载到全局的挂载树中,并在 test2 的目录项上做一个“此处有挂载”的标记。路径解析时,VFS 通过这个标记跳转到对应的 vfsmount 对象,从而实现从 /test2/xxx 到 /test1/xxx 的访问重定向。容。

简言之:访问 /test2 = 内核暗中读取 /test1,整个过程对用户空间完全透明。

之前提到 bind mount 屏蔽的是 test2 的目录项数据块,那目录本身的 inode 呢?来看实例:

1
2
3
4
5
6
$ mkdir -p ~/bind_src/sub ~/bind_dst/sub
$ ls -id ~/bind_src ~/bind_src/sub ~/bind_dst ~/bind_dst/sub
131070 /home/user/bind_src         # 源目录
131072 /home/user/bind_src/sub     # 源子目录
131071 /home/user/bind_dst         # 绑定前:目标目录 inode 不同
131073 /home/user/bind_dst/sub     # 绑定前:目标子目录 inode 不同
1
sudo mount --bind ~/bind_src ~/bind_dst
1
2
3
4
5
$ ls -id ~/bind_src ~/bind_src/sub ~/bind_dst ~/bind_dst/sub
131070 /home/user/bind_src         # 源目录
131072 /home/user/bind_src/sub     # 源子目录
131070 /home/user/bind_dst         # ← 绑定后:目录 inode 也一致了!
131072 /home/user/bind_dst/sub     # ← 子目录同样一致

绑定前后对比:

1
2
3
4
5
          绑定前各自行其是          绑定后全部统一
  bind_src  → 131070      bind_src → 131070
  bind_dst  → 131071  →   bind_dst → 131070     # ← 关键变化
  sub(src)  → 131072      sub(src) → 131072
  sub(dst)  → 131073  →   sub(dst) → 131072     # ← 连同子目录一起重定向

这直观地印证了前面的机制描述:bind mount 屏蔽的是 test2 整个目录树的数据块,内核在路径查找时,对 test2 的访问被整体重定向到 test1,文件 inode、目录 inode 无一例外

特性 ln -s 软链接 mount --bind
inode 关系 不同 inode 相同 inode
对进程透明 部分程序会跟随链接 完全透明,进程无感知
跨文件系统 支持 支持(但源和目标可以是不同文件系统)
权限模型 仍走源路径的权限 走目标路径挂载点的权限上下文
1
2
3
4
ln -s ~/bind_src/a.txt ~/soft_link_a.txt
$ ls -i ~/bind_src/a.txt ~/soft_link_a.txt
131078 /home/user/bind_src/a.txt
200055 /home/user/soft_link_a.txt   # ← inode 不同,这就是软链接

--rbind:递归绑定整个目录树

--bind 只绑定目录本身,目录内的子挂载点不会被带入。如果目录里有子目录也做了挂载(比如 /data 下有一个独立的 /data/logs 挂载点),只绑定 /data 时这些子挂载不会出现在目标路径下。

--rbind(recursive bind)会连带所有子挂载一起绑定

1
2
3
4
5
6
# 假设 /data/logs 是一个独立挂载点
sudo mount --rbind /data ~/bind_dst
# ~/bind_dst/logs 会保留原有的挂载属性

# 解除递归绑定时,需先解除~/bind_dst的子挂载,或使用 --recursive 选项
umount --recursive ~/bind_dst

递归绑定可能导致挂载点嵌套,需谨慎使用

持久化:内存中的映射

两个目录的对应关系只存在于内存里,一旦系统重启,挂载关系即消失。

如果需要开机自动挂载,需写入 /etc/fstab

1
/home/user/bind_src  /home/user/bind_dst  none  bind  0  0

常见用法

让根分区空间不够时"借"空间

1
2
3
4
# 把另一个盘上的大目录 bind 到 /var 下空间紧张的位置
sudo mkdir -p /bigdisk/var-lib
sudo rsync -av /var/lib/ /bigdisk/var-lib/
sudo mount --bind /bigdisk/var-lib /var/lib

可写入 /etc/fstab 持久化:

1
/bigdisk/var-lib  /var/lib  none  bind  0  0

只读保护

1
2
3
4
5
6
# 1. 绑定源路径到目标路径
sudo mount --bind ~/src ~/dst
# 2. 将目标路径重挂载为只读
sudo mount -o remount,ro,bind ~/dst
# 3. 验证:目标路径无法写入
echo "test" > ~/dst/1111  # 报错:Permission denied

这样通过 ~/dst 访问时是只读的,但 ~/src 依然可写。适合安全审计场景。

迁移服务数据不中断运行

1
2
3
4
5
6
7
8
9
# 1. 在新盘准备好目录
sudo rsync -av /var/lib/mysql/ /bigdisk/mysql/

# 2. bind mount 过去(服务进程无感知)
sudo mount --bind /bigdisk/mysql /var/lib/mysql

# 3. 确认无误后,卸载旧数据目录,fstab 写入新配置
# 4. 重启服务(可选,因为 bind 已经生效)
sudo systemctl restart mysql

安全地修改只读系统文件

固件开发中常遇到这种情况:需要修改某个系统文件来测试新功能,但该系统位于只读文件系统上(总不能为了一次小测试就重刷固件),或者虽然文件可写,但不敢直接修改。此时 mount --bind 可以派上用场。

以修改 /etc/hosts 为例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 1. 准备新的 hosts 文件
cp /etc/hosts /tmp/hosts.new
# 编辑 /tmp/hosts.new,加入测试所需的条目

# 2. bind mount(用修改版覆盖 /etc/hosts)
sudo mount --bind /tmp/hosts.new /etc/hosts

# 3. 此时 /etc/hosts 已"变成"修改后的版本,可以放心测试
cat /etc/hosts

# 4. 测试完毕后,卸载绑定,恢复原貌
sudo umount /etc/hosts

整个过程不触碰原始文件,测试完 umount 即可完全还原。

沙箱隔离(chroot 配套)

1
2
3
4
5
# 为 chroot 环境提供必要的系统目录
sudo mount --bind /dev /mychroot/dev
sudo mount --bind /proc /mychroot/proc
sudo mount --bind /sys /mychroot/sys
sudo chroot /mychroot /bin/bash

如何查看当前的 bind mount?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 方法一:findmnt 最直观
$ findmnt -t bind
TARGET    SOURCE         FSTYPE OPTIONS
/home/user/bind_dst  /home/user/bind_src  ext4  bind

# 方法二:cat /proc/mounts
$ grep bind /proc/mounts
/home/user/bind_src /home/user/bind_dst ext4 rw,bind 0 0

# 方法三:df 也能看到(绑定后大小与源一致)
$ df -h ~/bind_src ~/bind_dst
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1       100G   30G   70G  30% /home/user/bind_src
/dev/sda1       100G   30G   70G  30% /home/user/bind_dst  # ← 相同设备,印证同一份数据
Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计