U-Boot启动流程详解

U-Boot的多阶段启动架构

现代嵌入式系统中的U-Boot采用多阶段启动架构,这种设计主要是为了克服SoC内部SRAM容量有限(通常只有几十到几百KB)的限制。U-Boot的启动过程可以分为三个主要阶段:

  • BootROM/BL1阶段(SoC内部固化的ROM)

    • 负责最基础的硬件初始化
    • 配置内部时钟、设置复位向量
    • 初始化用于加载下一阶段代码的存储接口
    • 从预设的启动介质(如SD卡、eMMC、SPI Flash等)加载SPL(Secondary Program Loader次级程序加载器)到SRAM
    • 将控制权移交给SPL
  • SPL/BL2阶段(在芯片内部SRAM中运行)

    • 初始化DDR控制器和外部DRAM
    • 检测内存大小和类型,建立初步内存映射
    • 将完整的U-Boot镜像从外部存储设备加载到DRAM中
    • 完成基本的堆栈设置和C语言运行环境准备
    • 跳转到加载到DRAM中的完整U-Boot执行
  • U-Boot主体/BL3阶段(在外部DRAM中运行)

    • 使用C语言完成更复杂的外设初始化
    • 加载和处理设备树(DTB)
    • 读取环境变量设置(如bootargs、bootcmd)
    • 提供交互式命令行界面
    • 从存储介质加载Linux内核镜像
    • 设置内核启动参数并跳转执行内核

这种分阶段的设计使得U-Boot能够在有限的硬件资源下逐步扩展可用内存空间,最终实现完整的引导功能。

U-Boot启动过程详解

CPU初始化和异常向量设置

U-Boot的启动始于汇编代码的执行,这是因为在系统刚上电时,CPU处于一种非常原始的状态,没有栈、没有MMU,C代码无法直接运行。

1
2
3
reset:
    bl    _reset       /* 复位入口 */
    b     .

复位入口处的代码会执行以下关键操作:

  • 模式切换:关闭中断,切换到SVC模式(特权模式),确保执行环境稳定

    1
    
    CPSR = 0x1F  /* 进入SVC32模式,禁止FIQ/IRQ中断 */
    
  • 异常向量表设置:初始化中断/异常向量表,处理未定义指令、中断等事件

    1
    
    VBAR = 0x87800000  /* 设置向量表地址 */
    
  • CP15寄存器初始化:关闭MMU和Cache,清除TLB

    1
    2
    3
    4
    
    MRC p15, 0, r0, c1, c0, 0  /* 读取SCTLR */
    ORR r0, r0, #0x00000001  /* 设置V位,关闭MMU */
    ORR r0, r0, #0x00000010  /* 设置C位,关闭Cache */
    MCR p15, 0, r0, c1, c0, 0  /* 写回SCTLR */
    

这一阶段的核心目标是为后续的C代码执行创建一个稳定、可预测的运行环境,确保在没有MMU和Cache的情况下,程序能够正确地访问物理地址空间。

关键硬件初始化

在CPU基本配置完成后,U-Boot会执行一些关键硬件初始化操作:

  • 时钟配置:设置CPU核心、总线及外设时钟,确保各部分以正确频率运行

    1
    2
    
    /* 配置主频 */
    cpu_set_freq(800000000);
    
  • 内存控制器初始化:配置DRAM控制器,使物理内存可用(如DDR初始化)

    1
    2
    
    /* 初始化DDR控制器 */
    ddr_init();
    
  • Cache和MMU管理:关闭Cache和MMU,避免初始阶段因地址映射导致的错误

    1
    2
    
    /* 关闭缓存和MMU */
    mmu_cacheoff();
    

这些操作为后续的代码重定位和更复杂的系统初始化奠定了基础。值得注意的是,不同硬件平台的初始化细节会有很大差异,但核心思想保持一致。

代码重定位(Relocation)

