磁盘管理与文件系统

本文合并自:01-磁盘管理与文件系统.md、07-磁盘与文件系统管理.md、1-3-Linux磁盘与LVM管理.md、02-LVM逻辑卷命令速查.md

本文系统讲解 Linux 下磁盘从接入到可用的完整流程:存储基础与设备识别、分区表(MBR/GPT)与分区工具(fdisk/parted/gdisk)、文件系统的创建与挂载、/etc/fstab 开机自动挂载、swap 交换分区、LVM 逻辑卷管理、磁盘配额、LUKS 加密、软件 RAID、磁盘性能与排错。文中同时保留 CentOS 6 与 CentOS 7 两代系统的不同做法并标注适用版本。

本章要点

  • 理解块设备的标准操作流程:分区 → 格式化(创建文件系统)→ 挂载。
  • 掌握 Linux 设备文件命名规则(hd*/sd*/vd*/nvme*),理解 MBR 与 GPT 两种分区表的区别。
  • 掌握 fdisk 交互分区(n/d/p/t/l/w/q)、分区类型修改,以及保存后用 partprobe/partx 生成设备文件。
  • 掌握 GPT 与大于 2TB 磁盘必须使用的 parted/gdisk。
  • 掌握分区表的备份与恢复方法。
  • 掌握 mkfs/mkfs.ext4/mkfs.xfs/mkfs.vfat/mkfs.ntfs/mkswap 等格式化命令。
  • 掌握 mount/umount,包括挂载选项(ro/rw/remount/bind/loop/noauto/noexec 等)、NFS/ISO/NTFS 挂载、强制卸载与占用排查。
  • 掌握 swapon/swapoff/free -m 管理交换分区。
  • 掌握 /etc/fstab 六个字段含义、使用 UUID 挂载、mount -a、blkid。
  • 掌握文件系统检查修复(fsck/e2fsck/xfs_repair)与在线调整(resize2fs/xfs_growfs/tune2fs)。
  • 掌握 LVM 三层结构(PV→VG→LV)及 pvcreate/vgcreate/lvcreate/lvextend -r 等命令,理解扩容与缩减流程、快照原理。
  • 掌握磁盘配额 quota / xfs_quota 与 LUKS 加密分区的完整实验步骤。
  • 掌握 mdadm 软件 RAID 的创建、监控、故障模拟与恢复。
  • 能够独立排查 inode 耗尽、磁盘满、fstab 写错无法启动、设备忙无法卸载等常见故障。

一、存储基础与设备识别

1.1 磁盘创建流程总览

一块磁盘在 Linux 中从接入到可被使用,通常需要经历以下步骤:

RAID(可选)
  → 远程存储访问 iSCSI / GNBD(可选)
  → 分区
  → LVM(可选)
  → 格式化(创建文件系统)
  → 挂载
  → 分布式文件系统(可选)

其中最核心、必做的三个环节是 分区、格式化、挂载:

分区 → 格式化(创建文件系统) → 挂载

磁盘管理的对象是存储设备(块设备),包括硬盘、移动硬盘、U 盘、光盘、软盘、磁盘阵列等。

警告:编辑分区表是危险操作,操作不当可能导致分区无法访问、数据"丢失"(数据其实仍在磁盘上并未真正被擦除,但访问会非常困难)。安全做法是先将分区表备份到其他存储设备,再进行修改,以便随时恢复。

常用的分区管理工具:

工具 支持的分区表 适用版本/场景
fdisk MBR(MSDOS);CentOS 7 起的新版本也能操作 GPT,但功能不完整 CentOS 6 的默认分区工具;≤2TB 磁盘
parted MSDOS(MBR)与 GPT 都支持 GPT 分区必须使用它;>2TB 磁盘必须使用它
gdisk 专用于 GPT UEFI + GPT 环境,交互方式与 fdisk 类似

1.2 机械硬盘的物理结构

机械硬盘(HDD)由若干盘片(Platter)叠放构成,每个盘片的上下两面都有一个磁头(Head)负责读写:

盘片 Platter ── 磁性介质载体,双面可用,多片叠放在同一主轴上
磁头 Head    ── 每个盘面一个磁头,悬浮在盘面上读写数据
磁道 Track   ── 磁头在盘面上划出的同心圆
柱面 Cylinder── 所有盘面上半径相同的磁道,纵向组合成的"圆柱"
扇区 Sector  ── 磁道被等分成的小段,是磁盘读写的最小物理单位

关键概念与换算:

  • 扇区(Sector):传统扇区大小为 512 字节,是磁盘 I/O 的最小单位。
  • 4K 扇区(Advanced Format):现代大容量硬盘的物理扇区为 4096 字节(4K),为兼容旧系统对外仍模拟报告 512 字节逻辑扇区(称为 512e)。若分区起始位置未按 4K 对齐(4K 对齐 = 起始扇区号能被 8 整除),会造成"读写放大",性能显著下降。
  • fdisk -l 输出中的 Sector size (logical/physical): 512 bytes / 4096 bytes 就是逻辑扇区/物理扇区大小。
  • 柱面(Cylinder):旧版 fdisk 默认以柱面为分区单位(1 柱面 = 磁头数 × 每磁道扇区数 × 512 字节,常见为 255 × 63 × 512 = 8225280 字节 ≈ 8MB)。CentOS 7 的 fdisk 默认改用扇区为单位,并自动做 1MB 对齐。
  • MBR 位于 0 号磁头、0 号磁道、1 号扇区(即整块盘的第一个扇区)。

1.3 SSD 与机械硬盘的差异

对比项 机械硬盘 HDD 固态硬盘 SSD
存储介质 磁性盘片 + 磁头机械寻道 NAND 闪存芯片,无机械部件
随机 IOPS 低(受寻道时间限制,约 100~200) 极高(数千至数十万)
顺序吞吐 100~200 MB/s SATA SSD 约 500MB/s;NVMe 可达数 GB/s
访问延迟 毫秒级(含寻道 + 旋转延迟) 微秒级
抗震/噪音/功耗 怕震动、有噪音、功耗高 抗震、静音、功耗低
寿命特征 机械磨损,坏道 闪存有擦写次数上限(P/E 寿命)
特殊维护 需要碎片整理(Linux 下基本不需要) 需要 TRIM(fstrim -av 或挂载选项 discard);不要做碎片整理

SSD 使用建议:

  • 对 SSD 上的文件系统使用 noatime 或 relatime 挂载选项,减少无意义写入。
  • 使用 fstrim 定时任务(CentOS 7 提供 fstrim.timer)而不是挂载选项 discard,后者会带来即时写入延迟。
  • 分区务必 4K 对齐(现代 fdisk/parted 默认已对齐到 1MiB)。

1.4 接口类型与设备命名规则

接口 说明 典型设备文件
IDE(ATA/PATA) 早期并行接口,已淘汰 /dev/hda、/dev/hdb
SATA 主流桌面/服务器串行接口 /dev/sda、/dev/sdb
SCSI 传统企业级并行接口 /dev/sda …
SAS 串行 SCSI,企业级主流 /dev/sda …
USB(移动硬盘、U 盘) 内核按 SCSI 子系统处理 /dev/sdb、/dev/sdc …
NVMe(PCIe SSD) 高速固态盘专用协议 /dev/nvme0n1
虚拟机 virtio 磁盘 KVM/云主机半虚拟化磁盘 /dev/vda、/dev/vdb
光驱 光盘设备 /dev/sr0、/dev/cdrom(软链接)
软驱 已淘汰 /dev/fd0
内存盘 RAM disk /dev/ram0

设备名各位置的含义(以 /dev/sda1 为例):

  • 第一位 s/h:接口类型。SATA/SCSI/SAS/USB/移动硬盘为 s,IDE 为 h;
  • d:表示存储设备(disk);
  • 第三位 a/b/c…:同类型硬盘的序号,a 为第一块,b 为第二块,e 为第五块;
  • 末尾数字:该硬盘上的分区编号。

常见设备文件对照:

设备文件 含义
/dev/sda 第一块 SATA/SCSI/SAS/USB 硬盘
/dev/sdb 第二块 SATA/SCSI/SAS/USB 硬盘
/dev/hda 第一块 IDE 硬盘
/dev/sda1 第一块 SATA 硬盘的第一个分区
/dev/sda2 第一块 SATA 硬盘的第二个分区
/dev/vda1 虚拟机 virtio 第一块盘的第一个分区
/dev/nvme0n1p1 第一块 NVMe 控制器、第一个命名空间、第一个分区

NVMe 命名规则拆解:/dev/nvme0n1p2 = nvme + 控制器编号 0 + n 命名空间 1 + p 分区 2。注意 NVMe 与 mmcblk(eMMC/SD 卡,如 /dev/mmcblk0p1)的分区编号前带字母 p,而 sd/vd 系列直接接数字。

1.5 分区编号规则(MBR)

MBR 分区表下的编号有固定规则,必须记住:

/dev/sda1  ─┐
/dev/sda2   ├─ 主分区或扩展分区,编号固定占用 1~4
/dev/sda3   │
/dev/sda4  ─┘
/dev/sda5  ─┐
/dev/sda6   ├─ 逻辑分区,编号一律从 5 开始,依次递增,无上限
/dev/sda7  ─┘
  • 主分区(Primary)与扩展分区(Extended)共享 1~4 这四个编号。
  • 逻辑分区(Logical)的编号一定从 5 开始,即使只有 1 个主分区 + 1 个扩展分区,第一个逻辑分区依然是 /dev/sda5,不会是 /dev/sda3。
  • 删除中间的逻辑分区会导致后面的逻辑分区编号前移(例如删掉 sda6,原 sda7 变成 sda6),这也是 /etc/fstab 中不应写死设备名的重要原因。
  • GPT 分区表没有主/扩展/逻辑之分,编号从 1 开始连续排列。

1.6 设备文件与 /dev 目录

Linux 遵循"一切皆文件"的设计,所有硬件都通过 /dev 下的设备文件访问。设备文件分两类:

类型 标识 特点 举例
块设备(block) ls -l 首字符为 b 按块随机读写、带缓冲 /dev/sda、/dev/sdb1、/dev/md0、/dev/mapper/vg-lv
字符设备(character) ls -l 首字符为 c 按字节流顺序读写、无缓冲 /dev/tty、/dev/null、/dev/zero、/dev/random
ls -l /dev/sda /dev/tty0
# brw-rw---- 1 root disk 8, 0 ... /dev/sda    <- b 为块设备,8,0 是主/次设备号
# crw--w---- 1 root tty  4, 0 ... /dev/tty0   <- c 为字符设备

/dev 下常见设备文件:

设备文件 对应硬件/用途
/dev/pts 伪终端(远程登录终端)
/dev/tty 本地终端,可通过 w、who am i、tty 查看当前终端
/dev/ram 内存盘
/dev/input 输入设备(鼠标、键盘等)
/dev/sda 第一块 SCSI/SATA 硬盘
/dev/sda1 第一块硬盘的第一个分区
/dev/stdin 标准输入设备(键盘),文件描述符 0
/dev/stdout 标准输出设备(显示器),文件描述符 1
/dev/stderr 标准错误输出设备(显示器),文件描述符 2
/dev/null 黑洞设备,写入的数据被丢弃
/dev/zero 零设备,读取时无限输出 \0,常配合 dd 造文件
/dev/urandom 随机数设备,用于生成密钥、随机填充
/dev/loop0 回环设备,把普通文件当块设备使用
/dev/mapper/* device-mapper 设备(LVM 逻辑卷、LUKS 加密卷)

1.7 查看磁盘与分区信息的命令

lsblk:树形查看块设备(最常用)

lsblk                      # 以树型结构查看所有硬盘与分区
lsblk -f                   # 附带文件系统类型、LABEL、UUID、挂载点
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT,UUID   # 自定义输出列
lsblk -d                   # 只显示磁盘本身,不显示分区
lsblk -p                   # 显示完整设备路径(/dev/sda 而非 sda)
lsblk -b                   # 以字节为单位显示容量
lsblk -m                   # 显示权限与属主信息
lsblk /dev/sdb             # 只看指定磁盘

典型输出:

NAME            MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sda               8:0    0   20G  0 disk
├─sda1            8:1    0  500M  0 part /boot
└─sda2            8:2    0 19.5G  0 part
  ├─centos-root 253:0    0 17.5G  0 lvm  /
  └─centos-swap 253:1    0    2G  0 lvm  [SWAP]
sdb               8:16   0    5G  0 disk
sr0              11:0    1 1024M  0 rom

TYPE 列区分 disk(整盘)、part(分区)、lvm(逻辑卷)、raid5(阵列)、rom(光驱)、crypt(加密卷)。

fdisk -l:查看分区表

fdisk -l                   # 列出系统中所有块设备的分区表
fdisk -l /dev/sda          # 仅列出 /dev/sda 的分区表
sudo fdisk -l              # 非 root 用户需要 sudo

CentOS 6 风格示例输出:

Disk /dev/sda: 500.1 GB, 500107862016 bytes
255 heads, 63 sectors/track, 60801 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x0003f808

  Device Boot      Start         End      Blocks   Id  System
/dev/sda1   *           1          33      262144   83  Linux
Partition 1 does not end on cylinder boundary.
/dev/sda2              33        7682    61440000   8e  Linux LVM
/dev/sda3            7682        7747      524288   82  Linux swap / Solaris

输出解读:Boot 列的 * 表示引导分区;Id 列是分区类型 ID(83 Linux、8e Linux LVM、82 swap);Blocks 以 1KB 为单位。

CentOS 6 与 7 差异:CentOS 6 的 fdisk 默认单位是柱面(cylinders),CentOS 7 默认单位是扇区(sectors)且自动 1MiB 对齐,输出中不再有 Units = cylinders of ... 一行。

parted -l:查看分区表(支持 GPT)

parted -l                  # 列出所有磁盘分区信息,能正确显示 GPT
parted /dev/sdb print      # 查看指定磁盘
parted -s /dev/sdb print   # -s 脚本模式,不提示交互

对 GPT 磁盘必须用 parted -l 或 gdisk -l;用 CentOS 6 的 fdisk -l 查看 GPT 磁盘会给出 "WARNING: GPT (GUID Partition Table) detected on '/dev/sdb'! The util fdisk doesn't support GPT" 的告警。

blkid:查看 UUID、LABEL 与文件系统类型

blkid                      # 查看所有块设备的 UUID/LABEL/TYPE
blkid /dev/sdc1            # 查看指定分区
blkid -s UUID -o value /dev/sdb1   # 只输出 UUID 的值,便于脚本使用
blkid -L mydata            # 按卷标反查设备
blkid -U <uuid>            # 按 UUID 反查设备
/dev/sda1: LABEL="/vm" UUID="174c6b5a-0de6-469d-bc07-b38d4bfd3479" TYPE="ext4"

df:查看已挂载文件系统的空间使用

df -h                      # 以易读方式(K/M/G)显示各分区挂载点及使用情况
df -h /data                # 查看某目录所在文件系统
df -T                      # 额外显示文件系统类型
df -i                      # 查看 inode 使用情况(排查"磁盘没满却报空间不足")
df -hT --total             # 显示合计行
df -x tmpfs -x devtmpfs    # 排除指定类型的文件系统
df -a                      # 显示包括伪文件系统在内的全部条目
df -k / -m                 # 以 KB / MB 为单位

df -h 各列:Filesystem(设备)、Size(总容量)、Used(已用)、Avail(可用)、Use%(使用率)、Mounted on(挂载点)。

注意 Size ≠ Used + Avail,因为 ext 文件系统默认为 root 预留 5% 的保留块(可用 tune2fs -m 调整)。

du:统计目录/文件占用的空间

du -sh /var                # 统计 /var 总大小(-s 汇总,-h 易读)
du -sh *                   # 统计当前目录下每个条目的大小
du -h --max-depth=1 /var   # 只看一层子目录(等价 du -h -d 1)
du -sh --exclude=*.log /var/log   # 排除某类文件
du -a /etc | sort -rn | head -20  # 找出最大的文件(不带 -h 才能正确排序)
du -sh * | sort -rh | head -10    # 按大小倒序取前 10(GNU sort 支持 -h)
du --apparent-size -sh file       # 显示"表观大小",用于观察稀疏文件
du -x -sh /                # 不跨文件系统统计

df 与 du 的区别:df 从超级块读取统计值(快、看整个文件系统),du 遍历目录逐个累加(慢、看某个目录)。两者结果不一致的典型原因是"文件已删除但仍被进程占用"(见排错清单)。

其它设备信息查看命令

cat /proc/partitions       # 内核当前识别到的所有分区(主设备号、次设备号、块数、名称)
cat /proc/scsi/scsi        # 查看 SCSI 设备信息
lsscsi                     # 以易读格式列出 SCSI/SATA 设备(需 yum install lsscsi)
lsscsi -s                  # 附带容量显示
lsblk -S                   # CentOS 7 起可用,等效查看 SCSI 设备
cat /sys/block/sda/size    # 以 512 字节扇区数表示的磁盘容量
cat /sys/block/sda/queue/rotational   # 1=机械盘,0=SSD

hdparm 查看/设置硬盘参数:

hdparm -I /dev/sda         # 查看硬盘详细参数(型号、序列号、支持特性)
hdparm -i /dev/sda         # 查看内核识别到的简要参数
hdparm -tT /dev/sda        # 测试缓存读与磁盘读性能
hdparm -W /dev/sda         # 查看写缓存是否开启
hdparm -W 1 /dev/sda       # 开启写缓存(1 开 / 0 关)
hdparm -C /dev/sda         # 查看电源状态(active/standby)
hdparm -y /dev/sda         # 立即让硬盘进入待机

smartctl 查看硬盘健康状态(需 yum install smartmontools -y):

smartctl -i /dev/sda            # 查看设备基本信息与 SMART 是否启用
smartctl -s on /dev/sda         # 启用 SMART 支持
smartctl -H /dev/sda            # 查看健康状况总体结论(PASSED / FAILED!)
smartctl -A /dev/sda            # 查看所有 SMART 属性明细
smartctl -a /dev/sda            # 查看全部信息(信息 + 属性 + 日志)
smartctl -l selftest /dev/sda   # 查看自检日志
smartctl -t short /dev/sda      # 启动短自检(约 2 分钟)
smartctl -t long /dev/sda       # 启动长自检(可能数小时)
smartctl -d sat -a /dev/sda     # USB/SAT 转接盘需要指定设备类型

重点关注的 SMART 属性(数值持续增长即预示硬盘即将故障):

ID 属性名 含义
5 Reallocated_Sector_Ct 重映射扇区数,非 0 且增长即需换盘
197 Current_Pending_Sector 等待重映射的可疑扇区
198 Offline_Uncorrectable 无法纠正的扇区数
187 Reported_Uncorrect 报告的不可纠正错误
194 Temperature_Celsius 温度
9 Power_On_Hours 通电时长
241 Total_LBAs_Written 累计写入量(判断 SSD 寿命)

/dev/disk 下的持久化设备名

设备名 /dev/sdb 可能因磁盘插拔顺序变化而改变,/dev/disk/ 下提供了稳定的别名(都是指向 ../../sdXn 的软链接):

ls -l /dev/disk/by-uuid/    # 按文件系统 UUID 索引,fstab 首选
ls -l /dev/disk/by-label/   # 按文件系统卷标索引
ls -l /dev/disk/by-id/      # 按硬件序列号/WWN 索引,物理定位磁盘最可靠
ls -l /dev/disk/by-path/    # 按物理连接路径(PCI 总线、SCSI 通道)索引
ls -l /dev/disk/by-partuuid/ # 按 GPT 分区 UUID 索引
lrwxrwxrwx 1 root root 10 Aug 22 10:11 174c6b5a-0de6-... -> ../../sda1

用途区分:by-uuid / by-label 适合写进 /etc/fstab(跟着文件系统走,重新格式化会变);by-id / by-path 适合识别"物理上是哪块盘、插在哪个槽位",换盘、排查故障盘时非常有用。

1.8 虚拟机添加新硬盘的完整步骤

以 VMware Workstation / ESXi 为例:

  1. 关机或热添加:虚拟机 → 设置 → 添加 → 硬盘 → SCSI(推荐)→ 创建新虚拟磁盘 → 指定容量(如 5GB)→ 完成。

  2. 进入系统确认:

    lsblk                  # 看是否出现 sdb
    fdisk -l | grep -i disk
    ls /dev/sd*
  3. 若关机添加:开机后即可看到新盘 /dev/sdb。

  4. 若热添加(不重启):需要让内核重新扫描 SCSI 总线,见下节。

  5. 后续流程:分区 → 格式化 → 挂载 → 写入 /etc/fstab(或者直接把整盘做成 LVM 的 PV)。

1.9 在线扫描识别新磁盘(不重启)

# 1. 先找出系统中有哪些 SCSI host
ls /sys/class/scsi_host/
# host0  host1  host2

# 2. 对每个 host 触发重新扫描(三个减号分别代表 channel、target、lun 的通配)
echo '- - -' > /sys/class/scsi_host/host0/scan
echo '- - -' > /sys/class/scsi_host/host1/scan
echo '- - -' > /sys/class/scsi_host/host2/scan

# 一次性对所有 host 扫描
for h in /sys/class/scsi_host/host*/scan; do echo '- - -' > $h; done

# 3. 确认新盘已被识别
lsblk
fdisk -l
dmesg | tail -20            # 观察内核日志中新设备的识别过程

已有磁盘在虚拟化平台上被扩容后,让内核重新读取新容量:

echo 1 > /sys/class/block/sdb/device/rescan     # 重新读取该盘容量
# 或
echo 1 > /sys/block/sdb/device/rescan
lsblk                                            # 确认 SIZE 已变大

删除一块已拔掉的磁盘(避免残留 I/O 错误):

echo 1 > /sys/block/sdb/device/delete

使用 rescan-scsi-bus.sh(yum install sg3_utils -y)可以更方便地完成扫描:

rescan-scsi-bus.sh          # 扫描新增设备
rescan-scsi-bus.sh -r       # 同时移除已消失的设备
rescan-scsi-bus.sh -s       # 扫描容量变化(resize)

二、分区管理:MBR 与 GPT

2.1 引导方式与分区表的对应关系

系统引导方式与分区表、分区工具是一一对应的:

BIOS → MBR → fdisk(MBR 分区管理工具)
UEFI → GPT → gdisk / parted(GPT 分区管理工具)
  • MBR 是长期以来最流行的分区模式,稳定性高、兼容性最好;
  • GPT 是未来主流,支持 2TB 以上硬盘,而 MBR 不支持 2TB 以上的单盘容量。

2.2 MBR 分区表结构

硬盘的第一个逻辑块(扇区)用于存储 MBR(Master Boot Record,主引导记录)信息,共 512 字节:

MBR (512 字节)
├── 0   ~ 445   引导代码 Boot Code        446 字节
├── 446 ~ 509   分区表 Partition Table     64 字节(4 项 × 16 字节)
└── 510 ~ 511   结束标志 0x55AA             2 字节

简化记法:

MBR(512) = 启动代码(446) + 分区表(64) + 结束标志(2)
  • 分区表共 64 字节,每个分区信息占 16 字节,因此最多只能记录 4 个分区,这 4 个分区称为主分区(Primary Partition)。
  • 每个 16 字节分区项中用 4 字节记录分区起始 LBA、4 字节记录分区扇区总数,因此单分区最大 2^32 × 512 字节 = 2TB,这就是 MBR 的 2TB 容量上限来源。

如需超过 4 个分区,就要用扩展分区机制:

  • 扩展分区(Extended Partition):占用一个主分区位(即 4 个位置中的一个),本身不能直接存放数据、不能格式化,只用于"存放逻辑分区的信息",相当于一个容器。通常把剩余空间全部分配给它。
  • 逻辑分区(Logical Partition):在扩展分区内部继续划分出来的分区,可格式化使用,数量理论上不受限制(实际受内核设备数限制)。逻辑分区的信息以链表形式串在扩展分区内部(EBR)。
  • 常见组合:4 个主分区,或 3 个主分区 + 1 个扩展分区(内含 N 个逻辑分区)。
  • 逻辑分区编号一律从 5 开始。

2.3 GPT 分区表结构

GPT(GUID Partition Table,全局唯一标识分区表)是 UEFI 规范的一部分:

LBA 0            Protective MBR(保护性 MBR,供旧工具识别,防止误操作)
LBA 1            GPT 头(Primary GPT Header):签名、版本、分区项数量、CRC 校验
LBA 2 ~ 33       分区项数组(默认 128 项,每项 128 字节)
...              实际数据分区区域
倒数第 33 ~ 2    分区项数组备份
最后一个 LBA     GPT 头备份(Secondary GPT Header)

GPT 的关键特性:

  • 备份分区表:分区表在磁盘头部和尾部各存一份,且带 CRC32 校验,头部损坏可从尾部恢复,可靠性远高于 MBR。
  • GUID:磁盘本身有一个 Disk GUID,每个分区也有独立的 Partition GUID(PARTUUID)和分区类型 GUID(如 Linux 文件系统类型 GUID 为 0FC63DAF-8483-4772-8E79-3D69D8477DE4),彻底避免了 MBR 只有 1 字节类型 ID 的冲突问题。
  • 突破 4 分区限制:默认支持 128 个分区,且没有主分区/扩展分区/逻辑分区的概念,所有分区地位平等,编号从 1 连续递增。
  • 突破 2TB 限制:使用 64 位 LBA 寻址,理论支持 8 ZiB(约 90 亿 TB),实际使用中远超需求。大于 2TB 的磁盘必须使用 GPT。
  • 与 UEFI 的关系:UEFI 固件从 GPT 上的 ESP 分区(EFI System Partition,FAT32 格式,类型 GUID C12A7328-...,通常挂载在 /boot/efi)中读取 .efi 引导程序启动系统,不再依赖 MBR 里的 446 字节引导代码。若要在 GPT 磁盘上用传统 BIOS 引导,则需要额外创建一个 1MB 的 BIOS Boot Partition(类型 ef02)供 GRUB 存放 core.img。

2.4 MBR 与 GPT 对比表

特性 MBR GPT
全称 Master Boot Record GUID Partition Table
适用启动模式 BIOS(Legacy) UEFI(也可配合 BIOS + BIOS Boot 分区)
主要管理工具 fdisk gdisk、parted
最大支持单盘/分区容量 2 TB 以内 2 TB 以上(理论 8 ZiB)
分区数量 最多 4 个主分区,或 3 主分区 + 1 扩展分区(内含多个逻辑分区) 默认 128 个,无主/扩展/逻辑之分
分区表位置 仅磁盘第一个扇区,无备份 磁盘头尾各一份,带 CRC 校验
分区类型标识 1 字节 ID(83/8e/82/7…) 16 字节 GUID
稳定性/兼容性 稳定、兼容老系统(含 Windows XP 32 位) 主流趋势,老系统可能不识别
CentOS 支持 CentOS 5/6/7 均完全支持 CentOS 6 需用 parted;CentOS 7 起 fdisk 也部分支持

选择建议:

  • 单盘容量 ≤ 2TB、需要兼容老系统或老 BIOS → 用 MBR。
  • 单盘容量 > 2TB、使用 UEFI 引导、需要 4 个以上分区 → 必须用 GPT。
  • 服务器新装系统、云主机数据盘 → 一律建议 GPT。
  • 用作 LVM PV 的数据盘,也可以不分区直接把整盘做 PV(pvcreate /dev/sdb),这样连分区表的限制都绕开了。

2.5 常见分区类型 ID(MBR,fdisk 中用 t 设置)

ID 类型 用途
83 Linux 普通 Linux 文件系统分区(ext2/3/4、xfs),默认值
82 Linux swap / Solaris 交换分区
8e Linux LVM 作为 LVM 物理卷使用的分区
fd Linux raid autodetect 作为软件 RAID 成员的分区
7 HPFS/NTFS/exFAT Windows NTFS/exFAT 分区
b W95 FAT32 FAT32 分区
c W95 FAT32 (LBA) FAT32(LBA 模式)
ef EFI System EFI 系统分区(ESP)
ee GPT GPT 保护分区标记
5 Extended 扩展分区
f W95 Ext'd (LBA) 扩展分区(LBA 模式)

在 fdisk 中输入 l(或在 t 命令后输入 L)可以列出全部已知类型。

注意:分区类型 ID 只是"声明用途"的元数据标记,它不等于实际的文件系统。把分区 ID 改成 8e 并不会自动把它变成 PV,仍需执行 pvcreate;反之即使 ID 是 83,也一样可以 pvcreate。设置正确的 ID 是良好习惯,便于其他工具和人类正确识别用途。

2.6 分区方案示例

MBR 个人多系统分区方案

分区 大小 用途
第一个主分区 50 GB Windows C 盘
第二个主分区 50 GB 黑苹果系统分区
第三个主分区 4 GB /boot 分区
第四个主分区 剩余空间 扩展分区
└ 第一个逻辑分区 4 GB Swap 交换分区
└ 第二个逻辑分区 50 GB Linux 的 /home 个人数据
└ 第三个逻辑分区 30 GB Ubuntu 根分区
└ 第四个逻辑分区 20 GB CentOS 根分区
└ 第五个逻辑分区 20 GB 测试 Linux 根分区
└ 最后一个逻辑分区 剩余空间 Windows D 盘

GPT 个人分区方案(不再受 4 主分区限制)

序号 大小 用途
1 4 GB ESP 分区(boot 分区)
2 1 MB BIOS 兼容分区(BIOS Boot Partition)
3 50 GB Windows 7 C 盘
4 4 GB Swap 交换分区
5 50 GB Linux /home
6 30 GB Ubuntu 根分区
7 20 GB CentOS 根分区
8 20 GB 测试 Linux 根分区
最后 剩余空间 Windows D 盘

GPT + LVM 个人分区方案

结合 LVM 时,可以将若干系统分区合并为一个大的 LVM 分区(如 100GB),再在其中按需划分逻辑卷:

分区 大小 用途
分区 1 4 GB ESP(boot)分区
分区 2 1 MB BIOS 兼容分区
分区 3 50 GB Windows C 盘
分区 4 100 GB LVM 物理卷(内部再划分 root/home/swap/各测试系统)

实验用 MBR 分区方案(对应下文实操与练习)

设备 大小 挂载点/用途 文件系统 ID
/dev/sda1 30 GB /(CentOS) 83(Linux)
/dev/sda2 30 GB Windows C 盘 7(NTFS)
/dev/sda3 4 GB /boot 83(Linux)
/dev/sda4 剩余 扩展分区 —
/dev/sda5 4 GB Swap 82(Swap)
/dev/sda6 50 GB /home 83(Linux)
/dev/sda7 30 GB Ubuntu 根分区 83(Linux)
/dev/sda8 20 GB Mint 根分区 83(Linux)
最后逻辑分区 剩余 Windows D 盘 7(NTFS)

2.7 fdisk 完整实操

进入交互分区界面

fdisk /dev/sdb             # 对指定磁盘进行分区管理
sudo fdisk /dev/sda        # 普通用户需 sudo

进入交互命令提示符:

Command (m for help):

若不带磁盘参数直接执行 fdisk,只会打印用法帮助;务必明确指定要操作的磁盘,操作错盘会毁掉系统。

交互命令完整一览

在交互界面中输入 m 可查看所有可用命令:

命令 英文原文 含义
a toggle a bootable flag 切换分区的引导标志,将分区设为引导分区(*)
b edit bsd disklabel 编辑 BSD 磁盘标签
c toggle the dos compatibility flag 切换 DOS 兼容标志
d delete a partition 删除一个分区
l list known partition types 列出所有已知的分区类型 ID
m print this menu 打印帮助菜单
n add a new partition 新建一个分区
o create a new empty DOS partition table 创建一个新的空 DOS(MBR) 分区表
p print the partition table 打印当前分区表
q quit without saving changes 不保存直接退出(放弃所有修改)
s create a new empty Sun disklabel 创建空的 Sun 磁盘标签
t change a partition's system id 修改分区类型 ID
u change display/entry units 更改显示/输入单位(柱面/扇区切换)
v verify the partition table 校验分区表
w write table to disk and exit 保存分区表并退出
x extra functionality (experts only) 专家扩展功能
g create a new empty GPT partition table 创建 GPT 分区表(仅 CentOS 7 及以上)
G create an IRIX (SGI) partition table 创建 IRIX 分区表(CentOS 7)
F list free unpartitioned space 列出未分配空间(CentOS 7)
i print information about a partition 打印某分区详细信息(CentOS 7)

日常使用中重点掌握 a、d、l、n、p、q、t、w 即可。

创建主分区完整演练

输入 n 开始新建分区:

Command (m for help): n
Command action
   e   extended
   p   primary partition (1-4)
  • e:创建扩展分区;
  • p:创建主分区(MBR 一块磁盘最多 4 个主分区)。
  • 注意:只有还有主分区位(1~4 未用满)时才会出现这个询问;主分区位用满且已有扩展分区时,n 会直接创建逻辑分区。

选择 p 创建主分区:

p
Partition number (1-4): 1
First cylinder (1-652, default 1):
Using default value 1
Last cylinder, +cylinders or +size{K,M,G} (1-652, default 652): +1G

操作说明(CentOS 6 以柱面为单位;CentOS 7 提示词为 First sector/Last sector,以扇区为单位):

  1. Partition number:选择分区编号(主分区 1–4),一般直接回车用默认值;
  2. First cylinder / First sector:起始位置,直接回车使用默认值(紧接上一个分区之后,自动对齐);
  3. Last cylinder / +size:指定分区大小。推荐使用 +size{K,M,G} 形式,例如 +1G、+500M、+10240K、+30G;直接回车则把剩余空间全部用完。

创建扩展分区与逻辑分区完整演练

场景:已有 3 个主分区,把剩余空间全部划给扩展分区,再在其中划 2 个逻辑分区。

# 1) 创建扩展分区,占用第 4 个主分区位,大小用满剩余空间
Command (m for help): n
Command action
   e   extended
   p   primary partition (1-4)
e
Partition number (1-4): 4
First cylinder (…, default …): <回车>
Last cylinder, +cylinders or +size{K,M,G} (…, default …): <回车>   # 全部剩余空间

# 2) 创建第一个逻辑分区,注意编号自动从 5 开始
Command (m for help): n
Command action
   l   logical (5 or over)
   p   primary partition (1-4)
l
First cylinder (…): <回车>
Last cylinder, +cylinders or +size{K,M,G}: +2G

# 3) 创建第二个逻辑分区(自动为 sdb6)
Command (m for help): n
l
First cylinder: <回车>
Last cylinder, +cylinders or +size{K,M,G}: +2G

# 4) 查看结果
Command (m for help): p

要点:

  • 扩展分区本身不能格式化、不能挂载,/dev/sdb4 只是容器;
  • 逻辑分区从 /dev/sdb5 开始;
  • 逻辑分区的总容量不能超过扩展分区;
  • 若剩余空间已全部给扩展分区,就再也无法创建新的主分区。

修改分区类型 ID

Command (m for help): t
Partition number (1-6): 5
Hex code (type L to list all codes): 8e
Changed system type of partition 5 to 8e (Linux LVM)

步骤:先输入 t,再输入要修改的分区号,最后输入类型 ID。输入 L 可列出所有支持的分区类型。常用 ID:83(Linux,默认)、82(Linux swap)、8e(Linux LVM)、fd(Linux raid autodetect)、7(NTFS)、b(FAT32)、ef(EFI System)。

设置引导标志与删除分区

Command (m for help): a          # 切换引导标志
Partition number (1-4): 1        # 将 sdb1 设为可引导(p 输出的 Boot 列出现 *)

Command (m for help): d          # 删除分区
Partition number (1-6): 6        # 删除 sdb6

删除扩展分区会连带删除其内部所有逻辑分区;删除中间的逻辑分区会导致后续逻辑分区编号前移。

查看与保存

使用 p 查看当前分区表:

Command (m for help): p
Disk /dev/sdc: 5368 MB, 5368709120 bytes
255 heads, 63 sectors/track, 652 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0xd4a66feb

   Device Boot      Start         End      Blocks   Id  System
/dev/sdc1               1         132     1060258+  83  Linux
  • 确认无误后输入 w 保存并退出(此时才真正写入磁盘);
  • 发现操作错误就输入 q 放弃退出,磁盘不会有任何改动。这是 fdisk 最重要的安全阀。

非交互式(脚本化)用法

在自动化脚本中可以用管道喂给 fdisk 一串命令:

# 在 /dev/sdb 上创建一个占满整盘的主分区,类型 83
echo -e "n\np\n1\n\n\nw\n" | fdisk /dev/sdb

# 创建主分区并设置类型为 8e(LVM)
printf "n\np\n1\n\n\nt\n8e\nw\n" | fdisk /dev/sdb

# 更推荐使用 parted 的脚本模式(可读性更好)
parted -s /dev/sdb mklabel msdos
parted -s /dev/sdb mkpart primary ext4 1MiB 100%

# 或使用 sfdisk(按分区表描述批量创建,适合批量部署)
echo ',,83' | sfdisk /dev/sdb            # 单个占满全盘的 83 分区
sfdisk -d /dev/sda > sda.layout          # 导出分区布局
sfdisk /dev/sdb < sda.layout             # 把相同布局套用到另一块盘

CentOS 6 与 7 差异:CentOS 6 的 fdisk 不支持 GPT(会告警并可能损坏 GPT 表);CentOS 7 的 fdisk(util-linux 2.23+)支持 GPT,且新增了 g(创建 GPT 表)命令,但复杂 GPT 操作仍建议用 gdisk/parted。

2.8 parted 完整实操

parted 同时支持 MSDOS(MBR)和 GPT 分区表;当磁盘使用 GPT 或容量大于 2TB 时,必须使用 parted(或 gdisk)而非 CentOS 6 的 fdisk。

关键区别:parted 的每条命令都是立即生效、立即写入磁盘的,没有 fdisk 的 w 保存 / q 放弃机制。因此操作前必须再三确认设备名,rm 一旦回车就无法撤销。

模式一:交互模式

parted /dev/sdc

若不指定磁盘,parted 会默认操作系统中排序最靠前的磁盘(危险,务必显式指定):

GNU Parted 2.1
使用 /dev/sdc
Welcome to GNU Parted! Type 'help' to view a list of commands.
(parted)

常用交互命令:

命令 作用
help 查看帮助;help mkpart 查看单条命令帮助
select <设备> 切换到另一块磁盘操作
mklabel <类型> 创建分区表(gpt / msdos),会清空原有分区表
mktable 同 mklabel
mkpart 创建一个新分区
print(可简写 p) 查看当前磁盘的分区信息
print all 查看所有磁盘
print free 同时显示未使用的空闲区域
rm <编号> 删除指定编号的分区(立即生效!)
name <编号> <名称> 给 GPT 分区设置名称
set <编号> <标志> on/off 设置分区标志(boot、lvm、raid、esp、swap、hidden)
unit <单位> 设置显示单位(s 扇区、B、KB、MB、GB、TB、%、compact、chs、cyl)
resizepart <编号> <结束位置> 调整分区结束位置(扩大/缩小分区,不动文件系统)
align-check opt <编号> 检查分区是否最优对齐(4K 对齐检测)
toggle <编号> <标志> 翻转某个标志
rescue <起始> <结束> 尝试恢复该区间内丢失的分区
quit(简写 q) 退出 parted

查看分区示例:

(parted) print
Model: VMware, VMware Virtual S (scsi)
Disk /dev/sdc: 21.5GB
Sector size (logical/physical): 512B/512B
Partition Table: pc98

Number  Start   End     Size    File system  Name  标志
 1      1003MB  10.0GB  8998MB               sdc1  启动

Number 列即分区编号,rm 1 表示删除编号为 1 的分区。Partition Table: 行显示当前分区表类型(msdos/gpt/loop/unknown)。

模式二:命令行(脚本)模式

把交互命令直接作为参数传给 parted,适合脚本自动化:

parted /dev/sdc print                                # 查看
parted -s /dev/sdc mklabel gpt                       # -s 脚本模式,不交互确认
parted -s /dev/sdc mkpart primary ext4 1MiB 10GiB    # 创建分区
parted -s /dev/sdc mkpart data 1MiB 100%             # GPT 下第二个参数是分区名
parted -s /dev/sdc set 1 lvm on                      # 设置 lvm 标志
parted -s /dev/sdc rm 1                              # 删除分区
parted -s -a optimal /dev/sdc mkpart primary 0% 100% # -a optimal 保证最优对齐
parted /dev/sdc unit GB print                        # 以 GB 为单位查看
parted -l                                            # 列出所有磁盘分区

创建 GPT 分区表与 2TB 以上大分区的完整步骤

# 场景:一块 4TB 的新盘 /dev/sdb,要做成一个完整的 4TB 分区
# 1. 确认盘符与容量,避免操作错盘
lsblk -d
parted /dev/sdb print                   # 若提示 unrecognised disk label 说明是裸盘

# 2. 创建 GPT 分区表(MBR 无法管理 2TB 以上,必须 gpt)
parted /dev/sdb
(parted) mklabel gpt
Warning: The existing disk label on /dev/sdb will be destroyed and all data
on this disk will be lost. Do you want to continue?
是/Yes/否/No? Yes

# 3. 设置显示单位为 TB 便于阅读
(parted) unit TB

# 4. 创建占满整盘的分区(0% 100% 由 parted 自动对齐,最稳妥)
(parted) mkpart primary xfs 0% 100%
# 也可显式指定:mkpart primary xfs 1MiB 4001GB

# 5. 查看确认
(parted) print
Model: VMware Virtual disk (scsi)
Disk /dev/sdb: 4.00TB
Sector size (logical/physical): 512B/512B
Partition Table: gpt
Number  Start   End     Size    File system  Name     标志
 1      0.00TB  4.00TB  4.00TB               primary

# 6. 检查 4K 对齐是否最优
(parted) align-check opt 1
1 aligned

# 7. 退出(parted 已即时写盘,无需 w)
(parted) quit

# 8. 刷新分区表并格式化挂载
partprobe /dev/sdb
mkfs.xfs /dev/sdb1                      # CentOS 7 推荐 xfs(ext4 上限 50TB,xfs 更适合大盘)
mkdir -p /data
mount /dev/sdb1 /data
df -hT /data

一条命令完成(脚本化):

parted -s /dev/sdb mklabel gpt mkpart primary xfs 0% 100%
partprobe /dev/sdb; mkfs.xfs -f /dev/sdb1

parted 创建 MBR 分区

parted /dev/sdc
(parted) mklabel msdos                              # 创建 MBR 分区表
(parted) mkpart primary ext4 1MiB 2GiB              # 主分区
(parted) mkpart primary ext4 2GiB 4GiB              # 第二个主分区
(parted) mkpart extended 4GiB 100%                  # 扩展分区
(parted) mkpart logical ext4 4GiB 6GiB              # 逻辑分区(自动编号 5)
(parted) print
(parted) quit

调整分区大小(resizepart)

# 把第 1 个分区的结束位置扩大到 20GB(分区后方必须有连续空闲空间)
parted /dev/sdb
(parted) print                       # 先确认分区号与当前结束位置
(parted) resizepart 1
End?  [10.0GB]? 20GB
(parted) quit

# 分区扩大后还需要扩容文件系统
resize2fs /dev/sdb1        # ext4
xfs_growfs /data           # xfs(传挂载点)

resizepart 只改分区边界,不会动文件系统。缩小分区前必须先缩小文件系统,否则数据损坏;xfs 无法缩小,因此 xfs 分区不能缩。

parted 分区后的后续操作

使用 parted 创建好分区后,同样需要刷新分区表、格式化和挂载:

partx -a /dev/sdc        # 刷新系统分区表(或 partprobe /dev/sdc)
mkfs.ext4 /dev/sdc1      # 格式化为 ext4
mount /dev/sdc1 /sdc     # 挂载到 /sdc
df -h                    # 检查挂载情况

2.9 gdisk 简述

gdisk(GPT fdisk,yum install gdisk -y)是专为 GPT 设计的交互式工具,用法与 fdisk 高度相似,比 parted 更适合习惯 fdisk 的人:

gdisk /dev/sdb             # 进入交互界面
gdisk -l /dev/sdb          # 列出 GPT 分区表

常用交互命令:

命令 作用
? 帮助菜单
p 打印分区表
n 新建分区(依次输入分区号、起始扇区、结束扇区或 +size、类型码)
d 删除分区
t 修改分区类型码(GPT 类型码,如 8300 Linux、8200 swap、8e00 LVM、ef00 EFI、fd00 RAID)
c 修改分区名称
i 显示分区详细信息
l 列出所有已知类型码
v 校验磁盘
r 进入恢复/转换子菜单(含 MBR↔GPT 转换、从备份 GPT 头恢复)
w 保存并退出(会二次确认 Y)
q 不保存退出

GPT 常用类型码(注意与 MBR 的 1 字节 ID 不同):

类型码 含义
8300 Linux filesystem(默认)
8200 Linux swap
8e00 Linux LVM
fd00 Linux RAID
ef00 EFI System Partition(ESP)
ef02 BIOS Boot Partition(GPT + BIOS 引导必需)
0700 Microsoft basic data(NTFS/exFAT/FAT32)

MBR 与 GPT 互转(高风险操作,先备份):

gdisk /dev/sdb             # 打开 MBR 磁盘时 gdisk 会自动读入并准备转成 GPT,w 保存即完成转换
# GPT 转回 MBR:gdisk 中进入 r(recovery)子菜单,选择 g(convert GPT into MBR),再 w
sgdisk -g /dev/sdb         # 非交互方式将 MBR 转换为 GPT

2.10 刷新分区表让内核识别新分区

使用 w 写入分区表后,内核可能尚未生成对应的设备文件(尤其当该磁盘上有分区正在使用时),常见提示:

WARNING: Re-reading the partition table failed with error 16: Device or resource busy.
The kernel still uses the old table. The new table will be used at the next reboot
or after you run partprobe(8) or kpartx(8)

保存后先验证分区设备是否已生成:

ls /dev/sdc*
# /dev/sdc  /dev/sdc1
lsblk
cat /proc/partitions

若新分区设备未出现,用以下任一方式刷新:

partprobe                    # 通知内核重新读取所有磁盘的分区表(推荐)
partprobe /dev/sdb           # 只刷新指定磁盘(更安全,避免影响其他盘)
partprobe -s                 # 显示摘要信息

partx -a /dev/sdb            # 添加该磁盘上的所有分区到内核
partx -a /dev/sdb1 /dev/sdb  # 添加指定分区
partx -d /dev/sdb            # 从内核删除该磁盘的分区映射
partx -u /dev/sdb            # 更新分区映射
partx -l /dev/sdb            # 列出内核识别到的分区

kpartx -a /dev/sdb           # device-mapper 方式添加分区映射(常用于 loop 设备、多路径盘)
kpartx -d /dev/sdb           # 删除映射
kpartx -l disk.img           # 列出镜像文件中的分区

reboot                       # 终极方法:重启(在有分区被占用无法刷新时使用)

partx -a 的作用是告知系统"刚刚新建了分区,请重新识别"。

为什么有时必须重启? 当同一块磁盘上有分区已经被挂载或正在被使用(如根分区所在的 /dev/sda),内核出于安全考虑拒绝重新加载整张分区表(error 16 / Device or resource busy),此时:

  1. 优先尝试 partprobe /dev/sda;
  2. 若失败,用 partx -a --nr 3 /dev/sda 只添加新增的那个分区号(不动已有分区,通常能成功);
  3. 都不行则安排窗口期 reboot。

处理 "Device or resource busy" 的排查思路:

lsblk /dev/sdb               # 看是否有分区已挂载
mount | grep sdb             # 找出挂载点
lsof /dev/sdb1               # 找出占用该设备的进程
fuser -mv /mnt               # 找出占用挂载点的进程
dmsetup ls                   # 看是否被 LVM / multipath / LUKS 占用
swapon -s                    # 看是否被当作 swap 启用
cat /proc/mdstat             # 看是否是 RAID 成员
umount /mnt                  # 卸载后再刷新

2.11 分区表的备份与恢复

备份 MBR(dd / head 两种写法)

# 1) 连同启动加载程序(MBR 全部 512 字节)一起备份
dd if=/dev/sda of=/backup/mbr_full.bin bs=512 count=1
head -c 512 /dev/sda > /some/where/boot_sector.bin       # 等效写法

# 2) 只备份引导代码(前 446 字节,不含分区表)
dd if=/dev/sda of=/backup/mbr_boot.bin bs=446 count=1

# 3) 仅备份分区表(MBR 中偏移 446 开始的 64 字节)
dd if=/dev/sda of=/backup/mbr_parttable.bin bs=1 count=64 skip=446
head -c 510 /dev/sda | tail -c 64 > /some/where/partition_table.bin   # 等效写法

备份文件一定要存到其他磁盘或远程主机,存在本盘上等于没备份。

恢复 MBR(慎用,会覆盖目标盘的 MBR)

# 恢复完整 512 字节(引导代码 + 分区表)
dd if=/backup/mbr_full.bin of=/dev/sda bs=512 count=1
dd if=./mbr.txt of=/dev/sda bs=1 count=512        # 逐字节写法,等效

# 只恢复分区表 64 字节,不动引导代码
dd if=/backup/mbr_parttable.bin of=/dev/sda bs=1 count=64 seek=446

# 恢复后刷新分区表
partprobe /dev/sda
fdisk -l /dev/sda

备份与恢复 GPT(sgdisk)

GPT 分区表远大于 512 字节,不能用 dd bs=512 count=1 备份,必须用 sgdisk:

# 备份 GPT 分区表到文件
sgdisk --backup=/backup/sdb-gpt.bin /dev/sdb
sgdisk -b /backup/sdb-gpt.bin /dev/sdb          # 简写

# 从备份恢复
sgdisk --load-backup=/backup/sdb-gpt.bin /dev/sdb
sgdisk -l /backup/sdb-gpt.bin /dev/sdb          # 简写

# 把 sdb 的分区结构复制到 sdc(做 RAID 成员盘时很实用)
sgdisk -R /dev/sdc /dev/sdb                     # 复制分区表
sgdisk -G /dev/sdc                              # 随机化 GUID,避免与源盘冲突

# 用主 GPT 修复损坏的备份 GPT(或反向)
sgdisk -e /dev/sdb                              # 把备份 GPT 移到盘尾(扩容后必做)
gdisk /dev/sdb                                  # 交互式:r → b/c 从备份恢复主表

也可以用 parted/sfdisk 导出可读文本备份:

sfdisk -d /dev/sda > /backup/sda-partitions.txt   # 导出为文本(MBR/GPT 都支持)
sfdisk /dev/sda < /backup/sda-partitions.txt      # 按文本恢复
parted /dev/sda unit s print > /backup/sda-layout.txt  # 人工可读的记录,便于手工重建

恢复演练(实验步骤)

# 1. 在实验盘 /dev/sdb 上创建两个分区并备份 MBR
fdisk /dev/sdb                                  # 创建 sdb1、sdb2,w 保存
dd if=/dev/sdb of=/root/sdb-mbr.bin bs=512 count=1

# 2. 故意破坏分区表(只清掉 64 字节分区表,保留引导代码)
dd if=/dev/zero of=/dev/sdb bs=1 count=64 seek=446
partprobe /dev/sdb
fdisk -l /dev/sdb                               # 分区已"消失"

# 3. 从备份恢复
dd if=/root/sdb-mbr.bin of=/dev/sdb bs=512 count=1
partprobe /dev/sdb
fdisk -l /dev/sdb                               # 分区回来了
mount /dev/sdb1 /mnt && ls /mnt                 # 数据完好无损

结论:分区表丢失时数据并没有真正丢失(数据块还在原处),只要能恢复分区表的起止位置,数据就能恢复。若没有备份,可以用 testdisk、gpart、parted 的 rescue 命令扫描并猜测分区边界。

三、文件系统与挂载

3.1 文件系统的概念与作用

文件系统是磁盘上经过特定格式化的一片区域,操作系统通过它把裸块设备组织成"目录 + 文件"的层次结构,从而方便地查询和访问磁盘块。

注意:存取磁盘数据不一定需要文件系统,例如 swap 设备就没有文件系统,数据库也可以直接使用裸设备。

基本概念:

  • 文件系统:磁盘上经过格式化的区域,负责记录"哪个文件的数据放在哪些块上"。
  • 文件:文件系统中存储数据的命名对象;文件可以为空,但仍为操作系统提供元数据信息。
  • 目录:一种特殊文件,其中登记了若干文件条目(目录项),支撑文件系统的层次结构。每个文件至少登记在一个目录中。
  • 子目录:被包含在另一个目录中的目录。
  • 文件名:标记文件的字符串,最长 255 个字符。
  • 路径名:以 / 连接多个文件名形成的字符串,如 /etc/passwd。

文件系统层次结构

磁盘上的文件系统是一种层次结构,由目录和子目录构成,最顶层的目录称为根目录(/)。

Windows 与 Linux 文件系统对比:

Windows 文件系统(多根)          Linux 文件系统(单根,树状)
  硬盘                              /
  ├── C:\  分区                    ├── bin
  ├── D:\  分区                    ├── sbin
  ├── E:\  分区                    ├── etc
  └── F:\  分区                    ├── home
                                    ├── var
                                    ├── tmp
                                    ├── root
                                    ├── dev
                                    └── ...

在 Linux 中所有分区、设备都挂载到根目录下的某一子目录,形成一棵完整的目录树,不存在 Windows 那样的多个盘符根。

根目录下各子目录(/bin、/etc、/var…)的用途详见文末"附录 B:Linux 文件系统目录结构"。

3.2 inode、block、超级块与目录项

一个文件由两部分组成:

  • i 节点(inode,索引节点):记录文件的元数据——权限(mode)、属主 UID/GID、大小、时间戳(atime/mtime/ctime)、链接数、以及数据块的位置指针。文件名不在 inode 里。
  • 数据(data block):文件实际内容所在的磁盘块。

文件系统的整体布局(以 ext 系列为例):

结构 作用
引导块 Boot Block 文件系统开头预留给引导程序的空间
超级块 Super Block 记录整个文件系统的全局信息:块大小、总块数/空闲块数、总 inode 数/空闲 inode 数、挂载时间、挂载次数、文件系统状态(clean/dirty)、UUID、卷标等。超级块损坏文件系统就无法挂载,因此 ext 会在多个块组中保存备份超级块
块组描述符 Group Descriptor 描述每个块组的 inode 表位置、位图位置等
块位图 Block Bitmap 用位标记每个数据块是否已被占用
inode 位图 Inode Bitmap 标记每个 inode 是否已被使用
inode 表 Inode Table 存放所有 inode 结构,数量在格式化时固定(ext 系列)
数据块 Data Block 存放文件真实内容;目录也是文件,其数据块中存放的是"目录项"

目录项(dentry):目录的数据块中存放的是 文件名 → inode 号 的映射表。因此:

  • 文件名本身并不存于 inode 中,而是存在于其所在目录的条目里;
  • 访问 /etc/passwd 的过程是:根目录 inode → 根目录数据块 → 找到 etc 的 inode 号 → 读 etc 的 inode → 读 etc 的数据块 → 找到 passwd 的 inode 号 → 读 passwd 的 inode → 读数据块。

查看 inode 号与信息:

ls -i /etc/passwd                 # 查看 inode 号
ls -li /etc                       # 列出目录下所有文件的 inode 号
stat /etc/passwd                  # 查看文件的完整元数据(inode、块数、三个时间戳)
df -i                             # 查看各文件系统 inode 使用情况
find / -inum 1234567              # 按 inode 号反查文件(找硬链接的所有名字)
find / -inum 1234567 -delete      # 删除文件名含特殊字符无法删除的文件
tune2fs -l /dev/sdb1 | grep -i inode   # 查看 ext 文件系统 inode 参数

硬链接与软链接在文件系统层的实现

对比项 硬链接(hard link) 软链接/符号链接(symbolic link)
创建命令 ln 源文件 链接名 ln -s 源文件 链接名
本质 在目录中新增一个"文件名 → 同一个 inode 号"的目录项 新建一个独立 inode,其数据块中存放目标的路径字符串
inode 号 与源文件完全相同 与源文件不同
链接计数 源文件 inode 的 link count +1 不影响源文件的 link count
跨文件系统 不能(inode 号只在单个文件系统内唯一) 可以
链接目录 不能(除 . 和 ..,防止目录环) 可以
删除源文件 数据仍可通过其他硬链接访问(link count 减到 0 且无进程打开时才真正释放) 变成"断链"(dangling link),访问报 No such file or directory
文件大小 与源文件相同 等于目标路径字符串的长度
ls -l 标识 与普通文件相同 首字符 l,并显示 -> 目标
ln /data/a.txt /data/a_hard       # 硬链接
ln -s /data/a.txt /data/a_soft    # 软链接
ls -li /data/                     # 观察 inode 号与链接数的差异
stat /data/a.txt                  # Links: 2 表示有 2 个硬链接名

"删除文件"的真实含义:rm 只是从目录中移除目录项并把 inode 的 link count 减 1。只有当 link count 变为 0 并且没有任何进程还打开着该文件时,数据块才会被标记为空闲。这解释了两个现象:

  1. 删除了大文件但 df 显示空间没释放(有进程仍持有 fd,见排错清单);
  2. 目录写权限决定能否删除其中的文件,而不是文件本身的权限。

inode 耗尽的现象与排查

现象:创建新文件报 No space left on device,但 df -h 显示还有大量剩余空间。

原因:ext 系列文件系统的 inode 总数在格式化时就已固定(默认每 16KB 空间分配 1 个 inode)。如果文件系统里存在海量小文件(如 session 文件、邮件队列、缓存碎片、日志切片),inode 会先于 block 被用完。

排查与处理:

# 1. 确认是 inode 耗尽而不是空间耗尽
df -h                              # Use% 不高
df -i                              # IUse% 达到 100%

# 2. 定位哪个目录下小文件最多(找出 inode 消耗大户)
for d in /*; do echo -n "$d "; find $d -xdev -type f 2>/dev/null | wc -l; done
find /var -xdev -type d -exec sh -c 'echo "$(ls -A "$1" | wc -l) $1"' _ {} \; 2>/dev/null | sort -rn | head -20
du --inodes -x -d 1 /var | sort -rn | head    # 新版 coreutils 支持 --inodes

# 3. 清理无用小文件(如 PHP session、邮件队列、旧日志)
find /var/lib/php/session -type f -mtime +7 -delete
find /var/spool/postfix/maildrop -type f -mtime +30 -delete

# 4. 长期方案:重新格式化时增大 inode 数量(会丢数据,需先备份)
mkfs.ext4 -i 4096 /dev/sdb1        # 每 4KB 空间一个 inode(4 倍密度)
mkfs.ext4 -N 2000000 /dev/sdb1     # 直接指定 inode 总数
mkfs.ext4 -T small /dev/sdb1       # 使用小文件优化模板

# 或改用 xfs:xfs 的 inode 是动态分配的,几乎不会 inode 耗尽
mkfs.xfs /dev/sdb1

CentOS 6 与 7 差异:ext4(CentOS 6 默认)的 inode 数量固定,格式化后无法增加;xfs(CentOS 7 默认)的 inode 动态分配,天然不存在 inode 耗尽问题(除非 imaxpct 限制触发,可用 xfs_growfs -m 调整)。

3.3 常见文件系统对比

文件系统 日志 单文件/单卷上限 可否在线扩容/缩容 特点与适用场景
ext2 无 2TB / 32TB 扩容需卸载 / 可缩 无日志,速度略快但异常断电后 fsck 极慢;常用于 /boot(兼容性最好)
ext3 有 2TB / 32TB 可扩 / 可缩 ext2 + 日志,可与 ext2 相互转换(tune2fs -j),已被 ext4 取代
ext4 有 16TB / 1EB(实际推荐 ≤50TB) 在线可扩,缩容需卸载 CentOS 6 默认。extent 映射、延迟分配、多块分配、无日志模式可选、支持 fsck 快速检查
xfs 有(元数据日志) 8EB / 8EB 在线可扩,不可缩 CentOS 7 默认。高性能大文件与并发写、动态 inode、支持在线碎片整理(xfs_fsr)、配额功能强(含 project quota)
btrfs CoW 16EB 可扩可缩 写时复制、内建快照与多设备 RAID、自校验;CentOS 7 中为技术预览,RHEL 8 已移除,生产慎用
vfat / FAT32 无 4GB / 2TB 否 Windows 通用,跨平台 U 盘首选;单文件不能超过 4GB;无权限概念。Windows 下的 FAT32 在 Linux 中识别为 vfat
exfat 无 16EB 否 用于大容量 U 盘/SD 卡,突破 FAT32 的 4GB 限制;CentOS 需装 fuse-exfat、exfat-utils(EPEL 源)
ntfs / ntfs-3g 有 16TB+ 否(Linux 下) Windows 主文件系统;Linux 读写需 ntfs-3g(FUSE 实现,性能一般)
swap — — — 交换空间,没有文件系统结构,用 mkswap 创建
iso9660 无 — 只读 光盘/ISO 镜像标准格式,只读挂载
udf 无 — 只读 DVD/蓝光光盘格式
tmpfs 无 受内存限制 可(mount -o remount,size=) 基于内存(+swap)的临时文件系统,重启即失,如 /dev/shm、/run
proc / sysfs / devtmpfs — — — 伪文件系统,映射内核数据结构,不占磁盘
NFS — — — Unix/Linux 网络文件共享协议
CIFS / SMB — — — Windows 网络共享协议,Linux 用 cifs-utils 挂载

NFS 与 CIFS 属于网络文件系统,服务端配置与优化 > 详见 阶段09。

CentOS 6 与 7 的默认文件系统差异(重要):

项目 CentOS 6 CentOS 7
默认文件系统 ext4 xfs
/boot 默认 ext4 xfs(也常见 ext4/ext2)
创建命令 mkfs.ext4 mkfs.xfs
在线扩容 resize2fs(可扩可缩,缩容须卸载) xfs_growfs(只能扩,不能缩)
修复命令 e2fsck -f -y xfs_repair
查看信息 dumpe2fs、tune2fs -l xfs_info、xfs_admin
调整卷标 e2label / tune2fs -L xfs_admin -L
配额工具 quota 系列(quotacheck/edquota/repquota) xfs_quota(也支持部分 quota 命令)
备份工具 dump / restore xfsdump / xfsrestore
碎片整理 基本无需 xfs_fsr

选型建议:CentOS 7 上除有明确缩容需求的场景外,一律使用默认 xfs;若业务确实需要将来缩小文件系统,必须选 ext4(或使用 LVM 精简卷灵活分配)。

3.4 格式化(创建文件系统)

创建文件系统就是常说的"分区格式化",使用 mkfs 系列命令。分区完成后仍不能直接使用,还必须经过格式化和挂载。

mkfs 命令格式

mkfs -t <文件系统类型> <设备>
mkfs.<类型> <设备>              # 等价的简写形式

以下两条命令等价,都将分区 /dev/sdc1 格式化为 ext4:

mkfs -t ext4 /dev/sdc1
mkfs.ext4 /dev/sdc1

各类型格式化示例:

mkfs -t ext2 /dev/sda3            # 格式化 boot 分区为 ext2
mkfs.ext3 /dev/sdb1               # ext3
mkfs.ext4 /dev/sda6               # 格式化 home 分区为 ext4(CentOS 6 默认)
mkfs.xfs /dev/sdb1                # xfs(CentOS 7 默认)
mkfs.xfs -f /dev/sdb1             # -f 强制覆盖已有文件系统
mkfs.vfat /dev/sda2               # 格式化为 FAT32(Linux 中识别为 vfat)
mkfs.vfat -F 32 /dev/sdb1         # 明确指定 FAT32
mkfs.ntfs /dev/sda2               # 格式化为 NTFS(较慢)
mkfs.ntfs -f /dev/sda2            # 快速格式化 NTFS(-f/-Q 跳过全盘清零)
mkfs.btrfs /dev/sdb1              # btrfs
mkswap /dev/sda5                  # 格式化为交换空间(无文件系统)
mkfs -t ext4 /dev/mapper/vg0-lv0  # 格式化 LVM 逻辑卷
mkfs.xfs /dev/md0                 # 格式化 RAID 阵列

查看系统支持哪些 mkfs:

ls /sbin/mkfs*
# mkfs mkfs.btrfs mkfs.cramfs mkfs.ext2 mkfs.ext3 mkfs.ext4 mkfs.minix mkfs.vfat mkfs.xfs
cat /proc/filesystems             # 查看当前内核支持的文件系统

mkfs / mke2fs 常用选项(ext 系列)

mkfs.ext4 实际就是 mke2fs -t ext4,两者选项通用:

选项 作用
-t <类型> 指定文件系统类型(ext2/ext3/ext4/xfs/vfat…)
-b <字节> 指定块大小,可选 1024、2048、4096(默认 4096)。小文件多用小块省空间,大文件用 4096 性能好
-L <卷标> 设置卷标(LABEL),便于在 fstab 中用 LABEL= 挂载
-i <字节> 每多少字节数据分配一个 inode(值越小 inode 越多),如 -i 4096
-N <数量> 直接指定 inode 总数
-m <百分比> 为 root 保留的块百分比(默认 5%),大盘可设 -m 1 甚至 -m 0 节省空间
-T <类型> 使用预设模板:small(小文件多)、largefile(每 1MB 一个 inode)、largefile4(每 4MB 一个 inode)、news、floppy
-j 创建带日志的文件系统(ext3)
-c 格式化前检查坏道(-cc 做读写检查,很慢)
-F 强制执行(即使目标看起来不是块设备或已挂载)
-n 只模拟不实际写入(用于预演、查看备份超级块位置),非常安全
-q 静默模式
-v 详细输出
-U <uuid> 指定 UUID(-U random 随机生成)
-O <特性> 启用/禁用文件系统特性,如 -O ^has_journal 关闭日志
-E <扩展选项> 扩展选项,如 -E stride=16,stripe-width=64(RAID 对齐优化)、-E lazy_itable_init=1(加速格式化)

示例:

# 大量小文件的场景:块 1K + 高密度 inode + 保留 1%
mkfs.ext4 -b 1024 -i 4096 -m 1 -L smallfiles /dev/sdb1

# 存放大文件(视频、备份镜像):减少 inode 数量,省空间
mkfs.ext4 -T largefile -m 0 -L bigdata /dev/sdb1

# 指定卷标并查看备份超级块位置(模拟不写盘)
mkfs.ext4 -n /dev/sdb1 | grep -A2 "Backup superblock"

mkfs.xfs 常用选项

选项 作用
-f 强制覆盖已存在的文件系统(xfs 默认拒绝覆盖,重做实验时必用)
-L <卷标> 设置卷标(最长 12 字符)
-b size=<值> 数据块大小,如 -b size=4096
-i size=<值> inode 大小(256~2048)
-d agcount=<n> 分配组数量,影响并发性能
-d su=,sw= RAID 条带对齐(su 条带单元、sw 数据盘数量)
-l size=<值> 日志区大小;-l logdev=/dev/sdX 把日志放到独立设备(SSD)提升性能
-n size=<值> 目录块大小,目录内文件极多时可增大
-m crc=1 启用元数据 CRC 校验(CentOS 7 默认开启)
-q 静默
mkfs.xfs -f -L data -b size=4096 /dev/sdb1
mkfs.xfs -f -d su=64k,sw=3 /dev/md0     # 与 RAID5(3 数据盘、64K 条带)对齐

格式化前的确认习惯(避免灾难)

格式化是不可逆操作,务必养成以下习惯:

# 1. 确认设备身份:容量、型号、是否是你以为的那块盘
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT,SERIAL /dev/sdb
ls -l /dev/disk/by-id/ | grep sdb

# 2. 确认该设备上没有数据、没有已有文件系统
blkid /dev/sdb1                  # 有输出说明已有文件系统,务必确认可以覆盖
lsblk -f /dev/sdb

# 3. 确认没有被挂载、没有被 LVM/RAID/swap 占用
mount | grep sdb
swapon -s | grep sdb
pvs | grep sdb
cat /proc/mdstat

# 4. 关键盘先备份分区表与关键数据
dd if=/dev/sdb of=/backup/sdb-mbr.bin bs=512 count=1

# 5. 复制粘贴命令后,回车前再读一遍设备名(sda 与 sdb 只差一个字母)

血泪教训:mkfs.ext4 /dev/sda(漏写分区号)会直接摧毁整块盘的分区表;mkfs.ext4 /dev/sda1 打成 /dev/sda2 会格式化掉生产数据。格式化命令永远不要凭记忆盲打。

3.5 挂载(mount)

格式化完成后,分区仍需要挂载到系统目录下才能使用。

完整语法

mount [选项] <设备> <挂载点>
mount -t <类型> -o <挂载选项> <设备> <挂载点>

讲义原文中参数顺序写作 mount [选项] 挂载点 需挂载介质,实际使用的标准顺序为 设备 挂载点。

主要选项:

选项 作用
-t <类型> 指定挂载文件系统类型,可省略(mount 会通过 blkid 自动识别)
-o <选项> 指定挂载选项,可省略(使用默认值 defaults),多个选项用逗号分隔
-a 挂载 /etc/fstab 中所有尚未挂载的条目
-r 只读挂载(等价 -o ro)
-w 读写挂载(等价 -o rw,默认)
-L <卷标> 按卷标挂载,如 mount -L mydata /data
-U <UUID> 按 UUID 挂载
-n 挂载时不写入 /etc/mtab(根分区只读时使用)
-v 详细输出
-f 模拟挂载(配合 -v 排查问题,不真正挂载)
--bind 绑定挂载(等价 -o bind)
-B / -R --bind / --rbind(递归绑定,含子挂载)
-o remount 重新挂载(用于修改已挂载文件系统的选项)

-o 挂载选项全表

选项 含义
ro 只读挂载
rw 可读可写挂载(默认)
exec / noexec 允许 / 禁止执行该文件系统上的二进制程序(/tmp、/var 建议 noexec 加固)
suid / nosuid 允许 / 禁止 SUID、SGID 位生效(安全加固常用 nosuid)
dev / nodev 允许 / 禁止解释设备文件(挂载不可信介质必加 nodev)
auto / noauto mount -a 时自动挂载 / 不自动挂载(光驱、U 盘常用 noauto)
atime / noatime 记录 / 不记录文件访问时间。noatime 可显著减少写 I/O,SSD 与高并发读场景推荐
diratime / nodiratime 目录访问时间是否更新
relatime 折中方案:仅当 atime 早于 mtime/ctime 或超过 1 天才更新(现代内核默认)
strictatime 严格按 POSIX 更新 atime
sync 同步写入,每次写操作立即落盘(安全但极慢,U 盘可用)
async 异步写入,先写缓存后台落盘(默认,性能好)
defaults 等价于 rw,suid,dev,exec,auto,nouser,async(最常用)
remount 重新挂载已挂载的文件系统,用于在线修改选项
loop 将一个普通文件作为块设备挂载(常用于 ISO 镜像、大文件当磁盘)
bind 绑定目录,把一个目录挂载到另一个位置
user / nouser 允许 / 不允许普通用户挂载
users 允许任何用户挂载和卸载
owner 允许设备属主挂载
uid=<n> / gid=<n> 指定挂载后所有文件的属主/属组(用于 vfat/ntfs 这类无权限概念的文件系统)
umask= / dmask= / fmask= vfat/ntfs 的权限掩码(目录/文件分别控制)
iocharset=utf8 字符集,解决 vfat/ntfs/iso9660 中文名乱码
_netdev 标记为网络设备,系统会等网络就绪后再挂载(NFS/iSCSI 必加)
nofail 设备不存在时不报错、不阻塞启动(可插拔设备、数据盘强烈推荐)
errors=remount-ro ext 系列出错时自动重挂为只读(根分区常用)
data=journal/ordered/writeback ext3/4 日志模式:全数据日志(最安全最慢)/ 默认 / 最快最不安全
barrier=0/1 写屏障,带电池的 RAID 卡可设 barrier=0 提升性能
discard 删除文件时向 SSD 发送 TRIM(更推荐用 fstrim.timer 定时)
usrquota / grpquota 启用用户/组磁盘配额(ext 系列)
uquota / gquota / pquota xfs 的用户/组/项目配额
x-systemd.automount 首次访问挂载点时才挂载(systemd 按需挂载)
x-systemd.device-timeout=5 等待设备的超时时间
noquota 禁用配额
size=<值> tmpfs 大小限制,如 size=2G

使用示例

# 查看当前系统所有挂载信息
mount

# 最简形式(类型自动识别)
mount /dev/sdc1 /sdc

# 指定类型与选项
mount -t ext2 -o rw /dev/sda3 /disk/boot
mount -t ext4 -o defaults,noatime /dev/sdb1 /data
mount -t xfs /dev/sdb1 /data

# 只读挂载(取证、检查可疑磁盘时必用)
mount -o ro /dev/sdb1 /mnt
mount -r /dev/sdb1 /mnt

# 安全加固挂载(禁止执行、禁止 SUID、禁止设备文件)
mount -o defaults,noexec,nosuid,nodev /dev/sdb1 /data

# 挂载 vfat(Windows C 盘),指定属主与中文字符集
mount -t vfat /dev/sda2 /disk/C
mount -t vfat -o uid=1000,gid=1000,iocharset=utf8 /dev/sdb1 /media/usb

# 挂载 NTFS 分区(需安装 ntfs-3g,位于 EPEL 源)
wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo
yum install ntfs-3g -y
mount.ntfs-3g /dev/sda2 /disk/D
mount -t ntfs-3g /dev/sda2 /disk/D

# 挂载 NFS 网络文件系统
mount -t nfs 192.168.0.183:/soul /mnt

# 挂载 ISO 镜像(loop 方式)
mount -o loop /soft/CentOS6.5-x64.iso /iso
mount -t iso9660 -o ro,loop /soul/images/centos.iso /yum

# 挂载光驱
mount /dev/sr0 /mnt/cdrom
mount /dev/cdrom /media/cdrom

# 目录绑定(访问 /mnt 即访问 /soul)
mount -o bind /soul /mnt
mount --bind /data/www /var/www/html

# 重新挂载(修改挂载选项或修复只读根分区)
mount -o remount,rw /dev/sda1
mount -o remount,rw /                    # 救援模式下把只读根分区改为可写
mount -o remount,noatime /data           # 在线加上 noatime
mount -o remount,ro /data                # 在线改为只读(备份前保证一致性)

# 挂载 tmpfs(内存文件系统)
mount -t tmpfs -o size=2G tmpfs /tmp/ramdisk

挂载点的要求与挂载覆盖现象

挂载点必须是一个已存在的目录:

mkdir -p /data                # 挂载点不存在需先创建
mount /dev/sdb1 /data

挂载覆盖现象:如果挂载点目录在挂载前已有文件,挂载后这些文件会被"遮挡"——它们并没有被删除,只是暂时看不到,卸载后又会重新出现。

mkdir /data && touch /data/old.txt
ls /data                      # old.txt
mount /dev/sdb1 /data
ls /data                      # 只有 lost+found,old.txt 不见了(被遮挡)
umount /data
ls /data                      # old.txt 又回来了

因此:挂载点必须使用空目录,否则原有文件在挂载期间无法访问,还会白白占用底层文件系统空间(用 du 查不到,是磁盘空间"神秘消失"的常见原因之一)。

排查被遮挡数据的方法:

mkdir /tmp/probe
mount --bind / /tmp/probe     # 把根文件系统绑定到别处,绕过所有挂载点查看
du -sh /tmp/probe/data        # 看看挂载点下面藏了多少东西
umount /tmp/probe

其它注意事项:

  • 同一个设备可以挂载到多个挂载点;同一个挂载点也可以叠加挂载多个设备(后挂载的遮挡前面的)。
  • 正在使用的目录(有进程 cwd 在其中)不能作为挂载点被卸载。
  • 不要把设备挂载到 /、/etc、/usr 等系统关键目录上。

查看已挂载信息

mount                          # 输出格式:挂载源 on 挂载目标 type 类型 (挂载选项)
mount -l                       # 附带显示卷标
mount -t ext4                  # 只列出 ext4 类型的挂载
cat /proc/mounts               # 内核视角的真实挂载表(最权威)
cat /etc/mtab                  # 用户空间维护的挂载表
df -hT                         # 简要挂载信息 + 类型 + 使用率

findmnt                        # 树形展示所有挂载点(CentOS 7 推荐)
findmnt /data                  # 查看指定挂载点详情
findmnt -t xfs                 # 按类型过滤
findmnt -S /dev/sdb1           # 按源设备查找
findmnt -T /data/file          # 查某个路径属于哪个挂载点
findmnt --verify               # 校验 /etc/fstab 语法是否正确(改完 fstab 必做)
findmnt -D                     # 类似 df 的输出格式
findmnt -o TARGET,SOURCE,FSTYPE,OPTIONS   # 自定义列

3.6 卸载(umount)

umount /sdc                    # 按挂载点卸载(推荐)
umount /dev/sdc1               # 按设备卸载
umount -l /mnt                 # 延迟卸载(lazy umount):立即从目录树摘除,等设备不忙时真正卸载
umount -f /mnt                 # 强制卸载(主要用于失联的 NFS)
umount -a                      # 卸载 /etc/mtab 中所有文件系统(危险)
umount -R /mnt                 # 递归卸载该挂载点及其下所有子挂载
umount -v /mnt                 # 详细输出

卸载时需确保当前没有进程在使用该挂载点,否则报错:

umount: /data: target is busy.
        (In some cases useful info about processes that use
         the device is found by lsof(8) or fuser(1))

"target is busy" 的完整排查流程

# 0. 最常见原因:自己的 shell 正处在挂载点里
pwd
cd /                            # 先退出该目录再试

# 1. 用 lsof 找出占用该挂载点的进程
lsof /data
lsof +D /data                   # 递归检查目录下所有文件(较慢但彻底)
lsof | grep /data

# 2. 用 fuser 找出占用进程(-m 表示按挂载点查找,-v 详细显示)
fuser -mv /data
# USER  PID  ACCESS COMMAND
# root  1234 ..c..  bash        <- c=当前工作目录 cwd
# root  5678 F....  vim         <- f/F=打开的文件
#   ACCESS 含义:c=cwd, e=可执行文件, f=打开的文件, F=打开且在写, r=root 目录, m=mmap

# 3. 温和处理:通知相关进程退出,或手工结束
fuser -mk -TERM /data           # 向占用进程发送 SIGTERM(可被捕获、可优雅退出)
kill <PID>

# 4. 强制处理(有数据丢失风险,确认无重要写入后再用)
fuser -km /data                 # 杀掉所有占用该挂载点的进程(默认 SIGKILL)
fuser -ki /data                 # 逐个询问后再杀
umount /data

# 5. 仍失败则用延迟卸载
umount -l /data
# 之后用 findmnt / cat /proc/mounts 确认是否已真正卸载

# 6. 其它易忽略的占用来源
swapon -s | grep /data          # 挂载点内有 swap 文件被启用 → swapoff
exportfs -v                     # 该目录被 NFS 导出 → exportfs -u
pvs; lvs; dmsetup ls            # 被 LVM/LUKS 占用
cat /proc/mdstat                # 是 RAID 成员
docker ps; ctr c ls             # 容器挂载了该目录
systemctl status <相关服务>      # 服务持有文件 → systemctl stop

完成以上步骤后,新建分区即可正常读写。

3.7 开机自动挂载 /etc/fstab

使用 mount 命令手动挂载的分区在系统重启后会失效。若要实现开机自动挂载,需要将配置写入 /etc/fstab。

文件示例

cat /etc/fstab

CentOS 6 典型内容:

UUID=b40cacb1-1178-4228-be06-e6d2f135b23e  /          ext4    defaults        1 1
UUID=174c6b5a-0de6-469d-bc07-b38d4bfd3479  /boot      ext4    defaults        1 2
UUID=d608f49e-d50c-41ad-bf9a-7f7a0ef8707d  swap       swap    defaults        0 0
tmpfs                                      /dev/shm   tmpfs   defaults        0 0
devpts                                     /dev/pts   devpts  gid=5,mode=620  0 0
sysfs                                      /sys       sysfs   defaults        0 0
proc                                       /proc      proc    defaults        0 0

其它常见写法示例(含 vfat、iso、swap 的 none 挂载点):

UUID=db6a4392-ccb0-41af-863f-4de76cad1c0a  /           ext4     errors=remount-ro  0 1
UUID=67a2f488-1421-495a-ada1-dc022c29d77d  none        swap     sw                 0 0
UUID=96A4-6194                             /disk/C     vfat     defaults           0 0
UUID=51c35f8c-c9dd-498e-9f05-203d6f931fa6  /disk/boot  ext2     defaults           0 0
UUID=c9edda7d-dbc2-443a-81a4-5a729fc9b27a  none        swap     sw                 0 0
/disk/ubuntu-16.04.4-server-amd64.iso      /opt        iso9660  loop,ro            0 0

CentOS 7 常见内容(LVM + xfs):

/dev/mapper/centos-root  /         xfs      defaults        0 0
UUID=51c35f8c-c9dd-498e /boot      xfs      defaults        0 0
/dev/mapper/centos-swap  swap      swap     defaults        0 0
UUID=8f5c1c2a-6b3e-4c7a /data      xfs      defaults,noatime,nofail  0 0
192.168.1.10:/share     /mnt/nfs   nfs      defaults,_netdev  0 0

六个字段逐项详解

格式:

挂载源(设备)  挂载目标(挂载点)  文件系统类型  挂载选项  dump备份  开机自检顺序
列序 字段 含义与取值
第 1 列 要挂载的设备(挂载源) 可写设备名(/dev/sdb1)、LABEL=卷标、UUID=xxx、PARTUUID=xxx、网络地址(192.168.1.10:/share)、/dev/mapper/vg-lv、伪文件系统名(tmpfs/proc/sysfs/devpts)。强烈推荐 UUID
第 2 列 挂载点 设备挂载到的目录,必须是已存在的绝对路径。swap 没有挂载点,此处写 swap 或 none(两种写法都可以)
第 3 列 文件系统类型 ext2/ext3/ext4/xfs/vfat/ntfs-3g/swap/iso9660/tmpfs/proc/sysfs/devpts/nfs/cifs。也可写 auto 让系统自动探测
第 4 列 挂载参数 常用 defaults;也可用 ro、rw、noatime、noauto、loop、nofail、_netdev、usrquota,grpquota、errors=remount-ro。swap 常写 sw 或 defaults。多个选项用逗号分隔且不能有空格
第 5 列 dump 备份标志 0 不备份;1 表示 dump 命令会备份该文件系统。现代环境基本都写 0
第 6 列 fsck 自检顺序 0 开机不检查;1 优先级最高(只给根分区 /);2 次优先(其他分区,可并行检查)。xfs 一般写 0(xfs 靠日志自愈,不做开机 fsck)

第一列设备的三种写法

第一列支持三种写法,效果等价:

1. 直接使用设备名

/dev/sdc1    /sdc    ext4    defaults    0 0

缺点:设备名会因磁盘增删、插拔顺序、驱动加载顺序而变化,一旦 sdb 变成 sdc,开机就会挂载失败甚至无法启动。生产环境不推荐。

2. 使用 LABEL(卷标)

卷标是用户自定义的名称。

查看卷标(ext 系列):

e2label /dev/sdc1

若未设置过卷标或分区未格式化,会提示:

e2label: Bad magic number in super-block 当尝试打开 /dev/sdc1 时找不到有效的文件系统超级块.

设置卷标:

e2label /dev/sdc1 /abc            # ext 系列设置卷标
e2label /dev/sdc1                 # 再次查看,输出 /abc
tune2fs -L mydata /dev/sdc1       # ext 系列另一种写法
xfs_admin -L mydata /dev/sdb1     # xfs 设置卷标(必须先卸载)
xfs_admin -l /dev/sdb1            # xfs 查看卷标
fatlabel /dev/sdb1 MYUSB          # vfat 设置卷标
blkid | grep LABEL                # 统一查看所有卷标

在 fstab 中使用:

LABEL=/abc      /sdc    ext4    defaults    0 0
LABEL=mydata    /data   xfs     defaults    0 0

3. 使用 UUID(推荐)

UUID(Universally Unique Identifier)是格式化时生成的分区唯一标识,推荐使用。

查看 UUID:

blkid                             # 查看所有分区的 UUID
blkid /dev/sdc1                   # 查看指定分区
ls -l /dev/disk/by-uuid/
lsblk -f
/dev/sda1: LABEL="/vm" UUID="174c6b5a-0de6-469d-bc07-b38d4bfd3479" TYPE="ext4"

在 fstab 中使用:

UUID=174c6b5a-0de6-469d-bc07-b38d4bfd3479    /sdc    ext4    defaults    0 0

为什么推荐 UUID 而不是 /dev/sdb1:

  • 设备名称(如 /dev/sda1)可能因硬盘变动(删除中间分区、添加/移除硬盘、更换 HBA、驱动加载顺序变化、虚拟机迁移)而发生改变;
  • UUID 是分区(准确说是文件系统)的唯一标识符,只要不重新格式化,该分区的 UUID 就保持唯一、不会冲突,跟着数据走;
  • 用 UUID 写 fstab 后,即使磁盘换了槽位、盘符从 sdb 变成 sdd,依然能正确挂载。

修改 UUID:

uuidgen                                     # 生成一个新 UUID
tune2fs -U $(uuidgen) /dev/sdb1             # 修改 ext 系列的 UUID
tune2fs -U random /dev/sdb1                 # 随机生成
xfs_admin -U generate /dev/sdb1             # 修改 xfs 的 UUID(克隆虚拟机后必做,否则 UUID 冲突)

mount -a 验证的重要习惯

vim /etc/fstab                    # 修改配置
findmnt --verify                  # 先做语法校验(CentOS 7)
mount -a                          # 挂载 fstab 中所有未挂载的条目
df -h                             # 确认挂载成功
mount | grep /data                # 确认挂载选项生效

铁律:修改 /etc/fstab 后必须立即执行 mount -a 验证配置是否正确,若报错就当场修好。否则错误只会在下次重启时暴露,而那时系统可能已经起不来了。

对于非关键的数据盘,建议加上 nofail(设备缺失也不阻塞启动):

UUID=xxx  /data  xfs  defaults,nofail  0 0

写错 fstab 导致无法启动的救援方法

现象:开机停在 Welcome to emergency mode! 或 Give root password for maintenance,或者提示 Cannot open access to console, the root account is locked。

CentOS 7 救援步骤(emergency mode):

# 1. 输入 root 密码进入紧急模式(emergency shell)
# 2. 此时根分区是只读的,先重新挂载为可读写
mount -o remount,rw /

# 3. 编辑修复 fstab(把出错的行注释掉或改正)
vi /etc/fstab

# 4. 验证
mount -a
findmnt --verify

# 5. 重启
reboot
# 或直接继续启动:systemctl default / exit

若连紧急模式都进不去(root 密码未知或账户被锁):

1. 开机在 GRUB 菜单按 e 编辑启动项
2. 找到 linux16 / linux 开头的那一行,在行尾追加:
      rd.break            (进入 initramfs 阶段,CentOS 7)
   或 init=/bin/bash       (直接以 bash 为 1 号进程)
   或 systemd.unit=emergency.target
3. Ctrl+X 启动
4. 若用 rd.break:
      mount -o remount,rw /sysroot
      chroot /sysroot
      vi /etc/fstab                # 修复
      touch /.autorelabel          # 若开启 SELinux
      exit; exit                   # 重启
5. 若用 init=/bin/bash:
      mount -o remount,rw /
      vi /etc/fstab
      exec /sbin/init              # 或 reboot -f

CentOS 6 救援步骤:

1. 开机时进入单用户模式:GRUB 菜单按 a 或 e,在内核行尾加 single(或 1)
2. 进入后执行:
      mount -o remount,rw /
      vi /etc/fstab
      reboot
3. 若单用户模式也进不去,用光盘/U 盘 Linux Rescue 模式:
      选择 "Rescue installed system"
      系统会把原系统挂载到 /mnt/sysimage
      chroot /mnt/sysimage
      vi /etc/fstab
      exit; reboot

预防措施:

  1. 改 fstab 前先备份:cp /etc/fstab /etc/fstab.bak.$(date +%F);
  2. 改完立刻 mount -a + findmnt --verify;
  3. 非根分区一律加 nofail;
  4. 网络存储加 _netdev,nofail;
  5. 一律使用 UUID。

systemd 的 .mount 单元与按需挂载(CentOS 7)

CentOS 7 的 systemd 会把 /etc/fstab 自动转换成 .mount 单元(单元名由挂载点路径生成,/ 换成 -):

systemctl list-units --type=mount        # 列出所有挂载单元
systemctl status data.mount              # 查看 /data 的挂载单元状态
systemctl daemon-reload                  # 修改 fstab 后让 systemd 重新生成单元(重要)
systemctl start data.mount               # 挂载
systemctl stop data.mount                # 卸载

也可以手写 .mount 单元(/etc/systemd/system/data.mount):

[Unit]
Description=Mount /data
After=local-fs.target

[Mount]
What=/dev/sdb1
Where=/data
Type=xfs
Options=defaults,noatime

[Install]
WantedBy=multi-user.target

按需自动挂载(automount):只有当有进程访问挂载点时才真正挂载,空闲一段时间后自动卸载。适合光驱、NFS、移动介质。

在 fstab 中启用:

UUID=xxx  /data  xfs  defaults,x-systemd.automount,x-systemd.idle-timeout=60,x-systemd.device-timeout=5,nofail  0 0
192.168.1.10:/share /mnt/nfs nfs x-systemd.automount,_netdev,noauto 0 0

对应的 .automount 单元写法:

# /etc/systemd/system/data.automount
[Unit]
Description=Automount /data

[Automount]
Where=/data
TimeoutIdleSec=60

[Install]
WantedBy=multi-user.target
systemctl enable --now data.automount

CentOS 6 对应的按需挂载机制是 autofs 服务(/etc/auto.master + /etc/auto.misc),service autofs start。

3.8 loop 设备:ISO 镜像与"大文件当磁盘用"

挂载 ISO 镜像

mount -o loop /soft/CentOS6.5-x64.iso /iso
mount -t iso9660 -o ro,loop /soul/images/centos.iso /yum
umount /iso

写入 fstab 长期挂载(常用于搭建本地 yum 源):

/soft/CentOS7.iso   /mnt/iso   iso9660   loop,ro   0 0

loop 设备管理:

losetup -a                          # 查看所有已使用的 loop 设备
losetup -f                          # 显示第一个空闲的 loop 设备名
losetup /dev/loop0 disk.img         # 手工把文件关联到 loop 设备
losetup -d /dev/loop0               # 释放 loop 设备
losetup -P /dev/loop0 disk.img      # -P 让内核识别镜像内的分区(生成 loop0p1)
kpartx -av disk.img                 # 另一种识别镜像内分区的方法

创建一个大文件当磁盘用(完整演练)

用途:测试文件系统、模拟磁盘、做 swap 文件、给容器/用户做空间隔离。

# 1. 创建一个 1GB 的空文件
dd if=/dev/zero of=/data/disk.img bs=1M count=1024
#    if=/dev/zero  输入源为零设备
#    of=...        输出文件路径
#    bs=1M         每次写入 1M
#    count=1024    写入 1024 次,共 1024M(1G)

# 更快的等价做法(稀疏文件,瞬间完成但不预占空间)
dd if=/dev/zero of=/data/disk.img bs=1M count=0 seek=1024
truncate -s 1G /data/disk.img
fallocate -l 1G /data/disk.img       # 预分配真实空间(ext4/xfs 支持,最推荐)
head -c 1G /dev/zero > /data/disk.img   # 用 head 生成

# 2. 在该文件上创建文件系统(会提示 not a block device,输入 y 继续)
mkfs.ext4 /data/disk.img
# 或 mkfs.xfs -f /data/disk.img

# 3. 挂载使用
mkdir -p /mnt/vdisk
mount -o loop /data/disk.img /mnt/vdisk
df -hT /mnt/vdisk

# 4. 写入 fstab 实现开机自动挂载
echo '/data/disk.img  /mnt/vdisk  ext4  loop,defaults  0 0' >> /etc/fstab
mount -a

# 5. 扩容这个"虚拟磁盘"
umount /mnt/vdisk
dd if=/dev/zero bs=1M count=1024 >> /data/disk.img    # 追加 1G
# 或 truncate -s 2G /data/disk.img
e2fsck -f /data/disk.img
resize2fs /data/disk.img                              # ext4 扩容文件系统
mount -o loop /data/disk.img /mnt/vdisk

# 6. 用完清理
umount /mnt/vdisk
rm -f /data/disk.img

该文件是二进制文件,无法使用 cat 或 vim 正常查看和编辑。

3.9 文件系统检查与修复

铁律:fsck/e2fsck/xfs_repair 只能对未挂载(或只读挂载)的文件系统操作。对已挂载的读写文件系统执行检查修复会造成严重的文件系统损坏。

fsck(通用前端)

umount /dev/sdb1                  # 先卸载!
fsck /dev/sdb1                    # 自动按类型调用 e2fsck / xfs_repair 等
fsck -t ext4 /dev/sdb1            # 指定类型
fsck -y /dev/sdb1                 # 对所有问题自动回答 yes(无人值守)
fsck -a /dev/sdb1                 # 自动修复,不提问(比 -y 保守)
fsck -n /dev/sdb1                 # 只检查不修复(只读方式,可用于已挂载的粗略检查)
fsck -f /dev/sdb1                 # 强制检查(即使标记为 clean)
fsck -A                           # 检查 /etc/fstab 中所有文件系统
fsck -AR -y                       # 检查除根分区外的所有(-R 跳过根)
fsck -C /dev/sdb1                 # 显示进度条
fsck -V /dev/sdb1                 # 详细输出

返回码含义:0 无错误;1 已修复错误;2 需要重启;4 仍有未修复错误;8 操作错误。

e2fsck(ext2/3/4 专用,CentOS 6 主力)

e2fsck /dev/sdb1                  # 交互式检查
e2fsck -f /dev/sdb1               # 强制检查(缩容前必做)
e2fsck -f -y /dev/sdb1            # 强制检查并自动 yes(最常用组合)
e2fsck -p /dev/sdb1               # 自动修复安全的问题(preen 模式,开机自检用)
e2fsck -c /dev/sdb1               # 同时检查坏道并记入坏块列表
e2fsck -n /dev/sdb1               # 只读检查,不做任何修改
e2fsck -v /dev/sdb1               # 显示详细统计

# 超级块损坏时用备份超级块修复
mkfs.ext4 -n /dev/sdb1            # 先查出备份超级块位置(-n 不写盘)
dumpe2fs /dev/sdb1 | grep -i superblock
e2fsck -b 32768 /dev/sdb1         # 用位于 32768 块的备份超级块修复
e2fsck -b 32768 -B 4096 /dev/sdb1 # 同时指定块大小

xfs_repair(xfs 专用,CentOS 7 主力)

xfs 没有 fsck 的完整实现(fsck.xfs 是个什么都不做的空脚本),必须使用 xfs_repair:

umount /data                      # 必须先卸载
xfs_repair /dev/sdb1              # 检查并修复
xfs_repair -n /dev/sdb1           # 只检查不修复(dry run)
xfs_repair -L /dev/sdb1           # 清空日志再修复(!!! 最后手段,会丢失未提交的数据 !!!)
xfs_repair -v /dev/sdb1           # 详细输出
xfs_repair -m 2048 /dev/sdb1      # 限制使用的内存(MB),大文件系统修复时可能需要
xfs_repair -d /dev/sdb1           # 允许修复以只读方式挂载的(危险,仅单用户模式)

典型场景:xfs 因异常断电挂载失败,提示 Structure needs cleaning:

mount /dev/sdb1 /data
# mount: Structure needs cleaning
xfs_repair /dev/sdb1
# 若提示 "log dirty, mount the filesystem to replay the log",先尝试挂载让日志重放:
mount /dev/sdb1 /data && umount /data
# 若挂载不上,最后手段:
xfs_repair -L /dev/sdb1
mount /dev/sdb1 /data

lost+found 目录

  • lost+found 是 ext 系列文件系统格式化时自动创建在根目录下的特殊目录(xfs 没有)。

  • fsck/e2fsck 修复时,如果找到"有 inode 和数据块但目录项丢失"的孤儿文件(即数据还在但不知道原来叫什么名字、在哪个目录),就会把它们以 inode 号为文件名放入 lost+found。

  • 修复后应检查该目录:

    ls -l /lost+found/
    file /lost+found/#12345          # 判断文件类型
    head /lost+found/#12345          # 查看内容以确认是什么文件
    mv /lost+found/#12345 /data/recovered.conf   # 人工辨认后归位
  • 不要删除 lost+found 目录本身;若误删可用 mklost+found 重建。

3.10 调整文件系统大小

resize2fs(ext2/3/4)

# 扩容(可在线,文件系统挂载着也能扩)
resize2fs /dev/vg0/lv0                 # 扩到设备/分区的最大可用大小(最常用)
resize2fs /dev/vg0/lv0 20G             # 指定扩到 20G
resize2fs -p /dev/vg0/lv0              # 显示进度

# 缩容(必须先卸载 + 强制检查)
umount /data
e2fsck -f /dev/vg0/lv0                 # 缩容前强制检查,必做
resize2fs /dev/vg0/lv0 10G             # 把文件系统缩到 10G
mount /dev/vg0/lv0 /data

# 其它
resize2fs -M /dev/sdb1                 # 缩到最小可能尺寸
resize2fs -P /dev/sdb1                 # 打印最小可能尺寸(不修改)
resize2fs -f /dev/sdb1                 # 强制执行(跳过某些安全检查)

要点:

  • 扩容支持在线,缩容必须卸载;
  • 缩容目标不能小于已用数据量;
  • 缩容前必须 e2fsck -f,否则 resize2fs 会拒绝执行;
  • resize2fs 只调整文件系统,不会调整分区或逻辑卷的大小。

xfs_growfs(xfs,只能扩不能缩)

xfs_growfs /data                       # 扩到底层设备的最大可用大小(参数是挂载点!)
xfs_growfs -D 5242880 /data            # 指定扩到 5242880 个块
xfs_growfs -d /data                    # 只扩数据区(默认行为)
xfs_growfs -n /data                    # 只显示当前几何信息,不做修改
xfs_growfs -m 30 /data                 # 调整 inode 最大占比 imaxpct

要点:

  • xfs 必须在挂载状态下扩容,且参数传的是挂载点(不是设备名),这与 resize2fs 相反;
  • xfs 不支持缩容,这是设计上的限制,没有任何工具能缩小 xfs。需要缩小只能:备份数据 → 重建更小的文件系统 → 恢复数据。

对比记忆:

操作 ext4 xfs
扩容命令 resize2fs 设备 xfs_growfs 挂载点
扩容是否需卸载 不需要(在线可扩) 必须挂载着
缩容 支持(须卸载 + e2fsck -f) 不支持
一步完成(LVM) lvextend -r -L +10G /dev/vg/lv lvextend -r -L +10G /dev/vg/lv(内部自动调用 xfs_growfs)

tune2fs(调整 ext 文件系统参数)

tune2fs -l /dev/sdb1                   # 列出超级块中的所有参数(最常用)
tune2fs -L mydata /dev/sdb1            # 修改卷标(等价 e2label)
tune2fs -U random /dev/sdb1            # 修改 UUID
tune2fs -m 1 /dev/sdb1                 # 把 root 保留块从 5% 改为 1%(大盘能省出几十 GB)
tune2fs -r 10000 /dev/sdb1             # 直接指定保留块数量
tune2fs -c 30 /dev/sdb1                # 每挂载 30 次后强制自检
tune2fs -c -1 /dev/sdb1                # 关闭基于挂载次数的自检(生产常用,避免开机长时间等待)
tune2fs -i 30d /dev/sdb1               # 每 30 天强制自检;-i 0 关闭时间检查
tune2fs -C 0 /dev/sdb1                 # 重置挂载计数
tune2fs -j /dev/sdb1                   # 给 ext2 添加日志,升级为 ext3
tune2fs -O ^has_journal /dev/sdb1      # 关闭日志(^ 表示取消特性)
tune2fs -O has_journal /dev/sdb1       # 重新开启日志
tune2fs -e remount-ro /dev/sdb1        # 设置出错行为:continue / remount-ro / panic
tune2fs -o journal_data_writeback /dev/sdb1   # 设置默认挂载选项

dumpe2fs / xfs_info(查看文件系统详细信息)

# ext 系列
dumpe2fs /dev/sdb1                     # 显示超级块 + 所有块组信息(输出很长)
dumpe2fs -h /dev/sdb1                  # 只看超级块信息(-h = header,最常用)
dumpe2fs -b /dev/sdb1                  # 列出坏块
dumpe2fs -h /dev/sdb1 | grep -E 'Block size|Inode count|Free|Filesystem state'

关键字段含义:Filesystem state(clean/not clean)、Block count(总块数)、Block size(块大小)、Inode count(inode 总数)、Free blocks/inodes(空闲量)、Reserved block count(保留块)、Mount count / Maximum mount count(挂载次数/上限)、Last checked / Check interval。

# xfs 系列
xfs_info /data                         # 查看 xfs 详细几何信息(参数是挂载点)
xfs_info /dev/sdb1                     # 也可传设备(需未挂载或 CentOS 7.3+)
xfs_admin -l /dev/sdb1                 # 查看卷标
xfs_admin -u /dev/sdb1                 # 查看 UUID
xfs_admin -L newlabel /dev/sdb1        # 修改卷标(须卸载)
xfs_admin -U generate /dev/sdb1        # 重新生成 UUID(克隆虚拟机后必做)
xfs_db -r /dev/sdb1                    # 只读方式进入 xfs 调试器
xfs_bmap -v /data/bigfile              # 查看文件的块分布(观察碎片)
xfs_fsr /data                          # 在线碎片整理(reorganize)
xfs_fsr -v /dev/sdb1                   # 详细模式
filefrag -v /data/bigfile              # 通用的碎片查看工具(ext4/xfs 都支持)

xfs_info 输出解读:meta-data 段的 isize(inode 大小)、agcount(分配组数)、data 段的 bsize(块大小)、blocks(总块数)、naming 段的 version、log 段的日志大小、realtime 段。

3.11 文件系统级备份工具

ext 系列:dump / restore

yum install dump -y

dump -0uj -f /backup/data.dump.bz2 /data     # 0 级(全量)备份,-u 更新 /etc/dumpdates,-j bzip2 压缩
dump -1uj -f /backup/data.inc1.bz2 /data     # 1 级(增量)备份,只备份自 0 级以来的变化
dump -W                                      # 查看各文件系统的备份级别与时间
dump -0u -f /dev/st0 /home                   # 备份到磁带机

restore -t -f /backup/data.dump.bz2          # 查看备份内容(-t 列表)
restore -C -f /backup/data.dump.bz2          # 比较备份与当前文件系统的差异
restore -i -f /backup/data.dump.bz2          # 交互式恢复(add/extract 选择性恢复)
restore -r -f /backup/data.dump.bz2          # 完整恢复到当前目录(需先在空文件系统中执行)

要点:dump 支持 0~9 共 10 个备份级别(0 为全量,1~9 为增量),只能备份整个 ext 文件系统(对目录只支持 0 级)。

xfs 系列:xfsdump / xfsrestore

yum install xfsdump -y

xfsdump -l 0 -L "full_20260822" -M "media0" -f /backup/data.xfsdump /data
#   -l 备份级别(0 全量,1~9 增量)
#   -L 会话标签(session label)
#   -M 介质标签(media label)
#   -f 输出文件或磁带设备
xfsdump -l 1 -L "inc1" -M "media0" -f /backup/data.inc1 /data     # 增量备份
xfsdump -I                                   # 查看备份历史(inventory,记录在 /var/lib/xfsdump)
xfsdump -J -l 0 -f /backup/x.dump /data      # -J 不更新 inventory
xfsdump -e -l 0 -f - /data | ssh remote 'cat > /backup/x.dump'    # 通过管道远程备份

xfsrestore -f /backup/data.xfsdump /data     # 恢复到 /data
xfsrestore -f /backup/data.xfsdump -i /data  # 交互式恢复
xfsrestore -t -f /backup/data.xfsdump        # 只查看内容
xfsrestore -r -f /backup/data.inc1 /data     # 累积恢复增量备份(先恢复 0 级再按顺序恢复增量)

要点:xfsdump 只能备份已挂载的 xfs 文件系统,且必须以 root 运行;备份 inventory 存于 /var/lib/xfsdump/inventory,迁移机器时应一并保留。

备份策略、tar/rsync/快照备份、异地备份与恢复演练 > 详见 03-03-备份与恢复.md

四、swap 交换分区管理

swap 交换分区在物理内存不足时充当虚拟内存使用。当物理内存不够用时,内核会把不活跃的内存页换出到 swap 设备。swap 分区没有文件系统。

4.1 查看 swap 使用情况

free -m                            # 以 MB 为单位显示内存及 swap 使用情况
free -h                            # 自动选择易读单位
free -g                            # 以 GB 为单位
swapon -s                          # 列出所有已启用的 swap 设备及使用量
cat /proc/swaps                    # 等效于 swapon -s
swapon --show                      # CentOS 7 新格式输出
cat /proc/meminfo | grep -i swap   # 查看 swap 总量与空闲量

4.2 swap 相关命令

命令 作用
mkswap 将设备或文件格式化为 swap(写入 swap 签名)
swapon 启用 swap
swapoff 关闭 swap
free -m 查看内存与 swap 使用量

mkswap 支持两种载体:

mkswap /dev/sdc1                   # 使用一个分区作为 swap
mkswap /swapfile                   # 使用一个文件作为 swap
mkswap -L myswap /dev/sdc1         # 指定卷标
mkswap -c /dev/sdc1                # 格式化前检查坏块

4.3 使用分区作为 swap(完整演练)

# 1. 用 fdisk 新建一个分区(例如 /dev/sdc1,大小 1G),并把类型改为 82
fdisk /dev/sdc
#    n → p → 1 → 回车 → +1G → t → 82 → w
partprobe /dev/sdc

# 2. 格式化为 swap
mkswap /dev/sdc1

# 3. 启用
swapon /dev/sdc1
swapon -p 10 /dev/sdc1             # -p 指定优先级(数值越大越优先使用,-1~32767)

# 4. 查看 swap 容量是否增加
free -m
swapon -s

# 5. 写入 /etc/fstab 实现开机自动启用
blkid /dev/sdc1                    # 取 UUID
echo 'UUID=xxxx-xxxx  swap  swap  defaults  0 0' >> /etc/fstab
swapon -a                          # 启用 fstab 中所有 swap,验证配置

# 6. 关闭 swap
swapoff /dev/sdc1                  # 取消 sdc1 作为 swap
free -m                            # 确认容量已缩减
swapoff -a                         # 关闭所有 swap

注意:swapoff 会把 swap 中的数据全部换回物理内存,若物理内存不足会失败甚至触发 OOM,生产环境操作前先确认 free -m 有足够空闲内存。

4.4 使用文件作为 swap(完整演练)

当不便重新分区时,可使用 dd 生成一个二进制大文件作为 swap:

# 1. 创建 1GB 的文件
dd if=/dev/zero of=/swapfile bs=1M count=1024
#    if=/dev/zero  输入源为零设备
#    of=/swapfile  输出文件路径
#    bs=1M         每次写入 1M
#    count=1024    写入 1024 次,总计 1024M(1G)

# 等效写法
head -c 1G /dev/zero > /swapfile
dd if=/dev/zero of=./swapfile bs=1024 count=1024      # 注意这个只有 1MB(1024×1024B)
fallocate -l 1G /swapfile          # 注意:CentOS 7 的 swap 不支持稀疏文件,建议仍用 dd

# 2. 设置权限(否则 swapon 会告警 insecure permissions)
chmod 600 /swapfile

# 3. 格式化并启用
mkswap /swapfile
swapon /swapfile
free -m

# 4. 写入 fstab
echo '/swapfile  swap  swap  defaults  0 0' >> /etc/fstab

# 5. 关闭并删除
swapoff /swapfile
sed -i '\#^/swapfile#d' /etc/fstab
rm -f /swapfile

该文件是二进制文件,无法使用 cat 或 vim 正常查看和编辑。

4.5 swap 容量规划与 swappiness

经验规划:

物理内存 建议 swap 大小
≤ 2 GB 内存的 2 倍
2 ~ 8 GB 与内存相同
8 ~ 64 GB 4 GB ~ 内存的 0.5 倍
> 64 GB 4 GB 即可(或按休眠需求 = 内存大小)
数据库/Redis 等 保留少量 swap 防 OOM,但通过 swappiness 降低使用倾向
cat /proc/sys/vm/swappiness          # 查看当前值(CentOS 6 默认 60,CentOS 7 默认 30)
sysctl vm.swappiness=10              # 临时调整(0~100,越小越倾向于用物理内存)
echo 'vm.swappiness = 10' >> /etc/sysctl.conf   # 永久生效
sysctl -p

内存与 swap 的性能调优 > 详见 04-03-Linux系统优化.md

五、LVM 逻辑卷管理

5.1 LVM 的意义与解决的问题

LVM(Logical Volume Manager,逻辑卷管理器)是 Linux 下对磁盘空间进行动态管理(扩容/缩减)的方法,在物理磁盘/分区之上建立一个抽象层。

传统分区方式的痛点与 LVM 的对应解决:

传统分区的问题 LVM 的解决方案
分区大小在创建时就固定,/data 满了无法扩大 动态扩容:加块硬盘就能给现有文件系统扩容,业务不中断
单个文件系统不能超过单块磁盘的容量 跨磁盘:多块磁盘组成一个卷组,逻辑卷可横跨多块盘
扩容必须停机、备份、重新分区、恢复数据 在线扩容:lvextend -r 一条命令,无需卸载(ext4/xfs 都支持在线扩)
备份大文件系统时数据在变化,无法保证一致性 快照(Snapshot):瞬间创建某一时刻的一致性视图,基于快照做备份
空间预估错误导致某分区浪费、某分区不足 灵活再分配:从 ext4 卷缩容,把空间给别的卷(xfs 不能缩)
更换磁盘需要停机迁移数据 在线迁移:pvmove 把数据从旧盘搬到新盘,业务无感知
无法做条带化提升性能 条带化/镜像:lvcreate -i(条带)/-m(镜像)

安装:

yum install lvm2 -y                # CentOS 6/7 通用
rpm -qa | grep lvm2                # 确认已安装
systemctl status lvm2-lvmetad      # CentOS 7 的 LVM 元数据缓存服务

5.2 三层结构:PV / VG / LV / PE

层级 英文 说明
物理卷 PV Physical Volume 对应硬盘分区或整块硬盘(也可以是 RAID 阵列 /dev/md0、多路径设备)。创建前建议把分区类型 ID 标记为 8e
物理区域 PE Physical Extent PV 被划分成的等大小块,是 LVM 分配空间的最小单位,默认 4 MiB。一个 VG 内所有 PV 的 PE 大小必须相同
卷组 VG Volume Group 由一个或多个 PV 组成的存储池,用于统一管理物理空间。VG 的容量 = 所有 PV 容量之和
逻辑卷 LV Logical Volume 在 VG 之上划分出的"虚拟分区",由若干 PE 组成,可格式化、挂载使用,可动态调整大小
逻辑区域 LE Logical Extent LV 中的逻辑块,与 PE 一一对应(线性模式下)

关系图:

物理层    /dev/sdb1   /dev/sdc1   /dev/sdd(整盘)
             │            │            │
          pvcreate     pvcreate     pvcreate
             ▼            ▼            ▼
PV 层      [PV1]        [PV2]        [PV3]
          PE PE PE     PE PE PE     PE PE PE      ← 每个 PV 被切成等大的 PE(默认 4MiB)
             └────────────┴────────────┘
                      vgcreate
                          ▼
VG 层         ┌──────────  vgdata  ──────────┐     ← 一个大的 PE 池
              │  PE PE PE PE PE PE PE PE ... │
              └──────────────┬───────────────┘
                          lvcreate
              ┌───────────┬─┴──────────┬────────────┐
              ▼           ▼            ▼            ▼
LV 层     [lv_app]    [lv_data]    [lv_log]     (剩余空闲 PE)
           20 GB        50 GB        10 GB       可随时分配
              │           │            │
           mkfs.xfs   mkfs.ext4    mkfs.xfs
              ▼           ▼            ▼
文件系统   /app        /data        /var/log

一句话流程:

磁盘/分区 --pvcreate--> 物理卷 PV --vgcreate--> 卷组 VG --lvcreate--> 逻辑卷 LV --mkfs/mount--> 文件系统

整体流程描述:把硬盘分区或整块硬盘标记为 PV → 创建 VG 并将 PV 加入 → 在 VG 上划分 LV → 格式化 LV → 挂载使用。

5.3 完整实操流程

步骤 0:准备物理分区并设置类型 8e

# 方式一:分区后作为 PV(推荐,便于识别用途)
fdisk /dev/sdb
#   n → p → 1 → 回车 → 回车(用满整盘)
#   t → 8e (改为 Linux LVM)
#   p 确认 → w 保存
partprobe /dev/sdb
fdisk -l /dev/sdb | grep sdb1     # 确认 Id 为 8e,System 为 Linux LVM

# GPT 磁盘用 parted 设置 lvm 标志
parted -s /dev/sdb mklabel gpt mkpart primary 1MiB 100% set 1 lvm on

# 方式二:不分区,直接把整块裸盘做 PV(云主机数据盘常用,扩容更方便)
pvcreate /dev/sdb

说明:8e 只是"用途声明",不设置也能 pvcreate;但设置了它,fdisk -l 就能一眼看出这块分区归 LVM 使用,避免误格式化。

步骤 1:创建与管理物理卷(PV)

pvcreate /dev/sdb1                     # 将分区初始化为物理卷
pvcreate /dev/sdb1 /dev/sdc1           # 一次初始化多个
pvcreate /dev/sdb                      # 使用整块裸盘
pvcreate /dev/md0                      # 使用 RAID 阵列作为 PV
pvcreate -ff /dev/sdb1                 # 强制创建(清除已有 LVM 标记时使用)
pvcreate --dataalignment 1m /dev/sdb1  # 指定数据对齐,RAID/SSD 上优化性能

pvs                                    # 查看 PV 简要信息(最常用)
pvs -o +pv_used                        # 附加显示已用空间
pvs --units g                          # 以 GB 为单位显示
pvscan                                 # 扫描系统中所有 PV(稍详细)
pvscan --cache                         # 刷新 LVM 元数据缓存
pvdisplay                              # 查看所有 PV 详细信息
pvdisplay /dev/sdb1                    # 查看指定 PV 的详细属性(大小、PE 数、所属 VG)
pvdisplay -m /dev/sdb1                 # 显示 PE 的映射segment分布

pvchange -x n /dev/sdb1                # 禁止在该 PV 上分配 PE(准备移除该盘时使用)
pvchange -x y /dev/sdb1                # 重新允许分配
pvresize /dev/sdb1                     # 底层分区/磁盘扩容后,让 PV 识别新容量
pvck /dev/sdb1                         # 检查 PV 元数据一致性
pvremove /dev/sdb1                     # 删除 PV 标记(必须先从 VG 中移出)
pvremove -ff /dev/sdb1                 # 强制删除
lvmdiskscan                            # 检测系统中所有硬盘和分区,查找可用于 LVM 的设备

pvs 输出示例与各列含义:

  PV         VG     Fmt  Attr PSize   PFree
  /dev/sdb1  vgdata lvm2 a--   <5.00g  1.00g
  /dev/sdc1  vgdata lvm2 a--   <5.00g  5.00g

PV(设备)、VG(所属卷组,空白表示未加入任何 VG)、Fmt(元数据格式 lvm2)、Attr(属性,a = allocatable 可分配)、PSize(总容量)、PFree(剩余可分配容量)。

步骤 2:创建与管理卷组(VG)

vgcreate vgdata /dev/sdb1                    # 用一个 PV 创建卷组
vgcreate vgdata /dev/sdb1 /dev/sdc1          # 一次加入多个 PV
vgcreate myvg /dev/hdb1                      # 老式 IDE 盘写法
vgcreate -s 16M vgdata /dev/sdb1             # -s 指定 PE 大小为 16MiB(默认 4MiB)
vgcreate -s 32M -p 128 vgdata /dev/sdb1      # -p 限制最大 PV 数量

vgs                                          # 查看 VG 简要信息(最常用)
vgs --units g                                # 指定单位
vgscan                                       # 扫描系统中所有卷组
vgdisplay                                    # 查看所有 VG 详细信息
vgdisplay vgdata                             # 查看指定卷组(PE 大小、总 PE、空闲 PE)
vgdisplay -v vgdata                          # 同时显示其中的 PV 和 LV

vgextend vgdata /dev/sdd1                    # 向卷组添加新 PV,实现卷组在线扩容
vgextend myvg /dev/hdb2
vgreduce vgdata /dev/sdd1                    # 把未使用的 PV 移出卷组(缩小卷组)
vgreduce --removemissing vgdata              # 移除已丢失/损坏的 PV(磁盘物理损坏后救急)
vgrename vgdata vgnew                        # 重命名卷组(须处于非活动状态/未挂载)
vgrename <VG-UUID> vgnew                     # 用 UUID 重命名(VG 名冲突时使用)
vgremove vgdata                              # 删除卷组(必须先删除其中所有 LV)
vgremove -f vgdata                           # 强制删除

vgchange -a y vgdata                         # 激活卷组(使其 LV 可用)
vgchange -a n vgdata                          # 停用卷组
vgck vgdata                                  # 检测卷组 VGDA(卷组描述符区域)的一致性
vgmerge vgnew vgold                          # 合并两个已存在的卷组
vgsplit vgdata vgnew /dev/sdc1               # 把一个卷组拆分为两个
vgmknodes                                    # 重新建立卷组目录与其中的设备文件节点
vgconvert -M2 vgdata                         # 元数据格式从 LVM1 转 LVM2

vgdisplay 关键字段:

  --- Volume group ---
  VG Name               vgdata
  VG Size               9.99 GiB          <- 卷组总容量
  PE Size               4.00 MiB          <- 每个 PE 的大小
  Total PE              2558              <- PE 总数
  Alloc PE / Size       1280 / 5.00 GiB   <- 已分配
  Free  PE / Size       1278 / 4.99 GiB   <- 剩余可分配(扩容前必看这一行)
  VG UUID               xxxx-xxxx

关于 PE 大小的选择:

  • 默认 4 MiB,兼容性最好;
  • PE 大小决定单个 LV 的理论上限(LVM2 中已不再是硬限制,但影响元数据规模);
  • 超大卷组(几十 TB)可用 -s 32M 或 -s 64M 减少 PE 数量、提升管理效率;
  • PE 大小在 VG 创建后无法直接修改(需要删除重建 VG),务必一开始规划好。

步骤 3:创建与管理逻辑卷(LV)

逻辑卷从卷组中划分空间,对用户而言相当于一块可灵活调整大小的"虚拟磁盘"。

# -L 按容量指定(K/M/G/T)
lvcreate -L 2G -n mylv1 myvg                 # 在 myvg 中创建 2G 的 mylv1
lvcreate -L 10G -n lvdata vgdata
lvcreate --name lvhome --size 10G vgdata     # 长选项写法,等价

# -l 按 PE 数量或百分比指定
lvcreate -l 100 -n lv1 vgdata                # 用 100 个 PE(100×4M = 400M)
lvcreate -l 100%FREE -n lvall vgdata         # 用满卷组当前所有剩余空间(最常用)
lvcreate -l 50%FREE -n lvhalf vgdata         # 用掉剩余空间的一半
lvcreate -l 80%VG -n lvbig vgdata            # 用卷组总容量的 80%
lvcreate -l 100%PVS -n lvpv vgdata /dev/sdb1 # 用满指定 PV 的空间

# 指定在哪个 PV 上分配(控制数据落盘位置)
lvcreate -L 5G -n lvfast vgdata /dev/sdb1

# 条带化与镜像
lvcreate -L 10G -i 2 -I 64 -n lvstripe vgdata    # -i 条带数,-I 条带大小(KB)
lvcreate -L 10G -m 1 -n lvmirror vgdata          # -m 1 创建 1 份镜像

# 快照
lvcreate -s -L 2G -n lvdata_snap /dev/vgdata/lvdata

# 查看
lvs                                          # 查看所有 LV 简要信息(最常用)
lvs -o +devices                              # 附加显示 LV 落在哪些 PV 上
lvs -a                                       # 显示所有内部卷(含快照元数据、镜像子卷)
lvs --units g
lvscan                                       # 扫描并列出所有逻辑卷
lvdisplay                                    # 查看所有 LV 详细信息
lvdisplay /dev/vgdata/lvdata                 # 查看指定 LV
lvdisplay -m /dev/vgdata/lvdata              # 显示 LE→PE 的映射关系

# 修改与删除
lvrename vgdata lvold lvnew                  # 重命名(须非活动状态)
lvrename /dev/vgdata/lvold /dev/vgdata/lvnew # 等效写法
lvchange -a n /dev/vgdata/lvdata             # 停用逻辑卷
lvchange -a y /dev/vgdata/lvdata             # 激活逻辑卷
lvchange -p r /dev/vgdata/lvdata             # 设为只读(rw 为读写)
lvremove /dev/vgdata/lvdata                  # 删除逻辑卷(须先卸载)
lvremove -f /dev/vgdata/lvdata               # 强制删除,不提示确认
lvmchange                                    # 紧急情况下复位逻辑卷管理器(极少使用)
lvmdiskscan                                  # 列出所有可用于 LVM 的块设备
lvmsadc                                      # 收集逻辑卷读写统计数据
lvmsar                                       # 报告 lvmsadc 收集到的统计数据

lvs 输出示例:

  LV     VG     Attr       LSize  Pool Origin Data%  Meta%  Move Log Cpy%Sync
  lvdata vgdata -wi-ao---- 10.00g
  lvsnap vgdata swi-a-s--   2.00g      lvdata 12.34

Attr 各位含义(第 1 位类型:- 普通、s 快照、o 原始卷、m 镜像、t thin 池、V thin 卷;第 2 位权限 w/r;第 6 位状态 a 表示 active)。

步骤 4:格式化并挂载 LV

LV 设备路径有两种等价写法:

/dev/<卷组名>/<逻辑卷名>          如 /dev/vgdata/lvdata、/dev/centos/home
/dev/mapper/<卷组名>-<逻辑卷名>   如 /dev/mapper/vgdata-lvdata、/dev/mapper/centos-home
# 格式化
mkfs.xfs /dev/vgdata/lvdata                  # CentOS 7 推荐
mkfs.ext4 /dev/vgdata/lvdata                 # CentOS 6 推荐
mkfs.ext4 /dev/myvg/mylv1
mkfs -t ext4 /dev/mapper/vgdata-lvdata       # 等效

# 挂载
mkdir -p /data
mount /dev/vgdata/lvdata /data
mount /dev/myvg/mylv1 /mnt
df -hT /data
lsblk                                        # 可以看到 lvm 类型的层级关系

# 写入 fstab 开机自动挂载
blkid /dev/vgdata/lvdata                     # 也可以取 UUID 使用
# 三种写法都可以,LV 路径本身就是稳定的(不像 /dev/sdb1 会变)
/dev/vgdata/lvdata          /data  xfs   defaults  0 0
/dev/mapper/vgdata-lvdata   /data  xfs   defaults  0 0
UUID=8f5c1c2a-6b3e-4c7a     /data  xfs   defaults  0 0
mount -a                                     # 验证 fstab(必做)

说明:逻辑卷路径由 VG 名和 LV 名决定,只要不改名就永远不变,因此 fstab 中直接写 /dev/vg/lv 是安全的,这与裸分区必须用 UUID 的情况不同。但若可能出现 VG 重名(如挂载别的机器的盘),仍以 UUID 最稳妥。

5.4 在线扩容完整演练

核心原则:逻辑卷的大小不能超过所在卷组的剩余空间。 扩容顺序永远是:先扩 VG(如果 VG 没空间了)→ 再扩 LV → 最后扩文件系统。

场景一:VG 还有剩余空间,只扩 LV

# 1. 检查卷组剩余空间(Free PE / Size 一行)
vgs
vgdisplay vgdata | grep Free

# 2. 扩容逻辑卷(三种写法)
lvextend -L +10G /dev/vgdata/lvdata        # 增加 10G(注意 + 号)
lvextend -L 30G /dev/vgdata/lvdata         # 扩到总共 30G(无 + 号是绝对值)
lvextend -l +100%FREE /dev/vgdata/lvdata   # 把卷组所有剩余空间都给它
lvextend -l +100 /dev/vgdata/lvdata        # 增加 100 个 PE
lvextend -L +100M /dev/myvg/mylv1          # 增加 100M
lvresize -L +10G /dev/centos/home          # lvresize 等效(可扩可缩,lvextend 只能扩)

# 3. 确认逻辑卷已变大,但 df 显示的容量还没变(文件系统还没扩)
lvs
df -hT /data

# 4. 扩容文件系统 —— 区分 ext4 与 xfs!
resize2fs /dev/vgdata/lvdata               # ext2/3/4:参数是设备名,可在线执行
xfs_growfs /data                           # xfs:参数是挂载点,必须挂载着

# 5. 确认
df -hT /data
lsblk

场景二:VG 空间不足,先加盘扩 VG 再扩 LV(最常见)

# 1. 加一块新盘(虚拟机热添加后在线扫描)
for h in /sys/class/scsi_host/host*/scan; do echo '- - -' > $h; done
lsblk                                       # 出现 /dev/sdd

# 2. 分区并设置类型 8e(或直接用裸盘)
fdisk /dev/sdd                              # n → p → 1 → 回车 → 回车 → t → 8e → w
partprobe /dev/sdd

# 3. 创建 PV
pvcreate /dev/sdd1
pvs

# 4. 把新 PV 加入卷组(在线,业务无感知)
vgextend vgdata /dev/sdd1
vgs                                         # VFree 变大了

# 5. 扩容 LV
lvextend -l +100%FREE /dev/vgdata/lvdata

# 6. 扩容文件系统
xfs_growfs /data                            # xfs
# 或 resize2fs /dev/vgdata/lvdata           # ext4

# 7. 验证
df -hT /data

一步完成扩容:lvextend -r / lvresize -r

-r(--resizefs)会在调整逻辑卷后自动调用对应的文件系统调整工具(内部通过 fsadm),自动识别 ext4 调 resize2fs、xfs 调 xfs_growfs:

lvextend -r -L +10G /dev/vgdata/lvdata      # 扩 LV 并自动扩文件系统(推荐)
lvextend -r -l +100%FREE /dev/vgdata/lvdata
lvresize -r -L +10G /dev/centos/home        # lvresize 同样支持
lvresize -r -L +10G /dev/centos/home        # 等效

# 老式两步写法(等价)
lvextend -L +100M /dev/myvg/mylv1           # 1) 扩容逻辑卷
resize2fs /dev/myvg/mylv1                   # 2) 同步扩容文件系统

ext4 与 xfs 扩容差异对照

步骤 ext2/3/4 xfs
扩 LV lvextend -L +10G /dev/vg/lv 相同
扩文件系统命令 resize2fs /dev/vg/lv xfs_growfs /挂载点
参数是什么 设备名 挂载点
是否需要挂载 挂载或卸载都可以 必须处于挂载状态
一步完成 lvextend -r -L +10G ... lvextend -r -L +10G ...(同样支持)
可否缩容 可以(须卸载) 不可以

扩容根分区(/)的完整演练

# 场景:CentOS 7 默认安装,根分区在 /dev/mapper/centos-root(xfs),磁盘已扩容
# 1. 让内核识别磁盘新容量
echo 1 > /sys/class/block/sda/device/rescan
lsblk

# 2. 新建分区使用新增空间(不要动已有分区!)
fdisk /dev/sda            # n → p → 3 → 回车 → 回车 → t → 3 → 8e → w
partprobe /dev/sda        # 若失败:partx -a --nr 3 /dev/sda,或安排重启

# 3. 创建 PV 并扩 VG
pvcreate /dev/sda3
vgextend centos /dev/sda3

# 4. 扩 LV + 文件系统(根分区可在线扩容,无需重启)
lvextend -r -l +100%FREE /dev/mapper/centos-root

# 5. 验证
df -hT /

5.5 缩容完整演练

关键限制:xfs 文件系统不支持缩容! 这是设计上的限制,没有任何工具能缩小 xfs。CentOS 7 默认使用 xfs,因此在 CentOS 7 上"缩小根分区腾空间给别的卷"这件事做不到,只能:备份数据 → 删除 LV → 重建更小的 LV → 重新格式化 → 恢复数据。

ext4 缩容的严格顺序

卸载文件系统  →  e2fsck -f 强制检查  →  resize2fs 缩小文件系统  →  lvreduce 缩小逻辑卷  →  重新挂载
   umount            e2fsck -f            resize2fs 10G              lvreduce -L 10G          mount

顺序绝对不能颠倒:必须先缩文件系统,再缩逻辑卷。如果先缩了逻辑卷,文件系统尾部的数据所在的 PE 会被直接回收,导致文件系统结构损坏、数据永久丢失。

完整命令演练(把 20G 的 lvdata 缩到 10G):

# 0. 【必做】先完整备份数据!缩容是高风险操作
tar -czf /backup/data-$(date +%F).tar.gz /data
# 或 xfsdump / rsync 到其他位置

# 1. 确认当前实际使用量,缩容目标必须大于已用空间
df -h /data
#   Filesystem                Size  Used Avail Use% Mounted on
#   /dev/mapper/vgdata-lvdata  20G  6.5G   13G  34% /data
#   已用 6.5G,缩到 10G 是安全的

# 2. 卸载文件系统(缩容必须离线!)
umount /data
# 若报 busy:fuser -mv /data,处理占用进程后重试

# 3. 强制检查文件系统(不做这步 resize2fs 会拒绝执行)
e2fsck -f /dev/vgdata/lvdata
# 或 e2fsck -f -y /dev/vgdata/lvdata

# 4. 先缩小【文件系统】到 10G
resize2fs /dev/vgdata/lvdata 10G
#   输出 The filesystem on /dev/vgdata/lvdata is now 2621440 (4k) blocks long.

# 5. 再缩小【逻辑卷】到 10G
lvreduce -L 10G /dev/vgdata/lvdata
#   WARNING: Reducing active logical volume to 10.00 GiB.
#   THIS MAY DESTROY YOUR DATA (filesystem etc.)
#   Do you really want to reduce vgdata/lvdata? [y/n]: y
# 相对缩减写法:
# lvreduce -L -10G /dev/vgdata/lvdata     # 减少 10G
# lvreduce -L -100M /dev/myvg/mylv1       # 减少 100M
# lvreduce -l -100 /dev/vgdata/lvdata     # 减少 100 个 PE

# 6. 重新挂载并验证
mount /dev/vgdata/lvdata /data
df -hT /data
ls /data                                  # 确认数据完好

# 7. 现在卷组有了空闲空间,可以分配给其它 LV
vgs
lvextend -r -l +100%FREE /dev/vgdata/lvother

一步完成的写法(谨慎使用):

umount /data
e2fsck -f /dev/vgdata/lvdata
lvreduce -r -L 10G /dev/vgdata/lvdata     # -r 自动先缩文件系统再缩 LV
lvresize -r -L -10G /dev/centos/mnt       # lvresize 等效写法
mount /dev/vgdata/lvdata /data

-r 选项会自动按正确顺序操作,比手工分步更安全,但仍必须先卸载并备份。

缩容注意事项与常见错误

错误做法 后果
先 lvreduce 再 resize2fs 文件系统损坏、数据永久丢失(尾部数据所在 PE 已被回收)
不卸载就 resize2fs 缩容 报错拒绝执行(幸运);强制执行则文件系统损坏
不做 e2fsck -f resize2fs 报 Please run 'e2fsck -f' first 拒绝执行
缩容目标小于已用空间 resize2fs 报 New size smaller than minimum 拒绝执行
对 xfs 执行缩容 没有任何工具能做到,lvreduce -r 会报错;若强行 lvreduce 则文件系统损坏
缩容前不备份 一旦出错无法挽回

查询 ext4 文件系统能缩到的最小尺寸:

resize2fs -P /dev/vgdata/lvdata           # 打印最小块数
tune2fs -l /dev/vgdata/lvdata | grep -E 'Block count|Free blocks|Block size'

5.6 LVM 快照(Snapshot)

原理:写时复制(COW,Copy-On-Write)

  • 创建快照时不复制任何数据,只是建立一个指向原卷(origin)当前状态的元数据映射,因此瞬间完成。
  • 之后原卷上某个块第一次被修改前,LVM 会先把该块的原始内容复制到快照空间,再让写入落到原卷。
  • 读快照时:未被修改过的块直接读原卷,被修改过的块读快照空间里保存的旧副本。
  • 因此快照看到的永远是创建瞬间的一致性视图。
  • 快照空间只需要能容纳"快照存续期间原卷被修改的数据量",通常远小于原卷(经验值:原卷的 10%~20%,写入频繁的库要更大)。

创建与使用快照

# 创建快照(-s 表示 snapshot,最后的参数是原卷路径)
lvcreate -s -L 2G -n lvdata_snap /dev/vgdata/lvdata
lvcreate --snapshot --size 2G --name lvdata_snap /dev/vgdata/lvdata   # 长选项
lvcreate -s -l 20%ORIGIN -n lvdata_snap /dev/vgdata/lvdata            # 按原卷比例
lvcreate -s -L 5G -n snap_root /dev/mapper/centos-root                # 给根卷做快照

# 查看快照(Origin 列显示原卷,Data% 显示快照空间已用比例)
lvs
#   LV          VG     Attr       LSize  Origin Data%
#   lvdata      vgdata owi-aos-- 20.00g
#   lvdata_snap vgdata swi-a-s--  2.00g lvdata  3.21
lvdisplay /dev/vgdata/lvdata_snap
lvs -o lv_name,origin,data_percent,lv_size

# 挂载快照读取数据(快照是独立的块设备)
mkdir -p /mnt/snap
mount -o ro /dev/vgdata/lvdata_snap /mnt/snap      # ext4:只读挂载即可
mount -o ro,nouuid /dev/vgdata/snap_xfs /mnt/snap  # xfs:必须加 nouuid(UUID 与原卷相同会拒绝挂载)
ls /mnt/snap                                       # 看到的是快照创建瞬间的数据

# 删除快照(用完立刻删除,避免持续拖慢原卷性能)
umount /mnt/snap
lvremove /dev/vgdata/lvdata_snap
lvremove -f /dev/vgdata/lvdata_snap

用快照做一致性备份的完整流程

问题背景:直接 tar 一个正在被数据库写入的目录,备份出来的文件可能处于中间状态,恢复后数据不一致。快照解决了这个问题。

# 1.(数据库场景)让数据落盘并短暂加锁,保证快照点一致
mysql -e "FLUSH TABLES WITH READ LOCK;" &        # 保持会话持锁
sync                                              # 刷新文件系统缓存
# xfs 还可以冻结文件系统,保证元数据一致:
xfs_freeze -f /data

# 2. 创建快照(秒级完成,业务几乎无感知)
lvcreate -s -L 5G -n snap_data /dev/vgdata/lvdata

# 3. 立即解锁/解冻,业务恢复正常读写
xfs_freeze -u /data
# 结束持锁的 mysql 会话(UNLOCK TABLES)

# 4. 挂载快照
mkdir -p /mnt/snap
mount -o ro,nouuid /dev/vgdata/snap_data /mnt/snap     # xfs
# mount -o ro /dev/vgdata/snap_data /mnt/snap          # ext4

# 5. 从快照做备份(此时可以慢慢备份,原卷继续读写互不影响)
tar -czf /backup/data-$(date +%F).tar.gz -C /mnt/snap .
# 或
rsync -a --delete /mnt/snap/ /backup/data/
# 或 xfsdump -l 0 -f /backup/data.xfsdump /mnt/snap
# 或 dump -0uj -f /backup/data.dump.bz2 /mnt/snap

# 6. 备份完成,卸载并删除快照
umount /mnt/snap
lvremove -f /dev/vgdata/snap_data

# 7. 验证备份可用
tar -tzf /backup/data-$(date +%F).tar.gz | head

快照空间耗尽的后果

  • 快照空间被写满(lvs 的 Data% 达到 100%)后,快照立即失效(Invalid),无法再挂载读取,其中的备份视图彻底丢失;
  • 快照失效不会损坏原卷数据,原卷继续正常工作(这是唯一的好消息);
  • 因此必须监控快照使用率:
lvs -o lv_name,data_percent,origin           # 手工检查
watch -n 5 'lvs'                             # 备份期间持续观察

# 让 LVM 自动扩容快照,避免写满(推荐配置)
vim /etc/lvm/lvm.conf
#   snapshot_autoextend_threshold = 70        # 用到 70% 时自动扩容
#   snapshot_autoextend_percent = 20          # 每次扩容 20%
systemctl restart lvm2-monitor                # CentOS 7 需要 dmeventd 监控服务在运行

# 手工扩容快照
lvextend -L +2G /dev/vgdata/snap_data

合并快照(回滚原卷到快照时刻)

# lvconvert --merge 把快照内容合并回原卷,实现"回滚"
umount /data                                  # 先卸载原卷(根卷无法卸载则见下)
lvconvert --merge /dev/vgdata/snap_data
#   合并完成后快照会被自动删除
mount /dev/vgdata/lvdata /data
ls /data                                      # 数据已回到快照创建时的状态

# 若原卷正在使用(如根卷)无法卸载,合并会被延迟到下次激活时执行
lvconvert --merge /dev/vgdata/snap_root
#   Merging of snapshot will occur on next activation of vgdata/lvroot.
reboot                                        # 重启后自动完成合并

典型用途:升级/打补丁前先做快照,出问题就 lvconvert --merge 一键回滚。

快照与备份的关系(重要观念)

观点 说明
快照不是备份 快照与原卷在同一个卷组、同一批物理磁盘上,磁盘损坏则原卷与快照一起丢失
快照是"备份的辅助手段" 它解决的是"备份期间数据一直在变"的一致性问题,备份数据必须落到其它介质/主机
快照有性能代价 快照存在期间,原卷每次首次写入都要多一次读+写(COW),写性能明显下降。用完立刻删除
快照不能长期保留 空间会被逐渐写满而失效;多个快照叠加性能损耗更大
快照可用于回滚 lvconvert --merge 适合升级前的短期保险

完整的备份策略(全量/增量、异地、恢复演练)> 详见 03-03-备份与恢复.md

5.7 条带化与镜像

条带化(Striping,类似 RAID 0)

数据被切成条带轮流写到多个 PV 上,多块盘并行读写,性能成倍提升,但没有冗余(任一盘坏则整个 LV 失效)。

# -i 条带数(必须 ≤ VG 中可用 PV 数),-I 条带大小(KB,必须是 2 的幂)
lvcreate -L 10G -i 2 -I 64 -n lvstripe vgdata
lvcreate -L 20G -i 3 -I 128 -n lvfast vgdata /dev/sdb1 /dev/sdc1 /dev/sdd1
lvs -o +stripes,stripe_size,devices          # 查看条带配置
lvdisplay -m /dev/vgdata/lvstripe            # 查看 LE→PE 的分布,确认跨了多个 PV

要点:

  • -i N 要求 VG 中至少有 N 个不同的 PV,且每个 PV 都要有足够空间;
  • 条带大小 -I 建议与业务 I/O 大小匹配(数据库 64K,大文件顺序读写 256K 以上);
  • 条带 LV 扩容时必须一次扩满所有条带盘(lvextend 需要每个 PV 都有空间),否则需要用 --alloc anywhere;
  • 默认(不加 -i)是线性/连续分配(linear):先用满第一个 PV 再用第二个,没有性能提升但扩容灵活。

镜像(Mirroring,类似 RAID 1)

# -m 1 表示 1 份镜像(共 2 份数据),需要至少 2 个 PV + 1 个放日志(或用 --mirrorlog core)
lvcreate -L 10G -m 1 -n lvmirror vgdata
lvcreate -L 10G -m 1 --mirrorlog mirrored -n lvmirror vgdata   # 镜像日志也做镜像
lvcreate -L 10G -m 1 --mirrorlog core -n lvmirror vgdata       # 日志放内存(重启需完整同步)

# 用 RAID1 类型(CentOS 7 推荐,基于 md 的 dm-raid,比老的 dm-mirror 更好)
lvcreate --type raid1 -m 1 -L 10G -n lvraid1 vgdata
lvcreate --type raid5 -i 3 -L 30G -n lvraid5 vgdata
lvcreate --type raid10 -i 2 -m 1 -L 20G -n lvraid10 vgdata

# 查看同步进度
lvs -o +copy_percent,sync_percent
lvs -a -o name,segtype,sync_percent

# 给已有 LV 添加/移除镜像
lvconvert -m 1 /dev/vgdata/lvdata            # 添加镜像
lvconvert -m 0 /dev/vgdata/lvdata            # 移除镜像(变回单份)
lvconvert --repair /dev/vgdata/lvraid1       # 修复降级的镜像

LVM 内建 RAID 与 mdadm 软 RAID 的关系与选择

方案 特点 适用场景
mdadm 软 RAID + LVM 叠加(推荐) 分层清晰:磁盘 → md 阵列 → PV/VG/LV。RAID 管理成熟、监控工具完善(/proc/mdstat) 生产环境标准做法,同时获得冗余与灵活扩容
LVM 内建 RAID(--type raid1/5/6/10) 少一层,可对单个 LV 指定不同的 RAID 级别(同一 VG 内混用) 需要按卷差异化配置冗余;单机灵活实验
LVM 条带 -i 只提性能无冗余 底层已有硬件 RAID 保护时的性能优化

常见分层顺序:物理磁盘 → md 阵列 → PV/VG/LV → 文件系统。

# md 设备作为 PV 使用(在阵列上叠加 LVM)
pvcreate /dev/md0
vgcreate vgdata /dev/md0
lvcreate --name lvapp --size 100G vgdata

若已有带缓存电池的硬件 RAID 卡,则直接用硬件 RAID 提供的逻辑盘做 PV,不要再叠加软 RAID。RAID 详见本文第七章。

5.8 精简置备(Thin Provisioning)简述

thin provisioning(精简卷/超额分配)允许创建"名义容量大于实际物理容量"的逻辑卷,空间按实际写入量从 thin pool 中动态分配。

# 1. 创建 thin pool(精简池),大小为实际可用物理空间
lvcreate -L 20G --thinpool tp0 vgdata
lvcreate -L 20G --type thin-pool -n tp0 vgdata          # 等效写法
lvcreate -l 100%FREE --thinpool tp0 vgdata

# 2. 在池中创建 thin LV,-V 是"虚拟大小",可以超过池的实际容量(超额分配)
lvcreate -V 50G -T vgdata/tp0 -n lvthin1
lvcreate -V 50G --thin vgdata/tp0 -n lvthin2            # 两个卷各"号称"50G,池只有 20G

# 3. 正常格式化挂载使用
mkfs.xfs /dev/vgdata/lvthin1
mount /dev/vgdata/lvthin1 /data1

# 4. 监控池的实际使用率(Data% 和 Meta%)——这是 thin 的生命线
lvs -o lv_name,lv_size,data_percent,metadata_percent
lvs -a                                                  # 查看池的内部卷

# 5. 池不够了就扩容池
lvextend -L +10G vgdata/tp0

# 6. thin 卷的快照几乎不占空间(不需要预留固定大小)
lvcreate -s -n snap1 vgdata/lvthin1

优点:空间利用率高、快照极其廉价(可保留多个)、支持 discard 回收空间。

风险(必须理解):一旦 thin pool 被写满,所有基于它的 thin LV 都会立即 I/O 错误、文件系统被置为只读甚至损坏。因此:

# 配置自动扩容(/etc/lvm/lvm.conf)
thin_pool_autoextend_threshold = 70
thin_pool_autoextend_percent = 20
systemctl enable --now lvm2-monitor

生产使用 thin 必须配套容量监控告警,且 VG 中要保留可扩容的空闲空间。

5.9 LVM 常见问题与运维操作

VG 元数据备份与恢复

LVM 会在每次修改元数据时自动备份,无需手工操作:

  • 自动备份目录:/etc/lvm/backup/(当前元数据,每个 VG 一个文件)
  • 历史归档目录:/etc/lvm/archive/(每次变更前的旧版本,带序号和时间戳)
# 手工备份元数据
vgcfgbackup                                  # 备份所有 VG 到 /etc/lvm/backup/
vgcfgbackup vgdata                           # 备份指定 VG
vgcfgbackup -f /backup/vgdata.conf vgdata    # 备份到指定文件
#   (老文档中写作备份到 /etc/vmconf,现代 LVM2 为 /etc/lvm/backup)

# 查看可用的归档版本
ls -l /etc/lvm/archive/
vgcfgrestore --list vgdata                   # 列出该 VG 所有可恢复的元数据版本及时间

# 恢复元数据(典型场景:误删了 LV,想恢复元数据把 LV 找回来)
vgcfgrestore -f /etc/lvm/archive/vgdata_00003-123456789.vg vgdata
vgcfgrestore vgdata                          # 恢复到最近一次备份
vgchange -ay vgdata                          # 恢复后重新激活
lvs                                          # 确认 LV 回来了
mount /dev/vgdata/lvdata /data               # 数据通常完好(只要 PE 未被覆盖写入)

# PV 元数据损坏/UUID 丢失时的修复
pvcreate --uuid <原PV的UUID> --restorefile /etc/lvm/backup/vgdata /dev/sdb1
vgcfgrestore vgdata

重要:误 lvremove 后不要再做任何写操作(不要创建新 LV),立即用 vgcfgrestore 恢复元数据,数据大概率能救回来。

迁移 VG 到新机器(vgexport / vgimport)

# ===== 在源机器上 =====
umount /data                                 # 1. 卸载所有该 VG 上的文件系统
lvchange -an /dev/vgdata/lvdata               # 2. 停用逻辑卷
vgchange -an vgdata                          # 3. 停用卷组
vgexport vgdata                              # 4. 导出卷组(系统不再识别它)
vgs                                          # 状态显示 exported(Attr 中有 x)
vgcfgbackup vgdata                           # 5.(建议)备份元数据一起带走
#   6. 关机,把磁盘物理搬到新机器(或在存储上重新映射 LUN)

# ===== 在目标机器上 =====
pvscan                                       # 7. 扫描发现 PV(会提示 is in exported VG)
vgscan                                       # 8. 扫描发现卷组
vgimport vgdata                              # 9. 导入卷组
vgchange -ay vgdata                          # 10. 激活卷组
lvs                                          # 11. 查看逻辑卷
mount /dev/vgdata/lvdata /data               # 12. 挂载使用
vim /etc/fstab                               # 13. 写入 fstab

# 若两台机器上 VG 同名冲突,先重命名再导入
vgrename <VG-UUID> vgdata_old
vgs -o vg_name,vg_uuid                       # 查看 UUID

pvmove:在线迁移数据后移除旧盘

场景:一块旧盘即将故障(SMART 报警)或要换成 SSD,需要在业务不停机的情况下把数据搬走并拔掉旧盘。

# 1. 加入新盘作为新 PV,并扩入同一个卷组
pvcreate /dev/sde1
vgextend vgdata /dev/sde1
vgs                                          # 确认新盘容量已进入 VG,且 VFree ≥ 旧盘已用量

# 2.(建议)禁止在旧盘上再分配新的 PE
pvchange -x n /dev/sdb1

# 3. 在线迁移数据(把 sdb1 上的所有 PE 搬到其它 PV 上)
pvmove /dev/sdb1                             # 自动选择目标 PV
pvmove /dev/sdb1 /dev/sde1                   # 指定迁移到 sde1
pvmove -v /dev/sdb1                          # 详细输出
pvmove -i 10 /dev/sdb1                       # 每 10 秒报告一次进度
pvmove -n lvdata /dev/sdb1 /dev/sde1         # 只迁移指定 LV 的数据
#   迁移过程是在线的,业务可继续读写;速度取决于数据量与磁盘性能

# 中断与续传
pvmove --abort                               # 中止迁移(数据回退,安全)
pvmove                                       # 不带参数:继续之前被中断的迁移

# 4. 查看迁移进度
lvs -o +move_pv,copy_percent
pvs
dmsetup status                               # 观察 pvmove 镜像进度

# 5. 迁移完成后确认旧盘已空(PFree 等于 PSize)
pvs /dev/sdb1
pvdisplay /dev/sdb1 | grep -E 'Allocated|Free'

# 6. 从卷组中移出旧盘
vgreduce vgdata /dev/sdb1

# 7. 清除旧盘的 PV 标记
pvremove /dev/sdb1

# 8. 现在可以安全地物理拔掉旧盘了(先在系统里删除设备)
echo 1 > /sys/block/sdb/device/delete

磁盘物理损坏后的应急处理

vgdisplay vgdata                             # 提示 Couldn't find device with uuid ...
vgs                                          # 显示 [unknown] 设备
vgreduce --removemissing vgdata               # 移除丢失的 PV(该 PV 上的 LV 会被删除!)
vgreduce --removemissing --force vgdata       # 强制(会删除引用了丢失 PV 的 LV)
vgchange -ay --partial vgdata                  # 部分激活,尝试抢救仍完整的 LV 上的数据
vgcfgrestore vgdata                           # 换上新盘并恢复元数据后重建

其它常见问题

问题 原因与解决
lvcreate 报 Insufficient free space VG 空间不足:vgs 看 VFree,vgextend 加盘;或改用 -l 100%FREE
开机后 LV 未激活、fstab 挂载失败 vgchange -ay 激活;检查 lvm2-lvmetad/lvm2-monitor 服务;dracut -f 重建 initramfs
pvcreate 报 Device /dev/sdb1 excluded by a filter 设备上有旧的文件系统/RAID 签名:wipefs -a /dev/sdb1;或检查 /etc/lvm/lvm.conf 的 filter 配置
vgremove 报还有 LV 先 lvremove 删除所有 LV
pvremove 报 PV is used by VG 先 vgreduce 把它移出 VG
克隆虚拟机后两台机器 VG 同名 vgrename <UUID> newname;xfs 还需 xfs_admin -U generate 换 UUID
lvs 看不到某个 VG pvscan --cache && vgscan && vgchange -ay
想知道某个 LV 的数据落在哪块物理盘 lvdisplay -m /dev/vg/lv 或 lvs -o +devices

5.10 附录:LVM 命令速查表

本节完整保留《02-LVM逻辑卷命令速查.md》的速查内容,便于运维时快速查阅。

LVM 三层结构关系:

磁盘/分区  --pvcreate-->  物理卷 PV  --vgcreate-->  卷组 VG  --lvcreate-->  逻辑卷 LV  --mkfs/mount-->  文件系统

附录 A-1:物理卷(PV)管理命令

物理卷是 LVM 的底层存储提供者,通常由整块磁盘或磁盘分区构成。

序号 命令语法 说明
1 pvcreate /dev/hdb1 将指定磁盘或分区初始化为物理卷,供 LVM 使用
2 pvscan(或 pvs) 扫描并列出系统中所有的物理卷信息,也可指定具体设备查看
3 pvdisplay /dev/hdb1 显示指定物理卷的详细属性信息(大小、PE、所属 VG 等)
4 pvchange 改变物理卷的属性(如允许/禁止分配 PE:pvchange -x n /dev/hdb1)
5 pvmove 将物理卷上的数据搬移到其他物理卷,常用于移除磁盘前的数据迁移

补充说明:

  • pvs 是 pvscan 的精简输出形式,适合快速浏览;pvdisplay 输出更详细。
  • 若要从系统中移除一块物理卷,应先用 pvmove 搬移数据,再用 vgreduce 将其从卷组中移出,最后用 pvremove 删除物理卷标记。

附录 A-2:卷组(VG)管理命令

卷组由一个或多个物理卷组成,是物理卷与逻辑卷之间的中间抽象层。

序号 命令语法 说明
1 vgscan(或 vgs) 扫描并列出系统中所有的卷组信息,也可指定卷组名查看
2 vgcreate myvg /dev/hdb1 使用指定物理卷创建名为 myvg 的卷组
3 vgextend myvg /dev/hdb2 向卷组 myvg 中添加新的物理卷,实现卷组在线扩容
4 vgcfgbackup 备份卷组中各物理卷的 VGDA 信息,默认备份到 /etc/lvm/backup 目录(原文写作 /etc/vmconf)
5 vgcfgrestore 从备份文件中恢复卷组中物理卷的 VGDA 信息
6 vgchange 改变卷组属性,常用 -a y/-a n 激活或停用卷组
7 vgck 检测卷组中 VGDA(卷组描述符区域)的一致性
8 vgdisplay 显示卷组的详细信息(PE 大小、总 PE、空闲 PE 等)
9 vgexport 导出卷组,使系统不再识别该卷组(常用于迁移到其他机器)
10 vgimport 与 vgexport 相反,将导出的卷组重新导入系统使其可被识别
11 vgmerge 合并两个已存在的卷组
12 vgmknodes 重新建立已有卷组的卷组目录和其中的设备文件节点
13 vgreduce 把卷组中未使用的物理卷移出卷组(缩小卷组)
14 vgremove 删除空的(不包含任何逻辑卷的)卷组
15 vgrename 重命名非活动状态(未挂载使用)的卷组
16 vgsplit 把一个卷组拆分为两个卷组

补充说明:

  • vgs 是精简输出,vgdisplay 是详细输出。
  • 扩容卷组流程:pvcreate /dev/hdb2 → vgextend myvg /dev/hdb2。
  • 缩小卷组前,应先用 pvmove 把待移除物理卷上的数据搬移走,再执行 vgreduce。

附录 A-3:逻辑卷(LV)管理命令

逻辑卷从卷组中划分空间,对用户而言相当于一块可灵活调整大小的"虚拟磁盘",可在其上创建文件系统并挂载使用。

序号 命令语法 说明
1 lvscan(或 lvs) 扫描并列出系统中所有逻辑卷,也可指定具体逻辑卷查看
2 lvcreate -L 2G -n mylv1 myvg 在卷组 myvg 中新建名为 mylv1 的逻辑卷。-L 指定实际大小(如 2G),-l 可指定 PE 数量或百分比大小(如 -l +100%FREE)
3 lvextend -L +100M /dev/myvg/mylv1 扩充逻辑卷 mylv1 的容量,增加 100M
4 lvreduce -L -100M /dev/myvg/mylv1 缩小逻辑卷 mylv1 的容量,减少 100M
5 resize2fs /dev/myvg/mylv1 调整 ext2/ext3/ext4 文件系统的容量(适用于已挂载使用的逻辑卷,扩容后同步文件系统)
6 e2fsadm 调整未挂载的 ext2/ext3 文件系统对应的逻辑卷容量
7 lvremove 删除非活动状态(未挂载使用)的逻辑卷
8 resize_reiserfs 调整 ReiserFS 文件系统的容量
9 lvchange 改变逻辑卷的属性,如 -a y/-a n 激活或停用逻辑卷
10 lvdisplay 显示逻辑卷的详细属性信息
11 lvrename 对非活动状态的逻辑卷改名
12 lvmchange 在紧急情况下用于复位逻辑卷管理器
13 lvmdiskscan 检测系统中所有硬盘和分区,查找可用于 LVM 的存储设备
14 lvmsadc 收集逻辑卷的读写统计数据(原文写作 lvmsade,应为 lvmsadc)
15 lvmsar 报告 lvmsadc 命令收集到的逻辑卷读写统计数据

补充说明:

  • lvs 是精简输出,lvdisplay 是详细输出。

  • 逻辑卷设备路径有两种等价写法:/dev/myvg/mylv1 或 /dev/mapper/myvg-mylv1。

  • 扩容流程(ext 文件系统):

    lvextend -L +100M /dev/myvg/mylv1   # 扩容逻辑卷
    resize2fs /dev/myvg/mylv1           # 同步扩容文件系统
  • 缩容风险提示:缩容逻辑卷前务必备份数据,并先缩小文件系统再缩小逻辑卷,否则可能导致数据损坏。XFS 文件系统不支持缩容。

附录 A-4:典型操作流程速记

# 1. 创建物理卷
pvcreate /dev/hdb1

# 2. 创建卷组
vgcreate myvg /dev/hdb1

# 3. 创建逻辑卷
lvcreate -L 2G -n mylv1 myvg

# 4. 创建文件系统并挂载
mkfs.ext4 /dev/myvg/mylv1
mount /dev/myvg/mylv1 /mnt

# 5. 在线扩容逻辑卷与文件系统
lvextend -L +100M /dev/myvg/mylv1
resize2fs /dev/myvg/mylv1

六、磁盘配额(quota)

6.1 配额的作用与基本概念

作用:限制用户或组在某个文件系统上能使用的磁盘空间和文件数量,防止个别用户写满共享分区(典型场景:多用户共享的 /home、FTP 服务器、邮件服务器、共享开发机)。

关键概念:

概念 说明
限制对象 用户配额(user quota,按 UID 限制)、组配额(group quota,按 GID 限制)、项目配额(project quota,仅 xfs,按目录限制)
限制内容 block 配额(限制占用空间大小,单位 KB)、inode 配额(限制文件个数)
软限制 soft 警告线。超过后仍可继续写入,但进入宽限期倒计时,并在登录时收到警告
硬限制 hard 绝对上限。达到后立即禁止继续写入,报 Disk quota exceeded
宽限期 grace period 超过软限制后允许"超标"的时长(默认 7 天)。宽限期内必须把用量降到软限制以下,否则软限制会变成硬限制,无法再写入

重要前提:

  • 配额是针对文件系统(分区)生效的,不是针对目录(xfs project quota 除外);
  • 要限制 /home,/home 必须是独立的分区/逻辑卷;
  • root 用户不受配额限制。

6.2 ext4 上的配额完整实操(CentOS 6 / CentOS 7 通用)

步骤 0:准备实验环境

# 创建一个独立的文件系统作为实验对象
fdisk /dev/sdb                       # n → p → 1 → +2G → w
partprobe /dev/sdb
mkfs.ext4 /dev/sdb1
mkdir -p /home2
mount /dev/sdb1 /home2

# 安装配额工具
yum install quota -y
rpm -qa | grep quota

# 创建测试用户
useradd -d /home2/tom tom && echo '123456' | passwd --stdin tom
useradd -d /home2/jerry jerry && echo '123456' | passwd --stdin jerry
groupadd devgrp
usermod -aG devgrp tom
usermod -aG devgrp jerry
chmod 777 /home2

步骤 1:挂载时启用配额选项 usrquota / grpquota

# 临时启用(remount 方式,立即生效)
mount -o remount,usrquota,grpquota /home2
mount | grep home2                   # 确认选项中出现 usrquota,grpquota

# 永久启用:写入 /etc/fstab
vim /etc/fstab
UUID=xxxx-xxxx-xxxx   /home2   ext4   defaults,usrquota,grpquota   0 0
# 也可写设备名:/dev/sdb1  /home2  ext4  defaults,usrquota,grpquota  0 0
mount -a                             # 验证 fstab 配置
mount | grep home2                   # 再次确认配额选项已生效
# 如果 remount 不生效,则完整卸载再挂载:
umount /home2 && mount /home2

若要对根分区启用配额,加了 fstab 选项后需要重启(或 mount -o remount /)。

步骤 2:quotacheck 创建配额数据库文件

quotacheck -cugv /home2
#   -c  创建新的配额数据库文件(create),会覆盖已有的
#   -u  处理用户配额,生成 aquota.user
#   -g  处理组配额,生成 aquota.group
#   -v  显示详细过程
#   -a  对 /etc/mtab 中所有启用了配额的文件系统操作
#   -m  不要重新挂载为只读(文件系统正在使用时需要)
#   -M  强制以读写方式检查

quotacheck -avug                     # 对所有启用配额的文件系统创建
quotacheck -cugvm /home2             # 文件系统忙时加 -m

ls -l /home2/aquota.*
# -rw------- 1 root root 6144 ... /home2/aquota.group
# -rw------- 1 root root 6144 ... /home2/aquota.user

说明:aquota.user 与 aquota.group 是配额数据库,位于该文件系统的根目录下(CentOS 6/7 均为此名;更老的系统叫 quota.user/quota.group)。

注意:quotacheck 会扫描整个文件系统统计每个用户的用量,大文件系统上耗时较长,且要求文件系统最好处于只读或空闲状态。

步骤 3:quotaon 启用配额

quotaon -avug                        # 启用所有文件系统的用户与组配额(最常用)
quotaon -v /home2                    # 启用指定文件系统
quotaon -u /home2                    # 只启用用户配额
quotaon -g /home2                    # 只启用组配额
quotaon -p /home2                    # 查看配额是否已启用(print state)

# 关闭配额
quotaoff -avug                       # 关闭所有
quotaoff -v /home2                   # 关闭指定文件系统

步骤 4:edquota 设置配额(交互式)

edquota -u tom                       # 编辑用户 tom 的配额(打开 vi 编辑器)
edquota -g devgrp                    # 编辑组 devgrp 的配额

打开的编辑界面内容:

Disk quotas for user tom (uid 1001):
  Filesystem      blocks    soft    hard   inodes    soft    hard
  /dev/sdb1           32   20480   30720        8      80     100

字段含义:

  • blocks:当前已用空间(KB,由系统统计,不要修改);
  • 第一组 soft/hard:空间的软/硬限制(KB)。20480 = 20MB,30720 = 30MB;
  • inodes:当前已用文件数(由系统统计,不要修改);
  • 第二组 soft/hard:文件数量的软/硬限制;
  • 数值填 0 表示不限制。

保存退出(:wq)即生效。

其它 edquota 用法:

edquota -t                           # 设置宽限期(对整个文件系统生效,非单个用户)

宽限期编辑界面:

Grace period before enforcing soft limits for users:
Time units may be: days, hours, minutes, or seconds
  Filesystem             Block grace period     Inode grace period
  /dev/sdb1                     7days                  7days

可改为 3days、12hours、30minutes 等。

edquota -p tom jerry                 # 把 tom 的配额设置复制给 jerry(-p = prototype)
edquota -p tom -u jerry mike lucy    # 一次复制给多个用户(批量设置的利器)
for u in u1 u2 u3; do edquota -p tom $u; done   # 脚本批量
edquota -u tom -f /dev/sdb1          # 只编辑指定文件系统上的配额

步骤 5:setquota 非交互式设置(脚本首选)

# 语法:setquota -u 用户 块软限制 块硬限制 inode软限制 inode硬限制 文件系统
setquota -u tom 20480 30720 80 100 /home2
setquota -g devgrp 102400 204800 500 600 /home2

# 只限制空间不限制文件数(inode 限制填 0)
setquota -u jerry 51200 102400 0 0 /home2

# 批量为多个用户设置相同配额
for u in tom jerry mike; do setquota -u $u 20480 30720 80 100 /home2; done

# 用 -p 复制原型用户的配额
setquota -p tom -u jerry /home2

# 设置宽限期(-t,单位为秒)
setquota -t 259200 259200 /home2     # 3 天 = 259200 秒
setquota -u tom -T 86400 86400 /home2  # -T 为单个用户设置宽限期

步骤 6:查看配额使用情况

quota                                # 普通用户查看自己的配额
quota -u tom                         # 查看指定用户的配额(root 执行)
quota -g devgrp                      # 查看组配额
quota -uvs tom                       # -v 详细,-s 以易读单位(M/G)显示
quota -q tom                         # 只在超限时才输出

repquota -a                          # 报告所有启用配额的文件系统的配额情况(最常用)
repquota /home2                      # 报告指定文件系统
repquota -u /home2                   # 只报告用户配额
repquota -g /home2                   # 只报告组配额
repquota -as                         # 所有文件系统 + 易读单位
repquota -av                          # 详细报告

repquota -a 输出示例:

*** Report for user quotas on device /dev/sdb1
Block grace time: 7days; Inode grace time: 7days
                        Block limits                File limits
User            used    soft    hard  grace    used  soft  hard  grace
----------------------------------------------------------------------
root      --      20       0       0              2     0     0
tom       +-   25600   20480   30720  6days       12    80   100
jerry     --      32   51200  102400              4     0     0

--/+- 标记:第一位是 block 状态,第二位是 inode 状态;- 正常,+ 表示已超过软限制(此时 grace 列显示剩余宽限时间)。

步骤 7:超限现象演练与验证

# 切换到被限制的用户
su - tom

# 1) 写入未超软限制的数据(正常)
dd if=/dev/zero of=/home2/tom/f10m bs=1M count=10
quota -s                             # 查看用量

# 2) 写入超过软限制(20MB)但未到硬限制(30MB)—— 可以写入,但收到警告
dd if=/dev/zero of=/home2/tom/f15m bs=1M count=15
# sdb1: warning, user block quota exceeded.
quota -s                             # grace 列开始倒计时(如 6days)
repquota -as                         # root 执行,可见 tom 带 + 标记

# 3) 继续写入直到超过硬限制(30MB)—— 写入被截断并报错
dd if=/dev/zero of=/home2/tom/f20m bs=1M count=20
# sdb1: write failed, user block limit reached.
# dd: 写入'/home2/tom/f20m' 出错: 超出磁盘限额
# dd: Disk quota exceeded
ls -lh /home2/tom/                   # 文件只写到限额允许的大小就停止了

# 4) 验证 inode(文件数)配额
for i in $(seq 1 200); do touch /home2/tom/file$i; done
# touch: 无法创建 "/home2/tom/file101": 超出磁盘限额
ls /home2/tom | wc -l                # 停在硬限制数量上

# 5) 清理并确认恢复
rm -f /home2/tom/f*
quota -s                             # 用量降回,警告消失,grace 计时清除
exit

宽限期到期的验证:把宽限期改成 1 分钟观察软限制"转硬"的效果:

setquota -t 60 60 /home2             # 宽限期 60 秒
su - tom -c 'dd if=/dev/zero of=/home2/tom/big bs=1M count=25'   # 超软限制
sleep 70
su - tom -c 'dd if=/dev/zero of=/home2/tom/big2 bs=1M count=1'
# 宽限期已过:即使未到硬限制也无法写入,软限制变成了硬限制
setquota -t 604800 604800 /home2     # 恢复 7 天

配额的维护与自动化

quotacheck -avug                     # 定期校准配额数据库(建议在低峰期或单用户模式)
warnquota                            # 给超限用户自动发邮件告警(配置文件 /etc/warnquota.conf)
crontab -e
#   0 4 * * * /usr/sbin/warnquota           # 每天 4 点检查并发告警邮件
#   0 3 * * 0 /usr/sbin/quotacheck -avug    # 每周校准

# CentOS 6 服务方式(配额随开机自动启用由 fstab 选项驱动,无独立服务)
chkconfig --list | grep quota
# CentOS 7 也是由 systemd 的 quotaon.service 自动处理
systemctl status quotaon.service

6.3 xfs 的配额差异(CentOS 7 重点)

xfs 的配额机制与 ext4 完全不同,关键差异:

对比项 ext2/3/4 xfs
挂载选项 usrquota、grpquota uquota(或 usrquota)、gquota(或 grpquota)、pquota(项目配额)
配额数据库 需要 quotacheck 生成 aquota.user/aquota.group 文件 内建在文件系统元数据中,不需要 quotacheck,也没有 aquota 文件
启用方式 quotaon 挂载时由挂载选项决定,挂载后不能用 remount 添加/移除配额(必须卸载重挂或重启)
主要工具 edquota/setquota/repquota/quota xfs_quota(也兼容 quota/repquota 查看)
目录级限额 不支持 支持 project quota(按目录限额)
组配额与项目配额 可同时使用 gquota 与 pquota 互斥,不能同时启用

启用 xfs 配额

mkfs.xfs -f /dev/sdb1
mkdir -p /xfsdata
mount -o uquota,gquota /dev/sdb1 /xfsdata     # 用户 + 组配额
mount -o uquota,pquota /dev/sdb1 /xfsdata     # 用户 + 项目配额(gquota 与 pquota 互斥)
mount | grep xfsdata

写入 fstab:

/dev/sdb1   /xfsdata   xfs   defaults,uquota,gquota   0 0
UUID=xxxx   /xfsdata   xfs   defaults,uquota,pquota   0 0
umount /xfsdata && mount /xfsdata             # xfs 配额不能 remount 添加,必须重新挂载

对根分区启用 xfs 配额需要修改内核启动参数:在 /etc/default/grub 的 GRUB_CMDLINE_LINUX 中加入 rootflags=uquota,gquota,然后 grub2-mkconfig -o /boot/grub2/grub.cfg 并重启。

xfs_quota 命令用法

# -x 专家模式(可修改限额),-c 执行命令,不加 -c 则进入交互式
xfs_quota -x -c 'state' /xfsdata               # 查看配额启用状态
xfs_quota -x -c 'report' /xfsdata              # 报告配额使用情况
xfs_quota -x -c 'report -h' /xfsdata           # 易读单位
xfs_quota -x -c 'report -u -h' /xfsdata        # 只看用户
xfs_quota -x -c 'report -g -h' /xfsdata        # 只看组
xfs_quota -x -c 'report -p -h' /xfsdata        # 只看项目
xfs_quota -c 'quota -u tom' /xfsdata           # 查看某用户配额
xfs_quota -c 'quota -h tom' /xfsdata           # 易读单位
xfs_quota -x -c 'free -h' /xfsdata             # 查看文件系统空间

# 设置用户配额:bsoft/bhard 空间软硬限制,isoft/ihard 文件数软硬限制
xfs_quota -x -c 'limit -u bsoft=200M bhard=300M tom' /xfsdata
xfs_quota -x -c 'limit -u isoft=800 ihard=1000 tom' /xfsdata
xfs_quota -x -c 'limit -u bsoft=200M bhard=300M isoft=800 ihard=1000 tom' /xfsdata

# 设置组配额
xfs_quota -x -c 'limit -g bsoft=1G bhard=2G devgrp' /xfsdata

# 设置宽限期
xfs_quota -x -c 'timer -u -bsoft 3days' /xfsdata
xfs_quota -x -c 'timer -u 7days' /xfsdata
xfs_quota -x -c 'timer -g -bsoft 1day' /xfsdata

# 开关配额记账与强制执行
xfs_quota -x -c 'disable -u' /xfsdata          # 临时停止强制执行(仍记账)
xfs_quota -x -c 'enable -u' /xfsdata           # 恢复强制执行
xfs_quota -x -c 'off -u' /xfsdata              # 彻底关闭(关闭后只能卸载重挂才能再开)
xfs_quota -x -c 'remove -u' /xfsdata           # 清除所有用户限额(须先 off)

# 交互式模式
xfs_quota -x /xfsdata
xfs_quota> state
xfs_quota> limit -u bsoft=100m bhard=200m tom
xfs_quota> report -h
xfs_quota> quit

xfs project quota:对目录限额的完整步骤

这是 xfs 独有的强大功能——限制某个目录树的总大小,与用户无关(适合限制 /var/log/nginx、每个项目组的共享目录、容器数据目录)。

# 1. 挂载时启用 pquota(注意与 gquota 互斥)
umount /xfsdata
mount -o uquota,pquota /dev/sdb1 /xfsdata
# fstab: /dev/sdb1  /xfsdata  xfs  defaults,uquota,pquota  0 0
xfs_quota -x -c 'state' /xfsdata               # 确认 Project quota state 为 ON

# 2. 创建要限额的目录
mkdir -p /xfsdata/project_a /xfsdata/project_b

# 3. 定义项目 ID 与项目名的映射(/etc/projid)
cat >> /etc/projid <<'EOF'
proja:100
projb:101
EOF

# 4. 定义项目 ID 与目录的对应关系(/etc/projects)
cat >> /etc/projects <<'EOF'
100:/xfsdata/project_a
101:/xfsdata/project_b
EOF

# 5. 初始化项目(把目录树打上 project ID 标记)
xfs_quota -x -c 'project -s proja' /xfsdata
xfs_quota -x -c 'project -s projb' /xfsdata
xfs_quota -x -c 'project -s -p /xfsdata/project_a 100' /xfsdata   # 不用配置文件的写法

# 6. 设置项目限额
xfs_quota -x -c 'limit -p bsoft=500M bhard=1G proja' /xfsdata
xfs_quota -x -c 'limit -p bsoft=2G bhard=3G isoft=10000 ihard=12000 projb' /xfsdata

# 7. 查看项目配额
xfs_quota -x -c 'report -p -h' /xfsdata
# Project quota on /xfsdata (/dev/sdb1)
#                         Blocks
# Project ID   Used   Soft   Hard Warn/Grace
# proja         12K   500M     1G  00 [------]

# 8. 验证:往目录里写超量数据(任何用户写入都受限)
dd if=/dev/zero of=/xfsdata/project_a/big bs=1M count=1100
# dd: 写入'/xfsdata/project_a/big' 出错: 设备上没有空间
df -h /xfsdata/project_a
#   注意:启用 project quota 后,df 查看该目录会显示"配额大小"而不是整个文件系统大小

# 9. 清理项目标记
xfs_quota -x -c 'project -C proja' /xfsdata     # 清除目录上的 project ID

ext4 也在较新内核上支持 project quota(需 tune2fs -O project,quota 并挂载 prjquota),但 xfs 的实现更成熟、使用更广泛。

七、磁盘加密(LUKS)

7.1 应用场景与原理

LUKS(Linux Unified Key Setup)是 Linux 标准的块设备加密方案,基于内核 dm-crypt 实现。加密发生在块设备层,对上层文件系统完全透明。

应用场景:

  • 笔记本/移动硬盘/U 盘丢失后防止数据被读取;
  • 服务器下架、磁盘报废、返修时防止残留数据泄露;
  • 合规要求(等级保护、GDPR、PCI-DSS 等)要求静态数据加密;
  • 多租户环境中隔离敏感数据分区(如存放密钥、证书、用户隐私的分区)。

工作原理:LUKS 在设备头部写入一个 LUKS 头(header),其中包含加密算法参数和最多 8 个(LUKS2 为 32 个)密钥槽(key slot)。真正加密数据的是随机生成的主密钥(master key),每个密钥槽用用户口令(或 keyfile)派生的密钥把主密钥加密后保存。这样设计的好处是:可以设置多个口令、可以更换口令而无需重新加密全盘数据。

安装工具:

yum install cryptsetup -y
yum install cryptsetup* -y             # 连相关包一起装
cryptsetup --version
cat /proc/crypto | grep -i aes         # 确认内核支持的加密算法
lsmod | grep dm_crypt                  # 确认 dm_crypt 模块已加载
modprobe dm_crypt                      # 手工加载

7.2 创建与使用加密分区(完整流程)

前提:加密的分区必须是新创建的、未使用过的分区(正在使用或有数据的分区不能直接加密,luksFormat 会摧毁其上的全部数据)。

# ===== 1. 创建加密分区 =====
cryptsetup luksFormat /dev/sda5

交互提示:

WARNING!
========
This will overwrite data on /dev/sda5 irrevocably.

Are you sure? (Type uppercase yes): YES
Enter passphrase for /dev/sda5:
Verify passphrase:
  • 确认时必须输入大写的 YES(yes、y 都无效,这是有意的防误操作设计);
  • 然后设置加密密码,切记不可忘记,忘记则数据永久无法恢复。

带参数的高级用法:

cryptsetup luksFormat --type luks2 /dev/sda5                    # 使用 LUKS2 格式(CentOS 8+/新版)
cryptsetup luksFormat -c aes-xts-plain64 -s 512 -h sha512 /dev/sda5
#   -c/--cipher      加密算法(默认 aes-xts-plain64)
#   -s/--key-size    密钥长度(256/512)
#   -h/--hash        哈希算法
#   -i/--iter-time   PBKDF2 迭代毫秒数(越大越安全但解锁越慢,默认 2000)
cryptsetup luksFormat /dev/sda5 /root/mykey.bin                 # 用 keyfile 而非口令
cryptsetup -y -v luksFormat /dev/sda5                           # -y 二次确认口令,-v 详细
# ===== 2. 解密分区(打开),生成解密后的设备文件(soul 为自定义映射名)=====
cryptsetup luksOpen /dev/sda5 soul
cryptsetup open /dev/sda5 soul          # 新语法,等效
# 输入密码后,生成映射设备 /dev/mapper/soul
ls -l /dev/mapper/
lsblk                                   # 可以看到 sda5 下挂了一个 TYPE=crypt 的 soul

# ===== 3. 首次使用需格式化解密后的设备 =====
mkfs -t ext4 /dev/mapper/soul
mkfs.xfs /dev/mapper/soul               # 也可以用 xfs
#   注意:格式化的对象是 /dev/mapper/soul,绝不是 /dev/sda5!

# ===== 4. 挂载加密分区 =====
mkdir -p /mnt/secure
mount /dev/mapper/soul /mnt
mount /dev/mapper/soul /mnt/secure
df -hT | grep soul
# 此后对 /mnt 的操作即是对加密分区的操作,读写自动加解密,完全透明

echo "secret data" > /mnt/secret.txt

# ===== 5. 卸载并关闭加密分区 =====
umount /dev/mapper/soul
umount /mnt
cryptsetup luksClose soul
cryptsetup close soul                   # 新语法,等效
ls /dev/mapper/                         # soul 已消失

# 关闭后直接读裸设备只能看到随机数据,无法识别文件系统
blkid /dev/sda5                         # TYPE="crypto_LUKS"
mount /dev/sda5 /mnt                    # 报错:unknown filesystem type 'crypto_LUKS'

7.3 密钥管理

LUKS 支持多个密钥槽(LUKS1 有 8 个,编号 0~7),可以给不同的人/用途配置不同口令。

# 查看 LUKS 头信息与密钥槽占用情况(重要)
cryptsetup luksDump /dev/sda5

输出关键内容:

LUKS header information for /dev/sda5
Version:        1
Cipher name:    aes
Cipher mode:    xts-plain64
Hash spec:      sha256
Payload offset: 4096
MK bits:        256
Key Slot 0: ENABLED
        Iterations: 130000
        Salt: ...
Key Slot 1: DISABLED
Key Slot 2: DISABLED
...
# 添加一个新口令(需要提供任一已有的正确口令来验证)
cryptsetup luksAddKey /dev/sda5
cryptsetup luksAddKey /dev/sda5 /root/keyfile        # 把 keyfile 添加为新的解锁凭据
cryptsetup luksAddKey --key-slot 2 /dev/sda5         # 指定写入第 2 个槽

# 删除某个口令
cryptsetup luksRemoveKey /dev/sda5                   # 交互输入要删除的那个口令
cryptsetup luksKillSlot /dev/sda5 1                  # 按槽号删除(需另一个有效口令)
#   警告:删除最后一个可用密钥槽 = 数据永久丢失

# 更换口令(等价于 add + remove)
cryptsetup luksChangeKey /dev/sda5
cryptsetup luksChangeKey /dev/sda5 --key-slot 0

# 测试某个口令是否正确(不做任何修改)
cryptsetup luksOpen --test-passphrase /dev/sda5 && echo "口令正确"

# 生成一个安全的 keyfile(用于自动解锁)
dd if=/dev/urandom of=/root/luks-keyfile bs=512 count=8
chmod 400 /root/luks-keyfile
cryptsetup luksAddKey /dev/sda5 /root/luks-keyfile
cryptsetup luksOpen -d /root/luks-keyfile /dev/sda5 soul     # 用 keyfile 解锁,无需交互
cryptsetup luksOpen --key-file /root/luks-keyfile /dev/sda5 soul

7.4 头部备份的重要性

LUKS 头部(含所有密钥槽)只有几 MB,但它一旦损坏,即使口令正确也永远无法解密数据。 头部可能被误操作破坏(错误的 dd、误 mkfs、分区表操作失误、坏道正好落在头部)。因此:

# 备份 LUKS 头部(强烈建议在 luksFormat 之后立即执行)
cryptsetup luksHeaderBackup /dev/sda5 --header-backup-file /root/sda5-luks-header.img
# 把备份文件保存到【其他机器/加密的 U 盘/离线介质】,并注意:
#   该备份文件包含密钥槽,拿到它 + 口令就能解密数据 → 必须妥善保护!
chmod 400 /root/sda5-luks-header.img

# 恢复头部
cryptsetup luksHeaderRestore /dev/sda5 --header-backup-file /root/sda5-luks-header.img
#   警告:恢复会覆盖当前头部,之后只有备份时那些口令有效(备份后新增的口令会失效)

# 使用分离式头部(把头部单独存放,裸盘上完全没有加密痕迹)
cryptsetup luksFormat --header /root/detached-header.img /dev/sda5
cryptsetup luksOpen --header /root/detached-header.img /dev/sda5 soul

忘记密码的后果:LUKS 使用强加密与 PBKDF2 密钥派生,没有任何后门、没有找回途径、暴力破解在计算上不可行。忘记全部口令 = 数据永久性丢失。因此:

  1. 口令必须存放在企业密码管理系统中,并有第二人可取;
  2. 至少配置 2 个密钥槽(一个日常口令 + 一个应急恢复口令/keyfile);
  3. 立刻做头部备份;
  4. 加密不能替代备份,重要数据仍需独立备份(备份本身也应加密)。

7.5 /etc/crypttab 与开机自动解锁

/etc/crypttab 定义开机时需要解锁的加密设备,格式为四个字段:

映射名   底层设备(建议 UUID)   密钥文件(none 表示交互输入口令)   选项

示例:

# 开机时提示输入口令
soul   UUID=1a2b3c4d-5e6f-7890-abcd-ef1234567890   none                 luks

# 使用 keyfile 自动解锁(无需人工输入,但 keyfile 必须妥善保护)
data   UUID=aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee   /root/luks-keyfile   luks

# keyfile 放在 U 盘上(拔掉 U 盘就无法启动,实现"物理钥匙"效果)
data   /dev/sdb1                                   /media/usbkey/key.bin  luks,keyfile-timeout=30

# 常用选项:luks, discard(SSD TRIM), noauto, timeout=, tries=3, nofail
secure UUID=xxxx                                   none                 luks,discard,nofail

配合 /etc/fstab 挂载解锁后的映射设备:

/dev/mapper/soul   /mnt/secure   ext4   defaults   0 0
/dev/mapper/data   /data         xfs    defaults,nofail   0 0

操作与验证:

blkid /dev/sda5                       # 取 UUID(注意取的是 crypto_LUKS 的 UUID)
cryptsetup luksUUID /dev/sda5         # 直接查看 LUKS 设备的 UUID
vim /etc/crypttab
vim /etc/fstab
systemctl daemon-reload               # CentOS 7 让 systemd 重新生成加密单元
systemctl list-units | grep crypt     # 查看 systemd-cryptsetup@ 单元
dracut -f                             # 若根分区加密,需重建 initramfs
reboot                                # 验证开机流程

# 手工触发(不重启验证)
systemctl start systemd-cryptsetup@soul.service
mount -a

CentOS 6 也使用 /etc/crypttab,但由 rc.sysinit 处理,字段含义相同。

安全提醒:

  • keyfile 自动解锁虽然方便,但 keyfile 与磁盘在同一台机器上时,加密对"整机被盗"几乎失去意义(攻击者拿到机器就拿到了钥匙)。真正的安全做法是:口令交互输入、keyfile 放在可移动介质、或使用 TPM/网络密钥服务(如 Clevis + Tang 实现网络绑定自动解锁)。
  • 加密会带来一定 CPU 开销(现代 CPU 有 AES-NI 指令集加速,开销通常 <5%)。检查是否支持:grep -m1 aes /proc/cpuinfo。

7.6 加密 LVM 与已有数据的加密

# 在 LVM 逻辑卷上加密(常见组合)
lvcreate -L 10G -n lvsecure vgdata
cryptsetup luksFormat /dev/vgdata/lvsecure
cryptsetup luksOpen /dev/vgdata/lvsecure secure
mkfs.xfs /dev/mapper/secure
mount /dev/mapper/secure /secure

# 或者反过来:先加密整块盘,再在解锁后的设备上建 LVM(CentOS 安装器的默认方案)
cryptsetup luksFormat /dev/sdb
cryptsetup luksOpen /dev/sdb crypt_sdb
pvcreate /dev/mapper/crypt_sdb
vgcreate vgcrypt /dev/mapper/crypt_sdb
lvcreate -l 100%FREE -n lvdata vgcrypt
mkfs.xfs /dev/vgcrypt/lvdata

# 对已有数据的分区加密(原地加密,高风险,务必先完整备份)
cryptsetup-reencrypt --new --reduce-device-size 32M /dev/sdb1   # 需要 cryptsetup 1.6+
#   更安全的做法:备份数据 → luksFormat → 恢复数据

八、软件 RAID 管理(mdadm)

RAID(Redundant Array of Independent Disks,独立磁盘冗余阵列)把多块磁盘组合成一个逻辑块设备(如 /dev/md0),用于提升读写性能、提供磁盘级冗余,或两者兼顾。阵列设备与普通分区一样,仍需经过「格式化 → 挂载」才能使用。

8.1 RAID 级别原理与对比

各级别原理:

  • RAID 0(条带 Striping):数据被切块轮流写到各盘,多盘并行 → 读写最快、容量 100% 利用,但没有任何冗余,任一盘损坏则全部数据丢失(故障率是单盘的 N 倍)。
  • RAID 1(镜像 Mirroring):数据完整地同时写入两块盘 → 可靠性最高,读可并行加速,写要双写略慢,容量只有 50%。
  • RAID 5(分布式奇偶校验):数据 + 校验信息分散在所有盘上,任一盘损坏可用其余盘的数据 + 校验重算恢复 → 容量利用率 (n-1)/n,可坏 1 块,写入需读改写 + 计算校验有开销。
  • RAID 6(双重奇偶校验):使用两种独立校验(P + Q),容量 (n-2)/n,可坏 2 块,写开销大于 RAID 5,适合大容量盘(重建期长)。
  • RAID 10(1+0):先两两做镜像(RAID 1),再把镜像组条带化(RAID 0) → 读写都快、重建只需复制镜像盘(快且压力小),容量 50%。
  • RAID 01(0+1):先条带再镜像,可靠性低于 RAID 10,实际很少使用。
  • JBOD / linear:简单串联多块盘,无性能提升、无冗余。
级别 最少磁盘数 容量利用率 冗余能力 读写特点 典型用途
RAID 0 2 100% 无,坏 1 块即全部数据丢失 条带化并行,读写都最快 临时数据、缓存、可重建的中间结果
RAID 1 2 50% 每个镜像组可坏 1 块 读较快(可多盘并读),写略慢(需双写) 系统盘、/boot、小容量关键数据
RAID 5 3 (n-1)/n 可坏 1 块 读快,写有校验计算开销 通用存储,容量与冗余的折中方案
RAID 6 4 (n-2)/n 可坏 2 块 读快,写开销大于 RAID 5(双校验) 大容量磁盘阵列、重建时间长的场景
RAID 10 4(偶数) 50% 每个镜像组各可坏 1 块 读写都快,重建代价小 数据库等高 IOPS + 高可靠场景
  • RAID 10 是「先镜像(RAID 1)再条带(RAID 0)」的组合,性能与可靠性兼顾,代价是只有一半可用容量。
  • 容量利用率中的 n 指阵列中的数据盘数量(热备盘不计入可用容量)。

容量计算举例(每块 1TB):

配置 可用容量 可容忍故障
4 盘 RAID 0 4 TB 0 块
4 盘 RAID 1(2 组镜像) 2 TB 每组 1 块
4 盘 RAID 5 3 TB 1 块
4 盘 RAID 6 2 TB 2 块
4 盘 RAID 10 2 TB 每个镜像组 1 块
3 盘 RAID 5 + 1 热备 2 TB 1 块(+自动顶替)

8.2 硬件 RAID、软件 RAID 与 fakeraid

类型 实现方式 特点
硬件 RAID 独立 RAID 卡,带处理芯片与缓存(常配电池/电容) 性能好、对系统透明;成本高,卡损坏后通常需同型号卡才能读取阵列
软件 RAID 由内核 md 驱动实现,用 mdadm 管理 免费、灵活,不绑定硬件,换机器仍可组装阵列;占用少量 CPU
fakeraid 主板 BIOS「RAID」,实际仍靠驱动完成计算 兼具两者缺点:跨平台兼容性差、依赖特定主板驱动,Linux 下一般不建议使用

何时使用 mdadm:没有硬件 RAID 卡、需要跨主板/跨机器迁移阵列、虚拟机或云主机的多块数据盘、以及学习与测试环境。若已有带缓存的硬件 RAID 卡,则优先使用硬件 RAID,不要再叠加软件 RAID。

软 RAID / 硬 RAID / LVM 的关系与选择:

需求 推荐方案
只要冗余,不需要灵活调整大小 硬 RAID 或 mdadm,直接格式化阵列
只要灵活扩容,冗余靠底层存储/云盘 只用 LVM(云主机数据盘的典型场景)
既要冗余又要灵活扩容(最常见) RAID(硬或软)+ LVM 叠加:磁盘 → 阵列 → PV/VG/LV → 文件系统
只要性能不要冗余 RAID 0 或 LVM 条带 -i
单机想按卷差异化冗余级别 LVM 内建 RAID(--type raid1/5/6/10)

三者并不互斥:LVM 不提供磁盘冗余(除内建 RAID 类型外),RAID 不提供灵活的空间再分配,通常叠加使用。

8.3 安装 mdadm

yum install mdadm -y
mdadm --version

8.4 创建 RAID 阵列

创建前先用 fdisk 在各磁盘上建立分区(参考第二章),并把分区类型 ID 改为 fd(Linux raid autodetect)。也可以直接使用整块裸盘(如 /dev/sdb)。

# 准备 4 块盘的分区,类型设为 fd
for d in b c d e; do
  echo -e "n\np\n1\n\n\nt\nfd\nw\n" | fdisk /dev/sd$d
done
partprobe
lsblk

# 用 4 个分区创建 RAID 5:3 块数据盘 + 1 块热备盘
mdadm -C /dev/md0 -l 5 -n 3 -x 1 /dev/sd{b,c,d,e}1

# 长选项等效写法(可读性更好,推荐写在文档/脚本里)
mdadm --create /dev/md0 --level=5 --raid-devices=3 --spare-devices=1 \
      /dev/sdb1 /dev/sdc1 /dev/sdd1 /dev/sde1
选项 全称 作用
-C --create 创建新阵列,后接阵列设备名 /dev/md0
-l --level 指定 RAID 级别,如 0、1、5、6、10、linear
-n --raid-devices 组成阵列的活动磁盘数量(此处为 3)
-x --spare-devices 热备盘数量(此处为 1),成员盘故障时自动顶替并重建
-c --chunk 条带大小(KB),如 -c 64,影响性能
-a yes --auto=yes 自动创建设备文件
-e --metadata 元数据版本(1.2 默认、0.90 老格式)
-N --name 阵列名称
  • /dev/sd{b,c,d,e}1 是 Shell 的花括号展开,等价于 /dev/sdb1 /dev/sdc1 /dev/sdd1 /dev/sde1。
  • 创建后阵列会立即开始初始同步,此时已可格式化使用,但性能较低,建议等同步完成。

其它级别的创建示例:

mdadm -C /dev/md0 -l 0 -n 2 /dev/sdb1 /dev/sdc1                      # RAID 0
mdadm -C /dev/md1 -l 1 -n 2 /dev/sdb1 /dev/sdc1                      # RAID 1
mdadm -C /dev/md1 -l 1 -n 2 -x 1 /dev/sd{b,c,d}1                     # RAID 1 + 热备
mdadm -C /dev/md5 -l 5 -n 4 -c 128 /dev/sd{b,c,d,e}1                 # RAID 5,128K 条带
mdadm -C /dev/md6 -l 6 -n 4 /dev/sd{b,c,d,e}1                        # RAID 6
mdadm -C /dev/md10 -l 10 -n 4 /dev/sd{b,c,d,e}1                      # RAID 10
mdadm -C /dev/md0 -l 1 -n 2 /dev/sdb missing                         # 先用一块盘建降级阵列,稍后补盘

8.5 查看阵列状态

# 查看阵列详细信息(级别、容量、成员盘状态、同步进度)
mdadm -D /dev/md0
mdadm --detail /dev/md0

# 查看内核当前所有阵列及同步进度
cat /proc/mdstat
watch -n 1 cat /proc/mdstat            # 持续观察重建/同步进度

# 查看单个成员盘上的 RAID 超级块信息(属于哪个阵列、角色是什么)
mdadm -E /dev/sdb1
mdadm --examine /dev/sdb1
mdadm --examine --scan                 # 扫描所有盘上的阵列信息

# 其它
mdadm -Q /dev/md0                      # 快速查询
lsblk                                  # 树形查看,TYPE 列显示 raid5
blkid /dev/md0
  • mdadm -D 即 --detail(看阵列),mdadm -E 即 --examine(看成员盘)。
  • /proc/mdstat 中 [UUU] 表示三块盘均正常,[UU_] 表示有一块盘缺失或损坏。

/proc/mdstat 输出解读:

Personalities : [raid6] [raid5] [raid4]
md0 : active raid5 sde1[4](S) sdd1[3] sdc1[1] sdb1[0]
      10475520 blocks super 1.2 level 5, 512k chunk, algorithm 2 [3/3] [UUU]
      [====>................]  recovery = 22.5% (1180160/5237760) finish=1.2min speed=52007K/sec

unused devices: <none>
  • (S) 表示热备盘(Spare),(F) 表示故障盘(Faulty);
  • [3/3] 表示 3 个活动盘中 3 个正常;
  • [UUU] 每个 U 是一个正常盘,_ 表示缺失;
  • recovery/resync/reshape 行显示重建、同步、重构的进度与预计剩余时间。

mdadm -D 关键字段:State(clean / clean, degraded / clean, degraded, recovering)、Active Devices、Working Devices、Failed Devices、Spare Devices、UUID、成员盘列表及其 State(active sync / spare / faulty)。

8.6 格式化、挂载与开机自动挂载

# 格式化阵列设备
mkfs.xfs /dev/md0
mkfs.ext4 /dev/md0                              # CentOS 6
mkfs.xfs -f -d su=512k,sw=2 /dev/md0            # 与条带对齐(su=chunk 大小,sw=数据盘数)

# 挂载使用
mkdir -p /data
mount /dev/md0 /data
df -hT /data

# 获取阵列的 UUID
blkid /dev/md0

在 /etc/fstab 中建议使用 UUID 而非 /dev/md0(阵列编号在某些情况下可能变为 /dev/md127):

UUID=8f5c1c2a-6b3e-4c7a-9f21-0d4b7c9e1a55 /data xfs defaults 0 0

修改后执行 mount -a 验证,参考第三章 3.7 节。

8.7 保存阵列配置

mdadm -Ds > /etc/mdadm.conf
mdadm --detail --scan >> /etc/mdadm.conf        # 追加写法
mdadm --detail --scan --verbose >> /etc/mdadm.conf   # 含成员盘列表
cat /etc/mdadm.conf

/etc/mdadm.conf 内容示例:

ARRAY /dev/md0 metadata=1.2 spares=1 name=localhost:0 UUID=3a4b5c6d:7e8f9012:3456789a:bcdef012
MAILADDR root@example.com
  • -Ds 即 --detail --scan,扫描当前所有阵列并输出可用于配置文件的行(含 UUID 与成员盘信息)。
  • 系统启动时 mdadm 依据 /etc/mdadm.conf 自动组装(assemble)阵列。若不保存,阵列可能无法自动激活,或被组装成 /dev/md127 这类随机编号,导致 /etc/fstab 按设备名挂载失败。
  • 每次增删成员盘或新建阵列后都应重新生成该文件;若根分区或 /boot 位于阵列上,还需执行 dracut -f 把配置写入 initramfs。

配置故障邮件告警与监控:

echo 'MAILADDR ops@example.com' >> /etc/mdadm.conf
systemctl enable --now mdmonitor              # CentOS 7 的监控服务
service mdmonitor start                       # CentOS 6
mdadm --monitor --scan --daemonise --mail=ops@example.com   # 手工启动监控守护
mdadm --monitor --scan --oneshot              # 单次检查(可放 crontab)

8.8 故障模拟与恢复(完整演练)

# 1. 将 /dev/sdb1 标记为故障(-f 即 --fail),模拟磁盘损坏
mdadm /dev/md0 -f /dev/sdb1
mdadm --manage /dev/md0 --fail /dev/sdb1        # 等效长写法

# 2. 观察重建过程:热备盘会自动接替并开始同步
cat /proc/mdstat
watch -n 1 cat /proc/mdstat
#   md0 : active raid5 sde1[4] sdd1[3] sdc1[1] sdb1[0](F)
#         [3/2] [U_U]  recovery = 35.8% ...
#   同时确认此期间 /data 仍然可读写(这就是冗余的价值)
ls /data; echo test > /data/t.txt

# 3. 从阵列中移除故障盘(-r 即 --remove)
mdadm /dev/md0 -r /dev/sdb1
mdadm --manage /dev/md0 --remove /dev/sdb1

# 4. 换上新盘后加入阵列(-a 即 --add),作为新的热备或直接参与重建
mdadm /dev/md0 -a /dev/sdb1
mdadm --manage /dev/md0 --add /dev/sdb1

# 5. 确认阵列恢复正常
mdadm -D /dev/md0
cat /proc/mdstat                                # 应回到 [3/3] [UUU] 且有 (S) 热备

重建(recovery/resync)期间阵列仍可读写,但性能下降且冗余度降低,应尽量避免此时再做高负载操作。

其它常用管理操作:

mdadm /dev/md0 --replace /dev/sdb1              # 在线替换(先同步到新盘再移除,不经历降级)
mdadm --grow /dev/md0 --raid-devices=4          # 扩展阵列成员数(RAID 5 加盘扩容)
mdadm --grow /dev/md0 --size=max                # 成员盘变大后扩展阵列可用容量
mdadm --grow /dev/md1 --level=5                 # 在线变更 RAID 级别(reshape,风险高,先备份)
mdadm --grow /dev/md0 --bitmap=internal         # 启用写意图位图,加速重建
echo 200000 > /proc/sys/dev/raid/speed_limit_min    # 提高重建速度下限(KB/s)
echo 50000  > /proc/sys/dev/raid/speed_limit_max    # 限制重建速度,减少对业务影响
echo check > /sys/block/md0/md/sync_action      # 主动一致性巡检(建议每月做)
cat /sys/block/md0/md/mismatch_cnt              # 查看不一致块数量

8.9 停止与彻底删除阵列

# 1. 先卸载文件系统
umount /data

# 2. 停止阵列(-S 即 --stop),释放 /dev/md0
mdadm -S /dev/md0
mdadm --stop /dev/md0
mdadm --stop --scan                             # 停止所有阵列

# 3. 清除成员盘上的 RAID 元数据(超级块)
mdadm --zero-superblock /dev/sdb1
mdadm --zero-superblock /dev/sdc1
mdadm --zero-superblock /dev/sd{b,c,d,e}1       # 批量
wipefs -a /dev/sdb1                             # 更彻底地清除所有文件系统/RAID 签名

# 4. 重新组装已停止的阵列(不是删除,而是恢复使用)
mdadm -A /dev/md0 /dev/sd{b,c,d}1               # 手工组装
mdadm --assemble --scan                         # 按 /etc/mdadm.conf 自动组装所有
mdadm -A --run /dev/md0 /dev/sdb1 /dev/sdc1     # 强制以降级状态启动
  • 不执行 --zero-superblock 时,残留的超级块会让系统在下次启动时又把这些盘自动组装成阵列,或干扰该盘作为普通分区使用。
  • 同时记得删除 /etc/fstab 与 /etc/mdadm.conf 中的相关条目。

8.10 在阵列上叠加 LVM

md 设备可以像普通分区一样作为 LVM 的物理卷,从而同时获得「冗余 + 灵活扩容」:

pvcreate /dev/md0
vgcreate vgdata /dev/md0
lvcreate --name lvapp --size 100G vgdata
mkfs.xfs /dev/vgdata/lvapp
mount /dev/vgdata/lvapp /app

LVM 的完整用法参考第五章。常见分层顺序为:物理磁盘 → md 阵列 → PV/VG/LV → 文件系统。

8.11 RAID 使用注意事项

  1. RAID 不是备份。它只能防止磁盘硬件故障,无法防止误删除、误格式化、勒索软件、文件系统损坏或整机灾难——这些错误会同步写入所有成员盘。重要数据仍必须有独立备份。
  2. RAID 5 存在重建二次故障风险。大容量磁盘(数 TB 以上)重建耗时可达数小时甚至数天,期间阵列没有冗余,若第二块盘也故障则数据全失;同批次磁盘往往寿命相近,风险更高。因此大盘场景更推荐 RAID 6(可坏 2 块)或 RAID 10(重建只需复制镜像盘,速度快、压力小)。
  3. 尽量使用容量、型号、转速一致的磁盘;阵列可用容量以最小的成员盘为准。
  4. 配置热备盘(-x)可以在故障发生时立即自动重建,缩短无冗余窗口。
  5. 定期检查 /proc/mdstat 或配置监控告警,避免一块盘坏掉数月都无人发现。
  6. 创建 RAID 后务必执行 mdadm -Ds > /etc/mdadm.conf,否则重启后阵列可能无法自动组装。
  7. 定期做一致性巡检(echo check > /sys/block/md0/md/sync_action),及早发现静默数据损坏。

九、磁盘性能与日常运维

9.1 IO 性能指标

指标 含义 关注场景
IOPS 每秒 I/O 操作次数 随机小 I/O 为主的业务(数据库、虚拟化、消息队列)
吞吐量 Throughput 每秒传输的字节数(MB/s) 大文件顺序读写(备份、视频、日志归档、大数据)
延迟 Latency 单次 I/O 从发起到完成的时间(ms) 对响应时间敏感的业务;await 是最重要的观察值
队列深度 Queue Depth 同时在处理/等待的 I/O 数量 判断设备是否已成瓶颈
读写比例 读与写的比例 决定选用哪种 RAID 级别与缓存策略

经验参考值:

设备 随机 IOPS 顺序吞吐 单次延迟
7200 转 SATA HDD 75~100 100~160 MB/s 8~15 ms
15000 转 SAS HDD 150~200 150~200 MB/s 3~6 ms
SATA SSD 5万~10万 400~550 MB/s 0.05~0.2 ms
NVMe SSD 20万~100万+ 2000~7000 MB/s 0.01~0.1 ms

三者关系:吞吐量 ≈ IOPS × 单次 I/O 大小。同一块盘做 4K 随机与 1M 顺序,IOPS 和吞吐会呈现完全不同的数值,因此测试和评估必须说明 I/O 模型。

9.2 dd 测试顺序读写

# 顺序【写】测试:写 1GB
dd if=/dev/zero of=/data/testfile bs=1M count=1024
#   注意:这个结果偏乐观,因为数据可能还在页缓存里没落盘

# 加 oflag=direct 绕过页缓存,测真实磁盘写性能(推荐)
dd if=/dev/zero of=/data/testfile bs=1M count=1024 oflag=direct

# 加 conv=fdatasync:写完后强制刷盘再计时结束(也较真实)
dd if=/dev/zero of=/data/testfile bs=1M count=1024 conv=fdatasync

# oflag=dsync:每次写都同步(测最坏情况的写延迟,数值会非常低)
dd if=/dev/zero of=/data/testfile bs=1M count=1024 oflag=dsync

# 顺序【读】测试:先清空缓存,否则读的是内存
sync; echo 3 > /proc/sys/vm/drop_caches
dd if=/data/testfile of=/dev/null bs=1M count=1024
dd if=/data/testfile of=/dev/null bs=1M count=1024 iflag=direct   # 绕过缓存

# 小块随机写(模拟数据库场景,观察 IOPS)
dd if=/dev/zero of=/data/testfile bs=4k count=100000 oflag=direct

# 清理
rm -f /data/testfile

dd 输出解读:1073741824 bytes (1.1 GB) copied, 8.2 s, 131 MB/s —— 最后的 MB/s 即吞吐量。

dd 只能测顺序 I/O,且是单线程,不能反映真实并发场景,仅适合快速验证"这块盘大致是什么水平"。

9.3 hdparm 测试

hdparm -tT /dev/sda
# /dev/sda:
#  Timing cached reads:   12000 MB in  2.00 seconds = 6000 MB/sec   <- 内存/缓存读,不代表磁盘
#  Timing buffered disk reads: 400 MB in  3.01 seconds = 132 MB/sec  <- 磁盘顺序读,这个才有意义
hdparm -t --direct /dev/sda      # 绕过缓存
hdparm -Tt /dev/sda; hdparm -Tt /dev/sda   # 多测几次取平均

9.4 fio 简述(专业 IO 压测工具)

yum install fio -y

# 4K 随机读,测 IOPS(数据库场景)
fio -name=randread -filename=/data/fiotest -rw=randread -bs=4k -size=1G \
    -numjobs=4 -iodepth=32 -direct=1 -runtime=60 -time_based -group_reporting

# 4K 随机写
fio -name=randwrite -filename=/data/fiotest -rw=randwrite -bs=4k -size=1G \
    -numjobs=4 -iodepth=32 -direct=1 -runtime=60 -time_based -group_reporting

# 1M 顺序读,测吞吐
fio -name=seqread -filename=/data/fiotest -rw=read -bs=1M -size=2G \
    -numjobs=1 -iodepth=16 -direct=1 -group_reporting

# 70% 读 30% 写的混合模型(更贴近真实业务)
fio -name=mix -filename=/data/fiotest -rw=randrw -rwmixread=70 -bs=4k -size=2G \
    -numjobs=8 -iodepth=32 -direct=1 -runtime=120 -time_based -group_reporting

关键参数:-rw(read/write/randread/randwrite/randrw)、-bs(块大小)、-iodepth(队列深度)、-numjobs(并发线程)、-direct=1(绕过缓存,必加)、-runtime + -time_based(按时间跑)、-group_reporting(汇总输出)。

结果关注:IOPS、BW(带宽)、lat(延迟的 avg / 99th percentile)。测试务必在非生产磁盘上进行,fio 写测试会破坏指定文件。

9.5 iostat 各列含义与判读

yum install sysstat -y

iostat                          # 开机以来的平均值(第一次输出,参考意义有限)
iostat 2                        # 每 2 秒刷新一次
iostat -x 2 10                  # -x 扩展统计,每 2 秒一次共 10 次(最常用)
iostat -xd 2                    # 只看磁盘(-d),不看 CPU
iostat -xdm 2                   # -m 以 MB 为单位
iostat -xdk 2                   # -k 以 KB 为单位
iostat -x -p sda 2              # -p 显示指定设备及其各个分区
iostat -xN 2                    # -N 显示 LVM 设备名而非 dm-0

典型输出:

Device: rrqm/s wrqm/s   r/s   w/s  rkB/s  wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sda       0.00  12.50 25.00 75.00 1200.0 6000.0    72.00     8.50  85.00   10.00  110.00  2.00  98.50

各列含义:

列 含义与判读
rrqm/s / wrqm/s 每秒被合并的读/写请求数。数值高说明内核成功合并了相邻 I/O,是好事
r/s / w/s 每秒完成的读/写请求数,两者之和就是 IOPS
rkB/s / wkB/s 每秒读/写的数据量(吞吐量)
avgrq-sz 平均每个请求的扇区数(× 512 得字节)。值小说明是随机小 I/O,值大说明是顺序大 I/O
avgqu-sz 平均 I/O 队列长度。持续 > 1 说明有排队,> 10 说明严重拥塞
await 平均每次 I/O 的等待 + 服务总时间(ms),最重要的指标。SSD 应 < 1ms,HDD 正常 < 20ms;远大于 svctm 说明队列在排队
r_await / w_await 分别的读/写等待时间,可判断是读慢还是写慢
svctm 平均服务时间(新版已废弃、不可信,不要依赖它)
%util 设备繁忙百分比(有 I/O 在处理的时间占比)

判读要点:

  • %util 接近 100% 说明设备"一直在忙"。但对 SSD/RAID/多队列设备而言,%util 100% 不一定意味着饱和(它们能并行处理多个 I/O),此时应看 await 和 avgqu-sz;
  • await 明显升高(HDD > 50ms、SSD > 5ms)且 avgqu-sz > 几,说明磁盘已成瓶颈;
  • %util 高但 r/s + w/s 很低、await 很高 → 典型的随机小 I/O 打满机械盘,考虑换 SSD 或加缓存;
  • %util 低但业务慢 → 瓶颈不在磁盘,往 CPU/内存/网络/锁 上找;
  • 结合 iostat -c 看 CPU 的 %iowait:%iowait 高说明 CPU 在等 I/O。

9.6 iotop 找出高 IO 进程

yum install iotop -y

iotop                           # 交互式实时显示各进程的 I/O(需 root)
iotop -o                        # 只显示真正在产生 I/O 的进程(最常用)
iotop -oPa                      # -P 只看进程不看线程,-a 显示累计值
iotop -b -n 3 -o                # 批处理模式输出 3 次(便于脚本/日志记录)
iotop -b -o -n 1 >> /var/log/iotop.log     # 定时采样记录
iotop -u mysql                  # 只看指定用户

交互按键:o 切换只看活动进程,p 切换进程/线程,a 切换累计模式,←/→ 切换排序列,r 反转排序。

其它相关工具:

pidstat -d 2                    # 按进程统计 I/O(sysstat 包)
vmstat 2                        # bi/bo 列看块设备读写,wa 列看 I/O 等待
sar -d 2 5                      # 历史 I/O 数据(需开启 sysstat 采集)
sar -d -f /var/log/sa/sa22      # 查看某天的历史数据(事后排查必备)
dstat -d --disk-util --top-io   # 综合视图(需装 dstat)
cat /proc/<pid>/io              # 查看单个进程的累计读写字节数
blktrace -d /dev/sda -o - | blkparse -i -   # 内核级 I/O 跟踪(深度分析)
biolatency                      # bcc/eBPF 工具,I/O 延迟直方图

磁盘 I/O 调度器(cfq/deadline/noop/mq-deadline)、脏页回写参数(vm.dirty_ratio)、预读(blockdev --setra)等系统级调优 > 详见 04-03-Linux系统优化.md

9.7 磁盘空间日常巡检与告警

找出大文件与大目录

# 逐层缩小范围(最实用的手法)
du -h --max-depth=1 / | sort -rh | head -20
du -h --max-depth=1 /var | sort -rh | head -20
du -h --max-depth=1 /var/log | sort -rh | head -20
du -sh /* 2>/dev/null | sort -rh | head          # 一层写法
du -x -h --max-depth=1 /                         # -x 不跨文件系统(避免统计到别的挂载点)

# 用 find 直接找大文件
find / -xdev -type f -size +500M -exec ls -lh {} \; 2>/dev/null
find / -xdev -type f -size +1G -printf '%s %p\n' 2>/dev/null | sort -rn | head -20
find /var -type f -size +100M | xargs ls -lhS
find / -xdev -type f -size +100M -mtime +90       # 又大又久没动的文件(清理候选)
find /data -type f -name '*.log' -size +1G

# 按大小排序显示某目录下的文件
ls -lhS /var/log | head -20
du -a /var | sort -rn | head -20                  # 注意:不带 -h 才能正确排序
ncdu /                                            # 交互式磁盘用量浏览器(yum install ncdu)

磁盘空间告警脚本思路

思路要点:

  1. 用 df -hP(-P 保证一行一条,避免长设备名换行)取使用率;
  2. awk 去掉 % 转成数字与阈值比较;
  3. 排除 tmpfs、devtmpfs、iso9660 等无意义条目;
  4. 同时检查 inode 使用率(df -iP),这是最容易漏掉的一项;
  5. 触发时输出/发邮件/推送到监控系统,并附上 Top 大目录,便于直接定位;
  6. 加入 crontab 定时执行,并做告警抑制(同一分区短时间内不重复告警)。
#!/bin/bash
# /usr/local/bin/disk_alert.sh —— 磁盘与 inode 使用率告警
THRESHOLD=85
MAILTO="ops@example.com"

# 1) 检查空间使用率
df -hP -x tmpfs -x devtmpfs -x iso9660 | awk 'NR>1 {gsub("%","",$5); print $5, $6}' |
while read use mount; do
    if [ "$use" -ge "$THRESHOLD" ]; then
        echo "[WARN] 磁盘空间告警:$mount 已使用 ${use}%"
        echo "--- Top 5 大目录 ---"
        du -x -h --max-depth=1 "$mount" 2>/dev/null | sort -rh | head -6
    fi
done

# 2) 检查 inode 使用率(同样重要)
df -iP -x tmpfs -x devtmpfs | awk 'NR>1 {gsub("%","",$5); print $5, $6}' |
while read iuse mount; do
    if [ "$iuse" -ge "$THRESHOLD" ]; then
        echo "[WARN] inode 告警:$mount inode 已使用 ${iuse}%"
    fi
done
chmod +x /usr/local/bin/disk_alert.sh
crontab -e
#   */10 * * * * /usr/local/bin/disk_alert.sh | mail -s "磁盘巡检 $(hostname)" ops@example.com
#   仅在有输出时才发邮件:用 ifne(moreutils)或在脚本内判断后自行发送

生产环境更推荐用 Zabbix / Prometheus node_exporter 的 node_filesystem_avail_bytes、node_filesystem_files_free 做监控告警,脚本仅作应急与补充。

删除文件但空间未释放

现象:rm 掉了几十 GB 的日志,但 df -h 显示可用空间没有增加,而 du -sh 统计的用量已经变小(df 与 du 结果不一致)。

原因:文件的 inode 链接数已归零,但仍有进程持有该文件的文件描述符(fd),内核不会释放数据块,直到最后一个 fd 关闭。这是"正在被写入的日志文件被直接 rm"最典型的后果。

# 1. 确认症状:df 和 du 差距很大
df -h /var
du -sh /var

# 2. 找出已删除但仍被占用的文件(关键命令)
lsof +L1                          # 列出所有 link count < 1 的打开文件(即已删除仍占用的)
lsof | grep deleted
lsof -n | grep -i deleted | sort -k7 -rn | head    # 按大小排序
lsof +L1 /var                     # 只看某个文件系统

# 输出示例(NLINK 列为 0,SIZE 列显示占用的字节数)
# COMMAND  PID USER  FD  TYPE DEVICE   SIZE/OFF NLINK  NODE NAME
# nginx   2345 root  5w  REG    8,3 21474836480     0 13579 /var/log/nginx/access.log (deleted)

# 3. 处理方式(按优先级)
# 3a) 最优雅:让进程重新打开日志文件(不影响业务)
kill -USR1 <nginx_pid>            # nginx 重新打开日志
systemctl reload nginx
kill -HUP <pid>                   # 多数守护进程用 HUP 重开日志

# 3b) 不重启进程也能立即释放空间:通过 /proc 清空该 fd
> /proc/2345/fd/5                 # 把已删除文件的 fd 内容截断为 0,空间立即释放
truncate -s 0 /proc/2345/fd/5

# 3c) 兜底:重启持有该文件的服务(会短暂中断)
systemctl restart nginx

# 4. 验证空间已释放
df -h /var
lsof +L1

清理日志的正确做法

错误做法(会导致上面的"空间不释放"问题,或让服务丢失日志句柄):

rm -f /var/log/nginx/access.log      # ❌ 不要直接删除正在被写入的日志

正确做法:

# 方式一:清空文件内容而保留 inode(进程的 fd 依然有效,立即释放空间)
> /var/log/nginx/access.log
: > /var/log/nginx/access.log
truncate -s 0 /var/log/nginx/access.log
cat /dev/null > /var/log/nginx/access.log
echo -n "" > /var/log/nginx/access.log

# 方式二:先归档保留再清空(推荐,保留了历史日志)
cp /var/log/nginx/access.log /backup/access-$(date +%F).log
> /var/log/nginx/access.log
gzip /backup/access-$(date +%F).log

# 方式三:正规做法——交给 logrotate 管理(生产标准)
cat > /etc/logrotate.d/nginx <<'EOF'
/var/log/nginx/*.log {
    daily
    rotate 14
    compress
    delaycompress
    missingok
    notifempty
    create 0640 nginx adm
    sharedscripts
    postrotate
        [ -f /var/run/nginx.pid ] && kill -USR1 $(cat /var/run/nginx.pid)
    endscript
}
EOF
logrotate -d /etc/logrotate.d/nginx     # -d 只演练不执行,验证配置
logrotate -f /etc/logrotate.d/nginx     # -f 强制立即轮转一次

# 清理 systemd journal(CentOS 7)
journalctl --disk-usage
journalctl --vacuum-size=200M
journalctl --vacuum-time=7d
# 永久限制:/etc/systemd/journald.conf 中设置 SystemMaxUse=500M

日志轮转策略、集中式日志收集、journald 配置 > 详见 08-02-日志管理.md

稀疏文件(sparse file)

稀疏文件是"逻辑大小远大于实际占用空间"的文件,中间未写入的部分(空洞)不占磁盘块。虚拟机磁盘镜像、数据库预分配文件、truncate 创建的文件常是稀疏文件。

# 创建一个稀疏文件:逻辑 10G,实际占用 0
truncate -s 10G /data/sparse.img
dd if=/dev/zero of=/data/sparse.img bs=1M count=0 seek=10240

# 观察差异
ls -lh /data/sparse.img               # 显示 10G(表观大小 apparent size)
du -h /data/sparse.img                # 显示 0 或很小(实际占用)
du -h --apparent-size /data/sparse.img  # 显示 10G
stat /data/sparse.img                 # Size 与 Blocks 差距巨大即为稀疏文件
filefrag -v /data/sparse.img          # 查看 extent 分布与空洞

运维注意事项:

  • 拷贝稀疏文件会"膨胀":普通 cp 会把空洞填成真实的零,10G 稀疏文件复制后真占 10G。必须加参数:

    cp --sparse=always src dst
    rsync -S src dst                    # --sparse
    tar -Szcf backup.tar.gz /data       # -S 处理稀疏文件
    dd if=src of=dst conv=sparse
  • df 显示的用量比 ls -l 的总和小很多,可能就是稀疏文件造成的;

  • 稀疏文件被逐步写满会导致磁盘空间"意外"耗尽(如虚拟机磁盘镜像逐渐膨胀),需要预留余量或用 fallocate 预分配。

回收与整理空间的其它手段

fstrim -av                            # 对所有支持的挂载点执行 TRIM(SSD/精简置备存储)
fstrim -v /data
systemctl enable --now fstrim.timer   # CentOS 7 每周自动 TRIM

xfs_fsr /data                         # xfs 在线碎片整理
e4defrag /data                        # ext4 碎片整理
filefrag /data/bigfile                # 查看文件碎片数

yum clean all                         # 清理 yum 缓存
rm -rf /var/cache/yum/*
package-cleanup --oldkernels --count=2   # 清理旧内核(/boot 满的常见原因)
find /tmp -type f -atime +10 -delete  # 清理 /tmp 老文件
docker system prune -a                # 清理 docker 无用镜像与卷(容器主机)

十、排错清单

10.1 报 No space left on device,但 df -h 显示还有空间

原因一:inode 耗尽(最常见)。分区里有海量小文件,数据块没用完但 inode 用光了。

df -h /data                  # Use% 只有 40%
df -i /data                  # IUse% 却是 100%  ← 定位
# 找出小文件最多的目录
for d in /data/*; do echo "$(find $d -type f 2>/dev/null | wc -l) $d"; done | sort -rn | head
find /data -xdev -type f | awk -F/ '{print "/"$2"/"$3}' | sort | uniq -c | sort -rn | head
# 处理:删除无用小文件(如 session、缓存、邮件队列)
find /data/cache -type f -mtime +7 -delete
# 根治:备份数据后用更大的 inode 密度重新格式化(ext4 的 inode 数无法在线扩)
mkfs.ext4 -i 4096 /dev/sdb1        # 每 4KB 一个 inode
mkfs.ext4 -N 5000000 /dev/sdb1     # 直接指定 inode 总数
mkfs.ext4 -T small /dev/sdb1       # 使用 small 模板
#   或改用 xfs:inode 动态分配,不存在预先耗尽的问题(CentOS 7 默认)

原因二:已删除但被进程占用的文件(df 与 du 不一致)。

lsof +L1
> /proc/<pid>/fd/<n>

原因三:文件系统预留块被占满。ext 系列默认为 root 预留 5%,普通用户写入会提前报满。

tune2fs -l /dev/sdb1 | grep -i reserved
tune2fs -m 1 /dev/sdb1             # 把预留比例降到 1%,立即释放出可用空间

原因四:挂载点被覆盖。数据写到了挂载点下层的旧目录,实际占用的是根分区。

df -h /data                        # 看清 /data 到底落在哪个设备上
mount | grep data
mkdir /mnt/probe && mount --bind / /mnt/probe && du -sh /mnt/probe/data

原因五:磁盘配额限制(用户/组配额已达 hard limit)。

quota -u tom
repquota -a
xfs_quota -x -c 'report -h' /data

原因六:文件系统只读或损坏。

mount | grep ' / '                 # 是否出现 (ro,...)
dmesg | tail -30                   # 查找 I/O error、remounting read-only

10.2 磁盘写满导致服务无法启动

典型表现:数据库起不来、无法登录图形界面、sudo 报错、ssh 登录后立即断开、日志无法写入、yum 报错。

# 1. 应急腾空间(先让系统能动起来)
> /var/log/messages
journalctl --vacuum-size=100M
rm -f /var/log/*.gz /var/log/*.[0-9]
yum clean all; rm -rf /var/cache/yum/*
rm -rf /tmp/* /var/tmp/*

# 2. 若 / 已 100%,连 vim 都可能无法保存;用重定向或 sed -i 之外的手段
truncate -s 0 /var/log/httpd/access_log

# 3. 定位真凶
du -x -h --max-depth=1 / | sort -rh | head
lsof +L1

# 4. 检查 /boot 是否满(内核升级失败的常见原因)
df -h /boot
package-cleanup --oldkernels --count=2
rpm -qa kernel | sort -V

# 5. 长期措施:把 /var、/var/log、/home、/tmp 单独分区或做成 LV,
#    并配置磁盘告警,避免单个目录撑爆根分区

为什么根分区满了危害最大:/tmp、/var/run、/var/lib 都需要写;systemd、PAM、SELinux、数据库都会因无法写临时文件而失败。生产环境应当把易膨胀目录(/var/log、/home、/data)与根分区隔离。

10.3 /etc/fstab 配置错误导致系统无法启动

这是最经典也最容易致命的错误(在 fstab 里写错 UUID、设备名、文件系统类型,或写了不存在的设备)。

CentOS 7 修复流程:

# 1. 开机在 GRUB 菜单按 e 编辑启动项,在 linux16/linuxefi 行末追加:
#      rd.break            (进入 initramfs 阶段,根挂在 /sysroot 且为只读)
#    按 Ctrl+X 启动

# 2. 重新以读写方式挂载根
switch_root:/# mount -o remount,rw /sysroot
switch_root:/# chroot /sysroot

# 3. 修改 fstab(把错误行注释掉或改正)
sh-4.2# vi /etc/fstab

# 4. 校验语法与设备是否真实存在(关键一步,改完必须验证)
sh-4.2# findmnt --verify
sh-4.2# blkid
sh-4.2# mount -a          # 无报错才算安全

# 5. 若启用了 SELinux,需重新打标签
sh-4.2# touch /.autorelabel

# 6. 退出重启
sh-4.2# exit
switch_root:/# reboot -f

另一种更快的方式(跳过 systemd 直接进 shell):

# GRUB 编辑行末加:init=/bin/bash   (或 rw init=/sysroot/bin/sh)
mount -o remount,rw /
vi /etc/fstab
mount -a
exec /sbin/init          # 或 reboot -f

CentOS 6 修复流程:

# 方式一:单用户模式(GRUB 菜单按 a 或 e,在 kernel 行末加 single 或 1)
#   进入后:
mount -o remount,rw /
vi /etc/fstab
mount -a
reboot

# 方式二:救援模式(用安装光盘启动 → Rescue installed system)
chroot /mnt/sysimage
vi /etc/fstab
exit; reboot

# 方式三:emergency 模式(连 / 都挂不上时,在 kernel 行加 emergency)

预防措施(务必养成习惯):

mount -a                 # 每次改完 fstab 立即执行,无报错才能重启
findmnt --verify         # CentOS 7 语法与设备校验
cp /etc/fstab /etc/fstab.bak.$(date +%F)   # 改前先备份
# 对非关键的数据盘加 nofail,缺盘时不阻塞开机
UUID=xxxx  /data  xfs  defaults,nofail  0 0

rm -rf /etc/fstab 的后果与恢复方法见文末"练习"部分的思考题。

10.4 UUID 变化导致挂载失败

触发场景:重新格式化了分区、克隆虚拟机后重建了文件系统、更换了磁盘、恢复了镜像、LVM 快照回滚。

# 1. 现象:开机卡在 Dependency failed for /data 或直接进 emergency shell
systemctl --failed
journalctl -xb | grep -i mount
systemctl status data.mount

# 2. 取当前真实的 UUID
blkid
blkid /dev/sdb1
lsblk -f
ls -l /dev/disk/by-uuid/

# 3. 对照并修正 fstab
vi /etc/fstab
mount -a
systemctl daemon-reload        # CentOS 7 改完 fstab 必须重载,否则 .mount 单元仍用旧配置
systemctl restart local-fs.target

# 4. 另一条思路:把文件系统的 UUID 改回原值(保持 fstab 不变)
xfs_admin -U 12345678-1234-1234-1234-123456789abc /dev/sdb1    # xfs(需卸载)
xfs_admin -u /dev/sdb1                                          # 查看 xfs 的 UUID
tune2fs -U 12345678-1234-1234-1234-123456789abc /dev/sdb1      # ext2/3/4
tune2fs -U random /dev/sdb1                                     # 随机生成新 UUID
tune2fs -U $(uuidgen) /dev/sdb1

克隆虚拟机的特殊坑:整盘克隆会产生两块 UUID 完全相同的磁盘,挂载时随机命中其中一块,行为不可预测。必须为其中一块重新生成 UUID(xfs 用 xfs_admin -U generate,ext4 用 tune2fs -U random),LVM 则用 pvchange -u / vgchange -u 改 PV/VG 的 UUID。

根治建议:数据盘用 LABEL= 或 /dev/mapper/vg-lv(LVM 设备名稳定)挂载;不要用 /dev/sdb1 这类会随插槽顺序变化的名字。

e2label /dev/sdc1 /abc            # ext 设置卷标
xfs_admin -L data /dev/sdb1       # xfs 设置卷标(需卸载)
# fstab 中写:LABEL=data  /data  xfs  defaults  0 0

10.5 卸载时报 Device is busy

# 1. 查看是谁在占用
lsof /data                        # 打开该设备上文件的进程
lsof +D /data                     # 递归目录下所有打开的文件(较慢但更全)
fuser -mv /data                   # -m 挂载点,-v 详细;ACCESS 列 c=当前目录 f=打开文件 e=执行中 r=根目录
fuser -cu /data

# 2. 逐一处理(推荐,避免误杀)
cd /                              # 最常见原因:自己的 shell 还在 /data 里
kill <pid>
kill -9 <pid>

# 3. 批量杀(谨慎,会中断业务)
fuser -km /data                   # -k 杀掉所有占用进程,-m 按挂载点
fuser -k -TERM -m /data           # 先温和地发 TERM
lsof -t /data | xargs kill -9

# 4. 特殊情况
umount -l /data                   # lazy umount:先从命名空间摘除,占用释放后自动完成
umount -f /data                   # 强制卸载(主要用于 NFS 无响应)
umount -R /mnt                    # 递归卸载其下所有子挂载点
umount -f -l /data                # 网络文件系统失联时的组合拳

# 5. 容易忽略的占用来源
swapon -s                         # 该设备上有活动的 swap → swapoff /dev/sdb2
cat /proc/mounts | grep /data     # 是否有 bind 挂载指向它 → 先卸载 bind
findmnt -R /data                  # 是否有子挂载点
mount | grep /data
exportfs -v                       # 是否被 NFS 导出 → exportfs -ua
docker ps                         # 容器是否挂载了该目录
dmsetup ls --tree                 # 是否被 device-mapper / LVM 引用
lsof | grep deleted | grep /data  # 已删除但仍被占用的文件
losetup -a                        # 是否有 loop 设备关联该目录下的镜像文件

判断顺序建议:先 fuser -mv 看清全貌 → 若只是自己的 shell,cd / 即可 → 若是业务进程,走正常的 systemctl stop → 万不得已才 fuser -km 或 umount -l。

10.6 新建的分区在 lsblk 中看不到

# 1. 确认分区表里确实已经写入
fdisk -l /dev/sdb
parted /dev/sdb print
cat /proc/partitions              # 内核当前认知的分区(这才是关键)

# 2. 通知内核重读分区表(三种手段,依次尝试)
partprobe /dev/sdb
partprobe                         # 不带参数则处理所有磁盘
partx -a /dev/sdb                 # 添加该磁盘的所有分区
partx -a --nr 3 /dev/sdb          # 只添加第 3 个分区
partx -u /dev/sdb                 # 更新(分区大小改变后用)
partx -d /dev/sdb                 # 先全部删除再 -a 添加
kpartx -a /dev/sdb                # multipath-tools 提供
kpartx -av /dev/loop0             # loop 设备用
udevadm settle                    # 等待 udev 处理完设备事件
udevadm trigger --subsystem-match=block

# 3. 报 "WARNING: Re-reading the partition table failed with error 16:
#    Device or resource busy" 时
#    → 说明该磁盘上有分区正在被挂载/被 swap/被 LVM 使用,内核拒绝整体重读
mount | grep sdb                  # 卸载该盘上已挂载的分区
swapoff /dev/sdb2
partx -a --nr 3 /dev/sdb          # 仅让新分区单独生效,无需重读整张表
#    最终手段:reboot

# 4. 磁盘本身都看不到(新加的硬盘)
echo '- - -' > /sys/class/scsi_host/host0/scan
for h in /sys/class/scsi_host/host*/scan; do echo '- - -' > $h; done
rescan-scsi-bus.sh                # sg3_utils 包
echo 1 > /sys/block/sdb/device/rescan     # 扩容后刷新容量
lsblk; fdisk -l; dmesg | tail -20

扩容虚拟磁盘后容量没变:需要依次刷新三层——设备容量、分区大小、文件系统大小。

echo 1 > /sys/block/sdb/device/rescan     # 1) 让内核看到新容量
lsblk /dev/sdb                            # 确认 SIZE 已变大
growpart /dev/sdb 1                       # 2) 扩分区(cloud-utils-growpart)
#   或 parted /dev/sdb resizepart 1 100%
#   或 fdisk 删除再按原起始扇区重建(起始扇区必须一致!)
partprobe /dev/sdb
xfs_growfs /data                          # 3) 扩文件系统(xfs)
resize2fs /dev/sdb1                       #    ext4

10.7 resize2fs 报 "Nothing to do" 或 "文件系统已经是 xxx 个块"

原因:resize2fs 已经把文件系统扩到了它看到的设备边界,说明底层设备(分区 / LV)本身没有变大,扩容漏了前一步。

# 排查链条:设备容量 → 分区/LV 容量 → 文件系统容量,三者要逐层对齐
lsblk                                  # 磁盘和分区各多大
pvs; vgs; lvs                          # LVM 三层容量
vgdisplay vg_data | grep Free           # VG 里还有没有空闲 PE
df -h /data                             # 文件系统当前多大

# 常见漏掉的步骤:
# A) LVM 场景:忘了先 lvextend
vgextend vg_data /dev/sdc1              # VG 没空间时先加 PV
lvextend -L +10G /dev/vg_data/lv_data   # 先扩 LV
lvextend -l +100%FREE /dev/vg_data/lv_data
resize2fs /dev/vg_data/lv_data          # 再扩文件系统
#    一步完成(推荐):
lvextend -r -L +10G /dev/vg_data/lv_data
lvresize -r -L +10G /dev/vg_data/lv_data

# B) 普通分区场景:忘了先扩分区
parted /dev/sdb resizepart 1 100%
partprobe /dev/sdb
resize2fs /dev/sdb1

# C) 分区扩了但内核没重读 → 见 10.6

# D) 参数给错:resize2fs 的参数是【设备名】,xfs_growfs 的参数是【挂载点】
resize2fs /dev/vg_data/lv_data          # ✅
resize2fs /data                         # ❌
xfs_growfs /data                        # ✅
xfs_growfs /dev/vg_data/lv_data         # ❌(部分版本可接受,但不规范)

# E) 文件系统类型认错:对 xfs 用 resize2fs 会报 "Bad magic number in super-block"
df -T /data
blkid /dev/vg_data/lv_data

其它相关报错:

报错 原因与处理
Please run e2fsck -f first 缩容或某些操作前必须先强制检查:e2fsck -f /dev/xxx
On-line shrinking not supported ext4 不支持在线缩小,必须先 umount
New size smaller than minimum (xxx) 目标尺寸小于数据实际占用,用 resize2fs -P 查看最小可缩尺寸
Bad magic number in super-block 文件系统类型不匹配,或超级块损坏(见 10.9)

10.8 想缩小 xfs 文件系统

结论:xfs 不支持缩小,任何版本、任何工具都不行(xfs_growfs 只能增大,没有 shrink 功能)。这是 xfs 的设计取舍,也是 CentOS 7 默认用 xfs 后最常踩的坑。

唯一可行的路径是"备份 → 重建 → 恢复":

# 1. 完整备份数据(两种方式任选,建议异机保存)
xfsdump -l 0 -L full -M media0 -f /backup/data.dump /data
tar -czvf /backup/data.tar.gz -C /data .
rsync -avHAX /data/ /backup/data/          # 保留硬链接、ACL、扩展属性

# 2. 卸载并缩小底层 LV
umount /data
lvreduce -L 20G /dev/vg_data/lv_data       # 会警告可能丢数据,确认 y
#    注意:这一步会直接截断 xfs,文件系统随即损坏 —— 所以必须先备份

# 3. 重新格式化
mkfs.xfs -f /dev/vg_data/lv_data

# 4. 挂载并恢复数据
mount /dev/vg_data/lv_data /data
xfsrestore -f /backup/data.dump /data
#    或 tar -xzvf /backup/data.tar.gz -C /data
#    或 rsync -avHAX /backup/data/ /data/

# 5. 校验数据完整性后再删除备份
diff -r /backup/data /data

替代思路:

  • 如果目的是"把空间腾给别的 LV",可以考虑新建一个小 LV → 迁移数据 → 删除大 LV,效果相同且过程更可控;
  • 需要频繁伸缩的场景,应当在规划阶段就选 ext4(支持离线缩小),或使用 LVM thin provisioning 按需分配;
  • 生产环境的通用原则:LV 初始只分配所需容量,后续按需在线扩容,从根本上避免缩容需求。
文件系统 在线扩大 离线扩大 缩小
ext2/3/4 ✅ resize2fs ✅ ✅(必须卸载后操作)
xfs ✅ xfs_growfs ✅ ❌ 完全不支持
btrfs ✅ btrfs filesystem resize ✅ ✅(支持在线缩小)

10.9 LVM 缩容后文件系统损坏

根因:缩容的顺序颠倒了。lvreduce 直接把 LV 尾部的 PE 拿走,如果文件系统还认为自己是原来的大小,尾部的元数据和数据就被截断了。

正确顺序(必须严格遵守):先缩文件系统,再缩 LV。

umount /data
e2fsck -f /dev/vg_data/lv_data          # 1. 强制检查(必做,否则 resize2fs 拒绝执行)
resize2fs /dev/vg_data/lv_data 10G      # 2. 先把文件系统缩到 10G
lvreduce -L 10G /dev/vg_data/lv_data    # 3. 再把 LV 缩到 10G(数值要一致或 LV 略大)
mount /dev/vg_data/lv_data /data        # 4. 挂载验证
df -h /data
# 一步完成(更安全,工具自动按正确顺序处理):
lvreduce -r -L 10G /dev/vg_data/lv_data
lvresize -r -L 10G /dev/vg_data/lv_data

已经出事后的抢救步骤:

# 1. 立即停止一切写入,不要再挂载读写
# 2. 尝试把 LV 恢复到原来的大小(关键:只要 PE 还没被别的 LV 占用,就有救)
lvs -o +seg_pe_ranges
lvextend -L 30G /dev/vg_data/lv_data    # 恢复到缩容前的尺寸
#    或用 LVM 元数据归档回滚(推荐)
ls -lt /etc/lvm/archive/                # 找到缩容前的那份归档
vgcfgrestore -l vg_data                 # 列出可用的备份版本
vgcfgrestore -f /etc/lvm/archive/vg_data_00012-xxxx.vg vg_data
vgchange -ay vg_data

# 3. 修复文件系统
e2fsck -f -y /dev/vg_data/lv_data
#    超级块损坏时用备份超级块
dumpe2fs /dev/vg_data/lv_data | grep -i superblock
e2fsck -b 32768 /dev/vg_data/lv_data
e2fsck -b 98304 -B 4096 /dev/vg_data/lv_data
#    xfs
xfs_repair -n /dev/vg_data/lv_data      # 先只检查不修改
xfs_repair /dev/vg_data/lv_data
xfs_repair -L /dev/vg_data/lv_data      # 清空日志(最后手段,会丢数据)

# 4. 挂载后检查 lost+found,找回被修复程序摘出来的孤立文件
mount /dev/vg_data/lv_data /data
ls -l /data/lost+found

# 5. 若数据仍无法读取,使用底层恢复工具(先对整个 LV 做 dd 镜像再操作)
dd if=/dev/vg_data/lv_data of=/backup/lv.img bs=4M status=progress
testdisk /backup/lv.img
extundelete /backup/lv.img --restore-all
photorec

铁律:任何缩容操作前必须有完整备份;缩容不是常规运维动作,能扩容解决就不要缩容。

10.10 坏道检测(badblocks)

征兆:dmesg 出现 I/O error、Buffer I/O error on device、unrecoverable read error;读取某些文件时卡住或报输入/输出错误;文件系统频繁被自动 remount 成只读。

# 1. 先看内核日志确认是硬件层错误
dmesg | grep -iE 'error|fail|sense|medium'
dmesg -T | grep -i 'I/O error'
grep -iE 'error|fail' /var/log/messages | tail -30

# 2. badblocks 检测(务必对【未挂载】的设备操作)
badblocks -sv /dev/sdb                        # -s 显示进度,-v 详细;默认只读检测
badblocks -sv /dev/sdb1 > /root/badblocks.txt # 把坏块号输出到文件
badblocks -svn /dev/sdb1                      # -n 非破坏性读写检测(慢,但更彻底,数据保留)
badblocks -svw /dev/sdb1                      # -w 破坏性写测试(会清空数据!仅用于新盘/废盘)
badblocks -svw -t 0xff -t 0x00 -t 0xaa /dev/sdb1   # 指定多轮测试模式
badblocks -b 4096 -c 65536 -sv /dev/sdb1      # -b 块大小,-c 每次检测的块数(调大提速)
badblocks -sv -o /root/bb.list /dev/sdb1      # -o 输出坏块列表文件

# 3. 让文件系统避开这些坏块
#    ext 系列:把坏块列表登记进文件系统,之后不再分配这些块
e2fsck -l /root/bb.list /dev/sdb1
e2fsck -cc /dev/sdb1                          # 先做非破坏性读写检测再登记(-c 只读检测)
#    格式化时顺带检测(新盘上线前建议做一次)
mkfs.ext4 -c /dev/sdb1                        # 只读检测
mkfs.ext4 -cc /dev/sdb1                       # 读写检测(更慢更彻底)
mkfs.ext4 -l /root/bb.list /dev/sdb1          # 直接读入已有的坏块列表
#    xfs 没有坏块登记机制,出现坏道应直接更换磁盘

# 4. 定位坏块影响了哪个文件(ext4)
debugfs -R "icheck 12345" /dev/sdb1           # 块号 → inode 号
debugfs -R "ncheck 6789" /dev/sdb1            # inode 号 → 文件名
dd if=/dev/sdb1 of=/dev/null bs=4096 skip=12345 count=1   # 验证该块是否真的读不了

重要判断:现代硬盘内部有备用扇区,会自动重映射坏道。一旦操作系统层面能看到坏块,说明备用扇区已经用尽或错误在快速增长,这块盘应当立即更换,不要试图靠 badblocks 屏蔽后继续使用生产数据。

10.11 smartctl 预警磁盘即将损坏

yum install smartmontools -y

# 1. 检查是否支持并已启用 SMART
smartctl -i /dev/sda                     # 基本信息,看 SMART support is: Enabled
smartctl -s on /dev/sda                  # 启用 SMART

# 2. 整体健康判定(最快的一眼判断)
smartctl -H /dev/sda
#   SMART overall-health self-assessment test result: PASSED / FAILED!
#   出现 FAILED 表示磁盘已自评为即将失效 —— 立即更换

# 3. 查看全部属性(重点关注下表几项)
smartctl -A /dev/sda
smartctl -a /dev/sda                     # 全部信息(属性 + 日志 + 自检历史)
smartctl -x /dev/sda                     # 更详细
smartctl -l error /dev/sda               # 错误日志
smartctl -l selftest /dev/sda            # 自检历史记录

# 4. 特殊设备类型需指定 -d
smartctl -a -d sat /dev/sda              # USB/SATA 桥接
smartctl -a -d nvme /dev/nvme0
smartctl -a -d megaraid,0 /dev/sda       # LSI RAID 卡后面的物理盘
smartctl -a -d cciss,0 /dev/cciss/c0d0   # HP 阵列卡
smartctl --scan                          # 扫描所有可检测设备

# 5. 主动发起自检
smartctl -t short /dev/sda               # 短测试(约 2 分钟)
smartctl -t long /dev/sda                # 长测试(数小时,全盘扫描)
smartctl -t conveyance /dev/sda          # 运输损伤检测
smartctl -X /dev/sda                     # 中止正在进行的自检
smartctl -l selftest /dev/sda            # 查看自检结果

# 6. 开启守护进程做持续监控与邮件告警
systemctl enable --now smartd            # CentOS 7
service smartd start; chkconfig smartd on  # CentOS 6
# /etc/smartd.conf 示例:
#   /dev/sda -a -o on -S on -s (S/../.././02|L/../../6/03) -m ops@example.com

关键 SMART 属性判读:

ID 属性名 含义与告警阈值
5 Reallocated_Sector_Ct 已重映射扇区数。> 0 就要警惕,持续增长必须换盘
197 Current_Pending_Sector 等待重映射的扇区数。> 0 说明有读不出的扇区,最危险的信号之一
198 Offline_Uncorrectable 无法校正的扇区数。> 0 表示已发生不可恢复的数据错误
187 Reported_Uncorrect 报告给主机的不可纠正错误数。> 0 是 Google/Backblaze 统计中最强的故障预测指标
188 Command_Timeout 命令超时次数,数值大说明链路或盘体有问题
1 Raw_Read_Error_Rate 读错误率(不同厂商含义不同,看趋势而非绝对值)
7 Seek_Error_Rate 寻道错误率
194 Temperature_Celsius 温度。长期 > 50℃ 会显著缩短寿命,应改善散热
9 Power_On_Hours 通电小时数,判断磁盘服役年龄(> 3~5 年考虑轮换)
12 Power_Cycle_Count 通电次数
241 Total_LBAs_Written 累计写入量。SSD 用它评估寿命消耗(对照厂商 TBW 标称值)
177/231/233 Wear_Leveling_Count / SSD_Life_Left SSD 磨损均衡计数与剩余寿命百分比

VALUE 与 THRESH 的关系:VALUE 是归一化后的当前值,降到 THRESH 以下(WHEN_FAILED 列出现 FAILING_NOW)即为故障。RAW_VALUE 是原始计数,属性 5/197/198 要直接看 RAW_VALUE。

发现预警后的处置流程:

# 1. 立即做完整备份(最高优先级,不要先折腾诊断)
dd if=/dev/sda of=/backup/sda.img bs=4M conv=noerror,sync status=progress
ddrescue /dev/sda /backup/sda.img /backup/sda.log     # 坏道多时用 ddrescue 更可靠

# 2. 若是 RAID 成员盘:走标准换盘流程
mdadm --detail /dev/md0
mdadm /dev/md0 --replace /dev/sdb1                   # 有热备时先复制再剔除(更安全)
mdadm /dev/md0 -f /dev/sdb1 -r /dev/sdb1             # 标记故障并移除
#   换上新盘后
mdadm /dev/md0 -a /dev/sdf1
watch cat /proc/mdstat                               # 观察重建进度

# 3. 若是 LVM 的 PV:在线迁移数据到新盘,不中断业务
pvcreate /dev/sdf1
vgextend vg_data /dev/sdf1
pvmove /dev/sdb1 /dev/sdf1        # 在线搬迁(可用 pvmove 无参数继续被中断的迁移)
vgreduce vg_data /dev/sdb1
pvremove /dev/sdb1

# 4. 若是单盘系统盘:整盘克隆到新盘后替换
dd if=/dev/sda of=/dev/sdf bs=4M conv=noerror,sync status=progress
#   之后修复引导:grub2-install /dev/sdf

10.12 综合排错流程与常见错误对照表

通用排查顺序(从上层往下层,逐层排除):

业务报错
  ↓ df -h / df -i          空间与 inode 是否满
  ↓ mount | findmnt        是否挂载、是否被 remount 成 ro
  ↓ dmesg -T | tail        内核层是否有 I/O error(硬件问题的第一手证据)
  ↓ lsblk / blkid          设备与文件系统是否还在、UUID 是否变化
  ↓ pvs vgs lvs            LVM 各层是否正常、VG 是否有缺失的 PV
  ↓ cat /proc/mdstat       RAID 是否降级
  ↓ iostat -x 2            是否是性能瓶颈而非故障
  ↓ smartctl -H -A         磁盘硬件健康度
报错信息 常见原因 处理入口
No space left on device 空间满 / inode 满 / 预留块 / 配额 10.1
Read-only file system 文件系统检测到错误后自我保护 remount ro dmesg、fsck、10.9
Device or resource busy 有进程/swap/bind/LVM 占用 10.5、10.6
mount: unknown filesystem type 类型写错、缺内核模块(ntfs、exfat) blkid、装 ntfs-3g
mount: special device ... does not exist 设备名/UUID 错误或磁盘未识别 10.4、10.6
wrong fs type, bad option, bad superblock 类型不符或超级块损坏 blkid、e2fsck -b
Bad magic number in super-block 用错工具(对 xfs 用 e2fsck)或超级块损坏 10.7、10.9
Structure needs cleaning xfs 元数据损坏 xfs_repair
Input/output error 硬件坏道或链路故障 10.10、10.11
Nothing to do!(resize2fs) 底层设备未先扩容 10.7
Volume group "xxx" not found PV 丢失 / VG 未激活 vgscan、vgchange -ay、vgcfgrestore
Couldn't find device with uuid xxx(LVM) PV 磁盘掉线 vgreduce --removemissing --force
Insufficient free extents VG 空间不足 vgextend 加 PV
error 16: Device or resource busy(分区表) 该盘有分区在使用中 10.6 用 partx --nr
Dependency failed for /data(开机) fstab 错误 10.3、10.4
Timed out waiting for device fstab 里的设备不存在 加 nofail,10.3

练习

练习一:思考题(源自 01 文档)

1. 如果执行了 rm -rf /etc/fstab,会有什么后果?如何恢复?

后果分析:

  • 当前运行的系统不会立刻崩溃。因为所有文件系统在开机时已经挂载完成,挂载信息记录在内核中(/proc/mounts、/etc/mtab),运行中的业务不受影响。
  • 但会立即出现以下问题:
    • mount -a、mount /data(省略设备名的简写形式)失效;
    • df、mount 命令仍可用,但 swapon -a 无法工作;
    • CentOS 7 上 systemd 无法生成 *.mount 单元,systemctl daemon-reload 后相关单元消失。
  • 一旦重启,后果严重:
    • 除了内核通过 root= 参数挂载的根文件系统外,所有其它分区都不会被挂载(/boot、/home、/var、/data 等);
    • swap 不会启用,内存吃紧;
    • 根文件系统可能以只读方式留存(因为 fstab 中缺少 remount rw 的记录,视发行版而定);
    • 依赖这些挂载点的服务(数据库、Web、NFS 导出)全部启动失败;
    • 严重情况下系统会直接进入 emergency mode 要求输入 root 密码。

恢复方法(按优先级):

# 方法一(最佳):从备份恢复 —— 这就是为什么改 fstab 前必须先备份
cp /etc/fstab.bak /etc/fstab

# 方法二:趁系统还没重启,用当前的挂载信息重建(强烈推荐立刻做)
cat /proc/mounts                  # 当前所有挂载关系都在这里
cat /etc/mtab
mount                             # 人眼可读的形式
lsblk -f                          # 设备、类型、UUID、挂载点一览
blkid                             # 取各分区的 UUID、LABEL、TYPE
swapon -s                         # 找回 swap 分区
findmnt -s                        # 从 fstab 读(此时已空),findmnt 不带 -s 则从内核读

# 据此手写 fstab(示例)
cat > /etc/fstab <<'EOF'
UUID=8f3a1b2c-...   /        xfs     defaults        0 0
UUID=1a2b3c4d-...   /boot    xfs     defaults        0 0
UUID=5e6f7a8b-...   swap     swap    defaults        0 0
/dev/mapper/vg_data-lv_data  /data   xfs  defaults   0 0
EOF

# 方法三:从 RPM 包中提取(fstab 属于 setup 包,但包里是模板不含实际配置)
rpm -qf /etc/fstab                # setup-x.x.x
cd /tmp && rpm2cpio /path/to/setup-*.rpm | cpio -idmv ./etc/fstab

# 方法四:已经重启且进不去系统 → 用 rd.break 或救援模式(见 10.3)后手写 fstab

# 无论用哪种方法,最后必须验证
findmnt --verify
mount -a                          # 无任何报错才可以重启

经验教训:/etc/fstab、/etc/passwd、/etc/shadow、/boot/grub2/grub.cfg 属于"改一个字符就可能开不了机"的文件,修改前一律先 cp xxx xxx.bak.$(date +%F)。

2. 如果 /etc/fstab 中写错了 UUID 或设备名,会发生什么?

场景 CentOS 6 表现 CentOS 7 表现
写错非关键数据盘的 UUID/设备名 开机时 mount -a 失败,屏幕给出错误提示,多数情况下仍能继续启动到登录界面,仅该目录未挂载 systemd 认为 local-fs.target 依赖失败,默认会中断启动并进入 emergency mode,要求输入 root 密码;除非该行加了 nofail
写错根分区的 UUID 内核找不到根文件系统,Kernel panic - not syncing: VFS: Unable to mount root fs 同样 panic,或停在 dracut 的 initramfs> 提示符
写错文件系统类型(如 xfs 写成 ext4) mount: wrong fs type, bad option, bad superblock 同样报错,进入 emergency
写错挂载选项(拼写错误) mount: unknown option 同上
设备当前不存在(比如已拔掉的 U 盘) 报错但可继续 等待 90 秒后 Timed out waiting for device,进入 emergency
第 6 列 fsck 顺序写错(非根分区写成 1) 检查顺序混乱,可能导致检查失败 同上

危险的隐性错误:如果把某个数据盘的 UUID 误写成另一个已在使用的分区的 UUID,可能出现同一个设备被挂载到两个位置,或数据写入了错误的分区,造成数据混乱甚至覆盖,这比开不了机更危险。

恢复与预防:

# 恢复:进入救援环境(rd.break / single / 光盘救援)→ 修正 fstab → mount -a 验证(见 10.3、10.4)

# 预防(三条铁律)
mount -a                                       # 1. 改完立刻验证,报错就说明有问题
findmnt --verify                               # 2. CentOS 7 专用的语法与设备校验
UUID=xxx /data xfs defaults,nofail 0 0         # 3. 非关键数据盘加 nofail,缺盘不阻塞开机
#    追加 x-systemd.device-timeout=10 可缩短等待时间
UUID=xxx /data xfs defaults,nofail,x-systemd.device-timeout=10 0 0

练习二:分区规划实操(源自 07 文档 练习 PPP1)

题目:在虚拟机中新增一块硬盘,按下表完成分区、格式化与挂载,并配置为开机自动挂载。

序号 分区用途 大小 文件系统类型 分区类型 ID 挂载点
1 Linux 数据分区 2 GB ext4 83(Linux) /mnt/data1
2 Linux LVM 分区 4 GB 交由 LVM 管理 8e(Linux LVM) 由 LV 决定
3 Linux swap 分区 1 GB swap 82(Linux swap) swap
4 Windows 数据分区 2 GB ntfs 7(HPFS/NTFS) /mnt/win
5 FAT32 共享分区 1 GB vfat b(W95 FAT32) /mnt/fat

原题中的编号 20 / 19 / 4 / 7 是 fdisk 交互中 l 列表里的行号或不同版本的编号,实际输入的是上表的分区类型 ID(十六进制代码)。以 t 命令输入 83、8e、82、7、b 即可。

参考答案:

# 步骤 0:确认新硬盘已被识别
lsblk
fdisk -l /dev/sdb

# 步骤 1:分区(MBR 方案:3 个主分区 + 1 个扩展分区内放 2 个逻辑分区)
fdisk /dev/sdb
#   n → p → 1 → 回车 → +2G          创建 sdb1(2G,数据)
#   n → p → 2 → 回车 → +4G          创建 sdb2(4G,LVM)
#   n → p → 3 → 回车 → +1G          创建 sdb3(1G,swap)
#   n → e → 4 → 回车 → 回车         剩余空间全部给扩展分区
#   n → 回车 → +2G                  创建 sdb5(2G,NTFS)
#   n → 回车 → +1G                  创建 sdb6(1G,FAT32)
#   t → 2 → 8e                      把 sdb2 标记为 Linux LVM
#   t → 3 → 82                      把 sdb3 标记为 Linux swap
#   t → 5 → 7                       把 sdb5 标记为 HPFS/NTFS
#   t → 6 → b                       把 sdb6 标记为 W95 FAT32
#   (sdb1 默认就是 83 Linux,无需修改)
#   p                               打印确认
#   w                               写入并退出

partprobe /dev/sdb                  # 刷新分区表
lsblk /dev/sdb
fdisk -l /dev/sdb                   # 核对 Id 与 System 两列

# 步骤 2:格式化
mkfs.ext4 -L data1 /dev/sdb1
mkswap -L myswap /dev/sdb3
yum install epel-release -y && yum install ntfs-3g -y    # NTFS 支持需 EPEL 源
mkfs.ntfs -f -L windata /dev/sdb5   # -f 快速格式化
mkfs.vfat -F 32 -n SHARE /dev/sdb6  # -F 32 指定 FAT32,-n 卷标(必须大写)

# 步骤 3:LVM 部分(sdb2)
pvcreate /dev/sdb2
vgcreate vg_test /dev/sdb2
lvcreate -L 3G -n lv_test vg_test
mkfs.xfs /dev/vg_test/lv_test
mkdir -p /mnt/lvm
mount /dev/vg_test/lv_test /mnt/lvm

# 步骤 4:创建挂载点并挂载
mkdir -p /mnt/data1 /mnt/win /mnt/fat
mount /dev/sdb1 /mnt/data1
mount -t ntfs-3g /dev/sdb5 /mnt/win
mount -t vfat -o iocharset=utf8,umask=000 /dev/sdb6 /mnt/fat
swapon /dev/sdb3
free -m; swapon -s; df -hT

# 步骤 5:配置开机自动挂载
blkid                               # 取各分区 UUID
vi /etc/fstab
UUID=<sdb1的UUID>   /mnt/data1  ext4     defaults                      0 2
UUID=<sdb3的UUID>   swap        swap     defaults                      0 0
UUID=<sdb5的UUID>   /mnt/win    ntfs-3g  defaults,nofail               0 0
UUID=<sdb6的UUID>   /mnt/fat    vfat     defaults,iocharset=utf8,umask=000,nofail  0 0
/dev/vg_test/lv_test /mnt/lvm   xfs      defaults                      0 0
# 步骤 6:验证(必做)
umount /mnt/data1 /mnt/win /mnt/fat /mnt/lvm
swapoff /dev/sdb3
mount -a && swapon -a
df -hT; free -m
findmnt --verify                    # CentOS 7
reboot                              # 最终验证

练习三:卸载繁忙设备实操(源自 07 文档 练习 PPP2)

题目:按以下步骤制造并解决"设备繁忙无法卸载"的场景。

  1. 新建一个分区并格式化、挂载到 /mnt/test;
  2. 打开一个新终端,cd 进入 /mnt/test,并在其中创建文件、用 vim 打开一个文件不退出;
  3. 回到第一个终端,尝试 umount /mnt/test,观察报错;
  4. 使用 lsof 和 fuser 找出是哪些进程在占用;
  5. 分别用"温和方式"和"强制方式"解决,最终成功卸载。

参考答案:

# ===== 步骤 1:准备环境 =====
fdisk /dev/sdb                       # n → 回车 → +1G → w
partprobe /dev/sdb
mkfs.xfs /dev/sdb7                   # 或 mkfs.ext4
mkdir -p /mnt/test
mount /dev/sdb7 /mnt/test
df -hT | grep test

# ===== 步骤 2:制造占用(在【第二个终端】执行)=====
cd /mnt/test
touch a.txt b.txt
echo "hello" > a.txt
vim a.txt                            # 保持打开状态不退出
#   再开第三个终端执行 tail -f /mnt/test/a.txt 可制造更多占用

# ===== 步骤 3:尝试卸载(回到第一个终端)=====
umount /mnt/test
# umount: /mnt/test: target is busy.
#         (In some cases useful info about processes that use
#          the device is found by lsof(8) or fuser(1))
#   CentOS 6 的提示为:
# umount: /mnt/test: device is busy.

# ===== 步骤 4:定位占用者 =====
lsof /mnt/test
# COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
# bash    3021 root  cwd    DIR   8,23     4096  128 /mnt/test        <- cwd 停在该目录
# vim     3105 root  cwd    DIR   8,23     4096  128 /mnt/test
# vim     3105 root    4u   REG   8,23    12288  131 /mnt/test/.a.txt.swp

lsof +D /mnt/test                    # 递归查看目录下所有被打开的文件
fuser -mv /mnt/test
#                      USER    PID ACCESS COMMAND
# /mnt/test:           root   3021 ..c..  bash
#                      root   3105 ..c..  vim
#   ACCESS 含义:c=进程的当前工作目录在此  f=有打开的文件
#                e=正在执行该设备上的程序  r=该设备是进程的根目录  m=映射了库文件
fuser -mu /mnt/test                  # 显示进程所属用户
fuser -cu /mnt/test

# ===== 步骤 5a:温和方式(推荐,不丢数据)=====
#   在第二个终端:先在 vim 里 :wq 保存退出,再 cd 离开该目录
#   :wq
#   cd /
#   回到第一个终端:
umount /mnt/test                     # 成功
df -hT | grep test                   # 已无输出

# ===== 步骤 5b:强制方式(会中断进程,有丢数据风险)=====
mount /dev/sdb7 /mnt/test            # 重新挂载以便再演练一次
#   (在第二个终端重新 cd 进去并 vim 打开文件)

kill 3105                            # 逐个温和终止(先 TERM,给进程保存的机会)
fuser -k -TERM -m /mnt/test          # 批量发 TERM
umount /mnt/test

#   若仍不行,升级手段:
fuser -km /mnt/test                  # 发 KILL 杀掉全部占用进程
lsof -t /mnt/test | xargs -r kill -9
umount /mnt/test

#   终极手段:延迟卸载(立即从目录树摘除,占用释放后自动完成真正的卸载)
umount -l /mnt/test
mount | grep test                    # 已看不到
lsof /mnt/test                       # 进程仍持有旧的 inode,但新访问已不通
#   网络文件系统失联时:
umount -f /mnt/test
umount -f -l /mnt/test

# ===== 步骤 6:清理 =====
umount /mnt/test 2>/dev/null
rmdir /mnt/test

结论与要点:

  • 最常见的占用原因就是自己的 shell 还停在该目录里(cwd),cd / 即可解决,不要一上来就杀进程;
  • vim 会在目录里留下 .xxx.swp 交换文件,强杀后需要清理,且可能丢失未保存内容;
  • 生产环境优先走"停业务服务 → 卸载"的正规流程,fuser -km 属于应急手段;
  • umount -l 是最安全的兜底选择:不杀进程、不阻塞脚本,但要注意卸载是延迟生效的,紧接着的 mount 同一设备可能出现意外行为。

练习四:LVM 综合实操(补充)

题目:新增两块 10GB 硬盘,完成以下任务:

  1. 创建 VG vg_app(PE 大小 16MB),包含这两块盘;
  2. 创建 5GB 的 LV lv_web,格式化为 xfs,挂载到 /web;
  3. 写入测试数据后,在线扩容到 12GB;
  4. 创建 LV lv_db(3GB,ext4)挂载到 /db,然后离线缩容到 2GB;
  5. 为 lv_web 创建 1GB 快照并验证快照内容;
  6. 配置开机自动挂载。

参考答案:

# ===== 1. 创建 PV 与 VG =====
lsblk
pvcreate /dev/sdc /dev/sdd           # 整盘做 PV(也可先分区并标记 8e)
pvs; pvdisplay
vgcreate -s 16M vg_app /dev/sdc /dev/sdd    # -s 指定 PE 大小为 16MB
vgs; vgdisplay vg_app                # 确认 PE Size = 16.00 MiB,总容量约 20G

# ===== 2. 创建 lv_web 并挂载 =====
lvcreate -L 5G -n lv_web vg_app
lvs; lvdisplay /dev/vg_app/lv_web
mkfs.xfs /dev/vg_app/lv_web
mkdir -p /web
mount /dev/vg_app/lv_web /web
df -hT /web
# 写入测试数据
dd if=/dev/zero of=/web/testfile bs=1M count=500
echo "web data v1" > /web/readme.txt

# ===== 3. 在线扩容到 12GB(业务不中断)=====
vgs vg_app                           # 确认 VFree 足够
lvextend -L 12G /dev/vg_app/lv_web   # 扩 LV(也可写 -L +7G)
lvs
xfs_growfs /web                      # 扩 xfs 文件系统(参数是【挂载点】)
df -hT /web                          # 确认已变成 12G,且 /web 全程未卸载
cat /web/readme.txt                  # 数据完好
# 一步完成的写法:
#   lvextend -r -L 12G /dev/vg_app/lv_web
#   lvresize -r -L 12G /dev/vg_app/lv_web

# ===== 4. 创建 lv_db 并缩容(必须离线,且顺序不能错)=====
lvcreate -L 3G -n lv_db vg_app
mkfs.ext4 /dev/vg_app/lv_db
mkdir -p /db && mount /dev/vg_app/lv_db /db
echo "db data" > /db/data.txt
df -hT /db

umount /db                                    # ① 卸载
e2fsck -f /dev/vg_app/lv_db                   # ② 强制检查(必做)
resize2fs /dev/vg_app/lv_db 2G                # ③ 先缩【文件系统】
lvreduce -L 2G /dev/vg_app/lv_db              # ④ 再缩【LV】,输入 y 确认
mount /dev/vg_app/lv_db /db                   # ⑤ 挂载验证
df -hT /db; cat /db/data.txt                  # 确认 2G 且数据完好
# 一步完成(更安全):lvreduce -r -L 2G /dev/vg_app/lv_db

# ===== 5. 创建快照 =====
lvcreate -L 1G -s -n lv_web_snap /dev/vg_app/lv_web
lvs                                  # Origin 列显示 lv_web,Data% 显示快照已用比例
mkdir -p /snap
mount -o nouuid,ro /dev/vg_app/lv_web_snap /snap    # xfs 快照必须加 nouuid
ls -l /snap; cat /snap/readme.txt    # 内容与创建快照那一刻一致

# 验证快照的独立性:修改源卷,快照内容不变
echo "web data v2" > /web/readme.txt
cat /web/readme.txt                  # v2
cat /snap/readme.txt                 # 仍然是 v1  ← 快照生效

# 用快照做一致性备份
tar -czvf /backup/web-$(date +%F).tar.gz -C /snap .

# 从快照回滚(可选,需卸载源卷)
umount /snap /web
lvconvert --merge /dev/vg_app/lv_web_snap
mount /dev/vg_app/lv_web /web
cat /web/readme.txt                  # 回到 v1,快照自动删除

# 清理快照(不回滚时)
umount /snap
lvremove /dev/vg_app/lv_web_snap

# ===== 6. 配置开机自动挂载 =====
blkid /dev/vg_app/lv_web /dev/vg_app/lv_db
vi /etc/fstab
#   /dev/mapper/vg_app-lv_web  /web  xfs   defaults  0 0
#   /dev/mapper/vg_app-lv_db   /db   ext4  defaults  0 2
#   (LVM 设备名稳定,可直接使用;也可用 UUID)
umount /web /db && mount -a && df -hT
findmnt --verify

关键要点:

操作 顺序 是否需要卸载
扩容 先 lvextend,再 xfs_growfs/resize2fs 不需要,可在线
缩容 先 resize2fs,再 lvreduce 必须卸载,且 xfs 不支持

练习五:GPT 分区与大容量磁盘(补充)

题目:为一块 4TB 硬盘做 GPT 分区,划分为 1TB 和 3TB 两个分区,并验证 4K 对齐。

参考答案:

# 为什么必须用 GPT:MBR 单分区上限 2TB,4TB 盘用 MBR 会浪费一半以上空间
# 为什么必须用 parted/gdisk:CentOS 6 的 fdisk 不支持 GPT

parted /dev/sde
(parted) mklabel gpt                 # 建立 GPT 分区表(会清除原有分区表,确认 Yes)
(parted) unit TB                     # 或 unit GB / unit s(扇区)
(parted) print
(parted) mkpart data1 xfs 0% 25%     # 用百分比可自动保证对齐(推荐)
(parted) mkpart data2 xfs 25% 100%
(parted) print
(parted) align-check optimal 1       # 输出 "1 aligned" 表示已 4K 对齐
(parted) align-check optimal 2
(parted) quit
#   parted 是【即时写入】的,没有 fdisk 那样的 w 确认步骤,操作需格外谨慎

# 非交互式(脚本化,-s 静默)
parted -s /dev/sde mklabel gpt
parted -s /dev/sde mkpart data1 xfs 1MiB 1000GiB
parted -s /dev/sde mkpart data2 xfs 1000GiB 100%
parted -s /dev/sde align-check opt 1
parted -s /dev/sde print

# 用 gdisk 完成同样的事
gdisk /dev/sde
#   o → Y            新建空 GPT
#   n → 1 → 回车 → +1T → 8300      (8300 = Linux filesystem)
#   n → 2 → 回车 → 回车 → 8300
#   p                打印
#   w → Y            写入
sgdisk -p /dev/sde
sgdisk -n 1:0:+1T -t 1:8300 -c 1:data1 /dev/sde     # 非交互式创建

# 验证对齐(手工核对起始扇区)
parted /dev/sde unit s print
#   起始扇区能被 8 整除(512B 扇区)即为 4K 对齐;常见的起始值是 2048
fdisk -l /dev/sde                    # CentOS 7 的 fdisk 可以只读显示 GPT
blockdev --getpbsz /dev/sde          # 物理扇区大小
blockdev --getss /dev/sde            # 逻辑扇区大小
cat /sys/block/sde/queue/optimal_io_size
cat /sys/block/sde/queue/minimum_io_size

# 格式化、挂载
partprobe /dev/sde
mkfs.xfs -f -L bigdata1 /dev/sde1
mkfs.xfs -f -L bigdata2 /dev/sde2
mkdir -p /data1 /data2
mount /dev/sde1 /data1; mount /dev/sde2 /data2
df -hT

# GPT 分区表备份(GPT 有主备两份表,但整表备份仍是必要的)
sgdisk --backup=/root/sde-gpt.bak /dev/sde
sgdisk --load-backup=/root/sde-gpt.bak /dev/sde     # 恢复
sgdisk -R /dev/sdf /dev/sde && sgdisk -G /dev/sdf   # 复制分区表到另一盘并生成新 GUID
dd if=/dev/sde of=/root/sde-gpt-full.bak bs=512 count=34   # 主 GPT 头 + 分区项

练习六:磁盘配额实操(补充)

题目:在 /data(xfs)上为用户 tom 设置 100MB 软限制、150MB 硬限制,宽限期 3 天;并在 ext4 分区上完成同样的配置。

参考答案(xfs,CentOS 7):

# 1. 挂载时启用配额(xfs 的配额只能在挂载时开启,不能事后 quotacheck)
vi /etc/fstab
#   /dev/vg_app/lv_web  /data  xfs  defaults,usrquota,grpquota  0 0
#   xfs 也可写作 uquota,gquota;项目配额用 pquota
umount /data && mount -a
mount | grep data                     # 确认选项已生效
xfs_quota -x -c 'state' /data         # 查看配额状态

# 2. 设置限制
xfs_quota -x -c 'limit bsoft=100m bhard=150m tom' /data
xfs_quota -x -c 'limit isoft=1000 ihard=1200 tom' /data      # inode 数量限制
xfs_quota -x -c 'limit -g bsoft=500m bhard=600m dev' /data   # 组配额

# 3. 设置宽限期(xfs 的宽限期是全局的,不能按用户单独设置)
xfs_quota -x -c 'timer -bu 3days' /data
xfs_quota -x -c 'timer -iu 7days' /data

# 4. 查看
xfs_quota -x -c 'report -h' /data
xfs_quota -x -c 'report -ugh' /data
xfs_quota -x -c 'quota -h tom' /data
xfs_quota -x -c 'state -h' /data

# 5. 测试
su - tom
dd if=/dev/zero of=/data/tom/f1 bs=1M count=120     # 超软限制,有警告但可写
dd if=/dev/zero of=/data/tom/f2 bs=1M count=50      # 触碰硬限制,报 Disk quota exceeded
exit

参考答案(ext4,CentOS 6/7 通用):

# 1. 安装工具并启用挂载选项
yum install quota -y
vi /etc/fstab
#   /dev/vg_app/lv_db  /db  ext4  defaults,usrquota,grpquota  0 2
umount /db && mount -a
mount | grep db

# 2. 初始化配额数据库(ext4 独有的步骤,xfs 不需要)
quotacheck -cugv /db                  # -c 新建 -u 用户 -g 组 -v 详细
#   生成 /db/aquota.user 和 /db/aquota.group
ls -l /db/aquota.*
#   若报 "Cannot remount filesystem read-only",可加 -m 强制
quotacheck -cugvm /db

# 3. 启用配额
quotaon -avug
quotaon -p /db                        # 查看状态
#   关闭:quotaoff -avug

# 4. 设置限制(交互式编辑,单位是 KB)
edquota -u tom
#   Filesystem  blocks  soft    hard  inodes  soft  hard
#   /dev/...     0     102400  153600    0     0     0
#   (100MB=102400KB,150MB=153600KB)

# 非交互式(脚本友好,推荐)
setquota -u tom 102400 153600 0 0 /db
setquota -u tom 102400 153600 1000 1200 /db     # 同时限制 inode
setquota -g dev 512000 614400 0 0 /db

# 5. 设置宽限期
edquota -t
#   Block grace period: 3days    Inode grace period: 7days
setquota -t 259200 604800 /db         # 单位为秒:3天=259200,7天=604800

# 6. 批量复制配额模板(给多个用户设置相同配额的高效做法)
edquota -p tom -u jerry
edquota -p tom -u $(cut -d: -f1 /etc/passwd | awk -F: '$1>=1000')
for u in u1 u2 u3; do edquota -p tom -u $u; done

# 7. 查看与报表
quota -u tom
quota -vs tom                         # -s 人性化单位
quota -g dev
repquota /db
repquota -a                           # 所有启用了配额的文件系统
repquota -as                          # 人性化显示

# 8. 邮件告警
warnquota                             # 给超限用户发邮件
vi /etc/warnquota.conf                # 配置邮件模板与发件人
#   加入 crontab:0 8 * * * /usr/sbin/warnquota

xfs 项目配额(针对目录做配额,用户配额做不到):

# 挂载选项用 pquota(注意 pquota 与 grpquota 互斥,不能同时启用)
#   /dev/sdb1  /data  xfs  defaults,pquota  0 0
mount -o remount,pquota /data

echo "10:/data/project1" >> /etc/projid       # 项目 ID : 项目名映射
echo "10:/data/project1" >> /etc/projects     # 项目 ID : 目录路径
xfs_quota -x -c 'project -s project1' /data   # 初始化项目(-s 递归打标)
xfs_quota -x -c 'limit -p bsoft=1g bhard=1.2g project1' /data
xfs_quota -x -c 'report -p -h' /data

练习七:LUKS 磁盘加密实操(补充)

题目:对 /dev/sdf1 做 LUKS 加密,挂载到 /secure,并配置为使用密钥文件开机自动解锁。

参考答案:

# 0. 确认内核支持并安装工具
yum install cryptsetup -y
modprobe dm_crypt
lsmod | grep dm_crypt
cat /proc/crypto | grep -i aes

# 1. 加密分区(注意:会清除该分区所有数据)
cryptsetup luksFormat /dev/sdf1
#   WARNING! ... Are you sure? (Type uppercase yes): YES     ← 必须大写 YES
#   Enter passphrase: ****      (两次)
cryptsetup luksFormat -c aes-xts-plain64 -s 512 -h sha512 /dev/sdf1   # 指定算法
cryptsetup luksDump /dev/sdf1         # 查看头部信息与密钥槽使用情况
blkid /dev/sdf1                       # TYPE="crypto_LUKS"

# 2. 打开(映射为 /dev/mapper/secure)
cryptsetup luksOpen /dev/sdf1 secure
#   或 cryptsetup open /dev/sdf1 secure
ls -l /dev/mapper/secure
lsblk /dev/sdf                        # 可看到 sdf1 下多出 crypt 类型的 secure
dmsetup ls; dmsetup info secure
cryptsetup status secure

# 3. 格式化并挂载(对映射设备操作,不是对 /dev/sdf1)
mkfs.xfs /dev/mapper/secure
mkdir -p /secure
mount /dev/mapper/secure /secure
df -hT /secure
echo "secret" > /secure/secret.txt

# 4. 卸载并关闭
umount /secure
cryptsetup luksClose secure
#   此时 /dev/sdf1 的内容是密文,直接挂载会失败:
mount /dev/sdf1 /mnt                  # mount: unknown filesystem type 'crypto_LUKS'

# 5. 密钥管理(LUKS 有 8 个密钥槽,支持多个口令)
cryptsetup luksAddKey /dev/sdf1                    # 添加新口令
cryptsetup luksChangeKey /dev/sdf1                 # 修改口令
cryptsetup luksRemoveKey /dev/sdf1                 # 删除某个口令(输入要删的那个)
cryptsetup luksKillSlot /dev/sdf1 1                # 按槽位号删除
cryptsetup luksDump /dev/sdf1 | grep -A2 'Key Slot'

# 6. 使用密钥文件实现开机自动解锁
dd if=/dev/urandom of=/root/secure.key bs=512 count=8
chmod 400 /root/secure.key
cryptsetup luksAddKey /dev/sdf1 /root/secure.key   # 把密钥文件加入密钥槽
cryptsetup luksOpen -d /root/secure.key /dev/sdf1 secure   # 用密钥文件测试解锁
cryptsetup luksOpen --key-file /root/secure.key /dev/sdf1 secure

# 配置 /etc/crypttab(格式:映射名  底层设备  密钥文件  选项)
blkid /dev/sdf1                       # 取 UUID
vi /etc/crypttab
#   secure  UUID=<sdf1的UUID>  /root/secure.key  luks
#   (密钥文件写 none 或留空则开机时交互询问口令)
chmod 600 /etc/crypttab

# 配置 /etc/fstab(用映射后的设备名)
vi /etc/fstab
#   /dev/mapper/secure  /secure  xfs  defaults,_netdev  0 0
#   注意:必须确保 crypttab 先解锁再挂载,加 _netdev 或 nofail 更保险

dracut -f                             # 重建 initramfs(根分区加密时必做)
systemctl daemon-reload
reboot                                # 验证自动解锁

# 7. 头部备份(LUKS 头损坏 = 数据永久丢失,此步至关重要)
cryptsetup luksHeaderBackup /dev/sdf1 --header-backup-file /root/sdf1-luks.img
chmod 400 /root/sdf1-luks.img         # 头部含密钥材料,必须严格保护并异地保存
cryptsetup luksHeaderRestore /dev/sdf1 --header-backup-file /root/sdf1-luks.img

注意事项:LUKS 口令一旦全部遗忘,数据无法恢复;LUKS 头部(默认 1~2MB)被覆盖同样导致数据永久丢失,必须备份头部;加密会带来 5%~15% 的 CPU 开销(有 AES-NI 指令集时开销很小,用 grep aes /proc/cpuinfo 确认)。

练习八:软件 RAID 5 实操与故障演练(补充)

题目:用 4 块 1GB 分区创建 RAID 5(3 块数据盘 + 1 块热备),格式化挂载后模拟一块盘故障,观察热备自动接管与重建全过程,最后完成换盘。

参考答案:

# ===== 1. 准备 4 个分区(类型 ID 设为 fd,Linux raid autodetect)=====
fdisk /dev/sdb
#   n → p → 1 → +1G   ... 重复创建 sdb1~sdb4
#   t → 1 → fd  ...    逐个把类型改为 fd
#   w
partprobe /dev/sdb
lsblk; fdisk -l /dev/sdb | grep raid

# ===== 2. 创建 RAID 5 =====
yum install mdadm -y
mdadm -C /dev/md0 -l 5 -n 3 -x 1 /dev/sdb{1,2,3,4}
#   等价长选项写法:
#   mdadm --create /dev/md0 --level=5 --raid-devices=3 --spare-devices=1 /dev/sdb{1,2,3,4}
#   -C 创建  -l 级别  -n 活动设备数  -x 热备数  -c 指定 chunk 大小(默认 512K)
mdadm -C /dev/md0 -l 5 -n 3 -x 1 -c 64 /dev/sdb{1,2,3,4}   # 指定 64K chunk

# ===== 3. 查看状态与同步进度 =====
cat /proc/mdstat
# Personalities : [raid6] [raid5] [raid4]
# md0 : active raid5 sdb3[4] sdb4[3](S) sdb2[1] sdb1[0]
#       2093056 blocks super 1.2 level 5, 512k chunk, algorithm 2 [3/3] [UUU]
#   (S)=热备  (F)=故障  [3/3]=应有3块/在线3块  [UUU]=每块都正常(_ 表示缺失)
watch -n 1 cat /proc/mdstat           # 实时观察初始同步进度
mdadm -D /dev/md0                     # 详细信息(State、Active/Working/Failed/Spare Devices)
mdadm --detail /dev/md0
mdadm -E /dev/sdb1                    # 查看单个成员盘的超级块
mdadm -Q /dev/md0                     # 简要查询

# ===== 4. 格式化、挂载 =====
mkfs.xfs /dev/md0
mkdir -p /raid5
mount /dev/md0 /raid5
df -hT /raid5
#   容量验证:3 块 1G 的 RAID 5 可用约 2G(N-1),第 4 块作热备不计入容量
cp -r /etc /raid5/                    # 写入测试数据
md5sum /raid5/etc/passwd > /root/before.md5

# ===== 5. 保存配置(不做这步重启后阵列名可能变成 /dev/md127)=====
mdadm -Ds > /etc/mdadm.conf
mdadm --detail --scan >> /etc/mdadm.conf
cat /etc/mdadm.conf
#   ARRAY /dev/md0 metadata=1.2 spares=1 name=host:0 UUID=xxxx:xxxx:xxxx:xxxx
echo "MAILADDR ops@example.com" >> /etc/mdadm.conf     # 故障邮件告警
dracut -f                             # 若 RAID 承载 / 或 /boot,需重建 initramfs

systemctl enable --now mdmonitor      # CentOS 7 启用监控守护进程
service mdmonitor start; chkconfig mdmonitor on        # CentOS 6

# fstab 用 UUID 挂载(更稳妥)
blkid /dev/md0
vi /etc/fstab
#   UUID=<md0的UUID>  /raid5  xfs  defaults  0 0

# ===== 6. 模拟故障,观察热备自动接管 =====
mdadm /dev/md0 -f /dev/sdb1           # -f/--fail 标记为故障
#   或 mdadm --manage /dev/md0 --fail /dev/sdb1
cat /proc/mdstat
# md0 : active raid5 sdb4[3] sdb3[4] sdb2[1] sdb1[0](F)
#       ... [3/2] [_UU]
#       [====>............] recovery = 25.4% (532480/2093056) finish=0.5min speed=48000K/sec
#   观察到:sdb1 变成 (F),热备 sdb4 自动顶上并开始 recovery
watch -n 1 cat /proc/mdstat           # 等待重建完成,恢复为 [3/3] [UUU]
mdadm -D /dev/md0 | grep -E 'State|Devices'

# 重建期间业务不中断(降级运行),验证数据仍可读写
md5sum -c /root/before.md5
echo "still writable" > /raid5/test.txt

# 调整重建速度(重建会抢占 I/O,业务高峰期可限速)
cat /proc/sys/dev/raid/speed_limit_min      # 默认 1000 (KB/s)
cat /proc/sys/dev/raid/speed_limit_max      # 默认 200000
echo 50000 > /proc/sys/dev/raid/speed_limit_min     # 提速(加快重建,抢占更多 I/O)
echo 20000 > /proc/sys/dev/raid/speed_limit_max     # 限速(保业务)
sysctl -w dev.raid.speed_limit_min=50000            # 永久写入 /etc/sysctl.conf

# ===== 7. 移除坏盘并加入新盘 =====
mdadm /dev/md0 -r /dev/sdb1           # -r/--remove 移除故障盘
cat /proc/mdstat                      # sdb1 已消失,且当前无热备
mdadm -D /dev/md0

#   (物理上换盘、重新分区后)加入新盘作为热备
mdadm /dev/md0 -a /dev/sdb5           # -a/--add 添加
cat /proc/mdstat                      # sdb5 显示为 (S) 热备
mdadm -D /dev/md0 | grep spare

# 一条命令完成"移除 + 添加"
mdadm /dev/md0 -f /dev/sdb2 -r /dev/sdb2 -a /dev/sdb6

# 更优雅的换盘方式:先复制后剔除(不经历降级状态,风险更低)
mdadm /dev/md0 --replace /dev/sdb2 --with /dev/sdb6

# ===== 8. 主动一致性巡检(发现静默数据损坏)=====
echo check > /sys/block/md0/md/sync_action
cat /sys/block/md0/md/mismatch_cnt    # 应为 0;非 0 说明存在校验不一致
echo repair > /sys/block/md0/md/sync_action     # 修复不一致
echo idle > /sys/block/md0/md/sync_action       # 中止巡检

# ===== 9. 扩容 RAID(增加成员盘扩大容量)=====
mdadm /dev/md0 -a /dev/sdb7
mdadm -G /dev/md0 -n 4                # --grow 把活动设备数从 3 增到 4
watch cat /proc/mdstat                # reshape 过程较慢,期间不要断电
xfs_growfs /raid5                     # 扩文件系统
mdadm -Ds > /etc/mdadm.conf           # 配置变了要重新保存

# ===== 10. 停止与彻底删除 =====
umount /raid5
vi /etc/fstab                         # 先注释掉相关行,否则重启会失败
mdadm -S /dev/md0                     # --stop 停止阵列
cat /proc/mdstat                      # 已无 md0
mdadm --zero-superblock /dev/sdb1     # 清除成员盘上的 RAID 超级块(关键!否则会被自动重组)
mdadm --zero-superblock /dev/sdb{2,3,4,5,6,7}
wipefs -a /dev/sdb1                   # 更彻底地擦除各种文件系统/RAID 签名
rm -f /etc/mdadm.conf

# 重新组装已停止的阵列
mdadm -A /dev/md0 /dev/sdb{1,2,3}     # --assemble 手动组装
mdadm -A --scan                       # 按 /etc/mdadm.conf 自动组装全部
mdadm --assemble --scan --run         # 强制运行(即使降级)

思考与要点:

  1. RAID 5 的容量与冗余:N 块盘容量为 (N-1) 块,允许1 块盘故障;重建期间若第二块盘再故障则数据全失,这是 RAID 5 在大容量盘时代最被诟病的风险,因此重要数据建议 RAID 6 或 RAID 10。
  2. 热备的价值:故障发生后无需人工介入即刻开始重建,把"降级窗口"从数小时(等运维发现并换盘)缩短到数分钟。
  3. RAID 不是备份:RAID 只解决硬盘物理故障,无法防止误删除、误格式化、病毒加密、逻辑损坏、机房灾难。备份必须独立存在。
  4. mdadm -Ds > /etc/mdadm.conf 必须做,否则重启后阵列可能被识别为 /dev/md127,导致 fstab 挂载失败。

练习九:概念快答(补充)

1. MBR 和 GPT 各自的容量与分区数量限制是多少?为什么?

MBR:单分区/单磁盘最大 2TB,最多 4 个主分区(或 3 主 + 1 扩展,扩展分区内可有多个逻辑分区)。原因是 MBR 的 64 字节分区表被 4 个 16 字节表项占满,且每个表项用 4 字节记录起始扇区与总扇区数,2^32 × 512B = 2TB。

GPT:理论支持 8 ZiB(用 8 字节即 64 位记录 LBA),Linux 下默认支持 128 个分区。因为 GPT 头部之后有 128 个 128 字节的分区项区域,且分区项数量可扩展。

2. 为什么推荐用 UUID 而不是 /dev/sdb1 挂载?

设备名 sd* 由内核按磁盘被发现的顺序分配,并不固定。增删硬盘、改变 SATA 插槽、更换 HBA 卡、虚拟机调整磁盘顺序、内核版本变化,都可能让原来的 /dev/sdb1 变成 /dev/sdc1,导致挂载错误的分区或挂载失败。UUID 写在文件系统的超级块里,跟着数据走,唯一且稳定。

3. resize2fs 和 xfs_growfs 的参数有什么区别?

resize2fs 接受设备名(resize2fs /dev/vg/lv),xfs_growfs 接受挂载点(xfs_growfs /data)。此外 resize2fs 可增可减(缩小需卸载),xfs_growfs 只能增大。

4. 硬链接和软链接的区别?为什么硬链接不能跨文件系统?

硬链接是指向同一个 inode 的另一个目录项,与原文件完全平等,删除任一个只是减少链接计数;软链接是一个独立的小文件,内容是目标路径的字符串。inode 号只在单个文件系统内唯一,跨文件系统的 inode 号会冲突,因此硬链接不能跨文件系统,也不能指向目录(会破坏目录树的无环结构)。

5. df 和 du 结果不一致的常见原因?

(1)有已删除但仍被进程占用的文件(lsof +L1);(2)挂载点被覆盖,du 统计的是被隐藏的下层目录;(3)稀疏文件(du 显示实际占用,ls -l 显示逻辑大小);(4)ext 文件系统为 root 预留的 5% 空间;(5)du 无权限读取某些目录导致统计偏小。

6. LVM 中 PE 的大小为什么重要?

PE(Physical Extent)是 LVM 分配空间的最小单位,默认 4MB。LV 的容量必须是 PE 的整数倍,因此 PE 决定了容量调整的粒度。同时在旧版 LVM1 中,单个 LV 最多 65534 个 LE,PE 越小则 LV 上限越小(4MB × 65534 ≈ 256GB)。LVM2 已无此限制,但 PE 过小会增加元数据量、影响大容量 VG 的操作性能。创建后可用 vgchange -s 调整(有限制),因此建议在 vgcreate -s 时就规划好。

7. 为什么 CentOS 7 默认改用 xfs?它有什么代价?

优点:xfs 采用 B+ 树索引与延迟分配,在大文件、大容量(支持 EB 级)、高并发场景下性能显著优于 ext4;inode 动态分配,不会出现 inode 耗尽;mkfs 与 fsck 速度快。代价:不支持缩小;删除大量小文件的性能不如 ext4;早期版本元数据操作较慢(v5 已改善);断电后依赖日志恢复,xfs_repair -L 会丢数据。

8. mount -o remount,rw / 在什么场景下使用?

系统检测到文件系统错误后自动把根分区改为只读保护模式,或在救援环境(rd.break、单用户模式、init=/bin/bash)中根分区默认以只读方式挂载。此时需要该命令把根改为可写才能修改配置文件(如修 fstab)。

附录 A:Linux 运行级别

本节内容属于系统启动与服务管理范畴,因与磁盘救援(单用户模式修 fstab)密切相关而在此保留原文。 详见 阶段02(系统启动流程与服务管理)

Linux 用运行级别(runlevel)表示系统的运行状态,不同级别启动不同的服务集合。

运行级别 含义 说明
0 关机(halt) 系统关闭。不能设为默认级别,否则一开机就关机
1 单用户模式(single user) 只有 root 可登录,不启动网络与多数服务。用于修复系统、重置 root 密码、修 fstab
2 多用户模式,无 NFS 多用户命令行模式,但不启动 NFS 网络文件系统
3 完整多用户模式(命令行) 标准的字符界面多用户模式,服务器最常用
4 保留未使用 系统保留,用户可自定义
5 图形界面(X11) 带图形桌面的多用户模式,桌面系统默认
6 重启(reboot) 系统重启。不能设为默认级别,否则会无限重启

CentOS 6 的运行级别管理

配置文件为 /etc/inittab:

# Default runlevel. The runlevels used are:
#   0 - halt (Do NOT set initdefault to this)
#   1 - Single user mode
#   2 - Multiuser, without NFS (The same as 3, if you do not have networking)
#   3 - Full multiuser mode
#   4 - unused
#   5 - X11
#   6 - reboot (Do NOT set initdefault to this)
#
id:5:initdefault:

其中 id:5:initdefault: 表示默认运行级别为 5(图形界面)。把 5 改成 3 则下次开机进入字符界面。

cat /etc/inittab                  # 查看默认运行级别配置
vi /etc/inittab                   # 修改 id:5:initdefault: 中的数字

runlevel                          # 查看当前运行级别
# N 5                             ← N 表示上一个级别不存在(开机后未切换过),5 是当前级别

init 3                            # 临时切换到运行级别 3(字符界面)
runlevel
# 5 3                             ← 上一个级别是 5,当前级别是 3

init 5                            # 切回图形界面
init 0                            # 关机(等同 halt、poweroff、shutdown -h now)
init 6                            # 重启(等同 reboot、shutdown -r now)
init 1                            # 切换到单用户模式

startx                            # 在字符界面下临时启动图形界面(不改变运行级别)

telinit 3                         # 与 init 等效
chkconfig --list                  # 查看各服务在各运行级别下的启停状态
chkconfig --level 35 httpd on     # 让 httpd 在级别 3 和 5 下自启动

通过 GRUB 临时修改运行级别(救急手段):开机在 GRUB 菜单界面按 e 编辑启动项,在 kernel 行末尾追加数字或关键字,然后按 b 启动:

kernel /vmlinuz-2.6.32 ro root=/dev/sda1  1            <- 进入单用户模式
kernel /vmlinuz-2.6.32 ro root=/dev/sda1  single       <- 同上
kernel /vmlinuz-2.6.32 ro root=/dev/sda1  3            <- 进入字符界面
kernel /vmlinuz-2.6.32 ro root=/dev/sda1  emergency    <- 紧急模式
kernel /vmlinuz-2.6.32 ro root=/dev/sda1  init=/bin/bash   <- 直接进 shell

这是"误把默认级别设为 0 或 6 导致开不了机"以及"忘记 root 密码"时的标准补救方式。

CentOS 7 的对应关系(systemd target)

CentOS 7 用 systemd 的 target 取代了运行级别,但保留了兼容映射:

运行级别 systemd target 说明
0 poweroff.target(runlevel0.target) 关机
1 rescue.target(runlevel1.target) 救援模式(相当于单用户)
2 multi-user.target(runlevel2.target) 多用户
3 multi-user.target(runlevel3.target) 多用户命令行
4 multi-user.target(runlevel4.target) 多用户
5 graphical.target(runlevel5.target) 图形界面
6 reboot.target(runlevel6.target) 重启
— emergency.target 紧急模式,只挂载根文件系统且为只读
systemctl get-default                        # 查看默认 target
systemctl set-default multi-user.target      # 设为字符界面(等同原级别 3)
systemctl set-default graphical.target       # 设为图形界面(等同原级别 5)
#   本质是创建符号链接:
ls -l /etc/systemd/system/default.target
ln -sf /lib/systemd/system/multi-user.target /etc/systemd/system/default.target

systemctl isolate multi-user.target          # 临时切换(等同 init 3)
systemctl isolate graphical.target           # 等同 init 5
systemctl isolate rescue.target              # 等同 init 1
systemctl rescue                             # 进入救援模式
systemctl emergency                          # 进入紧急模式

runlevel                                     # 仍然可用(兼容命令)
init 3                                       # 仍然可用,被映射到对应 target
who -r                                       # 查看运行级别

systemctl list-units --type=target           # 列出当前已激活的 target
systemctl list-dependencies multi-user.target  # 查看 target 的依赖服务树

/etc/inittab 在 CentOS 7 中仍然存在,但只是一个说明文件,修改它不起任何作用,文件内明确写着 "inittab is no longer used"。

附录 B:Linux 文件系统目录结构

本节列出根目录下各子目录的用途,与文件系统概念、分区规划直接相关。 详见 阶段01(Linux 基础与文件目录管理)

Linux 采用单一的树形目录结构,一切从根 / 开始,所有分区、外部设备都以"挂载"的形式挂在这棵树的某个节点上,没有 Windows 的 C:、D: 盘符概念。

目录 用途说明
/ 根目录,整个文件系统树的起点,所有目录和文件都在其下
/bin 基本命令的二进制文件(binary),所有用户都可执行,如 ls、cp、mv、cat、bash。系统启动和单用户模式修复时必需,因此不能单独分区
/sbin 系统管理命令(system binary),主要供 root 使用,如 fdisk、mkfs、ifconfig、init、reboot。同样不能单独分区
/boot 内核与引导文件,存放 vmlinuz(内核)、initramfs(初始内存文件系统)、GRUB 配置。常单独分一个 200MB~1GB 的分区
/dev 设备文件目录(device),Linux 把所有硬件抽象为文件,如 /dev/sda、/dev/null、/dev/tty。由 udev 动态管理,实际是 devtmpfs 内存文件系统
/etc 系统与应用的配置文件(etcetera),如 /etc/fstab、/etc/passwd、/etc/inittab、/etc/hosts。绝不能单独分区(启动时就要读取),且是备份的重中之重
/home 普通用户的家目录,每个用户对应一个子目录如 /home/tom。用户数据集中地,生产环境建议单独分区,便于配额管理和防止撑爆根分区
/root root 用户的家目录(注意与根目录 / 区分)。放在根分区上,保证单用户模式下可用
/lib 共享库文件(library),程序运行所依赖的动态链接库(.so)以及内核模块(/lib/modules)。不能单独分区
/lib64 64 位系统的 64 位共享库
/media 可移动介质的挂载点,U 盘、光驱、移动硬盘插入后由桌面环境自动挂载到此
/mnt 临时挂载点,管理员手工挂载文件系统时的习惯用目录
/opt 第三方大型软件的安装目录(optional),如 Oracle、MATLAB、自研应用。软件较多时可单独分区
/proc 虚拟文件系统(procfs),不占磁盘空间,是内核与进程信息在内存中的映射,如 /proc/cpuinfo、/proc/meminfo、/proc/partitions、/proc/mdstat、/proc/<pid>/
/sys 虚拟文件系统(sysfs),导出内核的设备与驱动信息,如 /sys/block/sda/、/sys/class/scsi_host/。磁盘热插拔扫描就靠它
/run 运行时数据(tmpfs 内存文件系统),存放 PID 文件、socket 等,每次开机重建。CentOS 7 引入,取代部分 /var/run
/srv 服务数据目录(service),存放网络服务对外提供的数据,如 Web、FTP 的数据(实际用得较少)
/tmp 临时文件目录,所有用户可读写(带 sticky 位),系统重启或定期清理。建议单独分区并加 nodev,nosuid,noexec 选项以提升安全性
/usr 应用程序与文件的主要存放地(Unix Software Resource),是系统中最大的目录之一。包含 /usr/bin、/usr/sbin、/usr/lib、/usr/local、/usr/share(文档、man 手册)、/usr/src(源码)、/usr/include(头文件)。类似 Windows 的 Program Files
/usr/local 管理员手工编译安装的软件存放地,与包管理器安装的内容隔开,便于维护
/var 经常变化的数据(variable),如日志 /var/log、邮件 /var/spool/mail、打印队列、数据库文件 /var/lib/mysql、缓存 /var/cache。最容易膨胀,生产环境强烈建议单独分区
/var/log 系统与应用日志,如 messages、secure、dmesg、boot.log。日志暴涨撑满根分区是最常见的故障
/lost+found ext 文件系统专有,fsck 修复时把找到的孤立文件(inode 存在但无目录项)放在此处。每个 ext 分区的根下都有一个
/selinux SELinux 相关的虚拟文件系统(CentOS 6;CentOS 7 移到 /sys/fs/selinux)

Windows 与 Linux 文件系统结构对比:

Windows:多根结构(每个分区一个盘符)
    C:\            D:\            E:\
    ├─Windows      ├─data         └─backup
    ├─Users        └─software
    └─Program Files

Linux:单根结构(所有分区挂载到同一棵树上)
    /
    ├── bin        ← 根分区
    ├── etc        ← 根分区
    ├── boot       ← 独立分区 /dev/sda1
    ├── home       ← 独立分区 /dev/sda3(或 LV)
    ├── var        ← 独立分区 /dev/sda5(或 LV)
    ├── data       ← 独立分区 /dev/sdb1
    └── mnt
        └── usb    ← U 盘挂载点 /dev/sdc1

文件名规则:Linux 文件名最长 255 个字符,路径全长最长 4096 字符;区分大小写(File.txt 与 file.txt 是两个不同的文件);除 /(路径分隔符)和 \0 外几乎所有字符都可用,但应避免空格、*、?、$、- 开头等会引起 shell 解析问题的字符;以 . 开头的文件为隐藏文件(ls -a 可见)。

目录与分区规划的关系:

目录 能否单独分区 原因
/bin、/sbin、/lib、/lib64、/etc、/dev ❌ 绝对不能 系统启动早期就必须访问,此时其它分区尚未挂载
/boot ✅ 推荐 隔离内核文件;旧 BIOS 有 1024 柱面限制;UEFI 需要 ESP
/home ✅ 推荐 隔离用户数据,便于配额和重装系统时保留数据
/var、/var/log ✅ 强烈推荐 日志与数据库最易膨胀,隔离后不会撑爆根分区
/tmp ✅ 推荐 防止临时文件占满根分区,可加安全挂载选项
/usr ⚠️ 可以但需谨慎 需要在 initramfs 中正确处理,配置复杂,一般不单独分
/opt、/data、/srv ✅ 可以 应用与业务数据,按需规划

附录 C:GRUB 启动引导

本节内容属于系统启动引导范畴,因与 MBR、/boot 分区、分区表恢复密切相关而在此完整保留原文。 详见 阶段02(系统启动流程与引导修复)

C.1 引导程序的种类

引导程序 适用范围 说明
grub (GRUB Legacy / grub 0.97) CentOS 5/6、RHEL 5/6 配置文件 /boot/grub/grub.conf(/boot/grub/menu.lst 为其符号链接),语法用 title/root (hd0,0)/kernel/initrd
grub2 CentOS 7+、Ubuntu 10+ 配置文件 /boot/grub2/grub.cfg,语法用 menuentry/set root/linux16/initrd16,功能强大但配置文件不建议手工编辑(由脚本生成)
grub4dos Windows 下的多重引导 常用于 U 盘启动盘、从硬盘安装系统、加载 memdisk 引导镜像
lilo 早期 Linux 已基本淘汰
syslinux / isolinux / pxelinux U 盘 / 光盘 / 网络启动 轻量引导器,memdisk 属于该家族
bootmgr / NTLDR Windows Windows 自己的引导管理器

C.2 GRUB 的磁盘与分区命名方式

GRUB 有一套独立于 Linux 的设备命名法,且从 0 开始计数:

grub  (GRUB Legacy):
    (hd0)        第 1 块硬盘
    (hd0,0)      第 1 块硬盘的第 1 个分区   → 对应 /dev/sda1
    (hd0,1)      第 1 块硬盘的第 2 个分区   → 对应 /dev/sda2
    (hd1,4)      第 2 块硬盘的第 5 个分区   → 对应 /dev/sdb5(第一个逻辑分区)
    (fd0)        软驱
    (cd)         光驱

grub2:
    (hd0,msdos1)  第 1 块硬盘的 MBR 第 1 个分区  → /dev/sda1
    (hd0,gpt1)    第 1 块硬盘的 GPT 第 1 个分区
    (hd0,1)       简写形式,grub2 中【分区从 1 开始编号】(与 GRUB Legacy 不同!)
    (hd1,msdos5)  第 2 块硬盘的第 1 个逻辑分区   → /dev/sdb5

关键差异:GRUB Legacy 的分区号从 0 开始,grub2 的分区号从 1 开始,磁盘号两者都从 0 开始。这是修改配置时最容易出错的地方。

# 在 grub 命令行中探查设备
grub> ls                          # 列出所有设备与分区
grub> ls (hd0,msdos1)/            # 列出该分区根目录的内容
grub> ls (hd0,msdos1)/boot        # 确认内核文件在哪个分区
grub> cat (hd0,msdos1)/etc/fstab  # 直接查看文件内容
grub> set                         # 显示所有变量
grub> echo $prefix
grub> insmod ls

C.3 grub2 配置文件与常用指令

/boot/grub2/grub.cfg 的核心指令:

set default="0"                    # 默认启动第 0 个菜单项
set timeout=5                      # 菜单等待 5 秒
set root=(hd0,msdos1)              # 设置 GRUB 的根(内核文件所在的分区)

menuentry 'CentOS Linux 7' {       # 一个启动菜单项
    insmod part_msdos              # 加载 MBR 分区支持模块
    insmod xfs                     # 加载 xfs 文件系统模块(读 /boot 用)
    insmod ext2                    # 加载 ext2/3/4 支持
    insmod gzio                    # 解压支持
    insmod lvm                     # LVM 支持(/boot 不能在 LVM 上,但根可以)
    insmod mdraid09                # 软 RAID 支持
    set root='hd0,msdos1'
    search --no-floppy --fs-uuid --set=root 1a2b3c4d-...     # 按 UUID 定位分区
    linux16 /vmlinuz-3.10.0-1160.el7.x86_64 root=/dev/mapper/centos-root ro quiet
    initrd16 /initramfs-3.10.0-1160.el7.x86_64.img
}

各指令含义:

指令 作用
set root= 指定 GRUB 去哪个分区找内核文件(不是 Linux 的根文件系统)
search --fs-uuid --set=root 按文件系统 UUID 查找分区并设为 root,比写死设备名更可靠
linux / linux16 / linuxefi 加载内核。linux16 用 16 位实模式引导(BIOS),linuxefi 用于 UEFI
initrd / initrd16 / initrdefi 加载初始内存文件系统
root=/dev/xxx 内核参数,告诉内核真正的根文件系统在哪(这个才决定 Linux 的 /)
ro / rw 根文件系统初始以只读/读写方式挂载
insmod 加载 GRUB 模块(分区格式、文件系统、加密、LVM、RAID 等)
boot 执行启动(在交互命令行中手工输入时必须以此结束)
chainloader +1 链式加载,把控制权交给该分区的引导记录(用于引导 Windows)
set gfxpayload=keep 保持图形模式

关机与重启菜单项:

menuentry "Reboot" {
    reboot
}
menuentry "Shutdown" {
    halt
}
menuentry "Firmware Setup (UEFI)" {
    fwsetup
}

C.4 grub 交互命令行手工引导(救急)

当 grub.cfg 损坏或配置错误时,GRUB 会掉到命令行提示符。有两种提示符:

  • grub>:GRUB 已完整加载(能找到 /boot/grub2 下的模块),功能齐全;
  • grub rescue>:GRUB 只加载了最基础的部分(找不到 normal.mod 等模块),需要先设置 prefix 并加载模块。

在 grub> 下手工引导 CentOS 7:

grub> ls                                        # 找出分区
grub> ls (hd0,msdos1)/                          # 确认哪个分区有 vmlinuz
grub> set root=(hd0,msdos1)
grub> linux16 /vmlinuz-3.10.0-1160.el7.x86_64 root=/dev/mapper/centos-root ro
grub> initrd16 /initramfs-3.10.0-1160.el7.x86_64.img
grub> boot

如果 /boot 是独立分区,内核路径就是 /vmlinuz-...;如果 /boot 在根分区上,路径要写成 /boot/vmlinuz-...。这是最常见的失败原因。

在 grub rescue> 下恢复:

grub rescue> ls
grub rescue> ls (hd0,msdos1)/boot/grub2         # 找到 grub2 目录所在
grub rescue> set prefix=(hd0,msdos1)/boot/grub2
grub rescue> set root=(hd0,msdos1)
grub rescue> insmod normal
grub rescue> normal                             # 切换到完整的 grub> 环境
grub> ...                                       # 之后按上面的方法手工引导

在 GRUB Legacy(CentOS 6)中手工引导:

grub> root (hd0,0)
grub> kernel /vmlinuz-2.6.32-754.el6.x86_64 ro root=/dev/sda2
grub> initrd /initramfs-2.6.32-754.el6.x86_64.img
grub> boot

进入系统后必须做的事:手工引导只是一次性的,进入系统后要重新生成配置并重装引导。

# CentOS 7
grub2-mkconfig -o /boot/grub2/grub.cfg          # 重新生成配置(BIOS)
grub2-mkconfig -o /boot/efi/EFI/centos/grub.cfg # UEFI
grub2-install /dev/sda                          # 重装引导程序到 MBR(BIOS 才需要)

# CentOS 6
grub-install /dev/sda
vi /boot/grub/grub.conf                         # CentOS 6 直接手工编辑

# Ubuntu/Debian
update-grub
grub-install /dev/sda

C.5 /boot 分区的内容

ls -l /boot
CentOS 7 的 /boot:
    config-3.10.0-1160.el7.x86_64           内核编译时的配置项
    grub2/                                  grub2 的配置与模块
        grub.cfg                            主配置文件
        grubenv                             保存 saved_entry 等环境变量
        i386-pc/                            BIOS 平台的模块(*.mod)
    initramfs-3.10.0-1160.el7.x86_64.img    初始内存文件系统(含驱动,用于挂载真正的根)
    initrd-plymouth.img                     启动画面
    symvers-*.gz                            内核符号表
    System.map-3.10.0-1160.el7.x86_64       内核符号地址映射
    vmlinuz-3.10.0-1160.el7.x86_64          【内核本体】(压缩的可引导内核)
    efi/                                    UEFI 引导文件(UEFI 系统)
    loader/                                 systemd-boot 相关

CentOS 6 的 /boot:
    grub/
        grub.conf                           主配置文件
        menu.lst -> grub.conf               符号链接
        device.map                          GRUB 设备名与 Linux 设备名的映射
        stage1 / stage2 / *_stage1_5         GRUB 的各阶段引导代码
    vmlinuz-2.6.32-754.el6.x86_64
    initramfs-2.6.32-754.el6.x86_64.img

为什么 /boot 常单独分区:

  1. 早期 BIOS 只能访问硬盘前 1024 柱面(约 8GB),内核必须放在这个范围内;
  2. 根分区使用 LVM、RAID 5/6、加密、btrfs 等复杂格式时,引导程序可能无法读取,而 /boot 用简单的 ext4/xfs 可确保被读到(/boot 不能放在 LVM 上,grub2 虽支持但不推荐);
  3. 便于隔离与备份,也方便多系统共用;
  4. UEFI 必须有一个 ESP(EFI System Partition),FAT32 格式,通常挂载在 /boot/efi,大小 200~500MB。

/boot 满了的处理(内核升级失败的最常见原因):

df -h /boot
rpm -qa kernel | sort -V                        # 列出已安装的内核
uname -r                                        # 当前正在用的内核(绝对不能删)
package-cleanup --oldkernels --count=2          # 保留 2 个最新内核,删除其余(yum-utils 包)
yum remove kernel-3.10.0-862.el7                # 手工删除指定旧内核
#   永久限制保留数量:/etc/yum.conf 中设置 installonly_limit=2
grub2-mkconfig -o /boot/grub2/grub.cfg          # 删完重新生成菜单

C.6 引导修复:chroot 方式

系统装了 Windows 后 MBR 被覆盖、或误删了 GRUB,标准修复流程是用光盘/U 盘启动到救援环境,chroot 进原系统后重装引导。

# ===== CentOS 7 =====
# 1. 用安装盘启动 → Troubleshooting → Rescue a CentOS system → 1) Continue
#    安装程序会自动把原系统挂载到 /mnt/sysimage
chroot /mnt/sysimage

# 2. 重装引导并重建配置
grub2-install /dev/sda                          # BIOS 模式,写入 MBR
grub2-mkconfig -o /boot/grub2/grub.cfg
#    UEFI 模式(不需要 grub2-install,只需重装包并重建配置)
yum reinstall grub2-efi grub2-efi-modules shim -y
grub2-mkconfig -o /boot/efi/EFI/centos/grub.cfg

# 3. 若 initramfs 也损坏
dracut -f --kver $(uname -r)
dracut -f /boot/initramfs-$(uname -r).img $(uname -r)

# 4. 退出重启
exit
reboot

# ===== 手工挂载的通用做法(用任意 Live CD)=====
mkdir /mnt/sysroot
mount /dev/mapper/centos-root /mnt/sysroot      # 先挂根
mount /dev/sda1 /mnt/sysroot/boot               # 再挂 /boot
mount /dev/sda2 /mnt/sysroot/boot/efi           # UEFI 还要挂 ESP
mount --bind /dev  /mnt/sysroot/dev             # 关键:绑定这四个虚拟文件系统
mount --bind /proc /mnt/sysroot/proc
mount --bind /sys  /mnt/sysroot/sys
mount --bind /run  /mnt/sysroot/run
chroot /mnt/sysroot
grub2-install /dev/sda && grub2-mkconfig -o /boot/grub2/grub.cfg
exit
umount -R /mnt/sysroot
reboot

# ===== CentOS 6 =====
chroot /mnt/sysimage
grub-install /dev/sda
#   或在 grub 交互命令行中:
grub
grub> root (hd0,0)
grub> setup (hd0)                               # 把 GRUB 写入 hd0 的 MBR
grub> quit

C.7 多系统引导配置示例

一台机器同时装了 CentOS 7 与 Windows 7 时的 grub.cfg 片段:

set default=0
set timeout=10
set gfxpayload=keep

### Linux 启动项 ###
menuentry 'CentOS Linux 7 (3.10.0-1160.el7.x86_64)' {
    load_video
    set gfxpayload=keep
    insmod gzio
    insmod part_msdos
    insmod xfs
    set root='hd0,msdos1'
    search --no-floppy --fs-uuid --set=root 1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d
    linux16 /vmlinuz-3.10.0-1160.el7.x86_64 root=/dev/mapper/centos-root ro rhgb quiet
    initrd16 /initramfs-3.10.0-1160.el7.x86_64.img
}

### Windows 启动项(用 chainloader 链式加载)###
menuentry "Windows 7" {
    insmod part_msdos
    insmod ntfs
    insmod ntldr
    set root='hd0,msdos3'
    search --no-floppy --fs-uuid --set=root --hint='hd0,msdos3' 1A2B3C4D5E6F7A8B
    ntldr /bootmgr
}

### 另一种 Windows 写法 ###
menuentry "Windows 7 (chainloader)" {
    insmod chain
    insmod part_msdos
    insmod ntfs
    set root=(hd0,msdos3)
    chainloader +1
}

### UEFI 下的 Windows ###
menuentry "Windows Boot Manager (UEFI)" {
    insmod part_gpt
    insmod fat
    insmod chain
    set root=(hd0,gpt1)
    chainloader /EFI/Microsoft/Boot/bootmgfw.efi
}

### 关机与重启 ###
menuentry "Reboot" { reboot }
menuentry "Shutdown" { halt }

自动探测其它系统(比手写更省事):

yum install os-prober -y                        # 安装后 grub2-mkconfig 会自动探测 Windows
os-prober                                       # 手工执行看能探测到什么
grub2-mkconfig -o /boot/grub2/grub.cfg

注意:先装 Windows 再装 Linux 更省事,因为 Windows 安装程序会无条件覆盖 MBR 并且不认识 Linux;Linux 的 GRUB 则能引导 Windows。若顺序反了,需按 C.6 修复引导。

C.8 修改默认启动项与菜单持久化

# 1. 查看当前所有菜单项及其序号
grep -E '^menuentry' /boot/grub2/grub.cfg | cut -d"'" -f2
awk -F\' '/^menuentry/ {print i++ " : " $2}' /boot/grub2/grub.cfg
grubby --default-index                          # 当前默认项序号
grubby --default-kernel                         # 当前默认内核
grubby --info=ALL                               # 全部启动项详情

# 2. 按序号设置默认启动项
grub2-set-default 0                             # 第 1 项
grub2-set-default 2
# 按标题设置(更直观,标题必须与菜单里的完全一致)
grub2-set-default "Windows 7"
grub2-set-default "CentOS Linux 7 (3.10.0-1160.el7.x86_64)"

# 3. 查看设置结果(存放在 /boot/grub2/grubenv 中)
grub2-editenv list
# saved_entry=Windows 7
cat /boot/grub2/grubenv
grub2-editenv - unset saved_entry               # 清除

# 4. 只让【下一次】启动进入某项(一次性,重启后恢复原默认)
grub2-reboot 1
grub2-reboot "Windows 7"
grub2-editenv - set next_entry=1

# 5. 让 saved_entry 生效的前提:/etc/default/grub 中必须是 saved
vi /etc/default/grub
GRUB_TIMEOUT=5
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="crashkernel=auto rhgb quiet"
GRUB_DISABLE_RECOVERY="true"
GRUB_SAVEDEFAULT=true
# 改完 /etc/default/grub 后必须重新生成 grub.cfg 才生效
grub2-mkconfig -o /boot/grub2/grub.cfg          # BIOS
grub2-mkconfig -o /boot/efi/EFI/centos/grub.cfg # UEFI

GRUB_DEFAULT 的取值:

取值 含义
0、1、2 … 固定按序号启动第 N 项
saved 使用 grub2-set-default 保存的值(配合 grub2-set-default 使用时必须设为此值)
"CentOS Linux 7 ..." 按菜单标题固定
>0>1 子菜单中的项(第 0 个子菜单下的第 1 项)

自定义菜单项的持久化:/etc/grub.d/40_custom

直接编辑 /boot/grub2/grub.cfg 添加的菜单项,会在下次执行 grub2-mkconfig(如内核升级时自动触发)后被完全覆盖丢失。要让自定义项持久保留,必须写入 /etc/grub.d/40_custom:

vi /etc/grub.d/40_custom
#!/bin/sh
exec tail -n +3 $0
# 这一行以上的内容不要修改
# 下面添加自定义菜单项,它们会被原样追加到 grub.cfg 的末尾

menuentry "Windows 7" {
    insmod part_msdos
    insmod ntfs
    set root=(hd0,msdos3)
    chainloader +1
}

menuentry "Reboot" { reboot }
menuentry "Shutdown" { halt }
# 【关键】该文件必须有可执行权限,否则 grub2-mkconfig 会跳过它
chmod +x /etc/grub.d/40_custom
ls -l /etc/grub.d/                               # 所有脚本都应是 755
grub2-mkconfig -o /boot/grub2/grub.cfg           # 重新生成
grep -A5 'Windows 7' /boot/grub2/grub.cfg        # 验证已写入

/etc/grub.d/ 下各脚本的作用(按文件名数字顺序执行):

脚本 作用
00_header 生成头部:timeout、default、显示模式等
01_users GRUB 菜单密码保护相关
10_linux 扫描 /boot 下的内核,生成 Linux 启动项
20_linux_xen Xen 虚拟化启动项
20_ppc_terminfo PowerPC 终端
30_os-prober 探测其它操作系统(Windows 等)并生成启动项
40_custom 用户自定义菜单项(推荐在此添加)
41_custom 读取 /boot/grub2/custom.cfg 的内容

给 GRUB 菜单加密码保护(防止他人用单用户模式绕过 root 密码):

grub2-mkpasswd-pbkdf2                            # 输入密码,得到 grub.pbkdf2.sha512.xxx 串
vi /etc/grub.d/40_custom
#   set superusers="admin"
#   password_pbkdf2 admin grub.pbkdf2.sha512.10000.XXXX...
grub2-mkconfig -o /boot/grub2/grub.cfg
# CentOS 6 的做法:
grub-md5-crypt                                   # 生成 md5 串
#   在 /boot/grub/grub.conf 的 title 行之前加:password --md5 $1$xxxx

C.9 BIOS 与 UEFI 的差异对照

项目 BIOS(Legacy) UEFI
分区表 通常搭配 MBR(也可 GPT) 必须搭配 GPT
引导代码位置 硬盘的 MBR 前 446 字节 + 后续扇区 ESP 分区(FAT32)内的 .efi 文件
磁盘容量上限 MBR 限制 2TB 8 ZiB
grub2 配置文件路径 /boot/grub2/grub.cfg /boot/efi/EFI/centos/grub.cfg
grub2 模块目录 /boot/grub2/i386-pc/ /boot/efi/EFI/centos/ + /usr/lib/grub/x86_64-efi/
内核加载指令 linux16 / initrd16 linuxefi / initrdefi
安装引导命令 grub2-install /dev/sda 不用 grub2-install,重装 grub2-efi+shim 包后 grub2-mkconfig
必需的特殊分区 GPT+BIOS 时需 BIOS Boot Partition(1~2MB,类型 ef02,无文件系统) ESP(200~500MB,FAT32,类型 ef00,挂载 /boot/efi)
安全启动 不支持 支持 Secure Boot(需 shim 签名)
启动项管理 只能靠引导程序 固件中有 NVRAM 启动项,用 efibootmgr 管理
# 判断当前系统是 BIOS 还是 UEFI 启动
[ -d /sys/firmware/efi ] && echo "UEFI" || echo "BIOS"
ls /sys/firmware/efi
dmesg | grep -i efi
efibootmgr -v                                    # UEFI 下列出固件启动项

# UEFI 启动项管理
efibootmgr -v                                    # 查看
efibootmgr -o 0002,0000,0001                     # 调整启动顺序
efibootmgr -b 0003 -B                            # 删除编号为 0003 的启动项
efibootmgr -c -d /dev/sda -p 1 -L "CentOS" -l '\EFI\centos\shimx64.efi'   # 新建启动项

# ESP 分区的典型内容
ls -R /boot/efi/EFI/
#   EFI/BOOT/BOOTX64.EFI              通用后备引导文件
#   EFI/centos/shimx64.efi            签名的第一阶段引导(Secure Boot 用)
#   EFI/centos/grubx64.efi            grub2 主体
#   EFI/centos/grub.cfg               grub2 配置
#   EFI/Microsoft/Boot/bootmgfw.efi   Windows 引导管理器

GPT + BIOS 的特殊要求:在 BIOS 模式下用 GPT 分区表引导 Linux,必须额外创建一个 1~2MB 的 BIOS Boot Partition(parted 中设 set N bios_grub on,gdisk 中类型码 ef02),用来存放 GRUB 的 core.img。否则 grub2-install 会报 "embedding area is unusually small" 而失败。

# 用 parted 创建 BIOS Boot Partition
parted /dev/sda mkpart primary 1MiB 3MiB
parted /dev/sda set 1 bios_grub on
parted /dev/sda print                            # Flags 列显示 bios_grub
# 用 sgdisk
sgdisk -n 1:2048:+2M -t 1:ef02 -c 1:"BIOS boot" /dev/sda

附录 D:从硬盘安装操作系统

本节内容属于系统安装范畴,因涉及分区、引导、loop 挂载而在此完整保留原文。 详见 阶段01(Linux 系统安装与初始化)

在没有光驱、没有 U 盘的情况下,可以把安装镜像(ISO)放在硬盘的某个分区上,通过 GRUB 直接引导安装程序。这是运维现场常用的技巧。

通用前提条件:

  1. ISO 文件放在一个能被引导程序读取的分区上(ext4、xfs、NTFS、FAT32 均可,FAT32 注意单文件 4GB 上限);
  2. 记住该分区在 GRUB 中的编号((hd0,msdos1))和在 Linux 中的设备名(/dev/sda1);
  3. 从 ISO 中把 vmlinuz(内核) 和 initrd.img(初始内存文件系统) 提取到 /boot 或该分区根目录,因为 GRUB 需要先加载这两个文件才能启动安装程序;
  4. 安装目标分区不能是存放 ISO 的那个分区(会被格式化)。

D.1 安装 CentOS 7

# 1. 把 ISO 放到某个分区,例如 /dev/sda1 的根目录
cp CentOS-7-x86_64-DVD-2009.iso /boot/          # 或放在 /dev/sda1 挂载点的根下

# 2. 从 ISO 中提取内核与 initrd
mount -o loop CentOS-7-x86_64-DVD-2009.iso /mnt
ls /mnt/isolinux/                                # vmlinuz、initrd.img 在这里
cp /mnt/isolinux/vmlinuz /boot/vmlinuz-install
cp /mnt/isolinux/initrd.img /boot/initrd-install.img
umount /mnt

# 3. 添加 GRUB 菜单项
vi /etc/grub.d/40_custom
menuentry "Install CentOS 7 from HDD" {
    set root=(hd0,msdos1)
    linux /vmlinuz-install repo=hd:/dev/sda1:/ quiet
    initrd /initrd-install.img
}
chmod +x /etc/grub.d/40_custom
grub2-mkconfig -o /boot/grub2/grub.cfg
reboot                                           # 重启后选择该菜单项

关键内核参数 repo=(CentOS 7):

repo=hd:/dev/sda1:/                    ISO 位于 /dev/sda1 分区的【根目录】
repo=hd:/dev/sda1:/iso/                ISO 位于 /dev/sda1 的 iso 子目录
repo=hd:UUID=xxxx-xxxx:/               用 UUID 指定分区(设备名不确定时更可靠)
repo=hd:LABEL=DATA:/                   用卷标指定
inst.repo=hd:/dev/sda1:/               CentOS 7.x 新写法(inst. 前缀)
inst.stage2=hd:/dev/sda1:/             指定 stage2 镜像位置
inst.ks=hd:/dev/sda1:/ks.cfg           自动化安装的 kickstart 文件
linux dd                               进入驱动加载模式

CentOS 6 的对应参数写法为 repo=hd:/dev/sda1:/ 或旧式的 method=hd:/dev/sda1:/;CentOS 6 在安装界面中也可选择 "Hard drive" 作为安装源,然后手工指定分区和目录。

D.2 安装 Linux Mint 19 / Ubuntu(loopback 方式)

Debian 系的安装镜像使用 casper(Live 系统),参数与 CentOS 不同,用 iso-scan/filename 指定 ISO 路径。

# 1. 把 ISO 放到分区上,例如 /dev/sda1 的根目录
# 2. 从 ISO 中提取 casper 目录下的内核与 initrd
mount -o loop linuxmint-19-cinnamon-64bit.iso /mnt
ls /mnt/casper/                                  # vmlinuz、initrd.lz
cp /mnt/casper/vmlinuz /boot/vmlinuz-mint
cp /mnt/casper/initrd.lz /boot/initrd-mint.lz
umount /mnt

# 3. GRUB 菜单项(用 loopback 模块直接把 ISO 当作设备挂载)
vi /etc/grub.d/40_custom
menuentry "Install Linux Mint 19 from HDD" {
    insmod part_msdos
    insmod ext2
    insmod loopback
    insmod iso9660
    set isofile="/linuxmint-19-cinnamon-64bit.iso"
    set root=(hd0,msdos1)
    loopback loop $isofile
    linux (loop)/casper/vmlinuz boot=casper iso-scan/filename=$isofile noeject noprompt splash --
    initrd (loop)/casper/initrd.lz
}
chmod +x /etc/grub.d/40_custom
grub2-mkconfig -o /boot/grub2/grub.cfg

关键参数说明:

参数 作用
loopback loop $isofile 把 ISO 文件映射为 GRUB 的 (loop) 设备,之后可直接读取其内部文件
boot=casper 告诉 initrd 使用 casper 引导 Live 系统
iso-scan/filename= 最关键:让 casper 在各分区上搜索这个 ISO 文件并挂载为安装源
noeject noprompt 安装结束时不提示弹出介质
splash 显示启动画面
-- 参数结束标记

Ubuntu 的 ISO 内核路径可能是 /casper/vmlinuz.efi、initrd 可能是 /casper/initrd(不带 .lz),需先用 ls (loop)/casper/ 在 GRUB 命令行中确认实际文件名。

D.3 安装 Windows 7(memdisk + ghost 镜像方式)

Windows 的安装程序无法像 Linux 那样直接从 ISO 引导,需要借助 memdisk(syslinux 家族的工具)把镜像整体加载到内存中模拟为软驱/硬盘,或使用 ghost 镜像还原。

# 1. 准备文件(放在 /boot 或某个分区根目录)
#    memdisk          —— 来自 syslinux 包:/usr/share/syslinux/memdisk
#    ghost.img        —— 制作好的 DOS 启动镜像,内含 Ghost 程序
#    win7.gho         —— Windows 7 的 ghost 备份文件
yum install syslinux -y
cp /usr/share/syslinux/memdisk /boot/
cp ghost.img /boot/
#   win7.gho 放在数据分区上,如 /dev/sda5 的根目录

# 2. GRUB 菜单项:用 memdisk 加载 DOS 镜像
vi /etc/grub.d/40_custom
menuentry "Ghost for Windows 7 (memdisk)" {
    insmod part_msdos
    insmod ext2
    set root=(hd0,msdos1)
    linux16 /memdisk
    initrd16 /ghost.img
}

menuentry "DOS Tools (memdisk floppy image)" {
    set root=(hd0,msdos1)
    linux16 /memdisk floppy
    initrd16 /dos.img
}

menuentry "Windows 7 Installer (bootmgr)" {
    insmod ntfs
    set root=(hd0,msdos3)
    ntldr /bootmgr
}
chmod +x /etc/grub.d/40_custom
grub2-mkconfig -o /boot/grub2/grub.cfg

memdisk 的常用参数:

参数 作用
floppy 把镜像模拟为软驱(A:),适合 1.44MB / 2.88MB 的 DOS 启动盘
harddisk 模拟为硬盘(C:)
iso 模拟为光驱(配合可引导 ISO)
raw 原始模式,不做几何结构转换
bigraw 用于大镜像
c=N h=N s=N 手工指定柱面/磁头/扇区数

进入 DOS 后的 Ghost 操作:

1. 启动 Ghost(ghost.exe)
2. Local → Partition → From Image
3. 选择镜像文件:找到 win7.gho(在 1:5 之类的分区里,Ghost 用自己的编号)
4. 选择源分区(镜像中的分区)
5. 选择目标磁盘与目标分区(务必看清容量与卷标,选错会覆盖数据!)
6. 确认 Yes 开始还原
7. 还原完成后重启

还原后必须修复 Windows 引导(否则 Windows 起不来):

方式一:用 Windows 安装盘/PE 的"修复计算机"功能
方式二:在 PE 或 DOS 下执行
    bootsect /nt60 C: /mbr          写入 Windows 7 的引导代码
    bcdboot C:\Windows /s C:        重建 BCD 引导配置
方式三:用 NTBOOTautofix、BOOTICE 等第三方工具自动修复

注意:bootsect /nt60 C: /mbr 会覆盖 MBR 的引导代码,导致 GRUB 被冲掉、Linux 无法启动。修完 Windows 后需要按附录 C.6 的方法重装 GRUB。

D.4 三种方式的对比与选择

方式 适用系统 关键参数/工具 难点
repo=hd: CentOS / RHEL / Fedora repo= 或 inst.repo= 需提取 isolinux/vmlinuz 与 initrd.img
loopback + iso-scan Ubuntu / Mint / Debian boot=casper iso-scan/filename= 内核与 initrd 在 /casper/ 下,文件名版本间有差异
memdisk Windows / DOS / 各类工具盘 linux16 /memdisk + initrd16 镜像 镜像整体载入内存,受内存大小限制;还原后需修 Windows 引导

通用排错要点:

  1. file not found:GRUB 的分区编号写错(记住 grub2 分区从 1 开始),用 grub> ls 逐个确认;
  2. 加载内核后卡住或找不到安装源:repo= / iso-scan/filename= 的路径写错,或分区设备名变了(改用 UUID= 或 LABEL=);
  3. Kernel panic:内核与 initrd 版本不匹配(必须来自同一个 ISO);
  4. 提取的 vmlinuz 无法引导:BIOS 模式要用 linux16/initrd16,UEFI 模式要用 linuxefi/initrdefi;
  5. 安装过程中不要格式化存放 ISO 的分区,否则安装源会中途消失导致失败;
  6. 装完 Linux 后,/etc/grub.d/40_custom 里的安装项应删除并重新 grub2-mkconfig。

附录 E:命令速查总表

命令 用途说明
lsblk 树形列出所有块设备、容量、类型与挂载点(-f 带文件系统与 UUID)
fdisk -l 列出所有磁盘的分区表;fdisk /dev/sdb 交互式分区(MBR,CentOS 7 也支持 GPT)
parted 交互/脚本式分区工具,支持 GPT 与 2TB 以上磁盘,操作即时写入
gdisk / sgdisk 专用于 GPT 的分区工具,交互界面与 fdisk 类似
partprobe 通知内核重新读取分区表
partx -a/-d/-u 逐个添加/删除/更新分区到内核(比 partprobe 更细粒度)
kpartx -av 映射设备/镜像文件内的分区
sfdisk -d 导出/导入分区表(备份分区结构的脚本化手段)
mkfs / mkfs.ext4 / mkfs.xfs / mkfs.vfat / mkfs.ntfs 创建文件系统(格式化)
mke2fs ext 系列格式化的底层命令,选项最全
mount / umount 挂载/卸载文件系统;mount -a 挂载 fstab 中所有条目
mount -o loop 挂载 ISO 镜像或大文件模拟的磁盘
mount --bind 把一个目录挂载到另一个位置(可用于探查被覆盖的挂载点)
findmnt 树形显示挂载关系;--verify 校验 fstab(CentOS 7)
blkid 查看各分区的 UUID、LABEL、TYPE
df -h / df -i / df -T 查看空间用量 / inode 用量 / 文件系统类型
du -sh / du -h --max-depth=1 统计目录占用;逐层定位大目录
lsof / fuser 查找占用文件或挂载点的进程(解决 device is busy)
lsof +L1 列出已删除但仍被占用的文件(df 与 du 不一致的元凶)
e2label / xfs_admin -L 设置 ext / xfs 的卷标
tune2fs 调整 ext 文件系统参数(-l 查看、-m 预留比例、-U 改 UUID、-c/-i 自检周期、-L 卷标)
dumpe2fs -h 查看 ext 文件系统的超级块与详细信息
xfs_info 查看 xfs 文件系统信息(参数是挂载点)
xfs_admin -U/-L/-u/-l 修改/查看 xfs 的 UUID 与卷标(需卸载)
fsck / e2fsck -f -y 检查修复文件系统(ext);必须在未挂载状态下操作
xfs_repair 修复 xfs(-n 只检查,-L 清空日志、会丢数据)
resize2fs 调整 ext 文件系统大小(参数是设备名,可增可减)
xfs_growfs 扩大 xfs 文件系统(参数是挂载点,只能增大)
xfs_fsr / e4defrag xfs / ext4 碎片整理
filefrag 查看文件的碎片与 extent 分布
mkswap / swapon / swapoff 创建、启用、停用交换空间;swapon -s 查看,swapon -p 设优先级
free -m 查看内存与 swap 使用情况
dd 底层块复制:备份 MBR、创建 swap 文件、整盘克隆、性能测试
head -c / truncate / fallocate 创建指定大小的文件(用于 loop 设备或 swap 文件)
losetup -a/-P/-d 管理 loop 设备
pvcreate / pvs / pvdisplay / pvscan / pvremove 物理卷(PV)的创建、查看、删除
pvmove 在线把 PV 上的数据迁移到其它 PV(换盘不停机的关键命令)
vgcreate -s / vgs / vgdisplay / vgscan / vgremove 卷组(VG)管理,-s 指定 PE 大小
vgextend / vgreduce 向 VG 添加/移除 PV;vgreduce --removemissing 处理丢失的 PV
vgchange -ay/-an 激活/停用 VG
vgcfgbackup / vgcfgrestore 备份/恢复 LVM 元数据(/etc/lvm/backup、/etc/lvm/archive)
vgexport / vgimport 卷组的导出与导入(迁移磁盘到另一台主机)
lvcreate -L/-l/-n/-s/-i/-m 创建逻辑卷、快照、条带卷、镜像卷
lvs / lvdisplay / lvscan / lvremove 逻辑卷的查看与删除
lvextend -L/-l/-r 扩大 LV(-r 同时扩文件系统)
lvreduce -L/-r 缩小 LV(危险,必须先缩文件系统)
lvresize -r 可增可减的统一命令(-r 自动处理文件系统)
lvconvert --merge 把快照合并回源卷(回滚)
lvconvert -m1 把线性卷转为镜像卷
mdadm -C/-D/-E/-a/-r/-f/-S/-A/-G 软 RAID 的创建、查看、添加、移除、标记故障、停止、组装、扩容
mdadm -Ds > /etc/mdadm.conf 保存 RAID 配置(不做这步重启后阵列名会变)
cat /proc/mdstat 查看 RAID 状态与同步/重建进度
mdadm --zero-superblock 清除成员盘上的 RAID 超级块(彻底删除阵列必需)
wipefs -a 擦除设备上的所有文件系统/RAID/分区表签名
cryptsetup luksFormat/luksOpen/luksClose/luksDump LUKS 磁盘加密的格式化、打开、关闭、查看
cryptsetup luksHeaderBackup 备份 LUKS 头部(头部丢失等于数据永久丢失)
quotacheck -cugv 初始化 ext 的配额数据库
quotaon / quotaoff 启用/停用配额
edquota / setquota 交互式/非交互式设置配额;edquota -t 设宽限期,edquota -p 复制模板
quota / repquota -a 查看用户配额 / 输出配额报表
warnquota 给超限用户发送邮件告警
xfs_quota -x -c xfs 的配额管理(limit、report、state、timer、project)
dump / restore ext 文件系统的分级备份与恢复(0~9 级)
xfsdump / xfsrestore xfs 的分级备份与恢复(-l 级别、-L 会话标签、-f 目标、-I 查看清单)
tar / rsync -avHAX 通用的文件级备份(跨文件系统类型,最常用)
badblocks -sv 检测磁盘坏道(-n 非破坏性读写、-w 破坏性写测试)
smartctl -H/-A/-a/-t 读取 SMART 信息、判断磁盘健康、发起自检
hdparm -tT/-I 磁盘读取性能测试与参数查询
iostat -x 2 磁盘 I/O 统计(关注 %util、await、avgqu-sz)
iotop -o 实时查看各进程的 I/O 占用
fio 专业 I/O 压测工具(可指定读写模型、块大小、并发、队列深度)
fstrim -av 对 SSD / 精简置备存储执行 TRIM 回收
lsscsi / rescan-scsi-bus.sh 列出 SCSI 设备 / 重新扫描 SCSI 总线
echo '- - -' > /sys/class/scsi_host/hostX/scan 在线扫描新添加的磁盘
echo 1 > /sys/block/sdX/device/rescan 刷新磁盘容量(虚拟磁盘扩容后使用)
growpart 在线扩大分区(cloud-utils-growpart 包)
grub2-mkconfig -o / grub2-install 重新生成 GRUB 配置 / 重装引导程序
grub2-set-default / grub2-editenv list / grub2-reboot 设置默认启动项 / 查看 / 一次性指定下次启动项
dracut -f 重建 initramfs(改了 RAID/LVM/加密配置后必做)
efibootmgr -v 管理 UEFI 固件中的启动项

附录 F:注意事项与最佳实践

  1. 分区、格式化、缩容都是高危操作,动手前必须确认设备名。 fdisk /dev/sdb 与 fdisk /dev/sda 只差一个字母,后者可能直接摧毁系统盘。执行前用 lsblk、fdisk -l、df -h 三重确认,虚拟机上先做快照。

  2. parted 是即时写入的,没有 fdisk 的 w 确认环节。 一旦回车,mklabel、rm、mkpart 立刻生效且不可撤销。生产环境用 parted 时格外小心,脚本化操作前先在测试机验证。

  3. mkfs 之前务必确认分区上没有数据。 格式化不会询问,也没有回收站。养成 blkid、mount | grep、lsof 三查的习惯。

  4. 改完 /etc/fstab 必须立即执行 mount -a(CentOS 7 再加 findmnt --verify)验证。 报错就说明重启会出问题。改之前先 cp /etc/fstab /etc/fstab.bak.$(date +%F)。非关键数据盘一律加 nofail。

  5. 挂载点要用 UUID 或 LABEL,不要用 /dev/sdb1。 设备名会随磁盘发现顺序变化,可能导致挂载到错误的分区。LVM 设备名(/dev/mapper/vg-lv)是稳定的,可以直接用。

  6. 扩容与缩容的顺序是相反的,记错就丢数据:

    • 扩容:先扩底层(lvextend/resizepart),再扩文件系统(resize2fs/xfs_growfs);
    • 缩容:先缩文件系统(resize2fs),再缩底层(lvreduce);
    • 用 -r 选项(lvextend -r / lvreduce -r / lvresize -r)让工具自动按正确顺序处理,可有效避免人为失误。
  7. xfs 不能缩小,任何版本都不行。 CentOS 7 默认用 xfs,规划时就要考虑清楚:LV 初始只分配所需容量,靠后续在线扩容,从根本上避免缩容需求。确实需要频繁伸缩则选 ext4 或 LVM thin。

  8. fsck、e2fsck、xfs_repair 必须在文件系统未挂载时执行。 对已挂载(尤其是读写挂载)的文件系统执行修复,会造成更严重的损坏。根分区需要修复时,用救援模式或在 fstab 第 6 列设 1/2 让开机自动检查。

  9. RAID 不是备份。 RAID 只能防硬盘物理故障,不能防误删除、误格式化、勒索病毒、逻辑错误、机房灾难。备份必须独立于生产存储,并且要定期做恢复演练——没验证过能恢复的备份等于没有备份。

  10. 创建 RAID 后必须 mdadm -Ds > /etc/mdadm.conf,否则重启后阵列可能被识别为 /dev/md127,导致 fstab 挂载失败。同时启用 mdmonitor 并配置 MAILADDR,否则盘坏了没人知道,等第二块盘也坏就来不及了。

  11. LUKS 的口令与头部都必须妥善保管。 口令全部遗忘 = 数据永久丢失;头部(前 1~2MB)被覆盖 = 数据永久丢失。务必 luksHeaderBackup 并异地保存备份文件(该文件含密钥材料,需 chmod 400 严格保护)。

  12. dd 是双刃剑,if 与 of 写反就是灾难。 备份 MBR 是 if=/dev/sda of=./mbr.bak,恢复是 if=./mbr.bak of=/dev/sda。执行前把命令读三遍,尤其注意 of= 后面跟的是不是你想写入的目标。

  13. 不要直接 rm 正在被写入的日志文件。 会导致空间不释放(lsof +L1 可见)且服务丢失日志句柄。正确做法是 > file 清空,或交给 logrotate 管理。

  14. /boot 要留足空间并定期清理旧内核。 /boot 满会导致内核升级失败、dracut 无法生成 initramfs。用 package-cleanup --oldkernels --count=2 清理,并在 /etc/yum.conf 设 installonly_limit=2。

  15. /var、/var/log、/home、/tmp 生产环境建议单独分区或做成 LV。 根分区被撑满会导致系统性故障(无法登录、服务无法启动、sudo 失效)。/bin、/sbin、/lib、/etc、/dev 绝对不能单独分区。

  16. 配置磁盘空间与 inode 双重监控。 df -h 正常但 df -i 满的情况极易被忽略,报错同样是 No space left on device。

  17. 4K 对齐会显著影响性能。 用 parted 的百分比方式或 align-check optimal 验证;现代 fdisk/parted 默认从 2048 扇区开始,一般已对齐,但从旧系统迁移或手工指定起始扇区时要复核。

  18. 虚拟机磁盘扩容需要刷三层:设备容量(echo 1 > /sys/block/sdX/device/rescan)→ 分区(growpart/parted resizepart)→ 文件系统(xfs_growfs/resize2fs)。少一层就会出现"磁盘变大了但 df 没变"的困惑。

  19. 克隆虚拟机或整盘 dd 后,必须处理 UUID 冲突。 两块 UUID 相同的盘会导致挂载结果不可预测。用 xfs_admin -U generate / tune2fs -U random / pvchange -u / vgchange -u 重新生成。

  20. 重要变更前后都要留痕:变更前备份配置文件(fstab、mdadm.conf、lvm.conf)与分区表(sfdisk -d、sgdisk --backup、dd MBR),变更后记录 lsblk -f、pvs/vgs/lvs、cat /proc/mdstat 的输出,出问题时才有对照基准。

最后修改:2026 年 09 月 20 日
如果觉得我的文章对你有用,请随意赞赏