partprobe工具
操作系统目录/usr/sbin/partprobe
程序安装包parted-3.1-17.el7.x86_64.rpm
命令用法:
partprobe
是用来告知操作系统内核 分区表发生变化的工具,告知方式是请求内核重读分区表
选项如下:
-d
不会让内核重读分区表,分区表发生变化后使用该命令partproe -d /dev/sdi
不会告知内核分区发生了变化
-s
先让内核重读分区表,再显示设备已拥有的分区信息
[root@node1 ~]# partprobe -s /dev/hda
/dev/hda: msdos partitions 1 2 3 4 <5 6 7 8 9> #4分区为扩展分区,符号<内部的分区为逻辑分区
-h
显示帮助选项信息
-v
显示程序的版本信息
源码分析
partprobe
主要操作是 告知操作系统内核 磁盘分区表发生了变化,接下来操作由内核完成。内核会先删除当前系统缓存中的磁盘分区表,然后重新从磁盘中读取分区表,主要通过io驱动函数ioctl来完成
执行partprobe
命令基本代码流程如下:
- 正常的
getopt_long
函数的命令行解析 - 根据是否传入磁盘进行判断,如果传入磁盘,如
partprobe /dev/sdb
则进行磁盘处理;如未传入制定磁盘,则先获取到当前设备所有的磁盘,并加入到链表中,然后顺序执行分区表处理
partprobe.c
中的主函数处理如下int n_dev = argc - optind; //获取是否传入磁盘参数 if (n_dev != 0) {//如果传入则一个一个处理 int i; for (i = optind; i < argc; i++) { PedDevice *dev = ped_device_get (argv[i]); if (dev == NULL || process_dev (dev) == 0) //process_dev分区处理的主函数 status = 1; } } else {//否则先获取当前设备所有磁盘 ped_device_probe_all ();//将获取到的磁盘加入到全局链表中 PedDevice *dev; for (dev = ped_device_get_next (NULL); dev;//遍历链表,一个一个顺序执行分区检测 dev = ped_device_get_next (dev)) if (process_dev (dev) == 0) status = 1; }
- 从磁盘将分区表读到内存中,并告知操作系统内核分区表的布局
static int process_dev (PedDevice* dev) { PedDiskType* disk_type; PedDisk* disk; disk_type = ped_disk_probe (dev); //获取分区类型,gpt?msdos? if (!disk_type || !strcmp (disk_type->name, "loop")) return 1; /*创建一个新的分区表,并存放在内存中。当调用ped_disk_commit_to_dev函数,由内核决定是否写入磁盘*/ disk = ped_disk_new (dev); if (!disk) goto error; /*该参数为我们的-d参数,即partprobe -d /dev/sdb 表示不会执行告知内核磁盘分区情况的操作,如传入-d参数,则当前数值为1*/ if (!opt_no_inform) { if (!ped_disk_commit_to_os (disk)) //内核操作的主函数 goto error_destroy_disk; } /*该参数表示 partprobe -s参数,执行是否打印磁盘分区信息的操作*/ if (opt_summary) /*打印当前磁盘分区的情况*/ summary (disk); ped_disk_destroy (disk); return 1; error_destroy_disk: ped_disk_destroy (disk); error: return 0; }
- 内核操作分区表版本如下,
ped_disk_commit_to_os
函数操作- 在内核版本为2.4之前,partprobe会调用BLKRRPART ioctl函数让内核重读分区表
- 在2.4.x之后的内核已经更改为使用新的blkpg接口告诉内核每一个磁盘分区的起始,结束地址。所以,在新版本内核中,不会因为分区表类型不同而无法读取磁盘分区表
//通过ped_disk_commit_to_os 函数调用的回掉函数disk_commit(disk)进行内核分区的处理 ped_disk_commit_to_os(PedDisk* disk){ ... if (!ped_architecture->disk_ops->disk_commit (disk)) } PedDiskArchOps linux_disk_ops = { partition_get_path: linux_partition_get_path, partition_is_busy: linux_partition_is_busy, disk_commit: linux_disk_commit //通过改数据结构调用linux_disk_commit函数进行处理 }; /*告知内核磁盘分区的布局*/ static int linux_disk_commit (PedDisk* disk) { if (!_has_partitions (disk)) return 1; /*若系统支持逻辑卷方式管理磁盘,且磁盘分区类型为支持dm,一般为msdos格式即mbr分区,则重读分区表。一半存放在/dev/mapper目录*/ #ifdef ENABLE_DEVICE_MAPPER if (disk->dev->type == PED_DEVICE_DM) return _dm_reread_part_table (disk); #endif /*如果设备类型为普通文件类型,即文件形式存放的,一般为我们的gpt分区类型的磁盘*/ if (disk->dev->type != PED_DEVICE_FILE) { /*要求当前内核支持blkpg的新特性,即使用blkpg接口告知内核磁盘分区表的起始地址,不需要内核自己去读。如果 当前内核不支持该特性,或者这里发现有断言退出,可以直接将改行注释掉*/ assert (_have_blkpg ()); /*内核同步磁盘分区表的主函数*/ if (!_disk_sync_part_table (disk)) return 0; } return 1; }
- 内核同步磁盘分区表的操作,即我们partprobe的主要操作
- 通过io设备驱动程序的通道管理函数ioctl,先从内核移除所有的分区表。但是当ioctl执行失败时,不会对此时失败的分区表进行移除
- 从我们遍历到的磁盘中添加分区表。如果我们因为第一步移除分区表的失败无法添加(因为使用的是blkpg新特性,移除以及添加需要保证分区表的起始结束地址一直才能确定执行成功),则进行告警
static int _disk_sync_part_table (PedDisk* disk) { ... /*从/sys/block/dev_name/range文件中获取当前系统支持的单个磁盘最大分区数量,如果获取不到,则使用默认的64*/ lpn = _device_get_partition_range(disk->dev); ... /*对当前磁盘执行分区删除操作 通过调用_blkpg_part_command(disk->dev, &linux_part,BLKPG_DEL_PARTITION) 函数执行ioctl (arch_specific->fd, BLKPG, &ioctl_arg)的分区删除操作 */ _blkpg_remove_partition (disk, j + 1); ... /*同样通过 _blkpg_part_command (disk->dev, &linux_part, BLKPG_ADD_PARTITION) 函数执行ioctl (arch_specific->fd, BLKPG, &ioctl_arg)的分区添加操作 */ _blkpg_add_partition (disk, part); /*根据执行的结果是否抛出异常进行告警*/ }
关于常见的两种问题的告警描述如下:
- "Error informing the kernel about modifications to "
"partition %s – %s. This means Linux won’t know "
"about any changes you made to %s until you reboot "
"-- so you shouldn’t mount it or use it in any way "
"before rebooting.
该问题为内核向磁盘添加分区过程中ioctl程序执行失败,一般为当前分区表x相关元数据信息错误 - Partition(s) %s on %s have been written, but we have "
"been unable to inform the kernel of the change, "
"probably because it/they are in use. As a result, "
"the old partition(s) will remain in use. You "
"should reboot now before making further changes.
该问题为内核删除分区表时出现,因为分区仍然被占用,无法从缓存中清除该分区的起始和结束地址导致。