镜像打包工具链:dd命令、mkfs系列工具、mtd-utils工具集、simg2img/lpflash工具
做嵌入式系统镜像打包,说白了就是跟一堆命令行工具打交道。我刚开始接触这块时,也被各种工具搞得晕头转向——同一个镜像,在不同场景下要用完全不同的工具链。今天我就把这套工具链给你捋清楚。
2.1 dd命令:最底层的块拷贝工具
dd 是镜像打包的基石。它的工作方式很简单:从输入文件读块,往输出文件写块。但简单归简单,用不好会出大问题。
核心用法:
# 从SD卡读取整个设备到镜像文件
sudo dd if=/dev/mmcblk0 of=./backup.img bs=4M status=progress
# 将镜像写入目标设备
sudo dd if=./backup.img of=/dev/mmcblk0 bs=4M status=progress
# 跳过前512字节(MBR),只拷贝分区数据
sudo dd if=/dev/mmcblk0p1 of=./boot.img bs=512 skip=1
我个人习惯用 bs=4M 配合 status=progress。为什么是4M?因为大多数嵌入式设备的缓存行和闪存块大小都是4M的整数倍,这样读写效率最高。我曾经在某个项目里用默认的512字节块大小,结果一个8GB的镜像拷了快40分钟——改成4M后,不到10分钟就搞定了。
注意:dd命令没有「预览」功能。你指定了输出文件,它就真写。我曾经见过同事把 of=/dev/sda 写成 of=/dev/sda1,结果把整个磁盘搞崩了。所以每次执行前,务必检查三遍设备路径。
2.2 mkfs系列工具:文件系统格式化
镜像里不能只有裸数据,还得有文件系统。mkfs系列工具就是干这个的。
| 工具 | 适用场景 | 常用参数 |
|---|---|---|
mkfs.ext4 |
Linux根文件系统、数据分区 | -L label 设置卷标,-b 4096 设置块大小 |
mkfs.vfat |
UEFI启动分区、与Windows交换数据 | -F 32 指定FAT32,-n label 设置卷标 |
mkfs.ubifs |
NAND Flash上的UBI文件系统 | -m 2048 最小I/O单元,-e 128KiB 擦除块大小 |
mkfs.jffs2 |
老式NAND/NOR Flash | --pad 填充到指定大小,-r rootfs/ 指定根目录 |
你想想看,为什么同一个分区在不同设备上大小不一样?其实就是块大小和inode密度在作怪。我建议做系统镜像时,先用 mkfs 创建一个空文件系统,挂载后把文件拷进去,再卸载。这样比直接 dd 一个已有镜像更可控。
小技巧:用 mkfs.ext4 -d rootfs/ 可以直接把目录内容打包进文件系统镜像,省去挂载拷贝的步骤。这个参数在 e2fsprogs 1.45+ 版本才支持,老系统上别用。
2.3 mtd-utils工具集:Flash专属操作
嵌入式设备里大量使用NAND/NOR Flash,这类存储介质有坏块、需要擦除后才能写。mtd-utils就是专门处理这些问题的工具集。
核心工具包括:
flash_erase:擦除Flash块。NAND Flash写之前必须先擦除,否则数据会乱。nandwrite:向NAND Flash写入数据,自动跳过坏块。nanddump:从NAND Flash读取原始数据,包括OOB区域。ubinize:将UBI镜像打包成可用于NAND的格式。mtdinfo:查看MTD设备信息,比如擦除块大小、总大小。
实际打包流程示例:
# 1. 创建UBIFS镜像
mkfs.ubifs -r ./rootfs -m 2048 -e 128KiB -c 2048 -o rootfs.ubifs
# 2. 打包成UBI卷
ubinize -o ubi.img -m 2048 -p 128KiB ubinize.cfg
# 3. 烧录到NAND Flash
flash_erase /dev/mtd3 0 0
nandwrite /dev/mtd3 ubi.img
我记得第一次用 nandwrite 时,没加 -p 参数,结果写到坏块时直接报错退出了。后来才知道,-p 参数会让工具自动跳过坏块并记录映射关系。这个坑我踩过,你别再踩了。
2.4 simg2img/lpflash工具:Android生态的稀疏镜像
Android系统用的镜像格式跟传统Linux不太一样。它们用稀疏镜像(sparse image)来节省空间——说白了就是只存有效数据,空洞部分不存。
simg2img:将稀疏镜像转换为原始镜像。
# 转换system.img为原始格式
simg2img system.img system_raw.img
# 查看稀疏镜像信息
simg2img -i system.img
lpflash:处理逻辑分区(Logical Partition)镜像。这是Android 10之后引入的,把多个物理分区抽象成逻辑分区,方便OTA升级。
# 列出逻辑分区信息
lpdump super.img
# 解包逻辑分区镜像
lpunpack super.img ./output_dir
为什么要用稀疏镜像?我举个例子:一个8GB的分区,实际只用了2GB数据。普通镜像要占8GB空间,稀疏镜像只占2GB。这在工厂烧录时能省大量时间——你想想看,少传6GB数据,能快多少?
注意:simg2img转换后的原始镜像会包含所有空洞数据(填充为0),所以磁盘空间要预留够。我曾经在只有10GB空闲的机器上转换一个8GB的稀疏镜像,结果转换后变成16GB——直接磁盘写满了。
2.5 工具链选择决策树
这么多工具,到底该用哪个?我画了一张图帮你理清思路。
2.6 实战中的避坑指南
工具链讲完了,我分享几个实际项目中踩过的坑。
坑一:dd命令的bs参数不是越大越好。 我曾经在某个项目里用 bs=64M 想加速,结果因为内存不足导致系统卡死。建议4M到16M之间,根据设备内存大小调整。
坑二:mkfs.ext4的预留块。 默认会预留5%给root用户。做系统镜像时,如果空间紧张,可以用 -m 0 关掉预留。但生产环境建议至少留1%,否则磁盘满了系统会直接挂掉。
坑三:nandwrite的坏块处理。 有些NAND Flash出厂就有坏块,nandwrite 默认会跳过。但如果你用 dd 直接写,坏块会导致整个写入失败。所以NAND设备上,千万别偷懒用dd。
我的建议:搭建一个统一的打包脚本,把工具链封装起来。比如写一个 build_image.sh,根据传入的参数自动选择工具。这样既减少人为错误,也方便后续维护。我在团队里就是这么做的,效果很好。
好了,这套工具链的核心内容就这些。记住一句话:没有万能的工具,只有合适的场景。选对工具,你的镜像打包工作就成功了一半。