4. Bootloader与系统启动流程

4.1 Bootloader在嵌入式系统中的角色

在车载嵌入式Linux系统中,Bootloader是系统上电后第一个执行的软件,负责初始化硬件、加载操作系统内核并传递启动参数。U-Boot(Universal Boot Loader)是ARM架构下最主流的开源Bootloader,支持多种处理器架构和丰富的板级配置。

功能模块说明车载场景关键点
硬件初始化设置时钟、DDR、串口、存储控制器需适配不同SoC(如NXP i.MX8、TI TDA4)
存储介质访问支持eMMC、NAND Flash、SD卡、SPI Flash车载常用eMMC + QSPI双备份
网络协议栈TFTP/NFS下载内核与设备树产线刷写与OTA调试
环境变量管理持久化存储启动参数用于多分区启动策略
设备树传递将DTB地址传给内核必须与内核设备树匹配
关键概念: U-Boot分为SPL(Secondary Program Loader)和U-Boot proper两个阶段。SPL由片内ROM加载,初始化DDR后加载完整的U-Boot到内存,再由U-Boot加载内核。

4.2 U-Boot启动流程与移植要点

4.2.1 启动阶段划分

  1. ROM Code:SoC内部固化代码,从启动介质读取SPL到SRAM。
  2. SPL阶段:初始化时钟、DDR、串口,加载U-Boot proper到DDR。
  3. U-Boot proper:完整初始化外设(网络、存储、USB),解析环境变量,加载内核与DTB。
  4. Boot命令执行:根据bootcmd变量启动内核。
移植经验: 车载项目中最常见的移植工作包括:DDR时序参数配置、eMMC驱动适配、安全启动(HAB/ATF)集成、以及多核启动(如Cortex-A + Cortex-R)的核间通信初始化。

4.2.2 U-Boot移植核心步骤

步骤操作内容常见问题
1. 选择参考板基于相近SoC的defconfig开始需确认DDR型号与容量
2. 配置DDR参数修改board/目录下的dram.c或dts时序错误导致启动失败
3. 调整时钟树设置PLL、分频器匹配车载常用频率外设时钟不匹配
4. 添加板级驱动GPIO、I2C、PMIC、以太网PHYPHY地址冲突
5. 配置存储分区定义mtdparts或mmc分区表分区大小与内核不一致
6. 生成设备树编写u-boot.dts包含板级差异节点覆盖顺序错误
# 典型U-Boot环境变量配置(车载场景)
setenv bootargs 'console=ttyS0,115200 root=/dev/mmcblk0p2 rw rootwait'
setenv loadaddr 0x80200000
setenv fdtaddr 0x88000000
setenv bootcmd 'mmc dev 0; ext4load mmc 0:1 ${loadaddr} /boot/Image; ext4load mmc 0:1 ${fdtaddr} /boot/board.dtb; booti ${loadaddr} - ${fdtaddr}'
saveenv

4.3 Linux内核启动流程:从Bootloader到init进程

内核启动流程可划分为四个主要阶段,每个阶段都有明确的入口函数和职责边界。

Linux内核启动流程(从Bootloader到init进程) Bootloader 加载内核Image + DTB 跳转到入口 stext 内核引导阶段 __lookup_processor_type __vet_atags / 解压 内核初始化 start_kernel() setup_arch / rest_init init进程 /sbin/init Bootloader详细动作 • 关闭中断与MMU • 设置CPU为SVC模式 • 传递机器ID或DTB地址 • 跳转到内核入口(r0/r1/r2) • 车载:需配置ATF/OP-TEE start_kernel() 关键调用 • setup_arch():解析DTB • mm_init():内存管理初始化 • init_IRQ():中断控制器 • timer_init():系统定时器 • rest_init():创建kernel_init init进程启动 • kernel_init() 挂载rootfs • 执行 /sbin/init 或 /init • 车载:常使用systemd • 启动服务:网络/诊断/显示 • 进入用户空间应用程序
车载启动时间优化: 法规要求(如ISO 26262)和用户体验要求倒车影像在2秒内显示。常见优化手段包括:使用XIP(eXecute In Place)内核、裁剪U-Boot驱动、启用快速启动(Fastboot)、以及使用FIT Image合并内核与DTB减少加载次数。

4.4 设备树(Device Tree)基本概念与语法

4.4.1 为什么需要设备树

在ARM Linux早期,板级硬件信息通过大量C语言板文件(board-*.c)硬编码,导致内核代码冗余且难以维护。设备树(DT)将硬件描述从内核代码中分离,通过.dts源文件编译为.dtb二进制,由Bootloader传递给内核。

