
上周给一台物理机扩容数据盘fdisk /dev/sdb分完区之后lsblk里死活不出现sdb1。partprobe直接报Device or resource busy大半夜重启生产机器又不甘心。最后把我捞出来的是 util-linux 里一个平时不怎么起眼的命令partx。partx的定位很像“内核和磁盘分区表之间的对账员”。磁盘上明明有分区表内核却不一定认识内核认识的分区磁盘上也可能早就变了。它负责把两边同步起来。这篇文章我会围绕 8 个实际例子把partx的查询、添加、更新、删除、脚本化输出、镜像处理、范围限定这些主要用法全部过一遍顺便把我踩过的坑和排查思路一起写出来。不管你是刚接触 Linux 的新人还是和我一样经常被磁盘热插拔、克隆盘、镜像挂载折腾的人这篇应该都能直接拿来用。1. Partx到底在做什么先把“内核分区表”和“磁盘分区表”这对概念拆开1.1 Partx的定位一个“对账”工具磁盘上有一份分区表也就是你用fdisk、sgdisk、parted写进磁盘的 MBR 或 GPT。这份数据躺在磁盘扇区里不依赖系统状态单独一块盘也能解析。内核里也有一份“分区表视图”。当磁盘被系统识别后内核会把读到的分区信息记录在/sys/block/sdb/sdb1/partition这类路径下随后 udev 根据这些信息生成/dev/sdb1这样的块设备节点。日常操作中这两份表默认是一致的——但有几个典型场景会把它们拆散你用fdisk改了磁盘上的分区表但没通知内核刷新整块盘被克隆、恢复镜像新盘里已经带着完整的旧分区表内核却只看到一个“空盘”虚拟机热插拔或云主机挂载新数据盘整盘设备/dev/sdb出现了分区节点却没被创建盘换过了但机器从没重启过内核里还残留旧的分区布局。partx就是用来解决这几类问题的。其中最关键的一点是partx默认不会修改磁盘上的 MBR/GPT它只操作“内核里的分区表”。理解了这一点才不会把partx -d误会成“删分区”。1.2 它和fdisk、partprobe、kpartx到底差在哪这几个命令很容易混我直接列个对比表命令/工具操作对象是否写分区表典型用途fdisk -l直接读磁盘扇区否查看磁盘上真实的分区表fdisk、sgdisk、parted磁盘分区表是新建、删除、调整分区partprobe内核整盘重读否通知内核重新读取整块盘的分区表partx -a/-d/-u内核逐分区增删改否增量同步内核分区信息kpartxdevicemapper否创建 dm 映射设备常用于 LVM、多路径lsblksysfs否只读查看内核设备树partprobe和partx最容易被拿来做比较。partprobe的机制是向内核发BLKRRPART要求把整块盘的分区表全部重读一遍。这个操作很“暴力”只要盘上任何一个分区正被挂载或占用整盘重读就会被拒绝。partx是逐分区操作的它不需要整表重读所以遇到Device or resource busy时partx -u或partx -a往往能绕过去。kpartx的名字和partx很像但完全是另一条技术路线它基于 devicemapper 创建映射设备典型用法是kpartx -av disk.img后生成loop0p1这类 dm 设备。如果你只是想处理普通分区partx是 util-linux 自带的几乎每个发行版都有不需要额外装multipath-tools。2. 8个Examples逐一拆解从查看到写入内核以下命令默认你是 root 或具备 sudo 权限。我会按“先用来看、再用来改”的顺序排例子之间是递进的。2.1 Example 1partx --show /dev/sdb先看内核当前认得哪些分区$ partx --show /dev/sdb NR START SECTORS SIZE NAME 1 2048 10485760 5G sdb1 2 10487808 104857600 50G sdb2输出里的列分别是NR 分区编号、START 起始扇区、SECTORS 扇区数、SIZE 分区大小、NAME 设备名。这里有个新手最容易懵的点partx --show默认显示的是“内核当前已经认识的分区”不是fdisk重新扫过磁盘后得到的结果。换句话说如果内核压根没加载这块盘的分区表这条命令可能什么都打不出来而不是去读取磁盘上的分区表。这个差异会在后面的例子里反复出现。我平时用它来确认“系统现在的真实状态”。比如怀疑某块盘被改动过先跑一下看看内核里记录的编号和扇区范围再决定下一步是-a还是-d。2.2 Example 2partx --show --full /dev/sdb绕过内核直接解析磁盘$ partx --show --full /dev/sdb NR START SECTORS SIZE NAME 1 2048 10485760 5G sdb1 2 10487808 104857600 50G sdb2--full这里的作用是让partx不要依赖内核已知信息而是直接从磁盘扇区解析出真正的分区表。它和fdisk -l的“读盘”部分是同一个数据源但partx输出的格式更适合继续交给脚本处理。这个模式特别适合两个场景一是克隆盘或恢复镜像后fdisk -l能看到分区lsblk看不到分区节点。先用partx -s -f确认磁盘上的分区表完好再决定是否用-a让内核加载。二是分区表类型自动识别失败。此时可以显式指定类型$ partx --show --full --type gpt /dev/sdb--type支持gpt、dos、sun、mac等。如果磁盘是 GPT 但备份表损坏partx可能会拒绝读取这时候先修复分区表再继续别急着用-a。2.3 Example 3partx -u /dev/sdbfdisk分完区后的第一反应这是生产环境里最常用的一个动作。场景在/dev/sdb上用fdisk新建了一个分区但lsblk看不到sdb1$ sudo fdisk /dev/sdb # 新建分区完成后 $ sudo partprobe /dev/sdb partprobe: Error: Device or resource busypartprobe失败后别再犹豫要不要重启先试$ sudo partx -u /dev/sdb-u是 update 模式。它不对整块盘做全表重读而是读取磁盘上的新分区表后逐分区通知内核“这个分区的信息变了”。已经存在的分区保留原编号新发现的分区补进来。因为不要求整个表重读所以即使盘上某个分区正被占用通常也能成功。如果-u提示找不到分区信息或者报“分区号已经存在”之类的问题可以换成下一步的-a或者先删除旧条目再添加。2.4 Example 4partx -a /dev/sdb从零把分区节点“造”出来适合的场景是磁盘上带着完整的分区表但内核一个分区都不认识。比如热插拔的数据盘、恢复镜像后的新盘、从旧机器拔下来直接插上的盘$ lsblk /dev/sdb sdb 8:16 0 100G 0 disk $ sudo partx -a /dev/sdb $ lsblk /dev/sdb sdb 8:16 0 100G 0 disk ├─sdb1 8:17 0 5G 0 part └─sdb2 8:18 0 50G 0 part-a是 add 模式读取磁盘上的分区表后逐个把分区“添加”进内核内核随即生成对应的块设备节点。整个过程不写磁盘、不格式化数据原封不动非常安全。它本质上是把“内核没看见”的分区找回来而不是新建分区。执行前最好先跑一次例2确认磁盘上分区表没问题$ partx --show --full /dev/sdb如果内核里已经存在部分分区条目partx -a可能会报类似“partition 1 already exists”的错误。这时候要么改用partx -u要么先删除冲突条目再-a。我自己的经验是全新盘用-a已有部分节点的盘用-u。2.5 Example 5partx -d --nr 2 /dev/sdb删除内核中的分区条目$ sudo partx -d --nr 2 /dev/sdb $ sudo partx -d --nr 1:4 /dev/sdb # 删除 1 到 4 号分区再次强调-d删除的是“内核里的分区条目”不是磁盘上的分区表。数据一点没丢恢复也很简单重新partx -a /dev/sdb或者重启就能让分区回来。删除模式常见于两种场景一是虚拟机或物理机换了盘但没重启内核里残留着旧分区信息和当前磁盘布局冲突导致新分区无法正常加载。清理旧条目后再-a通常能解决。二是配合 loop 设备清理镜像分区下面例7会提到。危险点必须单独说如果分区里有文件系统并且正在被挂载使用直接-d会报Device or resource busy所以先umount再用lsof确认没有进程占用。如果确认要操作建议提前对分区表做备份后面的第3章会写备份方法。2.6 Example 6写给脚本用的高阶输出--raw、--pairs、-o、-bpartx不只是给人在终端里看的它在脚本里比fdisk -l好用太多。跳过表头、机器可读输出$ partx --show --raw --noheadings -o NR,START,SIZE /dev/sda 1 2048 50G 2 10487808 500G输出键值对适合 Bash 脚本逐行取字段$ partx --show --pairs /dev/sda NR1 START2048 SECTORS12345 SIZE50G NAMEsda1以字节显示大小避免自己乘 512$ partx --show --bytes -o NR,SIZE /dev/sda NR SIZE 1 53687091200 2 536870912000一个典型的巡检脚本片段提取某块盘第 2 个分区的字节大小。$ partx --show --raw --bytes -o NR,SIZE /dev/sda | awk -F $12 {print $2} 107374182400这里值得注意--raw是给程序读的纯文本--pairs是给脚本取变量用的两者不要同时用。不同 util-linux 版本的列名可能有细微差别写进正式脚本前先在你自己的机器上跑一次确认列名。2.7 Example 7配合loop设备读取磁盘镜像里的分区这个场景我试过很多次拿到一个镜像文件比如虚拟化导出的盘、树莓派系统镜像想看看里面到底分了哪些分区或者直接把某个分区挂载出来。先把镜像变成 loop 设备$ sudo losetup -f --show disk.img /dev/loop0此时/dev/loop0出现了但分区的/dev/loop0p1不一定有。先用例2确认镜像里的分区表$ sudo partx --show --full /dev/loop0 NR START SECTORS SIZE NAME 1 2048 10485760 5G loop0p1 2 10487808 10485760 5G loop0p2然后让内核加载这些分区$ sudo partx -a /dev/loop0 $ lsblk /dev/loop0 loop0 7:0 0 10G 0 loop ├─loop0p1 7:1 0 5G 0 part └─loop0p2 7:2 0 5G 0 part挂载需要的分区$ sudo mount /dev/loop0p1 /mnt/img用完清理时顺序很重要$ sudo umount /mnt/img $ sudo partx -d /dev/loop0 $ sudo losetup -d /dev/loop0如果镜像内部用的是 LVM情况会复杂一点partx -a只能创建分区节点还需要pvscan、vgscan激活卷组再挂载逻辑卷。partx管不到 devicemapper 那层那时候才需要kpartx或直接操作 LVM。2.8 Example 8用--nr限定范围配合--dry-run先预演再动手分区很多时不想一次把所有分区都同步进去可以用--nr指定范围。例如只更新第 2 到第 4 号分区$ sudo partx -u --nr 2:4 /dev/sdb只查看第 3 个分区$ partx --show --nr 3:3 /dev/sdb范围语法是START:END冒号分隔省略 END 表示只处理一个编号比如-n 5。更保险的做法是配合--dry-run先预演$ sudo partx -a --dry-run --verbose /dev/sdb--dry-run会把命令将要执行的操作打印出来但不真正改动内核。注意这是 util-linux 较新版本才支持的功能老版本可以只用--verbose观察输出或者干脆先看partx --show --full的内容再动手。这套“限定范围预演”的组合在我批量恢复克隆盘分区时特别管用。毕竟删除或添加分区条目虽然不写盘但如果编号搞错正在使用的分区设备可能会受影响。3. 实战中的高频问题与完整排查链路光会敲命令不够遇到输出不符合预期时还得知道怎么查。下面是我踩过并且整理好的几个高频问题。3.1 “fdisk -l有分区partx -s却空白”该怎么查现象fdisk -l /dev/sdb清清楚楚列着分区partx --show /dev/sdb却一个字都打不出来。原因很明确fdisk -l是直接读磁盘扇区partx --show默认看的是内核已经加载的信息。内核还没加载这块盘的分区表自然什么都没有。排查链路$ partx --show --full /dev/sdb # 第1步确认磁盘上分区表完好 $ sudo partx -a /dev/sdb # 第2步让内核加载分区 $ lsblk /dev/sdb # 第3步验证结果如果第2步报分区已存在就改成$ sudo partx -u /dev/sdb3.2 partprobe报Device busypartx凭什么能成功partprobe走的是整盘重读只要盘上任何一个分区被挂载或有进程在用整个BLKRRPART就会被拒绝。partx -u和partx -a是逐分区增量操作不需要整个表重读所以即使磁盘上有分区正被使用它也能只处理自己需要同步的那几个分区。这也是我在生产环境里更推荐先用partx而不是partprobe的原因。当然如果你确定整块盘完全没有被占用partprobe也是可靠的选择。有一类特殊情况磁盘上存在正在使用的分区此时partx -u也失败那多半是内核认为分区表本身变了但新旧布局冲突太大。处理办法是先用partx -d --nr删掉冲突的旧条目再partx -a。动手前一定要确认那些分区没被挂载并且备份分区表。3.3 分区节点就是不出来检查udev和内核日志有时候partx -a之后lsblk里能看到分区了但/dev/sdb1这个设备文件就是不存在。这不是分区加载失败而是 udev 还没来得及生成节点。先让 udev 处理完积压事件$ sudo udevadm settle仍然不行就手动触发$ sudo udevadm trigger --sysname-matchsdb再不行就要看内核到底做了什么$ dmesg | tail -n 30如果内核日志里有类似“New sdb1”的消息说明块设备层已经成功问题只在 udev。如果日志里报invalid partition table那问题在磁盘上的分区表回去跑例2检查分区表类型。3.4 操作前的保底动作备份分区表和记录现场任何涉及删除、批量恢复的操作我都建议先备份分区表。别嫌麻烦关键时刻能救命。GPT 分区表用sgdisk备份$ sudo sgdisk --backuptable-backup.gpt /dev/sdbMBR 或者不确定类型时$ sudo sfdisk -d /dev/sdb sdb-table.txt同时把当时的磁盘状态记录一份$ partx --show --full /dev/sdb partx-before.txt $ lsblk -f /dev/sdb partx-before.txt恢复时用sgdisk --load-backup或者sfdisk写回。还有一个和克隆盘强相关的坑从模板克隆出来的盘分区 UUID 很可能和模板完全一样。partx管不到 UUID但它经常和克隆盘场景一起出现所以特别提醒一句挂载前必须blkid确认分区身份必要时用tune2fs或xfs_admin修改文件系统 UUID避免和模板源盘同时挂载时冲突。4. Partx的适用边界什么时候不该用它partx很强大但不是万能的。用错场景反而会浪费时间。4.1 工具选型表partx、partprobe、kpartx、blockdev、udevadm我把不同需求对应的工具整理成一张表方便直接对照需求推荐命令备注查看磁盘上真实分区表fdisk -l、partx -s -fpartx更适合脚本查看内核已加载分区lsblk、partx -spartx输出更详细分区表变更后立刻生效partprobe或partx -u被占用时优先partx从零添加分区到内核partx -a镜像、克隆盘常用删除内核分区条目partx -d --nr N谨慎操作整盘强制重读blockdev --rereadpt /dev/sdb未占用时可用触发 udev 重扫udevadm settle / trigger节点没出现时使用镜像文件里的 dm 映射kpartx -av镜像内部是 LVM 时需要blockdev --rereadpt和partprobe本质类似都是整盘重读同样受占用影响。它们适合“我确定整块盘都没有被挂载”的维护窗口而partx的增量模式适合不能停服务的场景。4.2 几个明确的“别用partx”场景分区表本身损坏时别指望partx去修复。比如 GPT 备份表损坏、分区表类型丢失这是gdisk、sgdisk、fdisk的活儿。partx连读都读不出来的情况下先修复再同步。设备主机总线层面就不认识磁盘时也别用partx。云主机或物理机热插拔 SCSI 盘后如果连/dev/sdb都没出现需要先让驱动重新扫描总线比如$ echo - - - /sys/class/scsi_host/host0/scan镜像内部是 LVM 逻辑卷时partx -a只能建出分区节点卷组和逻辑卷还得靠pvscan、vgactivate或者直接用kpartx -av一次性映射。前面例7已经提过这里再强调一遍。4.3 我的日常组合拳时间久了我养成了固定的操作习惯。新盘挂载上来先partx --show --full确认分区表然后partx -a最后lsblk验证。fdisk改完分区首选partx -u失败再-a。处理镜像文件固定流程是losetup到 loop 设备partx -s -f确认partx -a加载挂载最后逆序清理。任何一步改到内核分区状态我都会在动手前先partx --show --full留个现场记录。这套流程固化下来之后生产环境里再没出过“分区表改了但系统不认”这种半夜上线问题。partx最大的价值不在于花哨而是给了你一个不重启、不卸载、可控的内核分区表编辑入口。如果你也遇到过类似的“内核不认分区”的困境建议从最小的partx --show --full开始先确认磁盘数据完好再尝试同步。希望这 8 个例子能让你少走我走过的弯路。