当你在终端敲下dd if=u-boot.imx of=/dev/sdb bs=1k seek=1命令时,是否思考过为什么要有seek=1这个参数?为什么uboot要从1K偏移开始写入?这些问题背后隐藏着i.MX6ULL启动流程的精妙设计。本文将带你深入理解SD卡在i.MX6ULL启动过程中的完整生命周期,从ROM Code加载uboot到最终挂载rootfs的每个环节。
i.MX6ULL的启动过程是一场精心编排的接力赛,每个环节都有其特定的职责和规则。理解这个流程,是掌握启动盘制作原理的基础。
当i.MX6ULL芯片上电后,内置的ROM Code会首先执行。这个固化在芯片内部的微型程序负责初始化最基本的硬件环境,并按照预定义的顺序寻找可启动的介质。对于SD卡启动模式,ROM Code会:
为什么是1K偏移? 这与历史沿革和硬件设计有关。早期的存储设备通常保留前几个扇区用于MBR等特殊用途,NXP延续了这一设计理念,将uboot放在1K偏移处(即跳过前两个512字节扇区),为可能的特殊用途预留空间。
一张标准的启动SD卡通常包含三个关键区域:
| 区域类型 | 起始扇区 | 大小 | 文件系统 | 内容 |
|---|---|---|---|---|
| Uboot区 | 2 | 20478 | 无 | uboot镜像 |
| FAT分区 | 20480 | 1024000 | FAT32 | kernel/dtb |
| RootFS分区 | 1228800 | 剩余空间 | Ext4 | 根文件系统 |
这种分区设计体现了启动过程的阶段性需求:
使用fdisk对SD卡分区时,那些看似随意的数字其实都有精确计算。以典型的8GB SD卡为例:
bash复制# 创建第一个分区(FAT)
起始扇区:20480
结束扇区:20480 + 1024000 - 1 = 1044479
# 创建第二个分区(Ext4)
起始扇区:1228800
结束扇区:SD卡总扇区数 - 1
这些数字来源于:
FAT分区的必要性:
Ext4分区的优势:
实际操作中,文件系统创建命令的关键参数:
bash复制# FAT分区创建(-F 16指定FAT16,-F 32指定FAT32)
mkfs.vfat -F 32 -n BOOT /dev/sdb1
# Ext4分区创建(-L设置卷标,-b指定块大小)
mkfs.ext4 -L rootfs -b 4096 /dev/sdb2
那个看似简单的dd命令实际上精确控制了写入位置:
bash复制dd if=u-boot.imx of=/dev/sdb bs=1k seek=1 conv=fsync
参数解析:
bs=1k:每次读写1KB(与SD卡扇区512B对齐)seek=1:跳过输出文件开始的1KB(正好是2048字节,即4个扇区)conv=fsync:确保数据完全写入物理介质为什么不是从绝对扇区地址2开始写? 因为dd操作的是块设备原始数据,不受分区表影响。seek=1正好对应扇区地址2(1KB=2×512B)。
Uboot在SD卡中的存储结构如下:
code复制扇区0-1:保留(可能用于MBR等)
扇区2-N:IVT + DCD + Uboot镜像
IVT(Image Vector Table)是关键数据结构,包含:
c复制typedef struct {
uint32_t header;
uint32_t entry;
uint32_t reserved1;
uint32_t dcd;
uint32_t boot_data;
uint32_t self;
uint32_t csf;
uint32_t reserved2;
} ivt_header;
ROM Code会解析IVT中的entry字段,找到uboot的入口地址,完成控制权移交。
当uboot尝试加载内核时,会执行以下步骤:
关键uboot命令示例:
bash复制# 设置内核加载地址
setenv loadaddr 0x80800000
# 设置设备树加载地址
setenv fdt_addr 0x83000000
# 从FAT分区加载文件
fatload mmc 0:1 ${loadaddr} zImage
fatload mmc 0:1 ${fdt_addr} imx6ull-14x14-evk.dtb
# 启动内核
bootz ${loadaddr} - ${fdt_addr}
关于dtb文件存放位置的常见困惑:
实际操作建议:
bash复制# 查看可用的dtb文件
ls /mountpoint/*.dtb
# 复制特定版本的dtb
cp imx6ull-14x14-evk.dtb /mountpoint/
根文件系统的挂载依赖于内核命令行参数。典型的bootargs设置:
bash复制setenv bootargs console=ttymxc0,115200 root=/dev/mmcblk0p2 rootwait rw
参数解析:
root=/dev/mmcblk0p2:指定第二个分区为根文件系统rootwait:等待设备就绪rw:以读写方式挂载Ext4文件系统的特性对嵌入式系统尤为重要:
创建优化过的Ext4文件系统:
bash复制# 禁用journal(牺牲安全性换取性能)
mkfs.ext4 -O ^has_journal /dev/sdb2
# 设置保留块比例(默认为5%,嵌入式系统可降低)
tune2fs -m 1 /dev/sdb2
现象1:卡在"Starting kernel..."
可能原因:
现象2:内核panic无法挂载rootfs
排查步骤:
使用uboot命令深入调试:
bash复制# 查看SD卡分区表
mmc part
# 测试文件系统读取
fatls mmc 0:1
# 内存内容检查
md.b 0x80800000 0x100
内核早期调试(添加earlyprintk参数):
bash复制setenv bootargs console=ttymxc0,115200 earlyprintk root=/dev/mmcblk0p2
针对不同应用场景的优化方案:
大容量存储应用:
快速启动需求:
Ext4的特性启用:
bash复制# 创建时启用额外特性
mkfs.ext4 -O extent,dir_index,filetype /dev/sdb2
# 挂载时优化选项
mount -o noatime,nodiratime,data=writeback /dev/sdb2 /mnt
FAT分区的优化:
bash复制# 使用更大的簇大小提升大文件性能
mkfs.vfat -F 32 -s 8 -n BOOT /dev/sdb1
掌握了这些底层原理后,你可以:
例如,实现双系统启动的关键步骤:
bash复制# 在FAT分区存放两套内核/dtb
fatwrite mmc 0:1 ${loadaddr} zImage_A 0x100000
fatwrite mmc 0:1 ${loadaddr} zImage_B 0x100000
# 通过环境变量选择启动项
setenv boot_system_A 'fatload...; bootz...'
setenv boot_system_B 'fatload...; bootz...'