
U-Boot 初始化硬件、分配内存后,将控制权交给 Linux 内核。
引导加载与内核启动的分层架构
U-Boot 作为独立引导加载程序,具备完全自主执行能力,无需外部干预即可完成初始化。Linux 内核则必须依赖 U-Boot 提供的预配置环境才能正常运行。
U-Boot 承担硬件抽象层的关键职责,核心任务如下:
硬件初始化与寄存器配置 — 直接与底层硬件交互,完成 CPU、内存控制器、时钟树、GPIO 等关键组件的寄存器配置,确保硬件平台处于可用状态。
内存重定位服务 — 将 Linux 内核镜像从存储设备(NAND Flash、eMMC 等)加载到 DRAM 指定地址空间,处理内核镜像的重定位工作,解决地址依赖问题。
系统环境准备 — 配置 MMU 基础映射关系、设置栈指针、准备设备树(Device Tree)等运行时环境。
启动参数传递 — 通过标准化参数传递机制(ARM 架构寄存器约定),将启动参数、设备树地址、机器 ID 等关键信息传递给 Linux 内核。
所有准备工作完成后,U-Boot 通过跳转指令将 CPU 控制权移交给 Linux 内核。地址
0x80800000 是控制权交接的关键节点 — Linux 内核入口点(Entry Point),标志从引导加载阶段向操作系统内核执行阶段的完整过渡。这种分层设计体现了嵌入式系统的模块化思想:U-Boot 专注硬件抽象和启动服务,Linux 内核专注操作系统功能实现,两者通过标准化接口实现有序的控制权移交。
从哪找内核镜像加载到 DDR
imx6ull EVK pro 开发板支持 3 种启动方式:
- NAND / EMMC 启动
- SD 卡启动
- USB 启动
此外,U-Boot 还支持网络启动:直接从远程服务器下载镜像到本地 DDR 运行,并可挂载根文件系统到远程服务器。
Linux 内核启动源码
bootz 函数
U-Boot 执行
bootcmd 命令后,最终调用 do_bootz 函数启动 Linux 内核。UBI_BOOT 启动脚本
脚本关键流程:检查
uEnv.txt → 加载环境变量 → 设置设备树 → 最终执行 bootz ${loadaddr} ${rdaddr}:${rdsize} ${fdtaddr} 启动内核。bootz 命令注册
do_bootz 函数
核心步骤:
bootz_start— 设置并找到 Linux 镜像入口点
bootm_disable_interrupts— 加载内核前必须关闭中断- 中断向量表失效 — U-Boot 的中断向量表(VBAR 指向 0x9FE70000)即将被废弃,而 Linux 内核尚未建立自己的中断向量表。若此时触发中断,CPU 会跳转到一个即将无效的处理程序,导致系统崩溃。
- 中断处理程序上下文不一致 — U-Boot 的中断处理程序依赖 U-Boot 的栈、GD、驱动模型等运行时环境。一旦开始执行内核代码,这些环境将被内核覆盖或重新初始化,中断处理程序的执行上下文不再安全。
- 内存布局被破坏 — 内核加载过程涉及设备树重定位(
boot_relocate_fdt)、内存预留(lmb_reserve)等操作,中断处理程序可能读写正在被修改的内存区域,造成数据竞争。 - Linux 内核的硬性要求 — ARM Linux 启动协议(Documentation/arm/booting.rst)明确要求:进入内核入口时 IRQ 和 FIQ 必须处于禁止状态。内核会在自身初始化完成后,建立好自己的中断向量表和处理框架,再主动使能中断。
- 第二阶段需要中断:U-Boot 的
main_loop需要响应定时器中断(用于bootdelay倒计时超时检测)、网络中断(TFTP/NFS 启动依赖网络收发)、串口中断(控制台输入响应)等。此时 U-Boot 完全掌控中断向量表、处理程序和运行时环境,中断处理是安全的。 - 内核加载前关闭中断:U-Boot 使命即将结束,中断的"所有权"要移交给内核。关闭中断是将硬件状态"清零"交还给内核的一部分,与
cleanup_before_linux(关闭/清空 cache)属于同一类"交接清理"动作。
为什么加载内核前必须关闭中断?
此时 U-Boot 即将把 CPU 控制权移交给 Linux 内核,中断处理的"主权"正在交接。如果不关闭中断,会导致以下问题:
与第二阶段打开中断的对比
第二阶段通过
interrupt_init + initr_enable_interrupts 使能中断,原因完全不同:本质:第二阶段开中断是因为 U-Boot 自身需要异步事件驱动;加载内核前关中断是因为中断基础设施的"主权"正在从 U-Boot 移交给 Linux 内核,过渡期间必须静默。
do_bootm_states— 执行状态机完成内核引导
bootz_start 函数
bootz_start 的作用是先通过 do_bootm_states(..., BOOTM_STATE_START) 初始化 bootm 流程,再确定 zImage 入口地址 images->ep(默认 load_addr 或由参数解析得到如 0x80800000),调用 bootz_setup 解析 zImage 起止范围并用 lmb_reserve 预留该内存区,随后 bootm_find_images 继续定位/准备 ramdisk 与 FDT 等镜像组件,最后在开启 Secure Boot 时对 zImage 做鉴权,任一步失败即返回错误终止启动。do_bootm_states 函数(状态机)
镜像执行状态流转:
BOOTM_STATE_START → BOOTM_STATE_RAMDISK → BOOTM_STATE_OS_PREP → BOOTM_STATE_OS_FAKE_GO → BOOTM_STATE_OS_GO状态流转含义(把 bootm 状态机按“准备 → 校验 → 交接”拆开理解)
BOOTM_STATE_START:启动 bootm 流程的“总入口”。解析参数并初始化images结构,建立 LMB 内存管理上下文,为后续加载、预留、重定位做准备。
BOOTM_STATE_RAMDISK:处理 initramfs / ramdisk(如果传入)。包括定位 ramdisk、必要的搬移,以及在 LMB 中预留其占用的内存区,避免与内核、FDT 发生覆盖。- ramdisk(传统方式):一个压缩的文件系统镜像(通常是 ext2 格式),由 U-Boot 加载到 DDR 的指定地址(
rdaddr)。内核启动时将其解压到一个 固定大小的 RAM 块设备(/dev/ram0)中,作为临时根文件系统挂载。占用双份内存(压缩镜像 + 解压后的块设备),且大小固定不可动态调整。 - initramfs(现代方式,Linux 2.6+):一个 cpio 格式的归档,可以嵌入内核镜像或由 U-Boot 单独加载。内核启动时将其解压到 tmpfs(基于页缓存的内存文件系统)中,不需要块设备抽象层。内存利用率更高(无双份占用),大小可动态伸缩。
- 挂载根文件系统需要特殊驱动(LVM、RAID、加密磁盘、网络启动等)
- 嵌入式救援/恢复系统
- 完全无存储设备的系统(如网络启动的无盘节点)
- 对于 i.MX6ULL 开发板,如果 rootfs 直接在 eMMC/SD 分区上且内核已内建对应驱动,则可不使用 ramdisk,直接通过
root=/dev/mmcblk1p2挂载根文件系统。
initramfs / ramdisk 是什么?
它们都是 内存中的临时根文件系统,在 Linux 内核启动早期、真正的根文件系统(eMMC/NAND/NFS 上的 rootfs)尚未挂载时,提供一个最小化的运行环境。
为什么需要它?
内核启动后需要挂载根文件系统,但挂载根文件系统本身可能需要驱动(如 eMMC 控制器驱动、文件系统模块、LVM/加密层等),而这些驱动可能是以内核模块形式存在的,存放在根文件系统上 — 这就形成了“鸡生蛋、蛋生鸡”的死循环。initramfs/ramdisk 就是打破这个循环的“过渡桥”。
两者的区别:
在 U-Boot 中的体现:
bootz ${loadaddr} ${rdaddr}:${rdsize} ${fdtaddr} 中的第二个参数 ${rdaddr}:${rdsize} 就是 ramdisk/initramfs 的加载地址和大小。如果不使用 ramdisk,传 - 即可:bootz ${loadaddr} - ${fdtaddr}。典型使用场景:
BOOTM_STATE_OS_PREP:OS 启动前准备阶段。对 Linux 来说主要是 准备启动参数与设备树,例如设置bootargs到/chosen,为内存、网卡等节点做 fixup,必要时重定位 FDT,并在 LMB 中预留 FDT 相关区域。
BOOTM_STATE_OS_FAKE_GO:一次“模拟 go”的过渡态。用于在真正跳转前完成收尾检查或打印信息,并触发一些与GO同路径的准备逻辑,但不真正把控制权交出去,便于在同一套框架里兼容不同镜像类型和启动路径。
BOOTM_STATE_OS_GO:真正的交接点。执行boot_selected_os,走到boot_jump_linux,完成cleanup_before_linux(关中断、清 cache 等),然后调用kernel_entry(0, machid, fdt_addr)跳入内核入口,U-Boot 使命结束。
do_bootm_linux 函数(核心引导流程)
关键要点
kernel_entry — 函数指针指向内存中 Linux 镜像的入口地址,即 Linux 第一条执行代码的位置。
启动标志 — 内核正常启动时打印
"Starting kernel ..." 。若未出现此字符串,应检查传参是否正确。ARM 传参约定(ATPCS)
r0= 0
r1= machid(机器 ID,告知内核使用哪个 CPU,调用对应初始化函数)
r2= 设备树首地址(FDT 方式,非传统 ATAG 方式)
总结
- 加载镜像 — 根据启动方式,从存储介质加载内核到 DDR
- 校验镜像 — 检查、校验镜像类型
- 传递参数 — 通过寄存器传递启动参数给内核
- 启动内核 — 跳转到入口点,运行 Linux 内核
U-Boot 完整启动流程总结
以下从全局视角梳理 Boot ROM → U-Boot 第一阶段 → U-Boot 第二阶段 → 内核加载 四个阶段的职责、关键动作与衔接关系。
四阶段职责一览
阶段 | 执行位置 | 核心目标 | 结束标志 |
Boot ROM | 片内 ROM | 确定启动介质、执行 DCD 初始化 DDR、将 U-Boot 拷贝到 DDR | 跳转到 _start |
第一阶段 board_init_f | DDR 低地址 (0x87800000),栈/GD 在 OCRAM | CPU 早期配置、DDR 探测、内存规划、代码重定位到 DDR 高地址 | 跳转到 board_init_r |
第二阶段 board_init_r | DDR 高地址 (0x9FE70000),完整 malloc/DM 可用 | DM 驱动初始化、eMMC/NAND/网络/控制台/中断初始化、环境变量加载 | 进入 main_loop |
内核加载 | DDR 高地址(U-Boot 侧)→ DDR 0x80800000(内核侧) | 从存储介质加载内核镜像到 DDR、校验、传参、跳转启动 Linux | kernel_entry(0, machid, fdt_addr) |
完整执行流程
关键地址速查(i.MX6ULL, 512MB DDR)
地址 / 符号 | 值 | 说明 |
DDR 起始 | 0x80000000 | DRAM 基地址 |
U-Boot 加载地址 | 0x87800000 | Boot ROM 拷贝目标 ( __image_copy_start) |
Linux 内核入口 | 0x80800000 | kernel_entry,zImage 加载地址 |
OCRAM 临时栈 | 0x0091FF00 | 第一阶段 CONFIG_SYS_INIT_SP_ADDR |
U-Boot 重定位地址 | 0x9FE70000 | gd->relocaddr,第二阶段执行位置 |
DDR 顶端 | 0xA0000000 | gd->ram_top (512MB) |
Linux 可用区域 | 0x80000000 – ~0x9EE6BFEF | ~494MB 连续内存 |
阶段间的衔接机制
Boot ROM → 第一阶段:Boot ROM 通过 IVT 中记录的入口地址跳转到
_start,此时 DDR 已由 DCD 初始化完成,U-Boot 镜像已在 DDR 低地址就位。第一阶段 → 第二阶段:
_main 完成重定位后,通过 bl board_init_r 跳转。此时代码已在 DDR 高地址运行,栈/GD 已切换到 DDR,BSS 已清零,C 运行时完整可用。第二阶段 → 内核:
main_loop 解析 bootcmd,执行 bootz 命令。经过镜像校验、设备树处理、中断关闭、cache 清理后,通过 kernel_entry(0, machid, fdt_addr) 将 CPU 控制权移交给 Linux 内核,U-Boot 使命结束。





