U-Boot启动探秘:从汇编到命令行的奇幻之旅 - 指南
引言
在嵌入式Linux系统中,U-Boot作为最主流的BootLoader,其启动过程犹如一场精心编排的舞台剧,分为上下两个半场。上半场(Stage1)是用汇编语言编写的硬件初始化“序章”,寂静而迅速;下半场(Stage2)则是用C语言构建的软件生态“主剧”,丰富而互动。理解这场“演出”的每一个环节,是进行U-Boot移植、定制和深度调试的基石。本文将带领大家深入U-Boot的源码世界,逐一解析其Stage1与Stage2的核心步骤。
一、 U-Boot的两种模式与两个阶段
在深入细节之前,我们首先回顾U-Boot的宏观架构:
- 两种工作模式:
- 启动加载模式:自主从Flash加载内核并启动,是产品发布时的正常工作模式。
- 下载模式:通过串口或网络从主机下载文件(如内核映像),用于开发和系统更新,通常提供一个命令行接口供用户交互。
- 两个启动阶段:
- Stage1:用汇编语言实现,完成最底层的、依赖于CPU体系结构的硬件初始化。
- Stage2:用C语言实现,完成更复杂的外设初始化和应用逻辑,最终提供交互界面。
二、 Stage1:汇编世界的奠基礼
Stage1是整个U-Boot的基石,其代码通常位于 cpu/<arch>/start.S
文件中(例如,对于ARM920T,路径为 cpu/arm920t/start.S
)。它的目标是构建一个能够运行C代码的最小化环境。
2.1 入口点与链接脚本
一个可执行的映像文件必须有一个唯一的全局入口点。U-Boot通过链接脚本(如 board/smdk2410/u-boot.lds
)来定义这个入口。
OUTPUT_ARCH(arm)
ENTRY(_start) // 定义入口点为 _start 符号
SECTIONS
{
. = 0x00000000; // 入口地址在Flash的0地址
.text :
{
cpu/arm920t/start.o (.text) // 确保start.S的代码在最前面
*(.text)
}
// ... 其他段(.rodata, .data, .bss等)
}
TEXT_BASE = 0x33F80000
定义了U-Boot在RAM中运行的基地址,Stage1后期会将自身重定位到此地址执行。
2.2 Stage1 核心步骤详解
1. 设置异常向量表
位于 _start
处,这是CPU上电后执行的第一条指令。
.globl _start
_start: b reset // 复位异常,跳转到真正的启动代码ldr pc, _undefined_instructionldr pc, _software_interrupt// ... 其他异常向量
当发生任何异常时,CPU会自动跳转到对应的地址执行。
2. 进入SVC模式
复位后,首先将CPU设置为管理模式,此模式拥有最高特权,可以执行所有操作。
mrs r0, cpsr // 读取当前程序状态寄存器CPSR到r0
bic r0, r0, #0x1f // 清除模式位
orr r0, r0, #0xd3 // 设置为SVC模式,并禁止IRQ和FIQ中断
msr cpsr, r0 // 写回CPSR
3. 关闭看门狗
看门狗的作用是防止程序死机,但在初始化阶段,我们需要先关闭它,防止其误触发复位。
ldr r0, =pWTCON // pWTCON为看门狗控制寄存器地址
mov r1, #0x0
str r1, [r0] // 向控制寄存器写入0,关闭看门狗
4. 屏蔽所有中断
在初始化过程中,不允许被中断打扰。
mov r1, #0xffffffff
ldr r0, =INTMSK
str r1, [r0] // 屏蔽所有主中断
5. 设置CPU时钟与分频
配置CPU核心时钟(FCLK)、AHB总线时钟(HCLK)和APB总线时钟(PCLK)的比例。
ldr r0, =CLKDIVN
mov r1, #3 // 设置分频比,例如 FCLK:HCLK:PCLK = 1:2:4
str r1, [r0]
6. 设置CP15协处理器
CP15用于控制MMU、Cache等系统核心功能。
cpu_init_crit:mov r0, #0mcr p15, 0, r0, c7, c7, 0 // 使ICache和DCache失效mcr p15, 0, r0, c8, c7, 0 // 使TLB失效// 关闭MMU和Cachemrc p15, 0, r0, c1, c0, 0bic r0, r0, #0x00002300 // 清除控制位bic r0, r0, #0x00000087orr r0, r0, #0x00000002 // 对齐检查使能orr r0, r0, #0x00001000 // 使能ICachemcr p15, 0, r0, c1, c0, 0
7. 初始化内存控制器
这是最关键也是最板级相关的一步。通过配置内存控制寄存器,让CPU能够正确地访问SDRAM。代码通常在 board/<board>/lowlevel_init.S
中。
mov ip, lr
bl lowlevel_init // 调用低级初始化函数,配置内存时序
mov lr, ip
mov pc, lr
8. 设置栈指针并为BSS段清零
C语言的运行需要栈空间。同时,未初始化的全局变量位于BSS段,需要将其初始化为0。
// 栈设置(在RAM中分配空间)
stack_setup:
ldr r0, _TEXT_BASE // TEXT_BASE = 0x33F80000
sub r0, r0, #CFG_MALLOC_LEN // 分配动态内存区
sub r0, r0, #CFG_GBL_DATA_SIZE // 分配全局数据结构体空间
sub sp, r0, #12 // 设置栈指针sp
// BSS段清零
clear_bss:
ldr r0, _bss_start
ldr r1, _bss_end
mov r2, #0
clbss_l:
str r2, [r0]
add r0, r0, #4
cmp r0, r1
ble clbss_l
9. 跳转到Stage2
至此,C语言运行环境已准备就绪,通过一条简单的指令,跳转到Stage2的C入口函数。
ldr pc, _start_armboot
_start_armboot: .word start_armboot // start_armboot是C函数地址
三、 Stage2:C语言的繁华世界
Stage2的入口函数是 lib_arm/board.c
中的 start_armboot()
。从这里开始,世界变得“丰富多彩”。
3.1 初始化函数序列
start_armboot
函数会依次调用一个初始化函数表 init_sequence[]
,完成一系列基础设置:
init_fnc_t *init_sequence[] = {
cpu_init, // 进一步的CPU设置
board_init, // 开发板相关的GPIO、时钟初始化
interrupt_init, // 初始化中断控制器
env_init, // 初始化环境变量存储
init_baudrate, // 初始化控制台波特率
serial_init, // 初始化串口
console_init_f, // 初始化控制台(第一阶段)
display_banner, // 打印U-Boot启动信息
print_cpuinfo, // 打印CPU信息
checkboard, // 打印开发板信息
dram_init, // 计算并保存SDRAM配置信息
NULL,
};
每个函数指针非NULL的函数都会被依次执行,如果任何函数执行失败,系统将挂起。
3.2 外围设备初始化全景
在基础初始化之后,U-Boot开始初始化更复杂的外设,为加载内核和用户交互做准备:
- Flash初始化:
flash_init()
- 识别并初始化Nor Flash,建立Flash分区信息。 - 内存分配器初始化:
mem_malloc_init()
- 初始化堆管理器,为后续的动态内存申请(如malloc
)做准备。 - NAND Flash初始化:
nand_init()
- 如果系统支持NAND,则初始化NAND控制器和驱动。 - 环境变量重定位:
env_relocate()
- 将环境变量从Flash(如Nor的特定扇区或NAND的OOB区)加载到RAM中。 - 外围设备初始化:
devices_init()
- 初始化其他平台设备。 - 中断使能:
enable_interrupts()
- 此时才使能系统中断。 - 网络初始化:如
cs8900_get_enetaddr()
- 初始化网卡,获取MAC地址,为网络下载功能做准备。 - 总线初始化:
i2c_init()
- 初始化I2C总线,用于访问EEPROM、PMIC等I2C设备。 - 显示设备初始化:
drv_lcd_init()
- 初始化LCD控制器和背光,为图形化启动界面或LOGO显示做准备。
3.3 终极舞台:主循环
当所有初始化工作完成后,U-Boot最终进入它的“舞台中心”——main_loop()
函数。
for (;;) {
main_loop(); // 位于 common/main.c
}
在 main_loop()
中,U-Boot会:
- 执行启动延时倒计时。
- 处理用户输入(在倒计时内按下任意键进入命令行模式)。
- 根据环境变量(如
bootcmd
)自动启动内核(启动加载模式)。 - 如果进入命令行模式,则解析并执行用户输入的各种命令(下载模式)。
总结
U-Boot的启动过程是一个从底层硬件到上层应用的完美递进:
- Stage1(汇编阶段):在“黑暗”中摸索,从设定异常向量开始,逐步构建光明(初始化SDRAM),最终搭建起C语言的舞台(设置栈、清零BSS段)。它的每一步都至关重要,任何错误都会导致系统“猝死”。
- Stage2(C语言阶段):在Stage1搭建的舞台上,各种“角色”依次登场(CPU、串口、内存、Flash、网卡等),按照严格的顺序完成初始化。最终,舞台准备就绪,U-Boot作为“主持人”登场,要么按照剧本(
bootcmd
)自动执行,要么等待观众(用户)的指令。
理解这个过程,就如同掌握了嵌入式系统从“沉睡”到“苏醒”的全景图,无论是进行系统移植、性能优化还是疑难排查,都将得心应手。