Linux 磁盘管理
Linux 磁盘从识别到上线的完整链路:用 lsblk 看清块设备、用 GPT 分区、用 ext4 / xfs 格式化、用 /etc/fstab 持久化挂载,用 LVM 在线扩容,并讲清 df 与 du 不一致、删了文件空间不回收等典型故障的真正成因。
线上的磁盘问题,几乎从来都不是“敲一两条命令”就能搞定的。你面对的是一摞分层的栈:底下是块设备(一块物理盘或一块云盘),上面是分区表(MBR 或 GPT),可选地夹一层 LVM 把文件系统从具体磁盘解耦出来,然后是文件系统驱动(ext4、xfs、btrfs)赋予原始字节“文件”的语义,最后是挂载点——应用真正打开文件的那个目录路径。我见过的大多数线上故障,只要你能说出“现在卡在哪一层”,就已经赢了一半。
本文沿着一条端到端的工作流走:识别一块新盘 → 分区 → 格式化 → 持久化挂载 → 用 LVM 在线扩容 → 排查典型故障。每一步都会讲清底层机制,目的是让你之后能“推理”系统在做什么,而不是死记一堆命令。
文件系统层级:东西到底放在哪里

Linux 遵循 Filesystem Hierarchy Standard(FHS)。/ 下每一个一级目录都有明确的职责,理解了这套约定,“哪些目录该独立成一块卷”这种决策就会变得很自然。
| 目录 | 用途 |
|---|---|
/ | 整棵目录树的根;操作系统本身。 |
/bin、/sbin | 系统启动所必需的可执行文件。 |
/etc | 系统级配置文件(纯文本)。 |
/home | 各用户的家目录。 |
/usr | 安装的软件和库(/usr/bin、/usr/lib)。 |
/var | 经常变化的数据:日志、邮件队列、包缓存、部分数据库。 |
/tmp | 临时文件;多数发行版上是 RAM 中的 tmpfs。 |
/dev | 设备节点(/dev/sda、/dev/null 等)。 |
/proc、/sys | 内核暴露出来的虚拟文件系统。 |
/mnt、/media | 习惯性的挂载点,用来挂可移动盘或额外存储。 |
生产环境里非常常见的布局是把 /、/var、/home、/data 放在不同的块设备上。这么做的理由完全是运维上的考虑:日志失控写满 /var 时,根分区还有空间登录和救援;/data 需要扩容时可以只动那一块卷,不影响系统盘。
块设备与命名:磁盘在 Linux 里长什么样
每一块挂到内核上的磁盘都会变成 /dev 下的一个块设备节点。命名规则编码了它走的是什么总线:
| 路径 | 含义 |
|---|---|
/dev/sda、/dev/sdb | SATA / SAS / USB 盘(走 SCSI 子系统) |
/dev/nvme0n1 | NVMe 固态盘,namespace 1 |
/dev/vda | virtio 盘(KVM / 云主机) |
/dev/xvda | Xen 虚拟盘(早期 AWS 实例) |
/dev/sr0 | 光驱 |
分区在后面接数字;NVMe 还会多一个 p 隔开:/dev/sda1、/dev/nvme0n1p1。
动手做任何破坏性操作之前,先跑这三条命令:
| |
lsblk -f 是这篇文章里最有用的一条命令——它一次列出块设备树、每个分区上的文件系统、当前挂载点和 UUID。如果别的都忘了,就记住它。
命名陷阱:/dev/sdb 为什么会“变”
设备名是内核启动时按枚举顺序分配的。多插一块盘、HBA 探测顺序不同、或者云主机做了一次实时迁移,原来的 /dev/sdb 就可能突然变成了 /dev/sdc。永远不要把 /dev/sdX 写进 /etc/fstab。 用稳定的标识符挂载:
- UUID——格式化时分配,跟随文件系统的整个生命周期不变(
UUID=8f1c-...)。 /dev/disk/by-id/...——厂商 + 序列号;当你需要知道“这是哪块物理盘”时很有用。/dev/disk/by-label/...——格式化时设置的人类可读标签。
分区表:MBR vs GPT

