1. kpartx 到底解决什么问题:从一次“挂不上镜像”说起
做嵌入式Linux开发、玩树莓派镜像、或者帮朋友恢复一张整盘备份的人,几乎都遇到过同一个尴尬:手里拿到一个xxx.img文件,明明里面有好几个分区,用mount -o loop去挂载,系统却只认出一个分区,或者干脆报错。
我第一次被这个问题卡住是在改树莓派系统镜像的时候。树莓派官方系统镜像里默认有两个分区:一个是 FAT 格式的 boot 分区,存放固件、内核和cmdline.txt;另一个是 ext4 的 rootfs 根文件系统分区。我当时想改一下cmdline.txt里的串口参数,结果发现整个镜像文件只能被当作单个块设备挂载,根本没有办法直接访问第二个分区。
后来查了一圈,才知道这类场景需要用到 kpartx。这个命令的名字拆开看就是“kernel + partition parts + x(扩展)”,核心作用是把磁盘镜像里的分区表解析出来,并通过 Linux 内核的 device mapper 机制,为每个分区生成一个独立的块设备节点。生成之后,每个分区就可以像普通磁盘分区一样被mount、被fsck、被读取和修改。
所以 kpartx 不是什么花哨工具,它是 Linux 磁盘管理、嵌入式镜像操作、系统备份恢复场景里非常实用的“桥接工具”。如果你是运维、嵌入式开发、玩开源硬件、或者经常跟整盘镜像打交道的人,这个命令值得花十分钟搞明白。这篇文章我把原理讲清楚,再给几个可以直接照抄的完整案例,以及我踩过的坑和排查经验。
2. 安装与环境准备:三分钟让环境就绪
2.1 各主流发行版的安装方式
kpartx 来自multipath-tools软件包,绝大多数发行版仓库里都有,安装很简单。
| 发行版 | 安装命令 |
|---|---|
| Debian / Ubuntu / Raspberry Pi OS | sudo apt install multipath-tools |
| CentOS / RHEL / Rocky / Fedora | sudo dnf install multipath-tools |
| openSUSE | sudo zypper install multipath-tools |
| Arch / Manjaro | sudo pacman -S multipath-tools |
装完之后可以用kpartx -v看一下版本号,确认命令可用。有些精简定制系统里可能找不到这个包,那就需要看有没有kpartx的独立二进制,或者直接使用 util-linux 里的losetup -P作为替代方案,这个我在后面会专门对比。
2.2 依赖的内核模块与权限问题
kpartx 依赖两个底层能力:loop设备支持,以及device mapper(devicemapper)。绝大多数现代发行版默认都启用了,但如果你用的是很精简的容器环境、或者自编译的内核,需要先确认一下。
检查 loop 设备是否可用:
ls /dev/loop*如果列表是空的,尝试加载模块:
sudo modprobe loop确认 device mapper 是否工作:
ls -l /dev/mapper/control如果/dev/mapper/control不存在,需要加载 dm_mod 模块并确认device-mapper相关服务正常。实际操作中,只要是正常安装的完整 Linux 系统,这两个依赖通常都不需要额外配置。我在 WSL 里也测试过 kpartx,基本可以用,但偶尔会遇到 loop 设备数量限制,这个问题的排查方法我会在第五部分专门讲。
2.3 使用权限建议
kpartx 创建映射设备、调用 losetup 关联镜像文件,都需要 root 权限。普通用户直接跑几乎肯定会遇到 Permission denied 或者找不到设备节点的问题。
命令行前面加sudo是基本操作。如果你在脚本里反复调用,建议不要偷懒省掉sudo检查逻辑,而是提前判断当前用户是否为 root 或拥有 sudo 权限,不然脚本跑一半才崩溃会很被动。
提示:千万不要用
sudo kpartx -a去处理一个你已经挂载正在使用的物理磁盘分区,这点在第 5.4 节会展开讲。
3. 核心命令详解:每个参数都不是白给的
3.1 命令语法与常用参数
kpartx 的基本用法可以总结成一句话:kpartx <参数> <设备或镜像文件>。
最常用的几个参数如下:
| 参数 | 作用 | 典型场景 |
|---|---|---|
-l | 列出镜像或设备中的分区映射情况,但不真正创建映射 | 动手前先看清楚分区表 |
-a | 根据分区表创建设备映射,生成 /dev/mapper/loopXpY 节点 | 挂载镜像前的核心步骤 |
-d | 删除之前创建的映射 | 卸载镜像后的清理步骤 |
-u | 更新某个现有映射,分区表变化后使用 | 修改了分区表之后刷新 |
-v | 输出详细执行过程 | 排查问题时强烈推荐 |
-s | 同步模式,等 udev 处理完映射节点再返回 | 在脚本里用,避免设备节点还没生成就继续执行 |
这几个参数里,-l和-a是使用频率最高的,-v和-s则是写脚本时的可靠保障。我最常用的组合是:
sudo kpartx -avs /path/to/disk.img-a创建映射,-v打印每个映射的起始扇区和大小,-s确保映射节点创建完成后再返回,三个参数配合起来非常顺手。
3.2 映射名称规则与设备节点
创建映射之后,kpartx 会按照设备名 + 分区号的规则生成映射设备节点。
假设你的镜像被关联到了/dev/loop0,那么:
- 第一个分区对应
/dev/mapper/loop0p1 - 第二个分区对应
/dev/mapper/loop0p2 - 第 N 个分区对应
/dev/mapper/loop0pN
这里有一个很容易忽略的细节:kpartx -a之后,原本的/dev/loop0这个设备仍然存在,但它的角色变成了“整个磁盘的块设备”,而你真正要挂载的分区是/dev/mapper/loop0p1这些映射设备。刚接触的人经常会不小心去mount /dev/loop0,结果报错说找不到文件系统,实际上他们应该挂载的是loop0p1。
如果你使用的是更加现代的方式,先手动losetup -P关联镜像,则内核会自动生成/dev/loop0p1这类节点,这时不通过 kpartx 也可以直接挂载分区。两种方式殊途同归,kpartx 的优势在于兼容性好、历史久、在各类脚本和教程中出现频率极高。
3.3 创建映射时系统后台到底做了什么
理解 kpartx 的工作原理,对排查问题很有帮助。当你执行:
sudo kpartx -av disk.imgkpartx 大致做了这几件事:
第一,如果传入的参数是一个普通文件而非块设备,它会先调用 losetup,找一个空闲的 loop 设备,把镜像文件关联上去。这一步等同于你手动执行sudo losetup -f disk.img。
第二,它读取这个块设备上的分区表(支持 MBR 和 GPT),解析出每个分区的起始扇区、扇区数量以及分区类型。
第三,根据分区表信息,调用 device mapper 的接口,创建名称为loopXpY的映射设备,并把这些映射设备挂到/dev/mapper/目录下。
整个过程可以用一个比喻来理解:你把一张写着“仓库货架分布图”的图纸交给 kpartx,它照着图纸搭出一个个独立的“取货口”,每个取货口对应一个真实的分区。之后你只需要去对应的取货口取货,而不用每次都把整个仓库的门打开,绕过货架从头找。
这也是为什么 kpartx 对单个文件系统镜像(比如只包含一个根文件系统的.ext4文件)没有必要使用,因为单个文件系统本身就可以直接用mount -o loop挂载,不需要解析分区表。
3.4 查看分区信息的标准动作
在任何写操作之前,先用-l看一下分区表信息,这是一个非常值得养成的习惯。
sudo kpartx -l disk.img输出会显示每个分区的映射名称、扇区数量、起始扇区等关键信息。例如:
loop0p1 : 0 131072 /dev/loop0 8192 loop0p2 : 0 9216000 /dev/loop0 139264从左到右分别是:映射设备名、分区扇区数量、底层块设备、分区起始扇区。这些信息能帮你确认这个镜像里到底有几个分区,分区大概多大,是否和预期一致。如果镜像文件本身有问题或者分区表损坏,这里会有异常输出或者直接报错。
提示:先
-l看清楚,再-a动手,这个顺序虽然简单,但能帮你避免大部分低级错误,尤其是对着一块不认识的“祖传镜像”时。
4. 完整实操案例:修改树莓派系统镜像里的配置和文件系统
4.1 案例背景与准备
假设你现在有一个树莓派系统镜像raspios.img,你需要做两件事:修改/boot/cmdline.txt里的内核启动参数,并且往 rootfs 分区里放一个自己的配置文件。
环境假设:一台 Linux 主机,已安装 kpartx,目标镜像文件路径为/data/raspios.img。
先创建一个挂载点目录备用:
mkdir -p /mnt/rpi-boot /mnt/rpi-rootfs4.2 用 kpartx -l 看清分区表
先不要急着映射,用-l预览一下:
sudo kpartx -l /data/raspios.img输出示例:
loop0p1 : 0 262144 /dev/loop0 8192 loop0p2 : 0 17563648 /dev/loop0 270336这表明镜像有两个分区:第一个分区从扇区 8192 开始,大小约 128MB;第二个分区从扇区 270336 开始,大小约 8GB。树莓派系统一般就是 boot 分区加 rootfs 分区,看到这个输出心里就有底了。
4.3 创建映射并挂载 boot 分区
接下来执行创建映射:
sudo kpartx -avs /data/raspios.img输出类似:
add map loop0p1 (253:0): 0 262144 linear 7:0 8192 add map loop0p2 (253:1): 0 17563648 linear 7:0 270336确认/dev/mapper/loop0p1和/dev/mapper/loop0p2都已经出现后,挂载 boot 分区:
sudo mount /dev/mapper/loop0p1 /mnt/rpi-bootboot 分区是 FAT 格式,挂载后可以直接读写里面的文件:
ls /mnt/rpi-boot sudo sed -i 's/console=serial0,115200//' /mnt/rpi-boot/cmdline.txt这里cmdline.txt是树莓派引导时读取的内核参数文件,修改后可以立即生效,适用于调整串口、显示输出、根文件系统挂载参数等。
4.4 挂载 rootfs 分区并修改内容
挂载 rootfs 分区的方式一样:
sudo mount /dev/mapper/loop0p2 /mnt/rpi-rootfs如果需要修改 rootfs 里的内容,比如把某个配置文件放进去:
sudo cp /data/my-config.conf /mnt/rpi-rootfs/etc/my-config.conf如果 rootfs 分区是 ext4 格式,直接写入没有任何问题。但如果需要修改的是系统目录(比如/etc/passwd、/etc/fstab),要特别注意不能只修改文件内容,还要考虑文件权限、selinux/AppArmor 上下文等因素,否则可能在真实硬件上出现奇怪的问题。嵌入式开发中常见的做法是通过 chroot 进入 rootfs 执行脚本,但这就涉及 chroot 环境下的/proc、/dev挂载等额外操作,本文先不展开。
4.5 卸载清理:先 umount 再 kpartx -d
修改完成后,清理步骤的顺序非常重要。
先卸载分区:
sudo umount /mnt/rpi-boot sudo umount /mnt/rpi-rootfs再删除映射:
sudo kpartx -d /data/raspios.img最后释放 loop 设备:
sudo losetup -d /dev/loop0这个顺序不能乱。如果映射没删除就试图losetup -d,会提示设备忙;如果 umount 没完成就kpartx -d,正在使用映射设备的进程会受影响,严重时可能导致正在写入的数据损坏。
很多时候你会发现明明umount执行成功了,kpartx -d却报/dev/loop0忙。这时候通常是某个进程的工作目录还在挂载点里面,或者文件被进程打开着。可以用lsof +D /mnt/rpi-rootfs查看是哪个进程占用了目录,找到后退出那个进程再清理。
如果对镜像文件直接执行过kpartx -a,在有些版本上执行kpartx -d并不会自动释放 loop 设备,所以我习惯于在清理映射后手动执行一遍losetup -d确保零残留。
4.6 重新打包与校验
如果你只是修改镜像内容,不涉及分区表调整,那么把修改后的 img 文件直接拿来写 SD 卡或作为产物分发即可,不需要重新打包。
如果你在修改过程中动了分区表(比如用 parted 调整了分区大小),那就要注意分区表一致性。kpartx 创建映射时读取的是当时的分区表,如果分区表发生了变化,需要先删掉旧映射再用kpartx -av重新创建,否则挂载到的还是旧分区信息。
写入 SD 卡或实际设备前,建议先用下面的命令做一次文件系统一致性检查:
sudo fsck -f /dev/mapper/loop0p1 sudo fsck -f /dev/mapper/loop0p2这一步在修改过文件系统内容、或者经历过不正常的断电中断后格外重要。树莓派系统镜像里 rootfs 分区是 ext4,Linux 的 ext4 在异常卸载后会有日志恢复机制,但运行 fsck 确认无损是最稳妥的做法。
4.7 一个快速脚本:把“映射、挂载、卸载、清理”封装起来
实际工作中,我通常会写一个简单的 bash 函数来管理这套流程,避免每次敲一长串命令还容易漏步骤。
#!/bin/bash # Usage: img-mount.sh <image-file> IMG="$1" if [ -z "$IMG" ]; then echo "Usage: $0 <image-file>" exit 1 fi sudo kpartx -avs "$IMG" echo "Mapping created. Use 'kpartx -l $IMG' to verify."再看一个清理函数:
#!/bin/bash # Usage: img-umount.sh <image-file> IMG="$1" LOOP_DEV=$(losetup -j "$IMG" | awk -F: '{print $1}') sudo kpartx -d "$IMG" if [ -n "$LOOP_DEV" ]; then sudo losetup -d "$LOOP_DEV" fi脚本里先通过losetup -j找到绑定到该镜像的 loop 设备,然后再关联删除,避免手动判断编号。这种封装方式在需要批量处理多个镜像、或是在 CI 流水线里自动处理镜像时非常实用。
5. 常见问题与排查技巧实录
5.1 分区映射设备不出现或消失
执行kpartx -av之后,/dev/mapper/loop0p1一直没有出现,这是新手最常见的问题。
排查思路分几步:
第一,确认 kpartx 是否真的识别到了分区。看-v输出,如果输出里就没有add map行,说明分区表解析本身就有问题。
第二,检查 udev 是否来得及创建设备节点。这也是我推荐使用-s参数的原因。没有-s时,kpartx 可能已经返回,但 udev 还在异步处理设备节点创建,脚本里紧接着去mount就会找不到设备。
第三,确认设备节点是否被创建但路径不对。有些系统里映射设备可能挂在/dev/dm-0、/dev/dm-1这种路径下,然后由 udev 在/dev/mapper/下创建软链接。如果/dev/mapper/目录下没有对应名称,可以查看ls -l /dev/dm-*确认是不是名称映射的问题。
第四,检查内核日志:
dmesg | tail -50如果出现类似device-mapper: create ioctl failed: Device or resource busy的信息,说明同名映射已经存在,需要先用kpartx -d清理。
5.2 target is busy 与占用根源
umount时报target is busy,或者kpartx -d时报设备忙,九成是有进程还在使用分区。
常见的占用来源包括:
- 某个 shell 的当前工作目录还停留在挂载点内
- 文本编辑器、文件管理器打开了挂载点内的文件
- chroot 环境没有退出
- 系统服务通过 systemd mount 单元接管了挂载点
解决思路是先用lsof或fuser找到占用进程:
lsof +D /mnt/rpi-rootfs或者:
fuser -vm /mnt/rpi-rootfs然后根据输出结果结束对应进程,或者切换 shell 目录后再执行 umount。如果是 systemd 挂载单元导致的,需要先systemctl stop相关单元。
5.3 loop 设备耗尽与模块参数
kpartx -a时如果提示找不到可用的 loop 设备,通常是因为空闲 loop 设备数量被用完了。
查看当前 loop 使用情况:
losetup -a默认情况下系统最多支持 8 个 loop 设备(/dev/loop0到/dev/loop7),现代发行版一般已经调整到 256 个。如果不够用,可以通过修改内核模块参数临时增加:
sudo sh -c 'echo "options loop max_loop=128" > /etc/modprobe.d/loop.conf' sudo modprobe -r loop sudo modprobe loop注意卸载 loop 模块前要确保所有 loop 设备都没在被使用,否则会报错。这种场景常见于长期在服务器上处理大量镜像文件的自动化流水线,排查到这一步基本就能解决了。
5.4 修改后的镜像无法启动的几个常见元凶
我在嵌入式开发和系统镜像维护过程中,见过不少“改了镜像之后设备起不来”的案例,kpartx 本身一般不是罪魁祸首,但和 kpartx 相关的操作流程常常是间接原因。
第一个常见元凶是/etc/fstab里使用了 UUID 引用分区,但修改镜像内容时不小心改变了文件系统的 UUID。比如拿 mkfs 重建文件系统、或者拷贝文件时用了错误的工具,UUID 一变,启动时根分区就找不到了。
第二个常见元凶是树莓派这类设备的引导参数里使用了PARTUUID。树莓派的cmdline.txt里一般会有root=PARTUUID=xxxx,这个 PARTUUID 是分区表级别的标识,跟文件系统 UUID 不是一回事。如果你用表编辑器改动过分区表,PARTUUID 可能会变化,就必须同步修改cmdline.txt里的引用。
第三个常见元凶是分区表类型被意外改变。比如原本是 GPT 分区表,处理镜像时用了某些老旧工具写成了 MBR,导致引导器找不到分区。用fdisk -l和parted -l都可以快速确认分区表类型。
第四个常见元凶是文件系统大小和分区大小不匹配。比如分区表里写了 8GB 分区,但文件系统实际只有 4GB,或者反过来。这种情况下系统能启动但空间异常,或者启动时根文件系统检查失败。可以用resize2fs或e2fsck修复。
5.5 kpartx 与 losetup、partx 的混淆问题
很多人在网上搜资料时会看到losetup -P和partx也能完成类似工作,容易搞混。
losetup -P /dev/loop0 disk.img是把镜像关联到 loop 设备,并且让内核扫描分区表,自动生成/dev/loop0p1之类的分区节点。这种方法很现代、依赖 util-linux 和内核的分区扫描能力,在很多新系统上效果不错。但它的局限在于分区节点是内核直接生成的普通块设备,没有经过 device mapper,在某些需要持久映射、多路径、快照的高级场景下不如 kpartx 灵活。
partx -a /dev/loop0是让内核重新读取并注册某个块设备上的分区,它修改的是内核里的分区表视图,不会在/dev/mapper下建节点。partx更多用于让内核识别新写入的分区表,比如在裸设备上刚用 fdisk 分完区后,执行一遍partx -a让系统立刻认识新分区,省去重启。
kpartx 的价值在于:它面向 device mapper 生态,在嵌入式镜像、多路径存储、系统备份恢复等场景中稳定可靠,而且可以直接对普通镜像文件操作。日常使用中你不需要在这几个工具之间纠结,记住一点就行:处理“整个磁盘镜像文件、里面有多个分区、想挂载某个分区”的场景,kpartx 是第一选择。
5.6 常见错误速查表
| 现象 | 原因 | 解决办法 |
|---|---|---|
No such file or directory | 镜像文件路径不对,或 loop 设备不存在 | 检查文件路径,执行ls /dev/loop*确认 loop 设备 |
device-mapper: create ioctl failed | 同名映射已存在 | 先kpartx -d清除旧映射 |
mount: can't read superblock | 挂载了整盘而不是分区 | 挂载/dev/mapper/loopXpY,而不是/dev/loopX |
target is busy | 有进程占用挂载点 | 用lsof/fuser找到进程并处理 |
loop: No such device or address | loop 模块未加载 | sudo modprobe loop |
| 修改后无法启动 | UUID / PARTUUID 引用不一致 | 检查/etc/fstab与cmdline.txt引用 |
6. 使用心得与几个容易忽略的细节
6.1 操作顺序是成功的一半
kpartx 的完整操作链看起来很简单,但顺序错了就会出各种莫名问题。我个人总结的黄金顺序是:
kpartx -l查看分区信息和预期是否一致kpartx -avs创建映射,并等 udev 完成设备节点创建mount /dev/mapper/loopXpY挂载具体分区- 执行所需修改
umount卸载所有挂载的分区kpartx -d删除映射losetup -d释放 loop 设备
这个顺序适用于绝大多数 kpartx 实战场景,写脚本时也建议固化成函数,避免漏步骤。尤其是最后两步,很多人只做到 umount 就以为完事了,结果下次处理时发现一堆残留的/dev/mapper设备,或者 loop 设备被占满。
6.2 脚本化自动化要点
如果要在自动化场景中反复使用 kpartx,有几个细节值得注意。
第一,尽量用-s参数强制同步模式,避免设备节点还没生成就被后续命令使用。第二,清理时要动态查找 loop 设备,而不是硬编码/dev/loop0,因为并发任务可能分配不同的空闲设备。第三,所有 kpartx 操作都要有 root 权限,脚本里建议在开头统一检查id -u。第四,处理完成后最好验证一下清理结果,确认/dev/mapper下没有残留映射。
#!/bin/bash if [ "$(id -u)" -ne 0 ]; then echo "Please run as root" exit 1 fi6.3 与备份恢复组合使用
kpartx 最常见的实际应用之一,就是从整盘镜像里恢复单个文件或目录。
假设你有一台设备的完整备份device.img,现在只想找回里面/etc目录下的一个配置文件。用 kpartx 挂载对应分区,找到文件拷贝出来,再按顺序清理,整个过程只需要几分钟,不需要把整张镜像恢复到物理设备上。这种处理方式比“整个设备恢复”效率高得多,也是企业环境里文件级恢复常用的思路。
更进一步,你还可以用 kpartx 挂载镜像后配合rsync增量同步部分目录到目标系统,这在批量部署和系统迁移时非常实用。
6.4 尽量避免在物理磁盘上滥用 kpartx
kpartx 不只可以处理镜像文件,它也可以直接对物理设备操作,比如:
sudo kpartx -a /dev/sdb这条命令会把/dev/sdb整块磁盘的分区表解析出来,并创建/dev/mapper/sdb1之类的映射设备。但这里有个隐患:如果该磁盘本身已经挂载了分区,或者系统已经通过内核认出了/dev/sdb1,你相当于同时存在两套访问同一分区的路径,处理不当很容易造成数据不一致,甚至误操作覆盖数据。
我的建议是:除非你明确知道自己在做什么,否则不要对正在使用的物理磁盘使用kpartx -a。处理镜像文件才是 kpartx 最舒服的舞台。
6.5 一个小技巧:处理 GPT 分区表的注意事项
kpartx 对 GPT 分区表的支持已经非常成熟,但有一个细节值得注意:GPT 分区表在磁盘开头不仅有保护性 MBR,还有 GPT 头,所以第一个分区的起始扇区一般不是 2048 就是 4096 的倍数,和 MBR 时代常见的 63 扇区对齐方式不同。
处理 GPT 镜像时用kpartx -l看到的第一分区起始扇区通常会很大,这完全正常,不用慌。真正需要担心的是有些老旧的第三方工具在处理 GPT 镜像时不认识分区表,会把镜像误判为“没有分区”,这时用 kpartx 反而能正确识别。
6.6 个人使用体会
折腾 Linux 这么多年,kpartx 是我在处理系统镜像、磁盘备份、嵌入式开发时离不开的一个小工具。它不像 fdisk、mkfs 那么常被提起,但真正遇到“多分区镜像挂不上”的问题时,它几乎是最顺手的解决方案。
我现在的习惯是:在公司内部的镜像构建脚本里、个人玩树莓派的折腾过程中、以及给客户做系统迁移方案时,都会默认把 kpartx 纳入标准工具链。它没有什么学习成本,规则简单、输出直观,只要记住“先 -l 查看、再 -a 映射、最后 -d 清理”这套流程,基本就不会出错。
最后再分享一个细节:如果你经常在一个不太熟悉的 Linux 环境里工作,第一次执行 kpartx 之前,先确认它是否已经安装。很多精简服务器镜像默认不装 multipath-tools,遇到问题时临时 apt install 虽然不慢,但第一次报错时能被这个原因节省几分钟排查时间,也挺值得的。