由于SPL通常运行在SRAM中,而SRAM容量有限,U-Boot需要将自身代码从Flash/ROM复制到更大的DRAM空间执行:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
/* 代码重定位 */
bl    board_init_f
...
board_init_f:
    /* 复制代码到DRAM */
    bl    image_copy
    /* 更新全局数据指针 */
    ldr    r0, _start
    str    r0, [sp, #GD_SIZE - 12]
    /* 跳转到重定位后的代码 */
    bl    _start

代码重定位过程包括:

  • 复制到RAM:将U-Boot自身从Flash/ROM复制到DRAM的高地址端(如0x8FF00000)
  • 地址重定位:调整全局变量和函数指针,确保重定位后代码能正确访问数据
  • BSS段清零:清除未初始化全局变量区域,避免随机值影响逻辑

代码重定位是U-Boot启动过程中的一个关键转折点,它使U-Boot能够利用外部大容量DRAM,从而执行更复杂的初始化任务。

C语言环境准备

重定位完成后,U-Boot需要建立完整的C语言运行环境:

1
2
3
4
5
6
7
8
9
/* 初始化堆栈 */
gd->sp = (long)CONFIG_SYS_INIT SP_ADDR;

/* 初始化全局数据 */
gd->bd = (struct bd_info*)(gd->sp - sizeof(struct bd_info));
gd->sp -= sizeof(struct bd_info);

/* 设置堆栈指针 */
__asm__ __volatile__ ("mov sp, %0" : : "r"(gd->sp));

这一阶段主要包括:

  • 堆栈设置:初始化堆栈指针,为C代码提供运行环境
  • 全局数据结构:创建并初始化global_data结构,存储系统关键信息
  • 内存管理初始化:设置内存分配器,为后续内存操作做准备

C语言环境准备完成后,U-Boot就可以使用更高级的C语言功能继续初始化系统

板级外设初始化

在C语言环境完全建立后,U-Boot会执行板级外设初始化:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
void board_init_f(int BootDRAM)
{
    /* 初始化硬件 */
    cpu_init();
    /* 初始化外设 */
    board_init();
    /* 初始化时钟 */
    clock_init();
    /* 初始化环境变量 */
    env_init();
    /* 其他初始化... */
}

板级初始化通常包括:

  • 串口调试:初始化UART,启用串口输出调试信息(如printf)

    1
    2
    
    /* 初始化串口 */
    serial_init();
    
  • 存储设备初始化:初始化SD卡、NAND Flash等存储控制器

    1
    2
    3
    
    /* 初始化存储设备 */
    nand_init();
     闪存编程擦除:这是为系统准备启动过程所必需的[(deep_research_source_group_web_11)]
    
  • 设备树加载:解析设备树(DTB),为内核提供硬件描述信息

    1
    2
    
    /* 加载设备树 */
    fdt_load();
    
  • 环境变量加载:从Flash或EEPROM读取环境变量(如bootargs、bootcmd)

    1
    2
    
    /* 加载环境变量 */
    env_load();
    

板级外设初始化是U-Boot启动过程中最复杂、最具平台差异性的部分,它为系统提供了基本的输入输出能力和存储访问能力。

环境变量加载机制

环境变量是U-Boot的重要特性,它允许用户配置启动参数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
int env_load(void)
{
    struct env_driver *drv;
    /* 根据启动介质获取对应的环境驱动 */
    drv = env_driver_lookup(ENVOP辅加载, 0);
    /* 调用驱动的加载函数 */
    if (drv->load())
        return 0;
    /* 如果加载失败,返回错误 */
    return -ENODEV;
}

环境变量加载过程包括:

  • 存储位置确定:根据启动介质类型(SPI Flash、NAND等)选择对应的驱动
  • 环境变量读取:从存储介质读取环境变量数据
  • 数据校验:通过CRC校验确保环境变量数据完整
  • 环境变量解析:将读取的二进制数据解析为键值对形式

环境变量为U-Boot提供了灵活性和可配置性,用户可以通过bootcmd环境变量指定自动启动命令,通过bootargs设置内核启动参数。

内核加载与启动参数设置

完成系统初始化后,U-Boot会加载Linux内核镜像:

1
2
3
4
5
/* 加载内核镜像 */
load_image("kernel", &kernel_addr, &kernel_size);

/* 设置内核启动参数 */
set Bootargs("console=ttyS0,115200 root=/dev/mmcblk0p2");

内核加载和启动参数设置包括:

  • 内核镜像加载:从指定存储介质加载内核镜像到内存中

    1
    2
    
    /* 加载zImage */
    loadz("kernel", &kernel_addr, &kernel_size);
    
  • 设备树加载:加载设备树二进制文件(DTB)到内存

    1
    2
    3
    4
    
    /* 设置设备树地址 */
    setenv("fdtaddr", "0x48000000");
    /* 加载设备树 */
    load("dtb", "0x48000000", "imx6ull-som-evk.dtb");
    
  • 启动参数配置:设置内核启动参数(bootargs)

    1
    2
    
    /* 设置内核启动参数 */
    setenv("bootargs", "console=ttyS0,115200 root=/dev/mmcblk0p2 earlycon");
    
  • 启动命令执行:通过bootcmd环境变量执行启动命令

    1
    2
    
    /* 设置自动启动命令 */
    setenv("bootcmd", "bootz ${kernel_addr} - ${fdtaddr}");
    

内核加载是U-Boot启动过程中的关键步骤,它决定了系统将运行哪个Linux内核版本,以及内核将如何初始化硬件。

U-Boot的跳转执行机制

完成所有初始化和内核加载后,U-Boot会通过bootzbootm命令跳转到内核入口:

1
2
/* 执行bootz命令 */
run Command("bootz");

跳转执行过程包括:

  1. 寄存器参数设置:根据ARM架构的启动协议设置寄存器

    1
    2
    3
    4
    
    /* 设置r0为内核入口地址 */
    mov r0, #0x80008000
    /* 设置r2为设备树地址 */
    ldr r2, _fdt_addr
    
  2. 内核启动参数传递:设置内核命令行参数

    1
    2
    
    /* 设置内核命令行参数 */
    str r1, [r0, #0x100]  /* 假设内核参数在0x100偏移处 */
    
  3. 跳转到内核入口:通过指令跳转到内核程序

    1
    2
    
    /* 跳转到内核入口 */
    blx r0
    

在ARM架构中,启动内核时需要遵循特定的寄存器约定:

  • r0:内核入口地址
  • r1:内核大小(可选)
  • r2:设备树地址(如果使用设备树)
  • sp:栈指针,指向内核堆栈

跳转到内核是整个启动流程的最终环节,此时U-Boot已完成所有准备工作,将控制权移交给了Linux内核

U-Boot与Linux内核的交互机制

U-Boot与Linux内核之间的交互主要通过以下几种方式实现:

设备树传递机制

设备树是现代嵌入式系统中描述硬件信息的标准方式:

1
2
3
4
5
6
7
8
/* 加载设备树 */
load_image("dtb", &fdt_addr, &fdt_size);

/* 调整设备树缓冲区大小 */
fdtResize();

/* 验证设备树完整性 */
fdtPrint();

设备树传递过程包括:

  • 设备树加载:从存储介质加载.dtb文件到内存指定地址
  • 内存分配:为设备树分配足够的内存空间
  • 数据校验:验证设备树的格式和完整性
  • 参数传递:通过bootz命令将设备树地址传递给内核

设备树机制使得同一份内核可以适配多种硬件平台,极大提高了系统的可移植性。

内核启动参数传递

内核启动参数通过bootargs环境变量传递给内核:

1
2
3
4
5
/* 设置启动参数 */
setenv("bootargs", "console=ttyS0,115200 root=/dev/mmcblk0p2 earlycon");

/* 启动内核 */
bootz kernel_addr - fdt_addr;

启动参数传递机制包括:

  • 参数格式:空格分隔的键值对字符串
  • 参数内容:包括控制台设置、根文件系统位置、内核调试选项等
  • 传递方式:通过ATAG或直接通过寄存器传递(设备树方式)

内核启动参数是U-Boot与Linux内核之间最重要的交互机制之一,它决定了内核的初始化行为和系统特性。

U-Boot启动流程的扩展功能

除了基本的启动功能外,U-Boot还提供了许多扩展功能:

多种启动介质支持

U-Boot支持从多种存储介质加载内核和文件系统:

1
2
3
4
5
6
7
8
/* 从SD卡加载内核 */
load Image("kernel", "MMC", &kernel_addr);

/* 从NAND Flash加载内核 */
load Image("kernel", "NAND", &kernel_addr);

/* 从网络TFTP加载内核 */
load Image("kernel", "TFTP", &kernel_addr);

这些支持使得系统具有灵活性和可扩展性,可以适应不同的硬件配置和部署环境。

网络启动功能

U-Boot提供了完整的网络启动支持,包括:

  • DHCP客户端:获取IP地址和网络参数
  • TFTP客户端:从网络服务器下载内核和文件系统
  • HTTP/HTTPS客户端:支持通过HTTP协议获取启动文件
  • PXE引导:完整的PXE引导支持

网络启动功能使嵌入式系统可以实现远程更新和管理,大大提高了系统的可维护性。

文件系统支持

U-Boot支持多种文件系统,用于加载内核和文件系统:

1
2
3
4
5
/* 从FAT32文件系统加载内核 */
fatLoad("MMC", 0, "zImage", &kernel_addr, &kernel_size);

/* 从ext4文件系统加载内核 */
ext4Load("NAND", 0, "uImage", &kernel_addr, &kernel_size);

支持的文件系统包括FAT16/FAT32、ext2/ext3/ext4、JFFS2等。

U-Boot启动流程的调试与定制

调试技术

U-Boot提供了丰富的调试功能:

  • 命令行调试:通过printkprintf输出调试信息
  • 环境变量调试:使用env print查看环境变量,env set修改环境变量
  • 内存操作:使用md查看内存,mm修改内存
  • 寄存器操作:使用mrcmcr读写ARM协处理器寄存器
  • 断点调试:使用b命令设置断点,c命令继续执行
1
2
3
4
5
6
7
8
/* 设置调试信息级别 */
setenv("debug", "1");

/* 查看环境变量 */
env Print;

/* 修改启动命令 */
setenv("bootcmd", "bootz ${kernel_addr} - ${fdt_addr}");

调试功能是U-Boot开发和维护过程中不可或缺的工具,它使开发者能够在系统启动的早期阶段就发现和解决潜在问题。

定制化方法

U-Boot可以通过多种方式定制以适应不同硬件平台:

  • 配置文件:通过.config文件选择编译选项
  • 板级支持包:实现board.clowlevel_init.S等文件
  • 环境变量:通过bootargsbootcmd等环境变量配置启动行为
  • 自定义命令:添加新的U-Boot命令以扩展功能
1
2
3
4
5
6
7
8
9
/* 配置U-Boot */
make mx6ull_defconfig

/* 编译U-Boot */
make -j8

/* 配置板级支持 */
cd board/myboard
cp mx6ull_som-evk.c mx6ull_som-evkCustom.c

U-Boot的灵活性使其成为嵌入式系统中强大的启动和管理工具,可以适应各种定制化需求。

总结

U-Boot作为嵌入式Linux系统中不可或缺的引导加载程序,其启动流程涉及从硬件复位到Linux内核启动的全过程。通过多阶段启动架构,U-Boot能够在有限的硬件资源下逐步扩展可用内存空间,最终实现完整的系统初始化和内核引导。

U-Boot启动流程的核心是逐步构建运行环境:从硬件复位开始,通过汇编代码完成CPU基本配置和异常向量表设置;然后在SPL阶段初始化外部DRAM,为完整U-Boot创建运行环境;最后在完整U-Boot阶段完成外设初始化、环境变量加载和内核引导。

随着嵌入式系统的发展,U-Boot也在不断演进和增强:

  • 安全性增强:支持环境变量签名验证、安全启动等安全特性
  • 网络功能扩展:支持更丰富的网络协议和远程启动方式
  • 设备树支持完善:提供更强大的设备树编辑和调试功能
  • 跨平台兼容性提升:支持更多处理器架构和硬件平台

参考链接:
嵌入式Linux系统启动过程分析
linux在arm上的启动流程
一张图秒懂嵌入式Linux系统的启动流程

Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计