分区表写在磁盘开头,告诉操作系统这块盘是怎么切分的。Linux 支持两种格式。
MBR(Master Boot Record) 是从 IBM PC 时代继承下来的老格式。整张分区表就塞在磁盘最开头那一个 512 字节扇区里。它的所有限制都是从这个事实直接推导出来的:
- 32 位 LBA 寻址,最大可寻址容量 2 TiB。
- 最多 4 个主分区。再多就要建一个“扩展分区”,里面再开“逻辑分区”——一种历来都嫌别扭的折中方案。
- 整张表只有一份。第一个扇区一旦损坏,分区信息就没了。
GPT(GUID Partition Table) 是 UEFI 规范定义的现代替代方案,完整解决了 MBR 的所有痛点:
- 64 位 LBA——容量上限基本没有边界(ZB 量级)。
- 默认最多 128 个分区,每个分区都用 GUID 命名。
- 在磁盘开头放一份主表头,结尾放一份备份表头;两者都有 CRC32 校验和——损坏可被检测,也能恢复。
- 第一个扇区放一个“保护性 MBR”,让那些不认 GPT 的老工具看到的是“一整块未知分区”,不会以为是空闲空间被覆盖。
默认就用 GPT,除非有非常具体的理由(很老的 BIOS 只能从 MBR 引导,或者要双系统装一个 32 位的 Windows)。所有现代发行版和云上系统都默认用 GPT。
工具:fdisk、gdisk、parted
fdisk——历史上只支持 MBR;现代版本也能处理 GPT。gdisk——专门面向 GPT,操作上更明确。parted——MBR / GPT 都支持,还有非交互的批处理模式,方便写脚本。
fdisk 的典型交互流程:
| |
用 parted 写成非交互的等价脚本:
| |
这里的起点 1MiB 不是为了好看——它保证分区对齐到 1 MiB 边界,与现代盘的 4 KiB 物理扇区匹配。分区不对齐时,文件系统的一次 4 KiB 逻辑写入会变成两次物理读 + 两次物理写,是一种沉默而恼人的性能杀手。现代工具默认就这么做,但你应该知道这条规则的存在。
文件系统:把分区变成“能用的东西”
分区只是磁盘上一段连续的字节区间。要在里面存有名字的文件,得先格式化——往里写入文件系统的盘上数据结构(superblock、inode 表、空闲块位图、日志等等),让内核里的文件系统驱动能解释这些字节。

在 Linux 服务器上你真正会遇到的就这三种文件系统:
- ext4——Debian / Ubuntu 的默认,也是通用工作负载的稳妥之选。工具链成熟(
fsck、tune2fs、e2label),失败模式经过充分研究,性能可预测。 - xfs——RHEL 7+ 及衍生发行版的默认。为大文件、高并行、超大文件系统设计。不能缩小——只能扩或者迁。
- btrfs——写时复制(CoW),原生支持快照、对数据和元数据都做校验和、内置 RAID 0/1/10。openSUSE 和 Fedora Workstation 默认。复杂度更高,某些配置历史上口碑一般;生产里用它,多半是冲着快照能力去的。
格式化
| |
格式化完,lsblk -f 就会显示文件系统类型、标签和 UUID。把 UUID 记下来——后面写 /etc/fstab 要用。
临时挂一下
| |
挂载是纯运行时操作。它不会改盘上任何东西,只是告诉内核:“从现在起,/mnt/data 下的路径请由 /dev/sdb1 上的文件系统提供服务。”重启之后,挂载就没了。
持久化:/etc/fstab

/etc/fstab 是持久化的挂载表——开机时 systemd(或者 init 脚本里的 mount -a)会把里面列出的全部挂上。
挂载永远用 UUID,绝不要用 /dev/sdX:
| |
往 /etc/fstab 加一行:
# <device> <mount> <fs> <options> <dump> <pass>
UUID=8f1c-...-3a /mnt/data ext4 defaults,noatime 0 2
这六列分别是:
- 来源——UUID、label,或设备路径。
- 挂载点——必须是已经存在的空目录。
- 文件系统类型——
ext4、xfs、tmpfs、nfs等。 - 挂载选项——
defaults,再叠上像noatime(不更新访问时间,对读多场景收益很大)、ro、nosuid、nodev、discard、_netdev之类。 - dump——固定写
0(老的dump备份工具早就退场了)。 - fsck pass——
/写1,其他文件系统写2,跳过检查写0。
改完一定要先验证再重启。 /etc/fstab 写错会让系统启动到救援模式起不来。安全的做法是:
| |
mount -a 报错就先修好,再去重启。
卸载与 “target is busy”
| |
最常见的失败是 umount: target is busy:还有某个进程持有挂载点下的文件描述符,或者把工作目录设在了挂载点下。定位这个进程:
| |
把对应进程 kill 掉或者重启。实在没办法时,umount -l /mnt/data 做“懒卸载”——挂载点立刻从命名空间消失,等到最后一个文件描述符关闭才真正释放。慎用,它会掩盖真正的问题。
LVM:把文件系统从物理盘解耦出来