对比项传统板文件方式设备树方式
硬件描述位置arch/arm/mach-xxx/board-xxx.c独立 .dts 文件
内核编译耦合修改板文件需重新编译内核仅需更新DTB
多板支持每个板子一个板文件通过DTSI包含 + 覆盖
外设参数调整修改C代码中的平台数据修改DTS属性值
车载场景优势—快速适配不同传感器/显示屏

4.4.2 设备树语法基础

设备树使用树形结构描述硬件,每个节点代表一个设备或总线,属性描述设备特性。

// 基础设备树结构示例
/dts-v1/;

/ {
    model = "车载信息娱乐系统 EVK";
    compatible = "automotive,infotainment-v1", "arm,cortex-a72";
    #address-cells = <2>;
    #size-cells = <2>;

    chosen {
        bootargs = "console=ttyS0,115200 root=/dev/mmcblk0p2 rw";
        stdout-path = &uart0;
    };

    memory@80000000 {
        device_type = "memory";
        reg = <0x0 0x80000000 0x0 0x80000000>;  // 2GB DDR
    };

    soc {
        compatible = "simple-bus";
        #address-cells = <2>;
        #size-cells = <2>;
        ranges;

        uart0: serial@ff010000 {
            compatible = "arm,pl011", "arm,primecell";
            reg = <0x0 0xff010000 0x0 0x1000>;
            interrupts = <0 33 4>;
            clock-names = "uartclk", "apb_pclk";
            clocks = <&uart_clk &apb_pclk>;
            status = "okay";
        };

        i2c0: i2c@ff020000 {
            compatible = "snps,designware-i2c";
            reg = <0x0 0xff020000 0x0 0x1000>;
            interrupts = <0 35 4>;
            clock-frequency = <400000>;
            #address-cells = <1>;
            #size-cells = <0>;
            status = "okay";

            touch@38 {
                compatible = "goodix,gt911";
                reg = <0x38>;
                interrupt-parent = <&gpio3>;
                interrupts = <5 2>;
                irq-gpios = <&gpio3 5 0>;
                reset-gpios = <&gpio3 6 0>;
            };
        };
    };
};

4.4.3 设备树关键语法元素

语法元素格式说明车载常见用法
节点node-name@unit-address唯一标识一个设备display@f0000000
属性property = value;描述设备特性clock-frequency = <400000>;
标签label: node@addr方便引用节点uart0: serial@...;
包含#include "file.dtsi"复用通用定义SoC级DTSI + 板级DTS
覆盖&label { ... };修改已有节点属性使能/禁用外设
状态status = "okay" / "disabled";控制设备使能根据SKU配置不同外设
中断interrupts = <type irq flags>;描述中断连接GPIO中断唤醒
GPIOxxx-gpios = <&gpioX pin flags>;描述GPIO使用复位/使能引脚

4.4.4 设备树在车载项目中的典型应用

车载设备树层级结构与外设映射 / (根节点) chosen memory soc (simple-bus) reserved-memory firmware serial (UART) i2c (触摸/音频) spi (显示屏) can (CAN总线) ethernet (TSN) usb (车载连接) 车载设备树设计要点 • 使用 &label 覆盖语法在板级DTS中使能/禁用外设,避免修改SoC级DTSI • 通过 pinctrl 节点配置引脚复用功能,确保UART/CAN/I2C引脚不冲突 • reserved-memory 用于预留CMA、IPU、安全监控等专用内存区域

4.4.5 设备树编译与调试

# 编译设备树源文件为DTB
dtc -I dts -O dtb -o board.dtb board.dts

# 反编译DTB为可读的DTS格式(调试用)
dtc -I dtb -O dts -o board_decompiled.dts board.dtb

# 在内核启动时验证设备树是否正确加载
# 内核启动日志中搜索:
# [    0.000000] OF: fdt: Machine model: 车载信息娱乐系统 EVK
# [    0.000000] OF: fdt: Reserved memory: created CMA at 0x00000000B0000000

# 在U-Boot中查看设备树信息
=> fdt addr 0x88000000
=> fdt list /soc/serial@ff010000
调试技巧: 当外设驱动无法probe时,首先检查设备树中节点的 compatible 属性是否与驱动中的of_match_table匹配,其次检查 status 是否为 "okay",最后确认中断号和GPIO号与原理图一致。

4.5 本章小结

  • U-Boot 是车载ARM系统启动的第一阶段软件,SPL + proper两阶段设计兼顾了SRAM容量限制和功能完整性。
  • 内核启动流程 从Bootloader跳转到stext开始,经过start_kernel()完成核心子系统初始化,最终由kernel_init启动用户空间的init进程。
  • 设备树 将硬件描述与内核代码解耦,通过.dts源文件描述SoC和板级外设,是车载项目快速适配不同硬件配置的关键技术。
  • 掌握U-Boot移植、内核启动流程分析和设备树编写,是车载Linux驱动开发工程师的核心能力。