很多时候我们都会遇到一个特别尴尬的场景:给一台跑得好好的Linux服务器加一块数据盘,fdisk切完分区,正准备mkfs格式化,系统却告诉你设备找不到;或者明明在虚拟机面板里把磁盘扩了容量,进系统一看,lsblk还是老样子。第一反应通常是重启,但生产环境哪能说重启就重启。其实,Linux完全可以做到不重启就重新认识你的分区表。
这篇文章就是围绕“Re-read The Partition Table Without Rebooting Linux System”这个主题,把分区表重读的原理、工具选型、实战操作和排错经验全部摊开讲清楚。适合正在做系统运维、搞虚拟化平台、或者自己折腾Linux主机的朋友。读完之后,你不仅能熟练使用partprobe、partx这些命令,还能搞明白它们背后的工作机制,以后再遇到“改完分区不生效”的问题,不用慌也不用重启。
1. 先搞清楚:为什么改完分区表内核就是不认
1.1 内核的分区表到底存在哪
Linux系统里的分区信息其实有两个“存储位置”。第一个是磁盘本身,也就是分区表数据写在磁盘的固定扇区里,传统MBR在0号扇区,GPT则分布在磁盘开头和结尾。第二个是内核内存里的分区结构,内核启动时或磁盘设备出现时,会读取磁盘上的分区表,在内存中建立一套对应的块设备模型,包括/dev/sda1、/dev/sda2这些设备节点和它们的大小、起始扇区、类型等信息。
问题就出在这里:你用fdisk、gdisk或者parted修改的是磁盘上的分区表,但内核内存里那套旧结构并不会自动跟着更新。内核依然是“按旧地图干活”,所以会出现磁盘上明明已经有了新分区,系统却死活不认的情况。内核也提供了相应的接口来更新自己内存里的分区信息,这就是“重读分区表”这个操作的本质。
把这个机制用生活化的方式理解:磁盘上的分区表是餐馆新印的菜单,内核内存里的分区结构是服务员已经背下来的旧菜单。你用fdisk改了磁盘,等于把新菜单放到了前台,但服务员还在按脑子的旧菜单给客人点菜。想让顾客吃到新菜,要么让服务员重新学一遍菜单(重读分区表),要么干脆换个服务员(重启系统)。
1.2 不重读会出什么问题
很多新手会问:我不重读,直接用/dev/sda3不就行了?实际上还真不行,而且坑还挺多。
最常见的情况就是fdisk分区完成后,系统提示“内核仍在使用旧的分区表”,然后你去mkfs /dev/sda3,系统直接回你一句“No such file or directory”。因为内核内存里根本没有建立sda3这个块设备对象,设备节点都不存在,格式化自然无从谈起。
还有一种更隐蔽的情况:如果新分区恰好复用了之前的分区号,比如原来sda2是500G,你删掉重建后把它变成了1T,但内核还记着旧的500G容量。这时候挂载/dev/sda2,看到的大小是不对的,如果往里面写数据,轻则写不进去,重则文件系统metadata错乱,后果相当麻烦。
所以在修改分区表之后,一定要有一个“让内核重新认识分区”的动作,这个动作就是本篇文章要讲的重点。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 三个常用工具的原理与选型
2.1 partprobe:最省事的首选
partprobe是util-linux软件包里的命令,核心功能就是“告诉内核重新读取磁盘上的分区表”。它会把磁盘上当前的分区情况一次性同步到内核里,适合那种分区表整体变化不大、只是新增或删除了几个分区、没有分区正被系统占用的场景。
用法也简单:
bash复制# 重读所有磁盘的分区表
partprobe
# 单独重读某一块盘
partprobe /dev/sda
# 查看执行过程输出
partprobe -s /dev/sda
执行partprobe之后,内核会重新解析指定磁盘的分区表,并更新内存里的分区结构。如果你的磁盘上所有分区都没有被挂载,也没有进程占用,这个过程通常不会报错,非常顺利。
但是partprobe有个让人蛋疼的地方:如果目标磁盘上存在“正在被系统使用”的分区(比如根分区所在的磁盘,或者正在挂载的数据盘),它可能会拒绝执行,甚至没有任何提示就直接失败。实际测试中它有时会返回非零退出码,但终端上又不打印错误信息,特别容易让人误以为已经成功了。这一点在后面排错部分会详细展开。
2.2 partx:精确控制分区状态的瑞士军刀
partx同样是util-linux软件包的命令,但它的工作机制和partprobe不太一样。partprobe是“整块磁盘重扫描”,partx则是通过内核的BLKPG接口,对单个分区做“增删改查”,可以只添加一个分区,也可以只删除一个分区,不需要整体重读磁盘。
最常用的几个方式:
bash复制# 从磁盘上的分区表读取分区信息并添加到内核
partx -a /dev/sdb
# 更新内核已有分区信息(分区号或大小变了)
partx -u /dev/sdb
# 删除内核中已有的指定分区号
partx -d /dev/sdb
# 显示磁盘上的分区与内核已有分区的差异
partx -s /dev/sdb
这个命令的灵活之处在于可以精细操作。比如你fdisk新建了一个分区,不想惊动其他正在使用的分区,就可以单独把新分区加进内核:
bash复制# 假设新分区是 /dev/sdb5
partx -a /dev/sdb5
很多老运维的习惯是:新加分区用partprobe,分区删除不干净就用partx -d来清理。这个习惯有它的道理,因为删除分区时,如果整个磁盘partprobe会失败,但单独删除不存在的分区对象反而更安全。
2.3 hdparm -z 和 udevadm settle:容易被忽略的补充手段
hdparm通常被用来调整磁盘参数,但它还有一个不为人知的功能:强制内核重新读取分区表。
bash复制# 重读 /dev/sda 的分区表
hdparm -z /dev/sda
这个命令在某些老设备上反而比partprobe好使,尤其是IDE接口的老硬盘,或者部分Marvell、Silicon Image芯片组的SATA控制器。它的原理是直接向设备发送重读分区的指令,让内核重新解析分区表。不过hdparm -z对部分NVMe设备或virtio-blk设备支持不佳,遇到这些设备时还是用partprobe或partx更靠谱。
udevadm settle则是用来“等待”的。我们知道,内核扫描到新设备后,udev会在用户空间创建设备节点、触发udev规则、加载驱动等,这些动作是异步的。如果分区重读成功了,但udev还没把设备节点创建好,后续命令一样找不到设备。所以实际操作中,我习惯在重读后加一句:
bash复制udevadm settle
这个命令会阻塞当前shell,直到udev处理完所有待处理的事件,确保/dev/sdb1这种设备节点已经真实存在。在自动化脚本里,加一行udevadm settle能省掉很多莫名其妙的间歇性故障。
下面用一个表格把主流的重读工具做个对比,方便大家按场景选用:
| 工具 | 作用机制 | 适用场景 | 注意点 |
|---|---|---|---|
| partprobe | 整盘重读分区表 | 新增分区、修改分区表结构后 | 有分区被占用时可能失败 |
| partx -a/-u | 按分区增删改内核分区表 | 单分区新增或变更、避免影响已有分区 | 需要分区号明确 |
| partx -d | 删除内核里的分区对象 | 清理内核中残留的旧分区信息 | 删除前确认分区未挂载 |
| hdparm -z | 设备层强制重读分区表 | 老设备、IDE硬盘等特殊场景 | NVMe/virtio设备可能不支持 |
| udevadm settle | 等待udev事件处理完成 | 重读后需要立即创建设备节点的自动化场景 | 本身不读分区表,只做等待 |
3. 不同场景下的重读操盘记录
3.1 新加一块盘:从扫描总线到分区生效
在物理机上新插一块硬盘,系统往往不能直接识别,需要先让内核扫描存储总线。这个“发现新磁盘”的动作和“重读分区表”是两个层面的操作,很多文章把它们混在一起讲,我在这里拆开来说。
先扫描SCSI/ATA总线让内核发现新磁盘:
bash复制# 扫描host0到host3的SCSI总线,具体数字以实际环境为准
echo "- - -" > /sys/class/scsi_host/host0/scan
echo "- - -" > /sys/class/scsi_host/host1/scan
echo "- - -" > /sys/class/scsi_host/host2/scan
扫描完成后用dmesg确认内核是否发现了新设备:
bash复制dmesg | tail -20
如果看到类似“sd 0:0:1:0: [sdb] 4194304 512-byte logical blocks”的日志,说明新磁盘已经就绪。此时再用fdisk或gdisk分区:
bash复制fdisk /dev/sdb
fdisk分区完成退出时,通常会自动触发一次内核重读分区表的操作。如果磁盘没有被任何进程占用,你往往看不到任何提示,此时lsblk应该就能看到新分区了。如果fdisk退出时提示“内核仍在使用旧的分区表”,那就需要手动重读:
bash复制partprobe /dev/sdb
# 或者
partx -a /dev/sdb
之后再用lsblk或者cat /proc/partitions确认结果。
这里有一个细节:如果是给虚拟机加盘,比如KVM或者VMware,guest系统里除了扫描SCSI总线,有时还需要针对具体的总线编号操作。比如KVM的virtio-blk设备会在/sys/class/block/vda下出现,但老版本的KVM或者使用IDE模拟设备时,就要执行上面那段scan命令。还有一种情况是主机已经热插拔了磁盘,guest里lsblk看不到,这种时候建议先检查/dmesg,确认设备是否正确枚举,不要盲目反复扫描。
3.2 删除分区后让内核“遗忘”它
删除分区的场景比重读新分区更容易出问题。很多运维都有过这样的经历:fdisk里删除了某个分区,但系统里/dev/sda2这个设备节点还在,甚至挂载点还能看到旧分区。这就是内核内存里没有同步删除分区对象的典型表现。
正确的删除流程是:
- 先卸载分区:umount /dev/sda2。
- 用fdisk或gdisk删除磁盘上的分区表项。
- 让内核同步删除分区对象:partprobe /dev/sda 或 partx -d /dev/sda(注意partx -d不加分区号时是删一整块盘的所有分区对象?这里需要确认。实际上partx -d /dev/sda2可以删除单独分区,partx -d /dev/sda会删除所有分区对象。建议用partx -d /dev/sda2精确指定)。
要注意的是,如果分区压根没有挂载过,但仍有进程使用它对应的块设备文件,比如数据库的裸设备、Oracle ASM磁盘,或者LVM的PV,那partprobe也会提示Device busy。这时候必须先把相关服务停掉,再用partx -d清理。
对于LVM环境的删除,优先级是“先vgextend/vgreduce,再partprobe”,不要反过来。很多人在LVM逻辑卷上直接删分区,结果发现PV元数据已经读不出来了,好在有vgcfgbackup,不然真心疼数据。
3.3 LVM和RAID环境下的注意事项
LVM环境下重读分区表有个特殊性:PV往往是建在整块磁盘或某个分区上的,重读分区表之后,内核的块设备结构变了,但LVM的缓存还指向旧的设备状态。所以重读之后通常要执行pvscan、vgscan、lvscan同步一下LVM缓存:
bash复制pvscan
vgscan
lvscan
如果只是新增了一块盘,在分区完成后partprobe识别到新分区,然后pvs就看得到新的PV设备。这里有个坑:新盘分区后如果直接用pvcreate,有时会因为内核还没完全同步分区信息而报“Device /dev/sdb1 not found”。解决办法是先lsblk确认分区存在,再执行partprobe,再看lsblk,还不够就udevadm settle,最后再pvcreate。
软件RAID的mdadm环境类似。如果是给已有阵列添加新磁盘,或者替换故障盘,操作顺序一般是:分区、partprobe、mdadm --add。在mdadm组阵列过程中,如果分区信息没同步好,会出现一条“device has no md superblock”的误导性报错,其实不是raid超块的问题,而是内核压根没把分区整明白。
3.4 虚拟化平台:KVM、VMware和国产虚拟化环境
虚拟化环境里“重读分区表”有两层含义。一层是宿主机层面的,比如扩容虚拟机磁盘文件后,宿主机不需要重启,因为磁盘文件容量变了,guest里的虚拟设备大小也会自动更新。另一层是guest系统里的,虚拟机操作系统需要重新感知磁盘的新大小或新分区。
在guest里,如果你用的是virtio-blk设备,磁盘扩容后执行partprobe通常能直接更新整块磁盘的容量信息,但分区是否自动扩容到新大小,取决于你改没改分区表。如果只改了虚拟磁盘大小,没动分区表,那guest内看到的还是原来的分区布局。正确操作是在guest里先让内核识别新的磁盘容量:
bash复制# 很多virtio设备支持直接rescan
echo 1 > /sys/class/block/vda/device/rescan
# 或者重新扫描SCSI设备
echo "- - -" > /sys/class/scsi_host/host0/scan
识别到新容量后,再执行parted或growpart扩展最后一个分区,最后partprobe重读,resize2fs或xfs_growfs扩容文件系统。
VMware的虚拟磁盘默认使用PVSCSI或LSI Logic控制器,guest里一般用partprobe就能搞定。但如果用的是老版本VMware Tools之下的buslogic驱动,可能不支持在线扩容分区表更新,这种情况下只能重启guest,没有太多办法。
国产化Linux系统,比如统信UOS、麒麟等,它们的工具链和上游基本一致,partprobe、partx都是完整支持的。实际测试下来,在麒麟V10上对ext4和xfs分区做在线重读,行为跟CentOS/RHEL保持一致,没有发现特殊限制。
4. 在线扩容中的完整实操流程
4.1 一个真实的扩容案例
来一个完整的实战复盘。假设有一台CentOS 7服务器,系统盘是/dev/sda,数据盘是/dev/sdb,数据盘上只有一个分区/dev/sdb1,挂载在/data下,文件系统是xfs。现在需求是:把这块数据盘从500G扩容到1T,不能重启,不能中断服务。
第一步,在虚拟机面板或磁盘阵列管理界面把磁盘容量从500G改成1T。这一步完成后,guest里lsblk看到的/dev/sdb总容量可能已经变成1T,也可能还是500G,取决于虚拟化平台是否支持在线SCSI设备大小更新。如果还是500G,手动rescan一次:
bash复制echo 1 > /sys/class/scsi_device/2\:0\:0\:0/device/rescan
这里/path要换成实际设备路径。或者更通用地扫描host总线:
bash复制for host in /sys/class/scsi_host/host*; do echo "- - -" > $host/scan; done
此时lsblk里/dev/sdb总容量应该是1T,但分区/dev/sdb1还是500G。接下来扩展分区。
用growpart或者parted把分区扩展到全盘。growpart语法在老系统和部分国产系统上不一定有,用parted更通用:
bash复制# 扩分区前先备份分区表,防手滑
sfdisk -d /dev/sdb > /root/sdb-partition-backup.txt
# 用parted把分区扩展到100%
parted /dev/sdb resizepart 1 100%
执行完后,重读分区表:
bash复制partprobe /dev/sdb
udevadm settle
验证分区大小是否更新:
bash复制lsblk /dev/sdb
cat /proc/partitions
扩展xfs文件系统:
bash复制xfs_growfs /data
整个流程下来,服务完全不用断,数据库连接不会受影响。这里最关键的一步就是重读分区表,如果漏了,parted虽然改完分区表,但内核里的/dev/sdb1还是旧大小,xfs_growfs反而会报错。另外注意:growpart要求分区号和磁盘设备名分开写,比如growpart /dev/sdb 1,别写错。
4.2 怎么判断有没有重读成功
判断分区表是否重读成功,有五个常用方法,按优先级排列:
第一,lsblk。它显示的信息来自内核的块设备结构,最直接。
bash复制lsblk
第二,cat /proc/partitions。这个文件是内核块设备层的分区统计信息,如果新的分区或分区容量在这里出现,那就说明内核已经识别成功。
bash复制cat /proc/partitions
第三,fdisk -l。fdisk会同时读磁盘分区表和内核信息,如果两者不一致,有时候fdisk会打印警告,比如“Partition 1 does not start on physical sector boundary”或者“Kernel still uses the old table”。
第四,blkid。这个命令读取设备上的文件系统标识,需要内核已经建立对应设备节点才能读到。如果你能在blkid输出里看到/dev/sdb1的UUID,说明设备节点已经创建成功。
第五,dmesg。内核日志里通常会有“sdb: sdb1”这样一行输出,表示内核已经扫描到了分区。重读成功后,dmesg尾部会出现类似“sdb: sdb1 sdb2”的打印,这是最底层的确认方式。
实战中我一般就是lsblk + cat /proc/partitions双确认,两个都正常才继续往下走。
5. 常见问题与排错实录
5.1 Device or resource busy:是谁占着分区不放
这是重读分区表最经典的报错。出现时,内核告诉你:这个分区正在被使用,不能安全地替换/删除。常见的占用场景有几种:分区已经挂载到目录上;分区是某个LVM的PV成员;分区正在被数据库裸设备使用;分区上有进程的当前工作目录。
排查手段也很成熟:
bash复制# 查看占用的进程
lsof /dev/sdb1
# 或
fuser -v /dev/sdb1
如果是挂载导致的,先用df -h确认挂载点,再umount。umount提示target is busy时,用lsof找到占用文件的进程,该停的停,该迁移的迁移。如果是LVM成员,用pvs确认它属于哪个VG,从VG里移除后再重读。实在不行就用partx -d单独删分区对象,不碰其他分区。
这里分享一个经验:不要想着用“echo 1 > /proc/sys/kernel/hotplug”或者“blockdev --rereadpt -q /dev/sdb”这种偏方硬绕过。内核拒绝busy设备的重读是一种保护机制,是为了防止你正在写数据的文件系统突然被替换掉。硬来的后果,轻则数据写穿,重则文件系统崩溃。分区重读失败就老老实实找占用,不能跟内核硬刚。
5.2 partprobe 明明没报错,lsblk 就是不变
这种情况最容易让人抓狂。partprobe执行完,退出码为0,终端没有任何报错,但lsblk就是看不到新分区。排查到这里,先别怀疑命令,从这几个方向去查。
第一,确认分区表是不是真的被修改了。用fdisk -l /dev/sdb看看磁盘上有没有新分区。有时候fdisk退出时自动重读失败,但它并不会自动回滚分区表变更,就会造成“磁盘上有分区,内核不知道”的错位状态。
第二,确认设备名是否写对。如果是NVMe磁盘,设备名是nvme0n1,分区是nvme0n1p1,不是nvme0n1sda。很多人partprobe /dev/nvme0n1之后,lsblk刷新出来了nvme0n1p1,但fdisk -l显示的还是旧数据,所以要确认自己看的是同一个东西。
第三,检查是不是udev事件队列卡住了。这种情况在udev规则比较多、或者系统负载较高的时候容易出现。执行udevadm settle等一会儿,再试lsblk。如果等待时间过长,还可以用udevadm control --reload-rules重载规则再试。
第四,有一种情况比较少见,就是磁盘去重或者RAID卡透传模式下,内核的blockdev会被驱动层缓存住。这种情况基本只能走驱动层重建或者重启,没有软件层面的优雅解。但这类场景多见于老旧硬件,新内核已经很少见了。
5.3 什么时候真的只能重启
我虽然一直在讲不需要重启,但必须承认,有些场景下重启才是最稳妥的选择。
如果修改的是根分区所在磁盘的分区表,尤其是更改了根分区的大小或起始扇区,即使partprobe能成功,系统在运行中也很难安全地对根分区做“无损扩容”之外的操作。稳妥方案是:用systemd或者grub的启动参数配置好,然后重启让引导器重新读取分区表,并在启动阶段挂载新分区布局。这条规则也适用于Linux的/data、/home等被关键服务占用的分区。
另外,如果是MBR和GPT之间做转换(比如用sgdisk把MBR转成GPT),部分老内核版本对在线转换支持不到位,强制重读可能失败。遇到这种情况,备份好数据,在启动引导层配置好之后重启,比在系统里折腾半天更值。
还有一类特殊情况是扩展分区(Extended Partition)和逻辑分区(Logical Partition)在fdisk里做了复杂调整(比如删除扩展分区再重建),内核的分区管理在这个环节有时会留下残留状态,表现就是partprobe和partx怎么试都缺一块分区。我遇到过一次,是在CentOS 6的老系统上,最后老老实实重启解决。
6. 一点个人体会
踩过那么多次分区表的坑之后,我现在的习惯是:所有改分区表的操作,先备份,再执行,执行完必查。备份用sfdisk -d输出到文件,重读用partprobe和partx双保险,确认用lsblk和/proc/partitions双验证。这套流程看着繁琐,但真能减少很多后半夜被电话叫醒的概率。
还有一个细节是操作用户的权限问题。很多发行版为了安全,会要求分区类命令在root下执行,但有时部分运维使用sudo执行partprobe时,因为环境变量或者PATH的原因,会调到不同版本的util-linux命令,行为可能有细微差别。建议执行前用which partprobe确认一下路径,确保用的是系统自带的版本。
最后再分享一个小技巧:如果服务器上有多块磁盘需要同时重读,可以在脚本里循环处理。
bash复制for disk in /dev/sda /dev/sdb /dev/sdc; do
partprobe "$disk" && echo "$disk re-read OK"
done
udevadm settle
这样能把操作记录和状态输出都留下来,方便事后排查。分区表重读不算高深技术,但属于那种“看着简单、踩坑致命”的日常操作,希望这篇文章能帮你在下一次磁盘操作中少走弯路。
