跳至正文
老丹的足迹 —— 代码写给机器,游记写给自己,感悟写给时间
老丹的足迹 老丹的足迹
老丹的足迹 老丹的足迹
  • 首页
  • 示例页面
  • 首页
  • 示例页面
老丹的足迹 老丹的足迹
老丹的足迹 老丹的足迹
  • 首页
  • 示例页面
  • 首页
  • 示例页面

Linux 挂载完全指南:从物理硬盘到容器隔离的终极拆解

先说一句颠覆认知的话:Linux 根本不在乎你挂载的是什么

Linux 内核只认一个东西:目录树。它不管你是插了一块硬盘,还是连了一个网络文件夹,甚至是你随手生成的一段内存数据。在它看来,挂载这件事只有三个要素:

  • 源(从哪里来)
  • 目标(挂到哪里去)
  • 连接方式(怎么挂)

所有“很多种挂载”,都是这三要素在不同维度上的排列组合。下面我们就把这些组合一个一个拆开,看它们到底在干什么。

一、本地磁盘挂载——最正统、最基础的挂载

这是你脑子里最先想到的那种。把一块真实的物理硬盘(或分区)挂到目录树上。

命令长这样:

mount /dev/sda3 /data

底层发生了什么:

  • /dev/sda3 是块设备文件,代表硬盘上的第三个分区
  • 内核通过 VFS(虚拟文件系统)读取该分区的超级块,识别出文件系统类型(ext4 / xfs / btrfs)
  • 内核把该分区的根目录 inode 与 /data 这个目录绑定
  • 从此,你访问 /data/1.txt,内核直接去 /dev/sda3 的对应块地址读取数据

关键注意事项:

  • 盘符会漂移。/dev/sda1 今天是这个盘,明天插拔一下硬盘线可能变成 /dev/sdb1。所以生产环境绝对不要用 /dev/sdX 来挂载,必须用 UUID=xxx 或 PARTUUID=xxx。UUID 是文件系统创建时生成的唯一标识,永远不会变。
  • 挂载点必须是空目录。如果 /data 里面本来有文件,挂载后这些文件会被“遮住”,直到你卸载设备,它们才会重新出现。这不是删除,是遮盖。
  • 卸载时提示 “target is busy”:说明有进程正在占用该目录。用 lsof | grep /data 或 fuser -vm /data 找到占用进程,kill 掉后再卸载。如果 NFS 卡死,可以用 umount -l /data 做惰性卸载,从目录树中分离但等进程释放后再真正清理。

二、伪文件系统挂载——内核在“撒谎”,但这是最伟大的设计

伪文件系统没有真实的存储设备,它只是内核用内存伪造出来的文件系统,目的是让你用操作文件的方式去操作内核数据。

代表选手:

  • /proc —— 进程信息。你执行 cat /proc/cpuinfo,看到的是内核实时生成的 CPU 信息文本。
  • /sys —— 设备和驱动信息。你往 /sys/class/leds/.../brightness 里写数字,LED 灯立刻改变亮度。
  • /dev —— 设备文件。你读写 /dev/sda,内核知道你在直接操作硬盘。
  • /run —— 运行时数据。很多临时 PID 文件和 socket 文件放在这里,它通常挂载为 tmpfs,重启即清空。

挂载命令示例:

mount -t proc none /proc
mount -t sysfs none /sys
mount -t tmpfs tmpfs /tmp

注意这里 none 和 tmpfs 都是占位符,因为根本不存在物理设备。

底层发生了什么:

  • 内核里有一组函数指针,专门处理对这些“假文件”的读写
  • 当你 read 一个 /proc/meminfo 时,内核不查硬盘,而是调用 meminfo_read() 函数,当场拼接字符串返回给你
  • 当你 write 到 /sys 下的某个文件时,内核直接调用相应的硬件控制函数,实时生效

为什么说它伟大:
因为它让一切配置都变成了“改文件”。没有专门的 API,没有专门的命令行工具,你只需要 echo 1 > /some/file 就能控制内核行为。这是 Linux 简洁哲学的极致体现。

三、网络文件系统挂载——把远方的电脑变成你的本地文件夹

当你挂载 NFS 或 SMB/CIFS 时,你的 Linux 机器充当了一个客户端,通过网络协议与远程服务器通信。

命令示例(挂载 Windows 共享文件夹):

mount -t cifs //192.168.1.100/share /mnt/smb -o username=zhangsan,password=123,vers=3.0

命令示例(挂载 NFS 共享):

mount -t nfs 192.168.1.100:/exports/data /mnt/nfs -o rw,hard,intr

