干Linux运维这些年,几乎每个存储相关的活儿都会碰到这样一个场景:新加的硬盘明明已经用fdisk分好区了,系统却怎么都不肯承认新分区的存在。有人二话不说直接重启,有人拿partprobe碰运气,但让我真正觉得顺手、可控的,还是命令行工具partx。它不修改磁盘上的一字节分区表数据,也不要求重启,只用一条命令就把内核里那份"已登记分区"的清单手动矫正一遍。今天就把这个低调但非常好用的命令彻底讲透,包括原理、参数、实战场景和那些文档里基本不会写的坑。如果你平时要跟磁盘分区、虚拟机扩容、嵌入式板卡或磁盘镜像打交道,这篇很适合你。
1. partx 在 Linux 命令行中的定位与核心原理
1.1 先从一个典型场景说起:为什么分区后总是要"重启才能识别"
Linux里磁盘分区的信息实际上存在两份:一份物理落在磁盘上,也就是MBR或GPT分区表里保存的原始数据;另一份是内核在内存中维护的分区状态,这份状态决定了/dev/sda1、/dev/sdb2这些设备节点是否出现、分区块设备是否可用。问题就出在这里:用 fdisk 写入新分区表之后,内核不会主动去重新读取磁盘上的分区表,它还停留在旧状态。这就是"分完区 lsblk 看不到新分区"的根本原因,跟文件系统格式化、驱动加载、磁盘写缓存都没关系。
处理方式无非几种:重启整机让内核重新扫描所有块设备;用 partprobe 通知内核重读;用 blockdev --rereadpt 强制重读;或者用这里的主角 partx 手动告诉内核"磁盘上有哪些分区"。前两种方式是"整体重读",第三种也接近整体重读,只有 partx 是真正细粒度的、可以精确到某个分区号的操作。生产环境里磁盘分区往往正被 LVM、mount、swap 占着,整体重读一旦碰上"设备忙"就会整体失败,这时候 partx 逐个分区操作的价值就出来了。
1.2 partx 到底做了什么:内核分区视图的一次"手动同步"
partx 来自 util-linux 工具集,全名不太好记,但你只需要理解一句话:partx 负责把磁盘分区表里的信息同步给内核,或者说,让内核知道分区表中每个分区的存在和编号。它底层调用的是内核的 BLKPG ioctl 接口,通过 BLKPG_ADD_PARTITION、BLKPG_DEL_PARTITION 这些操作动态增删内核分区块设备。这个机制和"重新扫描整张分区表"不一样,它更像是在操作内核维护的分区对象本身,所以可以单独添加一个分区、单独删除一个分区、单独更新一个分区的边界。
可以打个比方:磁盘上的分区表是"源文件",内核内存里的分区状态是"缓存"。fdisk 改了源文件以后,缓存不会自动刷新;partx 就是那个按一下才刷新的按钮,而且可以只刷新某一行而不是把整个缓存推倒重来。需要注意的是,partx 自己不会去修改磁盘上的分区表,你用它创建不出新分区,新分区必须先用 fdisk、sfdisk、parted 之类工具写好,再由 partx 通知内核去认。这个定位很多人一开始会搞混,以为 partx 是"分区工具",其实它是"内核分区同步工具"。
1.3 partx 与 fdisk、partprobe、kpartx 的边界划分
我见过不少同事把几个命令混着用,这里先一次性把边界划清楚。fdisk、sfdisk 是真正的分区表编辑器,直接读写磁盘上的 MBR/GPT 数据;partx 只负责把写好的分区表数据同步给内核;partprobe 是 parted 工具包里的命令,作用也是触发内核重新读取分区表,但它没有 partx 那种精确指定分区号的细粒度能力;kpartx 就更不一样了,它来自 multipath-tools,不直接操作原始磁盘设备,而是用 device mapper 机制创建一个映射层,最终生成/dev/mapper/下的分区设备,多路径、镜像挂载里常见;blockdev --rereadpt 也能强制内核重读分区表,但它是"整表重读",有一个分区被占用就直接报错。
| 工具 | 所属包 | 修改磁盘分区表 | 操作内核分区 | 生成独立映射设备 |
|---|---|---|---|---|
| fdisk / sfdisk | util-linux | 是 | 否(写入后需其他工具配合) | 否 |
| partx | util-linux | 否 | 是,可精确到分区号 | 否 |
| partprobe | parted | 否 | 是,整体重读 | 否 |
| kpartx | multipath-tools | 否 | 间接创建 dm 映射 | 是 |
| blockdev --rereadpt | util-linux | 否 | 是,整体重读 | 否 |
实际工作中,我更多把 partx 当成"生产环境里最安全的同步手段"来用。什么叫安全?整体重读时遇到一个被占用的分区就可能全部失败,而 partx 可以用--nr指定只同步某个分区,其他正在服务业务的分区碰都不碰。说白了,在不能随便重启的机器上,partx 是精细控制内核分区状态最顺手的那把刀。
2. partx 命令用法拆解:参数多但核心模式就四种
2.1 三条主线语法:读、加、减、改
partx 的语法看起来选项不少,但本质上就四条主线:显示分区、添加分区、删除分区、更新分区。显示用-s或直接-P,添加用-a,删除用-d,更新用-u。这四个模式对应了日常所有需求。举个例子,partx -s /dev/sdb是列出当前内核已经识别到的 sdb 上的分区;partx -a /dev/sdb是把磁盘上存在但内核还不知道的分区补登记上;partx -d /dev/sdb是删除内核里的分区记录;partx -u /dev/sdb则是更新已有分区的信息,比如分区边界变了、容量变了。
有个容易混的点先说清楚:partx -a并不是"把内核里所有分区重新添加一遍",它的默认行为是"只添加磁盘上有而内核没有的分区"。如果磁盘上已经有 5 个分区,内核这次只识别出了前 4 个,执行partx -a /dev/sdb只会补上第 5 个,不会把前面 4 个重复添加然后报错。这个设计非常聪明,符合增量同步的思路。不过要注意,如果分区号在内核里已经存在,再用-a去添加就会报 "File exists" 之类的错误,这时候就该用-u或先-d再-a。
2.2 常用参数逐个拆解
--nr M:N(简写-n)是最值得优先掌握的参数,它用来指定分区号范围。比如partx -a --nr 1 /dev/sdb只添加 1 号分区;partx --nr 3:5 -s /dev/sdb只显示 3 到 5 号分区。这个参数的价值在于精准控制:生产环境里磁盘正跑着业务,你只想同步新增的那个分区,用--nr就能避免动到其他分区。
-o用来指定输出列,常用于只看关心的字段,比如partx -o NR,SIZE,TYPE -s /dev/sda就能按需裁剪输出内容。-g是去掉表头,配合 awk 做脚本处理时很好用。-b让 Size 一列以字节为单位输出,方便计算,默认是人性化的 K/M/G 单位,脚本里反而不方便。-P会把结果输出成key="value"的 key-value 格式,-r是它的 raw 形态,更紧凑。--typename(-t)用来指定分区表类型,比如partx -t gpt -s /dev/sdb,在自动识别分区表类型失败时用得上。
还有两个容易被忽略但很实用的点:-v详细输出模式会打印出具体的 ioctl 操作过程,排查问题时能看到内核到底"做了什么";--fstype允许你在添加分区时指定文件系统类型,某些内核版本和场景下有助于让分区节点更快就绪。man page 里这些参数都有,但真正用起来,一个--nr加一个-u就能解决八成以上的现场问题。
2.3 输出解析与脚本化:从 -s 到 -P 的组合
partx -s /dev/sda的默认输出大概长这样:表头是 NR、START、END、SECTORS、SIZE、NAME、UUID。START 和 END 列的单位是扇区(sector),通常一个扇区 512 字节;SECTORS 是该分区占用的扇区总数;SIZE 是换算后的大小;NAME 多数情况下为空,GPT 分区可能有名字;UUID 是分区 UUID。看这些字段时要记住,partx 默认输出的是内核当前识别到的状态,而 fdisk -l 输出的是磁盘上分区表里的状态,两者在分区表刚修改但内核还没同步时会不一致,注意分辨。
脚本化处理最稳妥的组合是partx -s -g -o NR,START,END /dev/sdb,无表头、指定列,再用 awk 或 cut 直接消费。如果要保留字段名,partx -P -g -o NR,SIZE /dev/sdb输出的NR="1"格式在 shell 里可以用 eval 或 read 解析。我在自动化脚本里经常这么干:先partx -s -g -o NR /dev/sdb拿到当前已同步的分区号列表,再跟 fdisk 里期望的分区号比对,决定是-a补缺还是-u更新,整个过程不用碰交互式命令。
3. 五个拿来即用的实战场景
3.1 场景一:新盘分区后不重启直接生效
这是最基础也最高频的场景。假设新加了一块/dev/sdb,你刚用 fdisk 建好一个分区,现在想让系统立刻认出/dev/sdb1。完整流程如下:
# 1. 用 fdisk 建分区(交互过程省略,最终写好分区表) fdisk /dev/sdb # 2. 确认磁盘上分区表里的分区信息 fdisk -l /dev/sdb # 3. 此时 lsblk 通常看不到 sdb1,因为内核还没同步 lsblk /dev/sdb # 4. 调用 partx 同步内核分区记录 partx -a /dev/sdb # 5. 验证,sdb1 应该已经出现 lsblk /dev/sdb如果只想同步 1 号分区,可以用partx -a --nr 1 /dev/sdb。真正生产环境里我反而更推荐这种指定分区号的做法,因为有的机器上磁盘可能已经挂载了旧分区,partx -a的全量增量补缺虽然大概率没问题,但显式指定肯定更安全。执行完成后,/dev/sdb1节点正常出现,可以继续 mkfs 和 mount,全程不重启,业务无感知。
3.2 场景二:重建分区后整体刷新内核分区表
还有一种典型情况是你把某个分区删了又重建,分区号没变,但起始扇区或大小变了。此时partx -a会报错,因为内核里还残留着同号旧分区的占用;如果直接partx -u,它会把这个分区整体更新成新边界,一步到位。
# 假设 /dev/sdc 上重建了 2 号分区,大小从 1G 变成了 2G # 直接更新 2 号分区,不需要先删再加 partx -u --nr 2 /dev/sdc # 验证分区容量确实是新值 lsblk /dev/sdcman page 里的解释是:-u执行的操作类似于-d加-a的组合,但速度更快,且中间失败的风险更低。为什么风险低?因为整体"删除再添加"中间有一个时间窗口,如果操作在删除后中断,内核彻底失去这个分区,数据路径直接断裂;而 update 操作尽量合并减少了这种中间状态。所以遇到"分区表已经重建、内核状态必须重刷"的场景,我几乎总是先用-u,只有-u明确失败时才退回"先 -d 再 -a"。
3.3 场景三:虚拟磁盘镜像与 loop 设备的完整链路
在只拿 raw 镜像文件做实验、或者在构建容器镜像时,常需要把磁盘镜像里某个分区直接挂载出来。传统做法要动 loop 设备,而 partx 在这里也扮演着关键角色。
# 先造一个 raw 镜像并分区 dd if=/dev/zero of=/tmp/test.img bs=1M count=1024 fdisk /tmp/test.img # 交互创建一个分区,写好后退出 # 把镜像文件关联到 loop 设备,并自动扫描分区 losetup -P -f /tmp/test.img # 查看关联情况,losetup -P 已经让内核识别了分区,loop0p1 会出现 lsblk /dev/loop0 # 如果内核版本较老或没用 -P,可以手动用 partx 同步 losetup -f /tmp/test.img partx -a /dev/loop0 # 挂载镜像里的第一个分区 mount /dev/loop0p1 /mnt实际上losetup -P本身就携带了分区扫描功能,但很多时候你拿到的环境里内核配置、busybox 版本不支持自动扫描,这时 partx 就是救命的替代方案。还有个小技巧:挂载完镜像想卸载清理时,先umount,再partx -d /dev/loop0把内核分区记录清掉,最后losetup -d /dev/loop0,这套清理顺序最干净,不容易残留 loop 占用。
3.4 场景四:自动化脚本里无脑同步分区
写脚本批量处理多块磁盘时,partx 的价值更明显。比如机房批量初始化几十块数据盘,脚本可以是这样:
#!/bin/bash DISK=/dev/sdx # 重建分区表:直接创建一个主分区,占满全盘 printf 'o\nn\np\n1\n\n\nw\n' | fdisk "$DISK" >/dev/null 2>&1 # 关键步骤:不管内核当前是什么状态,先用 -d 清掉, # 再 -a 全量接入,保证最终状态与磁盘分区表一致 partx -d "$DISK" >/dev/null 2>&1 || true partx -a "$DISK" >/dev/null 2>&1 || true # 检查分区节点是否就绪 if [ -b "${DISK}1" ]; then echo "partition ${DISK}1 is ready" mkfs.ext4 "${DISK}1" fi这里有个经验:脚本里不要只写partx -a,因为磁盘可能之前被用过,内核已有残留分区,-a会因分区号已存在而报错。先执行partx -d清理内核记录、再-a重新添加,是一个幂等性更高的组合拳。如果不想删除后重加,也可以用partx -u直接覆盖更新。至于 fdisk 创建分区后立即 partx,偶尔会遇到"内核还没反应过来"的极小概率问题,加个sleep 1基本能规避,别问我是怎么知道的。
3.5 场景五:磁盘扩容后的分区大小更新
云服务器或虚拟机上给系统盘扩容后,分区表在磁盘上已经变大了,但内核还拿着旧的分区边界不放,lsblk看到的 sda2 还是老容量。这种场景下,partx 是除了重启之外最常用的解法。
# 以 /dev/sda 的 2 号分区扩容为例 # 先用 growpart 将磁盘上 2 号分区扩展到最后扇区 growpart /dev/sda 2 # 再用 partx 更新内核里的分区边界 partx -u --nr 2 /dev/sda # 确认内核视角下分区容量已经是新值 lsblk /dev/sda # 文件系统层扩容(如果文件系统支持在线扩展) resize2fs /dev/sda2这里其实还有个更常见的替代方案是partprobe /dev/sda,但我在实际环境里多次遇到同一台机器上partprobe成功了、lsblk却仍然显示旧容量,或者反过来partprobe报"无法读取分区表"但用partx -u就能顺利刷新。尤其是某些 seagate、某些老版本内核组合下,partx 的更新操作比整体重读更稳妥。遇到扩容不生效,先别急着重启,partx -u --nr值得先试一次。
4. 高频报错与排查实录
4.1 failed to delete partition:不是命令不给力,是分区被占用
partx -d最常见的报错就是partx: /dev/sdb: failed to delete partition #1,字面意思是删除分区失败。但真正原因基本都不是命令本身的问题,而是内核拒绝删除一个正在被使用的分区。比如这个分区正被某个进程读写、被 LVM 占用、或者被 swap 使用。此时最直接的排查是先看挂载:
mount | grep sdb1 # 如果已经挂载,先卸载 umount /dev/sdb1如果没有挂载但还在报错,那就用fuser和lsof查谁还握着这个块设备:
fuser -vm /dev/sdb1 lsof /dev/sdb1找到进程后,确认可以结束后 kill 掉再执行partx -d。如果确实有不能停的服务,那基本没有安全的在线删除办法,要么等窗口期,要么接受重启。另外一个小技巧:partx -d时如果指定了--nr,报错信息会明确指出是哪个分区号失败了,比全量删除好排查得多。
4.2 EBUSY:设备忙时的常规排查链路
partx操作时遇到Device or resource busy(EBUSY),情况比单纯的占用更复杂。可能是分区表与正在使用的块设备冲突,也可能是 loop 设备没解除关联,或者同一设备被 LVM 的 PV 卷标占用。常规排查链路我一般是这样走的:
# 1. 确认没有挂载 mount | grep sdb # 2. 确认不在 swap swapon --show | grep sdb # 3. 确认没有被 LVM 捕获 pvs | grep sdb lvs | grep sdb # 4. 确认没有被 md raid 使用 cat /proc/mdstat | grep sdb # 5. 最后再用 blkid 看看有没有残留卷标 blkid /dev/sdb1如果以上都没有发现,还可以检查是不是被某个容器运行时或系统服务占用了。我遇到过一次比较隐蔽的情况:某个监控 Agent 把整个块设备打开了,导致partx -d一直 EBUSY,最后用lsof /dev/sdb找到进程才解决。记住一个原则:EBUSY 的本质是有人占用了这个设备,partx 只是把你的操作挡了回去,别跟命令较劲,跟占用者较劲才对。
4.3 MBR 逻辑分区的编号陷阱
MBR 分区表有一个古老但至今影响很多人的设计:主分区最多 4 个,想分更多必须用扩展分区和逻辑分区。逻辑分区编号不是从 1 开始排队,而是固定从 5 开始递增。举个实例:一个 MBR 磁盘上有扩展分区(4 号),扩展分区里建了两个逻辑分区,那这两个逻辑分区的编号就是 5 和 6。如果你用partx --nr 1:4去操作,只能碰到主分区和扩展分区本身;要操作逻辑分区,必须显式指定 5、6 之后的范围。
另外,MBR 内核分区号上限是 15 个,扩展分区本身占一个名额,所以实际能在内核里同时存在的 MBR 分区记录是有限的。如果分区表里排出了超过上限的逻辑分区号,partx -a会报错说"没有可用的分区号"。遇到这种问题,要么改用 GPT 分区表,要么接受 MBR 的限制做取舍。GPT 就没有这个烦恼,分区数量上限要大得多,这也是新机器上我优先推荐 GPT 的原因之一。
4.4 partx 与 fdisk 显示不一致的真正原因
好几次朋友拿着截图来问我:partx -s /dev/sda显示的起始扇区怎么和fdisk -l /dev/sda对不上,是不是系统出 Bug 了?这里的关键差别前面提过一嘴:fdisk 读的是磁盘上分区表里的原始数据,partx 的-s默认展示的是内核已经同步的分区视图。假如分区表被修改了但内核还没更新,两者当然会对不上:fdisk 看到的是"新版本",partx 看到的是"旧缓存"。
所以看到不一致时不要慌,正确的做法是拿partprobe或partx -u把内核视图刷新一下,再对比两者。如果刷新之后仍然对不上,那才需要怀疑分区表本身是不是出了错,可以用partx --typename gpt -s /dev/sdb或partx -t dos -s /dev/sdb强制指定分区表类型去校验。这种"一个数据两个视图"的模型理解得越透,遇到问题就越不容易被表象迷惑。
4.5 问题速查表
| 症状 | 大概率原因 | 解决方向 |
|---|---|---|
| partx -a 报 File exists | 该分区号内核里已存在 | 改用partx -u或先-d再-a |
| partx -d 报 failed to delete | 分区被挂载/进程占用 | umount、lsof/fuser 排查后重试 |
| partx 报 EBUSY | 设备被 LVM、swap、loop 等占用 | 按 pvs、swapon、losetup 顺序排查 |
| lsblk 看不到新分区 | 分区表修改后未同步内核 | partx -a或partprobe |
| 扩容后容量不变 | 内核分区边界未更新 | partx -u --nr N更新指定分区 |
| partx 与 fdisk 显示不一致 | 内核视图与磁盘视图不同步 | partx -u同步后重新对比 |
| MBR 请求分区号过大报错 | 逻辑分区号超过内核上限 | 改用 GPT 或减少逻辑分区 |
5. 结尾
说一句大实话:partx 这个命令在我的日常运维里,频率没有 lsblk 高,但关键时刻从来不掉链子。它几乎不需要学习成本,掌握了 -a、-d、-u、--nr 这四个组合,就能覆盖磁盘初始化的绝大多数场景。我最喜欢它的一点就是"只说话、不动手"——它永远不主动改磁盘分区表,只负责把内核视图矫正到与磁盘分区表一致,想犯错的余地都不大。真心建议每个 Linux 系统管理员都把它装进自己的常用命令清单。如果要在生产环境里自动化使用,我个人的体会是:优先用-u而不是-a,能精确到--nr就别全量操作,遇到任何 busy 报错先查占用者再决定下一步,尊重内核的拒绝,比硬碰硬高效得多。希望这篇能让你少走一些我已经走过的弯路。