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 # ← 相同设备,印证同一份数据
|