i.MX6ULL Linux内核启动流程深度解析
概述
i.MX6ULL作为NXP推出的高性能、低功耗的ARM Cortex-A7处理器,在嵌入式领域广泛应用。其Linux内核启动流程与x86架构有显著差异,主要依赖于芯片厂商提供的引导固件和设备树机制。本文将详细剖析i.MX6ULL的完整启动过程。
启动流程总览
i.MX6ULL的Linux内核启动可以分为五个主要阶段:
1. “Boot ROM阶段”- 芯片内置固件
2. "引导加载程序阶段" - U-Boot主导
3. "内核解压与重定位" - zImage处理
4. "内核主体初始化" - 汇编与C语言阶段
5. "用户空间初始化" - 系统服务启动
阶段一:Boot ROM - 芯片固化的起点
Boot ROM是i.MX6ULL上电后执行的第一段代码,由NXP固化在芯片内部ROM中,不可修改。
执行流程:
1. "上电复位"
- 芯片加电,CPU从固定地址开始执行Boot ROM代码
2. "基础硬件初始化"
- 初始化时钟、临时存储等最基本硬件组件
3. "引导设备选择"
// Boot ROM根据BOOT_MODE[1:0]引脚电平决定启动源:
// - 00: 从FUSE启动
// - 01: 串行下载模式
// - 10: SD/TF卡
// - 11: eMMC/NAND Flash
4. "镜像加载与验证"
- 从存储设备的固定位置(通常是第1KB偏移处)加载启动镜像
- 镜像必须包含特定格式的头部:
- "IVT (Image Vector Table)" - 程序入口地址表
- "Boot Data" - 启动数据配置
- "DCD (Device Configuration Data)" - 关键外设初始化配置
5. "DDR初始化"
- DCD包含寄存器配置命令,用于初始化DDR3/LPDDR2等外部RAM
- 这是关键步骤,因为U-Boot需要运行在外部RAM中
6. "控制权转移"
- 验证通过后,跳转到IVT中指定的U-Boot入口地址
阶段二:U-Boot引导加载程序
U-Boot是ARM嵌入式领域最主流的Bootloader,通常分为两个阶段。
U-Boot SPL (Secondary Program Loader)
SPL是一个精简的U-Boot,主要任务包括:
// SPL主要执行流程:
1. 初始化时钟系统
2. 初始化DDR控制器(可能复用或增强DCD配置)
3. 从存储设备加载完整U-Boot到DDR
4. 跳转到Full U-Boot执行
Full U-Boot (第二阶段)
完整版U-Boot运行在DDR中,执行更复杂的初始化:
1. "外设初始化"
- 网卡、USB、串口等外设驱动加载
2. "环境变量解析"
# 关键环境变量示例:
bootcmd=mmc dev 0; fatload mmc 0:1 0x80800000 zImage; fatload mmc 0:1 0x83000000 imx6ull.dtb; bootz 0x80800000 - 0x83000000
bootargs=console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw
3. "内核镜像加载"
- 将压缩内核映像(zImage)加载到DDR指定地址(如0x80800000)
- 将设备树Blob(.dtb)加载到另一地址(如0x83000000)
- 可选:加载initramfs到内存
4. "启动参数准备"
- 修改设备树或准备ATAG列表,传递系统信息:
- 内存大小和布局
- 内核命令行参数
- 硬件配置信息
5. "内核启动"
- 通过`bootz`或`bootm`命令跳转到zImage地址
- 将设备树地址作为参数传递给内核
---
阶段三:内核解压与重定位
arch/arm/boot/compressed/head.S
这是压缩内核镜像(zImage)的入口点,执行环境:
- CPU模式:SVC
- MMU状态:关闭
- 缓存状态:关闭或未初始化
主要任务:
1. "基础核心设置"
- 关闭中断,设置临时栈
2. "自解压过程"
// 检查压缩格式(通常是gzip)
// 在当前位置解压zImage
// 将解压后的内核映像(Image)重定位到目标地址
// 典型目标地址:0x80008000(前面保留32KB页表空间)
3. "参数传递准备"
- 设置寄存器状态:
- r0 = 0
- r1 = ARM机器ID(现代内核中重要性降低)
- r2 = 设备树Blob在RAM中的物理地址
4. "跳转到内核"
- 调用`__enter_kernel`进入解压后的内核
阶段四:内核主体初始化
arch/arm/kernel/head.S
这是解压后内核的真正入口点,负责创建内核运行的完整环境。
核心工作流程:
1. "机器描述符匹配"
// 现代内核基于设备树兼容性字符串匹配:
// 设备树: compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
// 内核查找匹配的struct machine_desc
2. "初始页表创建"
- 建立1:1映射(虚拟地址=物理地址)的段映射
- 仅映射内核代码所在的几MB空间,为开启MMU做准备
3. "MMU启用"
- 通过设置协处理器寄存器开启MMU
- 这是关键转折点,之后所有内存访问都经过MMU转换
4. "环境清理"
- 执行绝对跳转清除处理器流水线
- 设置栈指针,清零BSS段
5. "进入C语言世界"
- 跳转到`start_kernel()`函数
init/main.c → start_kernel()
这是所有Linux架构共享的初始化起点,对i.MX6ULL特别重要的调用包括:
void __init start_kernel(void)
{
// 架构相关初始化
setup_arch(&command_line); // 解析设备树,初始化内存
// 定时器系统初始化
timer_init(); // 初始化i.MX6ULL系统定时器
// 中断控制器初始化
init_IRQ(); // 初始化GIC(通用中断控制器)
// 子系统初始化
console_init(); // 控制台初始化
rest_init(); // 创建第一个用户进程
}
在`setup_arch()`中:
- 解析设备树,获取内存布局信息
- 将物理内存信息添加到memblock分配器
- 识别系统硬件拓扑结构
阶段五:用户空间初始化
rest_init() → 用户空间的诞生
1. "核心进程创建"
// 创建关键内核线程:
kernel_thread(kernel_init, NULL, CLONE_FS); // PID 1
kernel_thread(kthreadd, NULL, CLONE_FS); // PID 2
```
2. "驱动初始化 - do_initcalls()"
- 按优先级顺序执行所有编译到内核中的初始化函数
- i.MX6ULL外设驱动初始化序列:
// 早期初始化
pure_initcall() // 最早期驱动
core_initcall() // 核心子系统
// 外设驱动初始化
postcore_initcall() // GPIO控制器、时钟框架
arch_initcall() // I2C、SPI控制器
subsys_initcall() // 网卡驱动(FEC)、MMC/SD控制器
fs_initcall() // 文件系统
device_initcall() // 其他设备驱动
late_initcall() // 最后期初始化
3. "根文件系统挂载"
- 根据U-Boot传递的`root=`参数确定根文件系统位置
- 常见配置:
# SD卡根文件系统
root=/dev/mmcblk1p2 rootwait rw
# NFS根文件系统
root=/dev/nfs nfsroot=192.168.1.100:/nfsroot ip=dhcp
4. "用户空间启动"
- 如果使用initramfs,执行其中的`/init`
- 否则直接寻找并执行根文件系统中的`/sbin/init`
- 最终启动systemd或busybox init等初始化系统
完整启动流程图
i.MX6ULL 上电复位
|
v
Boot ROM (芯片固件)
| → 根据BOOT引脚选择启动设备
| → 加载IVT、DCD和U-Boot SPL
v
U-Boot SPL (精简引导)
| → 初始化DDR内存
| → 加载Full U-Boot到DDR
v
U-Boot (完整引导)
| → 初始化网卡、USB等外设
| → 加载zImage和.dtb到指定地址
| → 通过bootz跳转,传递参数
v
内核解压 (head.S)
| → 自解压zImage
| → 重定位内核映像
v
内核主体 (head.S)
| → 匹配机器描述符
| → 创建初始页表
| → 开启MMU
v
通用内核初始化 (start_kernel)
| → 解析设备树,初始化内存
| → 初始化中断、定时器
| → 初始化i.MX6ULL外设驱动
v
用户空间启动 (rest_init)
| → 创建init进程(PID 1)
| → 执行do_initcalls()初始化驱动
| → 挂载根文件系统
v
执行 /sbin/init (systemd/busybox)
|
v
系统完全就绪
关键特性总结
1. "Boot ROM依赖":ARM SoC特有的芯片固化启动代码
2. "DCD配置关键性":DDR初始化在Bootloader运行前完成
3. "设备树核心地位":完全替代x86的BIOS信息传递机制
4. "渐进式MMU启用":需要在汇编阶段手动创建初始页表
5. "驱动初始化层级化":通过initcall机制确保正确的初始化顺序
理解i.MX6ULL的完整启动流程,对于嵌入式Linux开发、系统调试和性能优化都具有重要意义。这种深入的理解能够帮助开发者更好地定位启动问题,优化启动时间,以及进行系统级定制开发。