底层发生了什么:

  • 内核中有一个网络文件系统模块(如 cifs.ko 或 nfs.ko),它把标准的文件操作(open、read、write、close)翻译成网络协议包
  • 每次 read 一个文件,客户端向服务器发送“我要读这个文件”的请求包,服务器处理后把数据包传回来
  • 每次 write,客户端把数据打包发过去,服务器收到后写入自己的硬盘,然后回复“写完了”

你以为你在操作本地文件,实际上每个动作都走了一遍网络往返。

三个必须知道的坑:

  1. 延迟致命:一个 ls 命令可能要发几十个网络请求,如果服务器在国外,一秒才能列一个目录。
  2. 挂载超时卡死:如果网络断开了,你的 ls 命令会卡住几分钟。解决方案是加上 -o hard,intr(允许中断)或 soft,timeo=10(10 秒超时)参数。
  3. NFS 的用户映射问题:服务器上的 root 用户,到了客户端可能被映射成 nobody,导致你没权限写文件。需要在服务器端的 /etc/exports 加上 no_root_squash 选项,或在客户端指定 uid=1000,gid=1000 强制映射。

四、绑定挂载(Bind Mount)——目录的“镜像分身”,容器化最常用的挂载

这是最容易被忽视但最实用的挂载方式。它不挂载设备,而是挂载另一个已经存在的目录。

命令长这样:

mount --bind /home/zhangsan /mnt/backup

底层发生了什么:

  • 内核不创建新的文件系统,也不读取任何设备的超级块
  • 它只是把 /home/zhangsan 的 dentry(目录项)和 inode 指针复制一份,挂到 /mnt/backup 上
  • 两个路径指向内核中完全相同的物理数据

这意味着什么:

  • 你在 /mnt/backup/1.txt 里写东西,/home/zhangsan/1.txt 会同步变化,因为它们本来就是同一个文件
  • 这不是复制,不是软链接,是内核级别的“别名”

谁在用:
Docker 容器。你用 docker run -v /host/data:/container/data 时,背后就是 mount --bind。容器里的 /container/data 直接映射到宿主机的 /host/data,修改实时同步。

注意事项:

  • 绑定挂载是临时的,重启失效。如果要用永久绑定,需要写进 /etc/fstab:/home/zhangsan /mnt/backup none bind 0 0
  • 源目录如果有子挂载点(比如 /home/zhangsan/iso 下又挂了一个 ISO),默认情况下 --bind 不会连带子挂载点一起绑定。如果想把整棵树都绑过去,用 mount --rbind /home/zhangsan /mnt/backup(递归绑定)。

五、移动挂载(Move Mount)——把整棵挂载树“剪贴”到别处

它和绑定挂载相反,绑定是“复制一份镜像”,移动是“连根拔起搬走”。

命令长这样:

mount --move /oldmountpoint /newmountpoint

底层发生了什么:

  • 内核把 /oldmountpoint 这个挂载点从目录树的当前位置摘下来,重新挂到 /newmountpoint 上
  • 原挂载点变空,所有文件系统数据跟着移动到新位置

典型场景:
系统启动时,initramfs 阶段在 /run/initramfs 下挂载了临时根文件系统,等真正的根设备挂载到 /mnt/root 后,执行 mount --move /run/initramfs /mnt/root,把临时系统整体搬过去,完成根切换。

注意:
移动后,原来的绝对路径就失效了,所有进程如果还开着原路径下的文件句柄,会继续持有,但新的访问必须走新路径。

六、Loop 设备挂载——把普通文件“伪装”成硬盘

当你手头有一个 ISO 镜像文件或一个磁盘镜像文件(如 disk.img),想直接读取里面的内容,不需要刻盘或解压。Loop 挂载就是干这个的。

命令长这样:

mount -o loop ubuntu-22.04.iso /mnt/iso

底层发生了什么:

  1. 内核找到一个空闲的 /dev/loopX 设备节点
  2. 将 ubuntu-22.04.iso 文件映射到这个 loop 设备上(相当于让内核把这个普通文件视为一块虚拟硬盘)
  3. 然后就像挂载普通硬盘分区一样,把该 loop 设备挂载到 /mnt/iso
  4. 你看到的目录结构,就是 ISO 文件内部的目录结构

手动做也可以:

losetup /dev/loop0 ubuntu-22.04.iso   # 先关联
mount /dev/loop0 /mnt/iso             # 再挂载

-o loop 只是把两步合为一步的语法糖。

写镜像文件到 U 盘的原理与此相反: 你不是挂载,而是用 dd if=ubuntu.iso of=/dev/sdb 直接写入块设备,那是裸写,不经过文件系统。

七、OverlayFS 挂载——Docker 镜像分层的底层黑魔法

这不是传统意义上的“挂载设备”或“挂载目录”,而是把多个目录叠加(Union)成一个全新的虚拟目录。

命令长这样:

mount -t overlay overlay -o lowerdir=/lower,upperdir=/upper,workdir=/work /merged

