零基础详解:什么是Bootloader?U-Boot启动流程全解析!
一、什么是Bootloader?
Bootloader(引导加载程序),是一段特殊的“小程序”,它运行在 系统刚上电时,主要目的是:
- 初始化硬件环境(如:内存、时钟、中断、串口等)
- 然后将 Linux内核从 flash(NAND, NOR FLASH,SD,MMC等)拷贝到SDRAM中,最后启动Linux内核
通俗一点讲,Bootloader 就像电脑中的 BIOS —— 当你按下电源开关,它最先启动,完成最基础的硬件检测后,才将控制权交给操作系统(比如 Windows 或 Linux)。
📌 举个例子:
项目 | PC系统 | 嵌入式Linux系统 |
---|---|---|
初始程序 | BIOS | Bootloader |
操作系统 | Windows | Linux |
存储介质 | 硬盘 | Flash / SD卡等 |
二、U-Boot 是什么?
U-Boot(全称为 Universal Bootloader)是一种 开源、通用的Bootloader,支持多种架构(ARM、MIPS、x86等),被广泛应用于嵌入式Linux开发中。
它的主要任务:
- 初始化硬件资源(如内存、串口、存储器等)
- 加载Linux内核到内存中
- 传递启动参数给内核
- 启动Linux内核
三、U-Boot启动过程:分为两个阶段
🔹 第一阶段(汇编阶段)
此阶段主要用 汇编代码编写,功能包括:
- 初始化时钟系统
- 关闭看门狗(防止系统误复位)
- 关闭中断
- 启动指令Cache(ICache)
- 关闭数据Cache与TLB
- 关闭MMU
- 初始化SDRAM(用于加载后续程序)
- 初始化NAND Flash
- 代码重定位(将U-Boot代码搬运至SDRAM中运行)
💡 注意:这部分代码位置受限(通常在片内RAM或FLASH运行),所以通常要写得很精炼。
🔹 第二阶段(C语言阶段)
这部分用 C语言编写,主要功能是:
- 初始化串口(便于调试)
- 打印启动信息
- 检测内存映射情况
- 从存储介质读取Linux内核镜像和根文件系统(initrd)将内核映象和根文件系统映象从 Flash上读到SDRAM空间中
- 设置内核启动参数
- 跳转到Linux内核执行入口
四、U-Boot是怎么启动Linux内核的?
当U-Boot完成自身工作后,就会把控制权交给Linux内核。那么,怎么跳转呢?
👉 本质就是:修改PC寄存器的值为内核所在地址
这样CPU下一条执行指令就来自Linux内核,开始正式启动!
五、U-Boot与Linux之间的参数传递机制
这部分是面试常考,请务必掌握!
在跳转到内核之前,U-Boot 会设置好三个寄存器:
寄存器 | 含义 | 示例说明 |
---|---|---|
R0 | 保持为0 | 固定写0即可 |
R1 | 机器ID(开发板型号) | 如友善之臂开发板是199,见mach-types.h |
R2 | 参数的地址(tag结构体) | tag链表,里面保存了内存大小、命令行、根文件系统等 |
📌 这些参数是内核“启动时所需的环境信息”,如果不给内核传递参数,它无法知道设备的具体配置!
- R0寄存器:设置为0,通常用于标识内核启动时的参数。
- R1寄存器:传递给内核一个“机器ID”。这是一个唯一标识当前硬件平台的ID,每个开发板或CPU都会有一个唯一的ID。内核会根据该ID来判断是否支持当前硬件平台。
- R2寄存器:存储一个指向参数列表的指针,该列表包含了内核启动时需要的所有环境信息(例如内存大小、内存地址、文件系统等)。
💡 什么是 tag(启动参数)?
tag 是一组结构体,存放了启动参数,形式如下:
struct tag {
struct tag_header {
uint32_t size; // tag大小(单位:4字节)
uint32_t tag; // tag类型(如ATAG_MEM、ATAG_CMDLINE等)
} hdr;
union {
struct tag_mem32 mem;
struct tag_cmdline cmdline;
// 其他类型参数...
} u;
};
📌 常见的tag有:
- ATAG_CORE:表示起始
- ATAG_MEM:内存起始地址与大小
- ATAG_CMDLINE:命令行参数(如根文件系统路径)
- ATAG_NONE:表示结束
六、为什么U-Boot要关闭 Cache 和 MMU?
- 上电后RAM未初始化前,Cache 不能正常工作。
- 如果数据 Cache 开启了,会导致从未初始化的Cache中读取错误数据,程序可能崩溃!
- 所以:必须关闭MMU和Data Cache,指令Cache可以开也可以不开。
七.为什么要给内核传递参数?
在启动Linux内核之前,U-Boot完成了对硬件的基本初始化。此时,U-Boot已经“适应”了当前的开发板,但Linux内核并不一定能直接支持所有开发板。因为每个开发板的硬件配置都可能有所不同,例如不同的CPU型号、内存配置、外设接口等。而内核本身并不是为每个具体的开发板都做了硬件适配的,因此,内核在启动时需要了解当前开发板的硬件环境。为此,U-Boot需要将这些硬件信息传递给内核,以帮助内核做相应的配置和适配。
为什么要传递这些参数?
- 硬件适配:Linux内核需要了解当前硬件平台的配置(例如内存大小、CPU架构、设备树等)才能正确配置和初始化硬件资源。如果没有这些信息,内核就无法正确识别或初始化硬件设备。
- 启动过程的灵活性:开发板通常会有不同的硬件平台,U-Boot的作用就是提供一个通用的引导机制,而通过参数传递,U-Boot使得内核在加载后可以根据传递的参数调整自身的行为和配置。
- 内核启动时的配置:内核需要根据不同的硬件环境来调整内存、CPU等硬件的使用模式。例如,在某些开发板上可能需要配置特定的硬件接口,或者根据内存的大小调整内核的行为。通过参数传递,内核能够灵活地进行这些调整。
八、系统启动完整流程回顾
下面是系统上电到进入Linux系统的完整流程图:
[ 上电/复位 ]
↓
[ ROM代码执行 ]
↓
[ 初始化外设 & SDRAM ]
↓
[ 加载并执行U-Boot ]
↓
[ U-Boot初始化硬件 ]
↓
[ 加载Linux内核 + initrd ]
↓
[ 设置启动参数(R0, R1, R2)]
↓
[ 跳转到内核入口 ]
↓
[ Linux内核初始化 ]
↓
[ 挂载根文件系统 ]
↓
[ 启动用户空间进程 ]
九、总结一句话
Bootloader 就是操作系统的“搬运工 + 启动器”,而 U-Boot 就是最流行的 Bootloader,它让 Linux 成为可能!
📌 小贴士:常见面试题汇总
面试问题 | 回答要点 |
---|---|
什么是Bootloader? | 启动时执行的第一段程序,初始化硬件,加载并启动内核 |
U-Boot做了哪些事? | 两阶段:汇编(初始化)、C语言(加载内核+参数设置+跳转) |
U-Boot如何跳转到Linux内核? | 设置PC寄存器为内核地址 |
参数是如何传递给内核的? | 用R0=0, R1=机器ID, R2=tag结构体地址传递 |
为什么要关闭Data Cache? | 避免RAM未初始化时Cache取错误数据导致程序异常 |
什么是tag结构?如何组织? | struct tag,含header+联合体,ATAG_CORE起始、ATAG_NONE结束 |