目录
1. 项目目标
2. 整个系统启动流程理解
3. 整体流程概览(五步走)
3.1 存储分区规划(以32GB SD卡为例)
3.2 Uboot启动流程设计(上电自动运行)
3.3 修改Uboot源码并编译
3.4 制作完整镜像文件(.img)
3.5 烧录到目标SD卡(工厂操作)
3.6 启动后的额外挂载(自动化脚本)
1. 项目目标
将已经调试好的嵌入式 Linux 系统(Uboot + Linux Kernel + Device Tree + Rootfs)制作成一个完整 SD 卡镜像文件,交付给工厂进行批量烧写。
最终产物:imx6ull.img
工厂拿到这个文件后:
SD卡 ↓ 烧写 imx6ull.img ↓ 上电 ↓ 自动启动Linux系统2. 整个系统启动流程理解
一个嵌入式 Linux 开机过程:
上电 | BootROM: | 读取SD卡中的Uboot Uboot启动: | 加载Linux内核 | 加载设备树DTB | 设置启动参数bootargs Linux Kernel启动: | 挂载Rootfs 用户空间启动: | Linux系统运行3. 整体流程概览(五步走)
规划存储布局– 决定SD卡上每个分区放什么,多大。
配置Uboot启动参数– 让上电后自动加载内核和文件系统。
编译并烧写新的Uboot– 把配置写进代码,生成新的Uboot二进制。
打包所有文件成一个镜像– 将Uboot、内核、设备树、根文件系统按分区布局打包成单一文件。
烧录与验证– 用工具把镜像烧进SD卡,上电测试。
3.1 存储分区规划(以32GB SD卡为例)
| 分区 | 大小 | 格式 | 存放内容 |
|---|---|---|---|
| 第1分区(FAT) | ~50MB | FAT32 | Uboot二进制(u-boot.imx)、内核镜像(zImage)、设备树(.dtb)、可选的initrd(ramdisk.gz) |
| 第2分区(EXT4) | ~150MB | EXT4 | 根文件系统(即完整的Linux系统文件,如/bin,/etc,/lib,/usr等) |
| 剩余空间 | 未分配或后续使用 | — | 留给用户数据或其他分区(忽略) |
为什么这么分?
第1分区用FAT,因为Uboot可以轻松读取FAT分区来加载内核和设备树。
第2分区用EXT4作为根文件系统,稳定且支持读写。
3.2 Uboot启动流程设计(上电自动运行)
Uboot(引导加载程序)启动后,需要自动完成三件事:
设置启动参数(bootargs)– 告诉内核根文件系统在哪里、控制台串口参数等。
从SD卡第1分区加载内核(zImage)和设备树(.dtb)到内存。
启动内核(bootz命令),内核随后挂载第2分区作为根文件系统。
关键环境变量解释(新手勿怕,只需知道它们控制启动行为):
bootdelay– 倒计时秒数,超时后自动执行bootcmd中的命令。bootcmd– 倒计时结束后自动执行的命令。bootargs– 传递给内核的启动参数(例如 root 路径、控制台、文件系统类型)。load– 从存储设备加载文件到内存指定地址。bootz– 启动ARM Linux内核的命令。run:负责执行一个环境变量里预先写好的命令脚本(它可以包含一条或多条load、setenv、bootz等命令)。
设计的启动命令序列(简化版):
1. 设置 bootargs:root=/dev/mmcblk0p2 rw rootfstype=ext4 init=/linuxrc console=ttymxc0,115200 2. 加载内核:load mmc 0:1 0x80800000 zImage 3. 加载设备树:load mmc 0:1 0x83000000 imx6ull-mmc-npi.dtb 4. 启动内核:bootz 0x80800000 - 0x83000000注:
mmc 0:1表示第0个MMC设备(即SD卡)的第1个分区。
3.3 修改Uboot源码并编译
我们需要把上述启动命令固化到Uboot源码中,这样生成的Uboot二进制自带这些配置。
在Uboot源码目录下,找到板级配置文件(例如include/configs/mx6ullfire.h),添加或修改以下宏定义:
"bootdelay=2\0" \ "args_sd_boot=setenv bootargs console=ttymxc0,115200 root=/dev/mmcblk0p2 rw rootfstype=ext4 init=/linuxrc\0" \ "bootcmd_mmcx=load mmc 0:1 0x80800000 zImage;load mmc 0:1 0x83000000 imx6ull-mmc-npi.dtb;run args_sd_boot;bootz 0x80800000 - 0x83000000;\0" \ "bootcmd=run bootcmd_mmcx\0"然后重新编译Uboot,得到新的u-boot.imx文件。
3.4 制作完整镜像文件(.img)
将以上所有文件按分区布局打包成一个镜像文件(例如imx6ull.img)。
常用工具是dd配合分区表(如使用fdisk或sgdisk创建分区,再逐个写入数据)。
简化制作流程(假设你有一张已分区的SD卡,并已将各文件拷入对应分区):
将该脚本设为可执行,并添加到启动项即可,烧录完成后,将SD卡插入开发板,上电即可自动启动系统。
3.6 启动后的额外挂载(自动化脚本)
为了方便后期更新内核和调试,可以在根文件系统中加入一个开机自启动脚本(如/etc/init.d/auto_mount.sh),自动挂载NFS共享目录和SD卡的第1分区(FAT分区),以便拷贝新内核或设备树。
示例脚本内容:
#!/bin/sh # 设置IP(如果需要) ifconfig eth0 192.168.10.50 # 挂载NFS共享目录(开发机共享文件夹) mount -t nfs -o nolock 192.168.10.100:/home/rocky/ebf/shared /opt # 挂载SD卡第一个分区(FAT)到/mnt,方便更新内核 mount -t vfat /dev/mmcblk0p1 /mnt使用
dd将整个SD卡(包括分区表)导出为镜像文件:这样得到的imx6ull.img就是完整的量产镜像。sudo dd if=/dev/sdb of=imx6ull.img bs=4M status=progress3.5 烧录到目标SD卡(工厂操作)
工厂会使用两种方式之一:
使用
dd命令(在Linux烧录机):sudo dd if=imx6ull.img of=/dev/sdX bs=4M status=progress oflag=sync其中
/dev/sdX是SD卡设备(注意不要选错)。使用专用烧录工具(如Win32DiskImager或厂家提供的工具),直接选择
.img文件烧录。
3.6 shell脚本参考
一定要注意是自己的loop 不一定是loop0
# 创建两个目录用于挂载镜像的分区 mkdir -p sd/boot sd/root # 生成一个 200MB 的空白镜像文件(bs=512, count=409600 => 200MiB) # 其中约 50MB 给 boot 分区,150MB 给 rootfs 分区 dd if=/dev/zero of=./imx6ull.img bs=512 count=409600 # 在镜像上创建 MS-DOS(MBR)分区表 sudo parted imx6ull.img --script -- mklabel msdos # 创建第一个分区(FAT32),用于 /boot # 起始于 2048 扇区(为了对齐,2048*512=1MB),结束于 104447 扇区 # 大小约为 (104447 - 2048 + 1) * 512 ≈ 50MB sudo parted imx6ull.img --script -- mkpart primary fat32 2048s 104447s # 创建第二个分区(ext4),用于根文件系统 # 起始于 104448 扇区,结束于最后一个可用扇区(-1) sudo parted imx6ull.img --script -- mkpart primary ext4 104448s -1 # 将镜像文件关联到第一个可用的 loop 设备,并显示设备名(如 /dev/loop0) sudo losetup -f --show imx6ull.img # 为 loop 设备创建分区映射(生成 /dev/mapper/loop0p1 和 loop0p2) sudo kpartx -va /dev/loop0 # 格式化第一个分区为 vfat(FAT32) sudo mkfs.vfat /dev/mapper/loop0p1 # 格式化第二个分区为 ext4 sudo mkfs.ext4 /dev/mapper/loop0p2 # 挂载两个分区到先前创建的目录 sudo mount /dev/mapper/loop0p1 ./sd/boot/ sudo mount /dev/mapper/loop0p2 ./sd/root/ # 将 U-Boot 镜像(u-boot-dtb.imx)写入镜像的偏移 2 个扇区处(跳过前 1KB) # 因为 i.MX6ULL 从偏移 1KB(即第 2 个 block)处加载 U-Boot #U-Boot 不是放在SD卡分区里面的文件,而是放在SD卡最前面的固定裸地址区域,i.MX6ULL芯片上电后会直接#去这个位置找它。 sudo dd if=/opt/out/u-boot-dtb.imx of=/dev/loop0 bs=512 seek=2 conv=fsync # 复制启动相关文件(内核、设备树等)到 boot 分区 sudo cp ./all/boot/* ./sd/boot/ # 复制根文件系统内容到 rootfs 分区(保留属性,-a 表示归档模式) sudo cp -a ./all/rootfs/* ./sd/root/ # 卸载两个分区 sudo umount ./sd/boot/ ./sd/root/ # 移除分区映射 sudo kpartx -d /dev/loop0 # 释放 loop 设备 sudo losetup -d /dev/loop0 # 提示打包开始(压缩镜像,使用 xz 格式,减少体积) echo "正在打包,需要一定时间,请勿中断" xz -zf ./imx6ull.img # 清理临时目录(这里删除的是 ./gxa,但脚本中并未创建该目录,可能是用户失误或额外清理) rm -rf ./gxa ---------------------------------------备用 # 创建两个目录用于挂载镜像的分区 mkdir -p sd/boot sd/root # 创建 200MB 镜像 dd if=/dev/zero of=./imx6ull.img bs=512 count=409600 # 创建 MBR 分区表 sudo parted imx6ull.img --script -- mklabel msdos # 创建 boot 分区 sudo parted imx6ull.img --script -- mkpart primary fat32 2048s 104447s # 创建 rootfs 分区 sudo parted imx6ull.img --script -- mkpart primary ext4 104448s -1 # 绑定 loop LOOP=$(sudo losetup -f --show imx6ull.img) echo "使用设备: $LOOP" # 创建分区映射 sudo kpartx -av $LOOP # 获取分区设备名字 PART1="/dev/mapper/$(basename $LOOP)p1" PART2="/dev/mapper/$(basename $LOOP)p2" echo "boot分区: $PART1" echo "root分区: $PART2" # 格式化 sudo mkfs.vfat $PART1 sudo mkfs.ext4 $PART2 # 挂载 sudo mount $PART1 ./sd/boot/ sudo mount $PART2 ./sd/root/ # 写入 U-Boot sudo dd if=/home/xingxinliao/ebf/out/u-boot-dtb.imx of=$LOOP bs=512 seek=2 conv=fsync # 拷贝 boot 文件 sudo cp /home/xingxinliao/ebf/out/* ./sd/boot/ # 拷贝 rootfs sudo cp -a /home/xingxinliao/ebf/rootsfs/* ./sd/root/ # 卸载 sudo umount ./sd/boot/ sudo umount ./sd/root/ # 删除分区映射 sudo kpartx -d $LOOP # 释放 loop sudo losetup -d $LOOP echo "正在打包,需要一定时间,请勿中断" xz -zf ./imx6ull.img rm -rf ./gxa3.7 shell脚本语法解析参考
dd就是复制
- 可以复制磁盘
- 可以复制分区
- 可以按指定大小复制数据块
- 可以制作镜像文件
dd if=/dev/zero of=./imx6ull.img bs=512 count=409600的作用就是:创建一个200MB大小的空白SD卡镜像文件imx6ull.img。
dd if=输入文件 of=输出文件
if=/dev/zero of输入0
输出到of=./imx6ull.img