2. 嵌入式系统启动流程

拿到U-Boot源码的第一件事,别急着编译。我个人的习惯是,先花半小时把顶层目录扫一遍。说白了,你得知道每个文件夹是干嘛的,不然遇到问题就像无头苍蝇。

U-Boot的源码结构,其实比Linux内核简单得多。它没有复杂的设备树覆盖层,也没有几千个Kconfig选项。但它的目录划分非常清晰——board放板级代码,arch放架构相关,common放通用逻辑,drivers放驱动。咱们一个一个来看。

2.1 源码获取方式

先解决从哪里拿代码的问题。U-Boot的官方仓库在denx.de的Git服务器上,但国内访问速度不太理想。我建议你用以下方式:

  • 官方Git仓库:git clone https://source.denx.de/u-boot/u-boot.git
  • 国内镜像:清华、中科大的Git镜像站都有同步,速度更快
  • 厂商BSP包:如果你用的是NXP、TI、Rockchip的芯片,它们会提供基于某个U-Boot版本的定制包。嗯,这里要注意,厂商的代码往往加了私有的补丁,和主线有差异
我的建议:学习阶段用主线最新稳定版(比如v2024.01),别追最新开发版。主线代码经过充分测试,文档也最全。厂商BSP虽然能直接跑,但代码风格和主线差异大,不利于理解框架。

2.2 顶层目录结构分析

解压或clone完源码后,在根目录执行ls -l,你会看到类似下面的结构。我挑几个重点目录说:

u-boot/
├── arch/          # 架构相关代码(CPU、SoC)
├── board/         # 板级代码(具体硬件平台)
├── common/        # 通用功能(命令行、环境变量、启动流程)
├── configs/       # 默认配置文件(defconfig)
├── doc/           # 文档
├── drivers/       # 设备驱动(串口、网卡、存储等)
├── dts/           # 设备树源文件
├── fs/            # 文件系统支持(ext4、fat、ubifs等)
├── include/       # 头文件
├── lib/           # 库函数(crc、sha、lzma等)
├── net/           # 网络协议栈
├── scripts/       # 构建脚本
├── tools/         # 主机工具(mkimage、fdt等)
└── Makefile       # 顶层构建文件

这个结构从U-Boot诞生到现在,基本没大变过。你想想看,一个项目能保持二十多年的目录布局不变,说明当初的设计确实经得起考验。

核心原则:U-Boot的目录划分遵循架构→SoC→板级的三层抽象。arch/管CPU和SoC通用逻辑,board/管具体板卡的差异化逻辑。这种分层让你移植新板卡时,只需要关注board/目录下的文件。

2.3 关键子目录功能详解

2.3.1 arch/ —— 架构的根

这个目录存放所有与CPU架构相关的代码。进去看一眼:

arch/
├── arm/           # ARM架构(最庞大)
├── x86/           # x86架构
├── riscv/         # RISC-V架构
├── mips/          # MIPS架构
├── powerpc/       # PowerPC架构
└── sandbox/       # 模拟测试架构

每个架构目录下,又按SoC厂商细分。以arm为例:

arch/arm/
├── cpu/           # CPU核心初始化(reset、cache、mmu)
├── dts/           # 该架构的设备树
├── include/       # 架构级头文件
├── lib/           # 架构通用库(如bootm、cache操作)
└── mach-*/        # 各SoC厂商目录(mach-sunxi、mach-rockchip等)

我在移植全志H3的时候,就经常泡在arch/arm/mach-sunxi/里。这里放的是SoC级别的初始化代码,比如时钟配置、引脚复用、DRAM初始化。说白了,这部分代码决定了你的芯片能不能跑起来。

2.3.2 board/ —— 板卡的身份证

这是移植工作的主战场。每个板卡在board/下都有一个专属目录:

board/
├── allwinner/     # 全志系列
│   ├── orangepi_zero/
│   └── nanopi_neo/
├── freescale/     # NXP系列
│   ├── imx6q_sabresd/
│   └── ls1046ardb/
├── rockchip/      # Rockchip系列
│   ├── evb_rk3399/
│   └── tinker_board/
└── ti/            # TI系列
    ├── am335x_evm/
    └── dra7xx_evm/

每个板卡目录里通常包含:

  • board.c:板级初始化函数(board_init、board_late_init)
  • Kconfig:板级配置选项
  • Makefile:编译规则
  • imximage.cfg(某些平台):镜像头配置