三个核心层:

  • lowerdir(底层,只读):基础镜像层,比如 Ubuntu 的根文件系统。可以多个,用冒号分隔。
  • upperdir(上层,可读可写):容器的“增量修改层”。你在这个层里新建、修改、删除文件。
  • workdir(工作目录):OverlayFS 内部用的临时空间,必须和 upperdir 在同一文件系统上。

底层发生了什么:

  • 当你读 /merged/1.txt 时,内核先查 upperdir,如果存在就读上面的;如果不存在,就去 lowerdir 读。
  • 当你写 /merged/1.txt 时,内核用“写时复制”(Copy-on-Write)技术:先把 lowerdir 里的原文件复制到 upperdir,再在 upperdir 上修改。这样 lowerdir 永远不被污染。
  • 当你删除一个 lowerdir 里的文件时,内核在 upperdir 里创建一个“白名单”(whiteout)文件来标记该文件已被删除,但 lowerdir 里的原文件实际还在。

谁在用:
Docker 的镜像分层机制。你拉取一个 ubuntu:latest 镜像,它由多个只读层叠加;启动容器时,在顶层叠一个可写层,就是 OverlayFS。所以容器启动飞快,因为不需要复制整个根文件系统。

八、tmpfs 与 ramfs 挂载——用内存当硬盘,速度拉满但重启就丢

这不是挂载物理设备,而是向内核申请一块内存空间,把它当成文件系统来用。

命令长这样:

mount -t tmpfs tmpfs /mnt/ramdisk -o size=1G

与普通硬盘挂载的本质区别:

  • 所有数据只存在于内存和 swap 中
  • 写入速度极快(内存级延迟)
  • 系统断电或重启,数据灰飞烟灭

谁在用:

  • /dev/shm —— 标准 POSIX 共享内存,很多数据库和消息队列用它做高性能通信
  • /run —— systemd 放 PID 文件和 socket 文件的地方,重启即清空
  • /tmp —— 部分发行版把 /tmp 挂载为 tmpfs,用于临时编译缓存

ramfs vs tmpfs:

  • ramfs 会一直增长,直到把内存耗尽,没有上限,极危险。
  • tmpfs 可以用 size 限制最大容量,超过会返回 ENOSPC(空间不足),更安全。生产环境只用 tmpfs。

九、按挂载的生命周期——临时 vs 永久 vs 按需

这个维度不是挂载类型,而是挂载的“有效期”策略。

临时挂载(手动 mount):

  • 你敲 mount 命令挂的,重启就没了。
  • 适合测试、一次性任务、紧急修复。

永久挂载(写入 /etc/fstab):

  • 系统开机时自动挂载,依赖 systemd-fstab-generator 或传统 rc 脚本。
  • 这是生产环境硬盘、swap 分区、关键存储的标配。

按需挂载(autofs / systemd.automount):

  • 不写入 fstab 的传统写法,而是通过 autofs 服务或 systemd 的 .automount 单元实现。
  • 核心逻辑:你不 cd 进那个目录,系统就根本不挂载;一旦你进去,立即触发挂载。
  • 对网络挂载(NFS/SMB)极其友好,避免了开机时网络不通导致系统启动卡死的惨剧。

fstab 的六字段格式(必须背熟):

<file system>  <mount point>  <type>  <options>  <dump>  <pass>
  • file system:必须用 UUID,绝不用 /dev/sdX。查看 UUID 用 blkid 命令。
  • options:defaults 最常用。关键参数: noatime(提升 SSD 性能),nofail(挂载失败也允许启动,网络盘必加),noexec(禁止执行二进制,安全加固常用)。
  • pass:根目录 / 设为 1,其他本地盘设为 2,网络盘和伪文件系统必须设为 0(禁止开机检查,否则系统卡死)。

fstab 测试铁律:
写完 fstab 后,必须执行 mount -a 测试所有条目是否能正常挂载。测试通过再重启,否则写错会导致系统进入 emergency 模式,这是运维翻车的头号大坑。

十、挂载命名空间与传播属性——容器隔离的核心机密

这是挂载知识的最深处,也是容器化时代必须理解的部分。

背景:
Linux 内核有挂载命名空间(Mount Namespace)。每个命名空间拥有独立的目录树视图。Docker 容器启动时,会创建一个新的挂载命名空间,所以容器里看到的 / 和宿主机看到的 / 不是同一个。

问题来了:
在一个命名空间里挂载了一个 U 盘,其他命名空间能看到吗?
答案取决于该挂载点的传播类型(Propagation Type)。

四种传播类型:

私有(private)——默认行为

  • 我在我这个命名空间挂了一个 U 盘,只有我自己知道,其他命名空间完全无感。
  • 这是我的“私藏”,你们谁也看不到。