不用 LVM 时,文件系统直接坐在分区之上,扩文件系统就要扩分区,扩分区又要求紧挨着分区后面有连续的空闲空间。在生产环境里这条件几乎不成立。
LVM(Logical Volume Manager) 在物理盘和文件系统之间塞了一层间接层。它有三个核心概念:
- PV — Physical Volume(物理卷):被 LVM “认领”的整盘或某个分区。(
pvcreate) - VG — Volume Group(卷组):由一个或多个 PV 组成的容量池。VG 内部按固定大小的**物理扩展(PE)**切片管理(默认 4 MiB)。(
vgcreate、vgextend) - LV — Logical Volume(逻辑卷):从 VG 里切出来的虚拟块设备。在内核眼里,LV 长得就跟一个分区一样,你在它上面建文件系统。(
lvcreate)
关键洞见:LV 在底层 PV 上不必连续。 给 LV 扩容,本质就是从 VG 池子里多分配一些 PE,这些 PE 可以来自任何 PV——所以不需要重新分区也能扩容。
搭起这套栈
| |
任何时候都能查看当前栈的状态:
| |
在线扩容:最小停机的标准动作
/data 用到 80%,告警越来越频繁。LVM 把扩容做成了在线操作,整个过程只要几秒:
| |
整个过程没有重启服务,也没有数据搬迁。文件系统只是看到下面忽然多了块。
缩容是另一回事,难得多
- ext4 可以缩,但必须离线:先
umount,跑e2fsck -f,再resize2fs <新尺寸>,最后lvreduce。顺序错了文件系统就被截断了。 - xfs 完全不能缩容。官方推荐的做法是“建一个更小的 LV,用
rsync -aHAX把数据拷过去,然后切挂载点”。
经验法则:计划着扩,别计划着缩。 起手量给小一点,按需扩展。
快照(Snapshot)
LVM 可以给一个 LV 建写时复制的快照。快照本身也是一个 LV,最初和原 LV 共享所有 PE;原 LV 上的某个块发生变化时,变化前的内容会被复制到快照预留的空间里。
| |
快照适合做短时间一致性点(在快照上跑备份,让线上文件系统继续写入),也适合做快速回滚的窗口。但它不是备份——快照预留空间满了,快照就失效;底层 VG 整个挂了,原卷和快照一起没。
看用量:df vs du,以及它们为什么对不上
按理说应该给出同一个答案的两条命令,经常对不上号。
df 问文件系统:“你现在占了多少?”
| |
du 是去遍历目录,把每个文件的大小相加:
| |
df 报“满了”但 du 找不到罪魁祸首,常见原因有三个。
1. 进程还持有已删除文件的句柄
某进程把 /var/log/app.log 打开后写了好几周,有人把这个文件 rm 掉了。目录项消失——du 看不见、ls 看不见——但只要还有任何文件描述符引用这个 inode,inode 和数据块就不会被释放。进程继续写。磁盘继续满。
| |
修复方法是让那个进程关闭这个文件:重启服务、给它发 SIGHUP(如果它支持重新打开日志)、或者用 logrotate 的 copytruncate 模式(适用于不支持自我重开日志的进程)。
2. 挂载错了位置
你以为自己在看数据卷,实际上根本没挂上去,你正在把根分区写满。
| |
findmnt 什么都没返回,说明 /data 只是 / 上的一个普通目录。
3. ext4 的保留块
ext4 默认会给 root 留 5% 空间。设计意图是磁盘被普通用户写满时关键服务还能写。但在大数据卷上 5% 就是不小的一块,会让 df 在普通用户还能写之前就显示“满”。
| |
只在不承载操作系统本身的卷上这么做。
inode 耗尽
如果工作负载狂建小文件(缓存、邮件队列、构建产物),文件系统的字节是空的、inode 却用满了。df -i 会显示 IUse% 100%,而 df -h 显示一堆空闲。唯一的修复方法是删文件,或者用更高的 inode 密度重新格式化(mkfs.ext4 -N <数量>)。xfs 的 inode 是动态分配的,基本不会撞这个问题。
inode、硬链接、软链接:理解文件系统行为的钥匙
每个 Unix 文件系统底下都有一个核心数据结构——inode。inode 存一个文件的全部元数据:类型、属主、权限、大小、时间戳、链接计数、指向数据块的指针。它不存文件名。文件名住在目录里,目录本身也是文件,内容是一张 (名字 → inode 号) 的映射表。
这个解耦能解释很多看上去奇怪的行为:
- 硬链接——指向同一个 inode 的另一个目录项(
ln src dst)。两个名字地位完全平等——没有“原文件”和“复制文件”的区别。删一个,inode 的链接计数减一;只有当计数归零时,数据才被回收。硬链接不能跨文件系统,按惯例也不能给目录建。 - 软链接(符号链接)——一个内容是路径字符串的小文件(
ln -s src dst)。有自己的 inode,能跨文件系统。目标被删后会变成“悬空链接”。 - 同一文件系统内的重命名只是改写一个目录项,inode 和数据块完全不动——这就是它为什么是原子的、瞬间完成的。
- “我把文件删了,盘却没释放空间”——目录项那边链接计数降到零了,但还有进程持着这个 inode 的文件描述符(这也算一份引用)。数据要等到那个描述符关闭才被释放。这跟前面
df/du对不上是同一个机制。
| |
/dev 下的特殊文件
不是每个 /dev 下的节点都对应硬件。有几个是纯内核抽象,但你会反复用到:
/dev/null——丢弃一切写入它的内容;读它直接返回 EOF。常用于静默输出:command > /dev/null 2>&1。/dev/zero——无限输出零字节流。常用于预分配文件或写零擦除:dd if=/dev/zero of=test.bin bs=1M count=1024。/dev/random、/dev/urandom——熵源。几乎所有场景都该用urandom;老/dev/random那种“熵不够会阻塞”的行为是早期内核的历史包袱,现代 Linux 上做密码学并不需要它。
| |
端到端清单:从一块新盘到可用空间
把一块刚挂上的盘变成可用空间的标准路径:
- 识别新设备。
lsblk -f应该看到一块空的/dev/sdb(或/dev/nvme1n1),下面没分区,也没文件系统。 - 决定要不要走 LVM。 这块卷未来会扩容,就一开始就把它放到 LVM 下面。事后再加 LVM 要停机。
- 分区——用 GPT(
fdisk或parted)。如果要走 LVM,可以完全跳过分区步骤,直接对整盘pvcreate,更简洁,少一层。 - 格式化——通用场景用 ext4,大文件 / 高并行用 xfs。
- 挂载验证:
mount /dev/sdb1 /mnt/data && df -h /mnt/data。 - 持久化——往
/etc/fstab写一条以 UUID 为来源的记录,跑sudo mount -a测一遍。 - 重启验证一次——选个维护窗口,在你真正依赖这块卷之前,跑一次完整的重启流程。
把这套清单跑顺了,多数磁盘故障就从“惊慌”变成了“按部就班”——下面的排障章节也就成了一份参考手册,而不是抢救脚本。
排障手册
“盘满了”但我刚删了好几个 G 的日志
几乎一定是某个进程持有已删除文件的句柄。
| |
把那个进程重启,或者给它发信号让它重新打开日志文件。
“重启之后挂载失败”
按出现频率排:
/etc/fstab里的 UUID 写错了。用blkid核对。- initramfs 里缺文件系统驱动(少见,但 btrfs / zfs / xfs 在最小化安装上偶尔会撞到)。
- 启动顺序问题:你尝试在 LVM / RAID / 网络起来之前挂一个依赖它们的路径。网络文件系统加
_netdev选项;LVM 和软 RAID 一般 initramfs 会自动处理。
sudo mount -a 能复现开机时的挂载序列,journalctl -b | grep -i mount 看的是真正失败的细节。
“性能突然变差”
按层往下查,不要往上猜:
| |
await 高、%util 低,通常是底层存储慢(云盘、网络存储拥塞)。%util 高、队列深度低,常常是单线程 fsync 类的负载。vmstat 里 %wa 高、磁盘看起来又没问题,多半是在 swap——查 free -h 和 swapon --show。
文件系统忽然变成只读
内核检测到无法安全写入的损坏时,会把文件系统重新挂为只读。先看内核日志:
| |
如果底盘在挂(smartctl -a /dev/sda 显示有重映射扇区或介质错误),换盘。如果是文件系统本身坏了,先 umount,再跑离线修复——ext4 用 e2fsck -fy /dev/sdb1,xfs 用 xfs_repair /dev/sdb1。数据重要的话,先做快照或 dd 镜像。
命令速查
事故现场可以直接抄的一份速查。
探查与查看
| |
分区
| |
格式化与查看文件系统
| |
挂载与持久化
| |
LVM
| |
排障
| |
两条凌晨三点会救你命的提醒
- 任何破坏性命令之前,先跑一遍
lsblk -f确认目标。 一秒钟的事,能避免“我格式化错了盘”这种灾难。 - 每改完一层,就先确认上一层能看到这层的变化,再往下走。 块 → 分区 → LVM → 文件系统 → 挂载。某一层“消失”了就停下来查清楚,不要硬冲过去。