i.MX 8M嵌入式Linux存储分区与镜像烧录实战指南
1. 项目概述在基于NXP i.MX 8M系列处理器的嵌入式Linux产品开发中无论是快速原型验证还是最终产品部署存储介质通常是eMMC或SD卡的分区与镜像烧录都是绕不开的关键一步。这不仅仅是把几个文件拷贝到存储设备那么简单它直接关系到系统能否正常启动、运行以及后续的维护与升级。很多刚接触这个平台的工程师常常会卡在这一步从NXP官网下载的预构建镜像Pre-built Image烧进去就能用但一旦需要调整分区大小、更换内核或者添加自定义应用就不知从何下手了。而通过Yocto项目自己构建镜像虽然灵活性大增但其中涉及的分区配置又显得颇为复杂。我自己在多个i.MX 8M项目上踩过不少坑从最初对着官方文档照猫画虎到后来能根据产品需求灵活定制分区方案这个过程积累了不少实战经验。今天我就以i.MX 8M平台为例抛开那些晦涩的理论直接上干货把两种主流的分区与部署方法——使用预构建镜像和基于Yocto构建——的完整流程、核心原理以及避坑要点系统地梳理一遍。无论你是想快速上手评估板还是为量产产品定制固件这篇文章都能给你提供一份可直接“抄作业”的指南。2. 核心概念与部署流程总览在动手操作之前我们必须先理清几个核心概念和整体的工作流。嵌入式Linux系统的启动依赖于存储介质上特定位置的特定数据这个布局是硬件SoC的ROM代码和软件U-Boot约定好的。2.1 i.MX 8M启动流程与存储布局i.MX 8M系列处理器上电后内部的ROM代码会首先运行。它会根据启动模式引脚Boot Mode的配置去指定的存储设备如eMMC、SD卡、QSPI NOR的固定位置寻找并加载第一阶段的引导程序。对于我们常用的eMMC/SD卡启动这个固定位置通常是从存储设备起始偏移33KB即0x8400字节或1KB开始具体取决于芯片型号和启动类型普通启动或快速启动。这个被加载的程序就是我们常说的imx-boot包含了SPL和U-Boot。imx-boot运行起来后它的任务就是初始化更丰富的硬件环境然后去加载Linux内核Image文件和设备树.dtb文件。内核和设备树放在哪里呢它们通常位于一个FAT32格式的“Boot分区”里。U-Boot知道去这个分区的根目录下寻找特定名称的文件比如Image和imx8mq-evk.dtb。内核启动后会挂载“根文件系统分区”Rootfs这是一个EXT4或EXT3格式的分区里面包含了完整的Linux系统目录和我们的应用程序。所以一个典型的i.MX 8M存储布局包含三个关键部分Bootloader区非分区形式是写在存储设备绝对起始偏移地址如33KB的一段裸数据。Boot分区一个FAT32格式的、可启动的主分区存放内核镜像和设备树。Rootfs分区一个EXT4格式的主分区存放根文件系统。注意Boot分区和Rootfs分区的位置和大小是可以调整的但有一个铁律它们绝对不能覆盖Bootloader的存放区域。否则系统将无法启动。同时分区的大小也不能小于你要放入的镜像文件本身。2.2 两种部署路径的选择根据镜像来源的不同分区和部署的操作方法也分为两条主要路径路径一使用预构建镜像这是最快捷的上手方式。NXP会为每一版BSPBoard Support Package提供针对评估板编译好的完整镜像包。你下载后可以直接使用UUU工具配合一个脚本将整个系统Bootloader、Boot分区、Rootfs分区一键烧录到板子的存储介质上。这个脚本已经内置了默认的分区方案。这种方法的优点是开箱即用适合功能验证和早期开发。缺点是分区布局固定难以定制镜像内容也无法灵活增减。路径二使用Yocto构建的镜像这是产品开发的必经之路。通过Yocto项目你可以从源码开始完全自定义你的Linux系统选择需要的软件包、配置内核、调整根文件系统大小当然也包括定义分区的布局。Yocto最终会生成一个包含完整分区信息的.wic镜像文件或者分离的各个分区镜像。部署时你可以选择直接烧录.wic文件也可以像处理预构建镜像那样用脚本分别烧录各组件。这种方法灵活性极高但需要搭建Yocto构建环境学习曲线较陡。无论选择哪条路UUUUniversal Update Utility都是我们与板子通信、完成烧录的核心工具。它是一个跨平台的命令行工具通过USB OTG接口与处于下载模式Serial Downloader Mode的i.MX板卡连接可以执行U-Boot命令或Linux命令从而实现复杂的部署操作。3. 基于预构建镜像的分区与部署实战当你拿到一块i.MX 8M EVK板想最快速度看到系统跑起来用预构建镜像是最佳选择。我们以i.MX 8MQ EVK和Linux BSP 5.15.32_2.0.0为例详细走一遍流程。3.1 准备工作与环境搭建首先你需要从NXP官网下载对应版本的Linux BSP发布包。解压后在samples文件夹里你能找到一个名为example_kernel_emmc的脚本这是我们工作的起点。同时确保你的开发主机Linux或Windows上已经安装了UUU工具版本1.4.193或更高。将板子设置为串行下载模式Serial Downloader Mode。对于i.MX 8MQ EVK这通常涉及调整板上的拨码开关SW1101具体请参考你的板子手册。用USB线连接板子的USB OTG口通常是J1301到主机。3.2 部署脚本深度解析与定制官方提供的脚本是一个强大的模板但直接使用前理解每一行命令的作用至关重要。下面我结合一个修改后的、更清晰的脚本来逐段解析uuu_version 1.2.39 # 阶段1将Bootloader加载到RAM并运行 SDP: boot -f imx-boot-imx8mqevk-sd.bin-flash_evk # 注意-sd.bin-flash_evk后缀的镜像用于从SD/eMMC启动并包含刷写指令。 # 阶段2通过Fastboot协议加载内核、设备树和临时根文件系统到板子内存 FB: ucmd setenv fastboot_buffer ${loadaddr} FB: download -f Image-imx8mqevk.bin FB: ucmd setenv fastboot_buffer ${fdt_addr} FB: download -f imx8mq-evk.dtb FB: ucmd setenv fastboot_buffer ${initrd_addr} FB: download -f fsl-image-mfgtool-initramfs-imx_mfgtools.cpio.zst.u-boot FB: acmd ${kboot} ${loadaddr} ${initrd_addr} ${fdt_addr} # 执行到这里一个最小的Linux系统已经在板子的RAM中运行了为我们后续操作提供了环境。 # 阶段3在板载Linux系统中对eMMC进行分区 # 等待eMMC设备节点出现 FBK: ucmd while [ ! -e /dev/mmcblk*boot0 ]; do sleep 1; echo \wait for /dev/mmcblk*boot* appear\; done; # 识别eMMC设备号例如可能是mmcblk2 FBK: ucmd devls /dev/mmcblk*boot*; dev($dev); dev${dev[0]}; dev${dev#/dev/mmcblk}; dev${dev%boot*}; echo $dev /tmp/mmcdev; # 清除可能存在的旧MBR分区表 FBK: ucmd mmccat /tmp/mmcdev; dd if/dev/zero of/dev/mmcblk${mmc} bs512 count1 # **核心步骤创建新分区表** FBK: ucmd mmccat /tmp/mmcdev; PARTSTR$10M,500M,0c\\n600M,,83\\n; echo \$PARTSTR\ | sfdisk --force /dev/mmcblk${mmc}这里就是定义分区布局的关键。sfdisk工具通过标准输入接收分区指令。PARTSTR变量定义了两个分区10M,500M,0c第一个分区从10MB偏移开始大小为500MB类型为0cFAT32 LBA并且是启动分区。600M,,83第二个分区从600MB偏移开始大小未指定,留空表示占用剩余所有空间类型为83Linux。重要提示第一个分区的起始地址10M必须大于Bootloader的结束地址。对于i.MX 8MQBootloaderimx-boot通常写在偏移33KB约0.03MB开始的位置大小约几百KB所以10MB是安全的。务必根据你的芯片型号查阅《参考手册》确认。# 阶段4写入Bootloader到eMMC的指定位置 # 解锁eMMC boot0分区属性允许写入 FBK: ucmd mmccat /tmp/mmcdev; echo 0 /sys/block/mmcblk${mmc}boot0/force_ro # 将Bootloader镜像写入boot0区域偏移33KB即seek33块大小1K FBK: ucp imx-boot-imx8mqevk-sd.bin-flash_evk t:/tmp FBK: ucmd mmccat /tmp/mmcdev; dd if/tmp/imx-boot-imx8mqevk-sd.bin-flash_evk of/dev/mmc${mmc}boot0 bs1K seek33 # 重新锁定防止误操作 FBK: ucmd mmccat /tmp/mmcdev; echo 1 /sys/block/mmcblk${mmc}boot0/force_ro # 阶段5格式化Boot分区并放入内核、设备树 FBK: ucmd mmccat /tmp/mmcdev; while [ ! -e /dev/mmcblk${mmc}p1 ]; do sleep 1; done FBK: ucmd mmccat /tmp/mmcdev; mkfs.vfat /dev/mmcblk${mmc}p1 FBK: ucmd mmccat /tmp/mmcdev; mkdir -p /mnt/fat FBK: ucmd mmccat /tmp/mmcdev; mount -t vfat /dev/mmcblk${mmc}p1 /mnt/fat FBK: ucp Image-imx8mqevk.bin t:/mnt/fat # **关键重命名**U-Boot默认寻找名为Image的内核文件 FBK: ucmd mmccat /tmp/mmcdev; mv /mnt/fat/Image-imx8mqevk.bin /mnt/fat/Image FBK: ucp imx8mq-evk.dtb t:/mnt/fat FBK: ucmd umount /mnt/fat # 阶段6格式化Rootfs分区并解压根文件系统 FBK: ucmd mmccat /tmp/mmcdev; mkfs.ext3 -F -E nodiscard /dev/mmcblk${mmc}p2 FBK: ucmd mkdir -p /mnt/ext3 FBK: ucmd mmccat /tmp/mmcdev; mount /dev/mmcblk${mmc}p2 /mnt/ext3 FBK: acmd export EXTRACT_UNSAFE_SYMLINKS1; tar -jx -C /mnt/ext3 FBK: ucp imx-image-full-imx8mqevk.tar.bz2 t:- FBK: Sync FBK: ucmd umount /mnt/ext3 FBK: DONE3.3 实操心得与避坑指南镜像文件命名一致性这是最常见的启动失败原因。脚本中从主机下载到板子Boot分区的内核文件是Image-imx8mqevk.bin但随后被重命名为Image。这是因为U-Boot的环境变量如bootcmd里默认指定了从mmc设备加载名为Image的文件。如果你的内核文件名不同要么在脚本里正确重命名要么去修改U-Boot的环境变量。设备树文件.dtb同理。分区起始地址计算在修改PARTSTR时一定要留足安全空间。Bootloader的大小会随版本和配置变化。一个稳妥的做法是第一个分区Boot分区的起始地址至少设为Bootloader起始地址 Bootloader最大预估大小 1MB的余量。例如Bootloader在33KB大小约1MB那么从10MB开始是安全的。UUU脚本的版本与平台适配脚本开头的uuu_version指明了脚本语法版本。不同版本的UUU工具可能支持的特性不同。如果你的UUU工具版本较新而脚本较旧可能某些命令不兼容。建议使用BSP包内自带的UUU工具版本或查阅UUU文档进行适配。存储设备号识别脚本中通过/dev/mmcblk*boot0来识别eMMC。如果你的板子上有多个eMMC或SD卡槽这个通配符可能会匹配到错误的设备。在复杂硬件环境下更可靠的方法是在Linux启动后通过lsblk或dmesg | grep mmc命令手动确认设备号如mmcblk2然后硬编码到脚本中替换mmccat /tmp/mmcdev部分。4. 基于Yocto构建镜像的分区与部署当你需要定制系统比如增加软件包、修改内核配置、或者调整分区大小时就必须走Yocto构建这条路。Yocto不仅生成镜像内容还能定义镜像的布局。4.1 在镜像构建阶段定义分区这是最“Yocto”的方式通过修改配方Recipe和配置文件让构建系统直接产出符合你分区要求的镜像。调整根文件系统Rootfs分区大小根文件系统的大小主要由三个变量控制你可以在conf/local.conf文件中进行设置# 定义根文件系统镜像的初始大小单位KB IMAGE_ROOTFS_SIZE \524288\ # 例如设置为512MB # 定义系统开销的乘数因子默认约为1.3倍 IMAGE_OVERHEAD_FACTOR \1.3\ # 定义额外的空闲空间单位KB IMAGE_ROOTFS_EXTRA_SPACE \65536\ # 例如额外增加64MB最终Rootfs镜像的大小大致为(IMAGE_ROOTFS_SIZE * IMAGE_OVERHEAD_FACTOR) IMAGE_ROOTFS_EXTRA_SPACE。设置好后重新构建镜像即可。定义整体分区布局Wic ImageYocto可以使用WicWindows Imaging Creator工具创建包含分区表的完整磁盘镜像.wic文件。分区布局由.wkskickstart文件定义。对于i.MX平台默认的.wks文件通常在meta-freescale层中定义。例如查看或自定义imx-imx-boot-bootpart.wks.in文件或其继承者你会看到类似内容part u-boot --source rawcopy --sourceparams\fileimx-boot\ --ondisk mmcblk --no-table --align ${IMX_BOOT_SEEK} part /boot --source bootimg-partition --ondisk mmcblk --fstypevfat --label boot --active --align 8192 --size 64 part / --source rootfs --ondisk mmcblk --fstypeext4 --label root --align 8192 bootloader --ptable msdospart u-boot: 指定imx-boot写入的位置--align ${IMX_BOOT_SEEK}确保了它与硬件要求的偏移对齐。part /boot: 定义Boot分区。--size 64指定分区大小为64MB。你可以根据内核和设备树的总大小来调整预留一些余量。part /: 定义Rootfs分区大小由前面提到的IMAGE_ROOTFS_*变量决定。bootloader --ptable msdos: 指定生成MBR格式的分区表。你可以在你的机器配置.conf文件中通过WKS_FILE变量指定自定义的.wks文件。构建完成后在tmp/deploy/images/machine/目录下除了单独的Image、dtb、rootfs.tar.bz2还会生成一个image-name-machine.wic文件。这个.wic文件就是一个包含了完整分区表和所有数据的磁盘镜像可以直接用dd命令或UUU烧录到存储设备无需额外的分区脚本。注意使用UUU烧录.wic镜像需要版本1.4.165或更高。命令非常简单uuu your_board_specific_script.uuu在脚本中对应位置使用FB: flash -raw2sparse all image-name.wic即可。4.2 在镜像部署阶段进行分区U-Boot环境这种方法类似于处理预构建镜像但使用的是从Yocto构建产出中提取的、更纯净的组件。它适合需要在U-Boot环境下进行动态分区或者你的部署流程已经固化在U-Boot脚本中的场景。首先你需要确保U-Boot配置中启用了CONFIG_CMD_MBR支持以便使用mbr write命令。可以通过在U-Boot源码目录执行make menuconfig然后在Command line interface - MMC utilities下找到并启用。构建出支持MBR的U-Boot后Yocto构建产物中会包含分离的分区镜像。在deploy/images/machine/目录下.wic文件旁边通常会有image-name-machine.wic.bmap和分解出的分区镜像如image-name-machine.direct完整镜像以及image-name-machine.direct.0.fatBoot分区、image-name-machine.direct.1.imgRootfs分区。部署脚本的核心部分从使用Linux下的sfdisk转变为在U-Boot中使用mbr write命令# 在UUU脚本的FBFastboot阶段向U-Boot传递命令 FB: ucmd setenv mbr_parts nameboot,start8M,size128M,bootable,id0x0e;namerootfs,start140M,size4096M,id0x83 FB: ucmd mbr write mmc ${emmc_dev}mbr_parts环境变量的语法定义了分区名称、起始偏移、大小、是否可启动、分区类型ID。定义好后mbr write命令会将其写入指定MMC设备。后续的烧录命令也变为使用flash -raw2sparse直接写入对应的分区句柄FB: flash -raw2sparse mmcsda1 0.fat FB: flash -raw2sparse mmcsda2 1.img FB: flash bootloader imx-boot-imx8mqevk-sd.bin-flash_evk这里mmcsda1和mmcsda2是U-Boot中对eMMC第一个和第二个分区的命名。这个命名需要根据你的平台和存储设备类型来确认。最准确的方法是在成功烧录一次预构建镜像后在U-Boot命令行下输入mmc part或gpt read mmc 2假设eMMC是设备2来查看分区列表和对应的句柄。4.3 Yocto部署的注意事项分区扩展问题无论是通过.wks文件定义分区还是通过U-Boot的mbr write命令创建分区如果Rootfs分区的大小被设置得大于实际根文件系统镜像的大小那么在首次启动Linux后你需要手动使用resize2fs命令来扩展文件系统以填满整个分区空间。否则多出来的空间无法被使用。命令通常是resize2fs /dev/mmcblk2p2请替换为你的实际Rootfs分区设备。Wic镜像的通用性直接烧录.wic镜像虽然方便但它的分区布局是固定的。如果你的产品线有多种存储容量如8GB和16GB的eMMC使用固定大小的.wic镜像可能不适合小容量设备或者会浪费大容量设备的空间。此时使用部署阶段分区的动态脚本可能更灵活。构建产物管理Yocto构建一次会产生大量中间文件和最终镜像占用大量磁盘空间。定期清理tmp和cache目录是必要的。同时建议对重要的构建配置local.conf,bblayers.conf和自定义的.wks文件进行版本管理。5. 常见问题排查与高级技巧在实际操作中你肯定会遇到各种问题。这里我总结了一些典型故障和排查思路。5.1 系统无法启动Bootloader阶段失败现象上电后串口无输出或输出少量信息后停止。排查步骤检查启动模式确认板子的启动模式拨码开关是否正确设置为从eMMC/SD卡启动并且处于正常启动Normal Boot模式而非下载模式。确认Bootloader镜像确保烧录的imx-boot镜像与你的芯片型号如8MQ, 8MM和启动设备-sd表示SD/eMMC完全匹配。烧录到了正确的偏移地址如33KB。检查存储介质尝试更换一张新的或已知良好的SD卡/eMMC。有时存储介质损坏或兼容性问题会导致Bootloader无法被正确读取。测量电压与时钟使用示波器检查核心电压、DDR电压和时钟是否正常。不稳定的电源是Bootloader运行失败的常见硬件原因。5.2 系统无法启动内核加载阶段失败现象U-Boot启动成功打印出版本信息但在尝试加载内核Image或设备树dtb时失败提示“File not found”、“Bad Linux ARM64 Image magic!”或直接复位。排查步骤检查文件名在U-Boot中使用fatls mmc dev:part命令例如fatls mmc 2:1列出Boot分区内容确认Image和.dtb文件是否存在且名称与U-Boot环境变量bootcmd或bootargs中指定的一致。检查文件完整性在U-Boot中尝试使用iminfo命令检查内核镜像头信息或使用fatload加载一小段数据看看是否成功。也可以在主机的Linux环境下使用file命令检查Image文件是否为有效的ARM64内核镜像。检查设备树确保设备树二进制文件.dtb是针对你当前使用的具体板型包括内存大小、外设连接编译的。错误的设备树会导致内核在初始化硬件时崩溃。检查Boot分区格式和大小确认Boot分区是FAT32格式并且有足够的剩余空间。损坏的文件系统可能导致读取失败。5.3 系统无法启动内核启动后挂载根文件系统失败现象内核解压成功开始启动但最后卡在“Kernel panic - not syncing: VFS: Unable to mount root fs”或类似错误。排查步骤检查内核命令行参数在U-Boot中使用printenv查看bootargs变量。关键参数是root它必须正确指向你的Rootfs分区例如root/dev/mmcblk2p2 rootwait rw。确认分区号p2与实际相符。检查文件系统格式确认Rootfs分区是用ext4或ext3格式化的并且与内核中启用的文件系统驱动匹配通常ext4是默认编译的。检查根文件系统内容在U-Boot中如果支持ext4ls命令可以尝试列出Rootfs分区根目录看是否有/bin,/sbin,/lib等目录。或者将存储介质连接到电脑用读卡器检查文件系统是否完整解压。启用更详细的内核日志在U-Boot的bootargs中添加earlycon、loglevel8或debug等参数让内核输出更详细的启动信息有助于定位挂载失败的具体原因。5.4 UUU工具使用问题现象执行UUU脚本时失败提示无法识别设备、命令错误或传输失败。排查步骤设备连接确保板子处于串行下载模式USB线连接正确连接到板子的USB OTG口而非USB HOST口。在Linux主机上使用lsusb命令应能看到NXP Semiconductor或Freescale的USB下载设备。权限问题Linux在Linux下可能需要将当前用户加入plugdev组或为UUU工具设置sudo权限或创建合适的udev规则以避免Permission denied错误。脚本语法确保UUU脚本的语法与你的UUU工具版本兼容。较新的UUU工具可能废弃了某些旧命令如SDPU推荐使用SDPV。镜像文件路径确保UUU脚本中引用的镜像文件-f参数路径正确或者这些文件与UUU脚本在同一目录下UUU会在当前目录和脚本所在目录查找。5.5 高级技巧创建多套分区方案脚本对于需要频繁切换测试不同配置如不同内核版本、不同根文件系统的开发者可以准备多个部署脚本。例如flash_emmc_default.uuu: 烧录默认的预构建镜像。flash_emmc_large_rootfs.uuu: 烧录自定义的、Rootfs分区更大的镜像。flash_emmc_custom_kernel.uuu: 烧录预构建的Rootfs但使用自己编译的内核和设备树。通过修改脚本中的PARTSTR、镜像文件名和路径可以快速切换。将这些脚本和对应的镜像文件归档管理能极大提升开发和测试效率。5.6 高级技巧在U-Boot中动态调整启动参数如果你不想每次修改内核或设备树都重新烧录Boot分区可以利用U-Boot的环境变量。将内核和设备树文件放在Boot分区里然后在U-Boot中设置setenv loadimage fatload mmc 2:1 ${loadaddr} Image setenv loadfdt fatload mmc 2:1 ${fdt_addr} imx8mq-my-custom.dtb setenv bootargs consolettymxc0,115200 earlyconec_imx6q,0x30860000,115200 root/dev/mmcblk2p2 rootwait rw setenv bootcmd run loadimage; run loadfdt; booti ${loadaddr} - ${fdt_addr} saveenv这样你只需要替换Boot分区里的文件就可以改变启动内容而无需修改分区表或重写整个存储介质。这对于内核调试阶段非常有用。