共享(shared)

  • 我和几个兄弟属于同一个“对等组”(peer group)。
  • 我挂了一个 U 盘,所有同组的兄弟自动也能看到这个挂载。
  • 我卸载,大家也同步卸载。
  • 适合宿主机和容器共享某些目录(比如 /dev 或 /data)。

从属(slave)——单向同步

  • 我认了一个“老大”(master),老大怎么做我就跟着怎么做。
  • 老大挂载了我跟着挂,老大卸载了我跟着卸载。
  • 但是我自己的操作,老大不理会。
  • 适合安全隔离场景:容器内随便挂载东西,不影响宿主机;但宿主机新增的存储,容器必须能看见。

不可绑定(unbindable)

  • 禁止任何人对我执行 mount --bind 操作。
  • 加了这层保护,谁也别想复制我的挂载点。

实际运维场景:
你启动 Docker 容器时,如果发现容器内无法挂载宿主机设备,或者容器内挂载的目录在宿主机看不到,99% 的原因是宿主机根目录的传播类型是 private。
解法:执行 mount --make-shared /,把根目录设为共享模式,然后再启动容器。

查看传播类型:

findmnt -o TARGET,PROPAGATION

输出会显示每个挂载点是 private、shared、slave 还是 unbindable。

总结:从“会挂载”到“懂挂载”,你只差一张对照表

你要干什么用什么挂载方式关键命令 / 文件
插入 U 盘临时用临时挂载(手动 mount)mount /dev/sdb1 /mnt
数据盘开机自动挂永久挂载(/etc/fstab)用 UUID=... 写入 fstab
把 A 目录映射到 B 目录(不复制)绑定挂载mount --bind /A /B
挂载 ISO 镜像文件Loop 设备挂载mount -o loop file.iso /mnt
挂载 NAS 网络盘NFS / CIFS 挂载mount -t nfs 或 mount -t cifs
查看内核和进程信息伪文件系统挂载系统自动挂载 /proc /sys
用内存当高速临时盘tmpfs 挂载mount -t tmpfs -o size=1G tmpfs /ram
Docker 容器镜像分层OverlayFS 挂载mount -t overlay
避免开机挂载网络盘卡死按需挂载(autofs)配置 /etc/auto.master
容器间共享挂载点设置传播属性为 sharedmount --make-shared /
强制卸载卡死的 NFS惰性卸载umount -l /mnt

你现在已经清楚了:Linux 挂载之所以“有很多种”,是因为它覆盖了本地存储、网络共享、内核接口、容器隔离、性能优化、安全加固等全栈场景。

物理机维护硬盘时,你需要掌握 本地挂载 + /etc/fstab。
开发微服务跑 Docker 时,你需要精通 Bind Mount + OverlayFS。
排查云原生故障时,你需要理解 挂载命名空间 + shared/slave 传播。

挂载的本质从来不是死记硬背命令,而是理解 Linux 如何把一切资源抽象成文件系统的桥梁。你掌握得越深,遇到存储类、启动类、容器类问题时的排查速度就越快。

作者

老丹

关注我
其他文章
上一个

Docker 数据卷完全指南:概念、原理、操作与深度实践

下一个

Ubuntu 系统 Web 监控方案全攻略:五款开源工具推荐

关于博主

    老丹是一名C/C++后台开发工程师,信奉“无抽象不设计,无性能不生产”。

  • 技术栈:Modern C++、Linux环境编程、多线程/并发、网络编程等。
  • 信条:能用constexpr解决的问题绝不拖到运行时,能靠RAII避免的泄漏绝不写析构。
  • 正在填坑:从解封装到渲染的C++全链路实现,正在驯服FFmpeg与H.264/H.265。
  • 输出原则:这里的每一段代码都经过-Wall -Wextra -Werror -O2的洗礼。

近期文章

  • Linux系统的安全基石:深入理解可插拔认证模块(PAM) 2026年7月27日
  • vsftpd 完全指南:从核心原理到Docker容器化部署 2026年7月27日
  • 互联网的”导航”安全卫士:深入解读DNSSEC 2026年7月27日
  • Ubuntu DNS 配置完全指南 2026年7月27日
  • 在 Ubuntu 中使用 Certbot 的操作指南 2026年7月27日

文章分类

  • C/C++开发 (13)
  • Docker容器 (3)
  • Linux工具包 (10)
  • Linux服务配置 (33)
  • Linux系统 (10)
  • OpenWrt路由 (2)
  • Shell脚本 (3)
  • 安防技术 (4)
  • 数据安全 (30)
  • 网络协议 (17)
  • 计算机理论 (22)
联系我们:📍 地址:中国·广东省深圳市   |   ✉️ 邮箱:support@tanglinux.com   |   💬 QQ:870866607
版权所有:老丹的足迹粤ICP备2026061170号-1       公安备案图标 粤公网安备44030002013274号