我曾经踩过的坑:board目录下的代码和arch/mach-*目录下的代码,职责容易混淆。记住一条原则——arch/mach-*管SoC内部(时钟、DDR、中断控制器),board/管板级外设(网卡PHY地址、LED引脚、按键映射)。把SoC初始化代码放到board/里,会导致你的板卡代码无法在其他同SoC板卡上复用。

2.3.3 common/ —— 心脏地带

这个目录存放U-Boot的核心逻辑,不依赖具体硬件。我挑几个关键文件:

文件/目录 功能 我的备注
main.c U-Boot主循环(命令行解析) 启动后就在这里循环等待用户输入
cli_hush.c 命令行解释器(类似bash) 支持管道、变量替换,比你想的强
env_*.c 环境变量存储(env_nand、env_mmc、env_sf) 移植时一定要确认存储介质驱动正常
bootm.c 启动内核的核心函数 解析镜像头、跳转到内核入口
autoboot.c 自动启动逻辑 倒计时、bootcmd执行

common/里的代码,你不需要全部读懂。但main.c和bootm.c建议精读一遍。我当年就是靠啃这两个文件,才真正理解了U-Boot的启动流程。

2.3.4 drivers/ —— 外设驱动仓库

这个目录的结构和Linux内核的drivers/很像:

drivers/
├── serial/        # 串口驱动(最基础,没有它你啥也看不到)
├── net/           # 网卡驱动(e1000、dwmac、sunxi_emac等)
├── mmc/           # MMC/SD卡驱动
├── spi/           # SPI控制器驱动
├── i2c/           # I2C控制器驱动
├── mtd/           # NAND Flash、SPI Flash驱动
├── usb/           # USB主机/设备驱动
├── gpio/          # GPIO驱动
├── pinctrl/       # 引脚控制驱动
└── video/         # 显示驱动(HDMI、LCD)

每个驱动子目录下,又按芯片厂商或IP核分类。比如drivers/net/里,有sunxi_emac.c(全志)、dwmac.c(DesignWare)、fec_mxc.c(NXP)等。

移植技巧:如果你要移植一个新板卡,先从drivers/serial/开始。串口驱动正常了,你才能看到打印信息。我习惯的做法是:先让串口输出"U-Boot 2024.01",然后逐步添加MMC、网卡、NAND驱动。每一步都验证,不要一口气加完所有驱动再调试。

2.4 其他重要目录

除了上面四个核心目录,还有几个地方值得关注:

  • configs/:存放所有板卡的默认配置文件(orangepi_zero_defconfig)。移植时,你通常从这里复制一份修改
  • dts/:设备树源文件。U-Boot从2016年开始全面采用设备树,现在新板卡必须提供dts
  • include/configs/:旧版配置头文件(#define CONFIG_*)。虽然逐渐被Kconfig取代,但很多老项目还在用
  • tools/:主机工具,比如mkimage(制作U-Boot镜像)、fdtgrep(过滤设备树)

2.5 整体架构关系图

下面这张图,帮你理清各个目录之间的调用关系。我画的时候特意把arch/和board/放在底层,因为它们是硬件相关的;common/和drivers/在上层,通过抽象接口调用底层代码。

U-Boot源码架构关系图 U-Boot 源码架构关系图 common/ (命令行、启动流程、环境变量) drivers/ (串口、网卡、MMC等) lib/、fs/、net/ (库、文件系统、网络协议) arch/ (CPU初始化、Cache、MMU、SoC代码) board/ (板级初始化、外设配置、引脚复用) configs/ + dts/ (配置 + 设备树) 说明: • 实线箭头表示函数调用/依赖关系,虚线箭头表示配置影响 • common/ 通过驱动接口调用 drivers/,drivers/ 再调用 arch/ 和 board/ 的硬件操作 • configs/ 和 dts/ 在编译时决定哪些代码被编译进镜像

从这张图你能看出,U-Boot的分层非常清晰。上层代码不直接操作硬件寄存器,而是通过驱动框架调用底层接口。这种设计让你在移植时,只需要关注arch/和board/两个目录,其他部分基本不用动。

2.6 小结

这一章我们把U-Boot的源码结构拆了个遍。你记住几个关键点:

  • arch/管CPU和SoC,board/管板级硬件,职责要分清楚
  • common/是核心逻辑,drivers/是外设驱动,这两部分基本不用改
  • 移植新板卡,从board/目录开始,配合configs/和dts/的配置
  • 源码获取首选主线Git仓库,学习阶段用稳定版