4、Bootloader基础:Bootloader的作用、U-Boot简介、U-Boot的编译与烧写、U-Boot常用命令

大家好,我是你们的嵌入式Linux讲师。今天咱们聊聊Bootloader。说实话,很多初学者觉得Bootloader就是个“黑盒子”,上电后闪一下就没了。但我要告诉你,这玩意儿要是没弄好,你的整个系统都起不来。

我刚开始做嵌入式那会儿,就吃过这个亏。板子焊好了,内核也编好了,一上电啥反应没有。折腾了两天,最后发现是Bootloader的配置参数写错了。嗯,从那以后,我再也不敢小看这个“小东西”了。

一句话总结:Bootloader是嵌入式系统的“第一段代码”,它负责把硬件从“原始状态”带到“可运行状态”,然后加载操作系统内核。

4.1 Bootloader到底在干啥?

说白了,Bootloader就是上电后第一个跑起来的程序。你想想看,CPU刚上电时,内存还没初始化,外设还没配置,这时候你让它直接跑Linux内核?不可能的。所以需要Bootloader来做这些脏活累活。

它的核心任务有三个:

  • 硬件初始化:设置时钟、初始化DDR内存、配置串口等基本外设
  • 引导加载:从Flash、SD卡或网络把内核镜像读到内存里
  • 传递参数:给内核传递启动参数,比如根文件系统在哪、控制台用哪个串口

我在项目中遇到过一种情况:换了颗DDR颗粒,结果系统死活起不来。查了半天,原来是Bootloader里DDR的时序参数没改。你看,一个小小的参数就能让整个项目卡住。

我的建议:拿到新板子,第一件事就是确认Bootloader能正常打印信息。串口有输出,说明最基础的硬件通路是通的。

4.2 U-Boot是什么?为什么选它?

U-Boot,全称是“Universal Bootloader”。这名字起得好,因为它确实很“通用”。ARM、x86、RISC-V、MIPS……市面上主流的处理器架构它基本都支持。

为什么大家都用U-Boot?我个人觉得有这几个原因:

  • 开源免费:GPL协议,随便用,社区活跃
  • 硬件支持广:几乎你能想到的开发板,都有现成的U-Boot移植
  • 命令丰富:像一个小型的操作系统,可以在命令行里操作内存、读写Flash、网络传输
  • 可定制性强:你可以裁剪掉不需要的功能,让U-Boot体积更小、启动更快

我记得有一次给客户做产品,他们要求启动时间必须控制在3秒以内。我花了整整一周裁剪U-Boot,去掉网络协议栈、去掉不必要的驱动、优化初始化流程……最后做到了2.8秒。嗯,那种成就感,你懂的。

4.3 编译U-Boot,其实没那么难

编译U-Boot,说白了就是三板斧:配置、编译、烧写。咱们以最常用的ARM开发板为例。

先下载源码:

git clone https://github.com/u-boot/u-boot.git
cd u-boot

然后配置:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- <板子名称>_defconfig

这里的CROSS_COMPILE指定了交叉编译工具链的前缀。你想想看,你的电脑是x86架构,但目标板是ARM架构,不用交叉编译怎么行?

最后编译:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4

编译完成后,你会得到一个u-boot.bin文件。这就是我们要烧写到板子上的二进制镜像。

注意:不同的板子,配置名不一样。比如树莓派是rpi_3_defconfig,友善之臂的NanoPi是nanopi_neo_defconfig。别搞混了,否则编译出来的U-Boot跑不起来。

4.4 烧写U-Boot,小心变砖

烧写U-Boot的方法取决于你的板子用什么存储介质。最常见的是SD卡和SPI Flash。

烧写到SD卡:

sudo dd if=u-boot.bin of=/dev/sdb bs=1024 seek=1
sync

这里的seek=1表示跳过第一个扇区(MBR),从第二个扇区开始写。为什么?因为SD卡的第一个扇区要留给分区表。

烧写到SPI Flash:

这个稍微麻烦点,一般需要在U-Boot命令行里操作:

# 先通过tftp把镜像下载到内存
tftp 0x42000000 u-boot.bin
# 然后擦除Flash
sf probe 0
sf erase 0x0 0x80000
# 最后写入
sf write 0x42000000 0x0 0x80000

我曾经犯过一个低级错误:烧写SPI Flash时忘了先擦除,直接写。结果U-Boot启动到一半就挂了。后来才知道,SPI Flash写入前必须先擦除,这是硬件特性决定的。

避坑指南:烧写Bootloader时,一定要确保电源稳定。我见过有人用USB供电烧写,结果电压波动导致Flash写入错误,板子直接变砖。建议用稳压电源。

4.5 U-Boot常用命令,背下来

U-Boot启动后,你会看到一个命令行提示符,通常是=> 。这里我挑三个最常用的命令讲讲。

printenv — 查看环境变量

这个命令我用的最多。它会打印出U-Boot的所有环境变量,比如启动参数、IP地址、引导命令等。

=> printenv
baudrate=115200
bootargs=console=ttyS0,115200 root=/dev/mmcblk0p2 rw
bootcmd=mmc read 0x42000000 0x800 0x4000; bootm 0x42000000
bootdelay=2
ipaddr=192.168.1.100
serverip=192.168.1.10
...

你看,bootargs就是传给内核的启动参数,bootcmd是自动启动时执行的命令。这些变量都可以修改。

setenv — 设置环境变量

想改参数?用setenv。比如我想把IP地址改一下:

=> setenv ipaddr 192.168.1.200
=> saveenv
Saving Environment to MMC... OK

注意,改完之后要执行saveenv保存,否则重启后就丢了。这个坑我踩过好几次,调试时改了参数没保存,重启后一切回到原点。

bootm — 启动内核

这是最核心的命令。它从内存中启动一个内核镜像。用法很简单:

=> bootm 0x42000000

这条命令告诉U-Boot:内存地址0x42000000处放着一个内核镜像,帮我启动它。U-Boot会解析镜像头,解压内核,然后跳转到内核入口点。

我习惯把内核镜像先读到内存里,再用bootm启动。比如从SD卡读:

=> mmc read 0x42000000 0x800 0x4000
=> bootm 0x42000000

这里的0x800是SD卡上的起始扇区,0x4000是要读取的扇区数。说白了,就是从SD卡的某个位置把内核读到内存,然后启动。

小技巧:调试时可以把bootdelay设长一点,比如5秒。这样你有足够的时间在启动前按任意键打断自动启动,进入命令行手动操作。

4.6 一张图看懂Bootloader

说了这么多,咱们用一张SVG图来总结一下Bootloader在整个系统启动流程中的位置和作用。

嵌入式系统启动流程 — Bootloader的核心位置 上电复位 Bootloader(U-Boot) 硬件初始化 → 加载内核 → 传递参数 Linux内核 解压 → 初始化子系统 → 挂载根文件系统 用户空间(应用程序) init进程 → 服务启动 → 用户界面 本章重点 U-Boot常用命令 • printenv • setenv / saveenv • bootm • mmc read / write • sf probe / erase / write • tftp

从这张图可以看得很清楚:Bootloader就是连接硬件和内核的桥梁。没有它,内核连内存都用不了,更别提启动了。

4.7 写在最后

Bootloader这部分内容,说难不难,说简单也不简单。我见过太多人在这上面栽跟头了。我的建议是:

  • 先拿一块成熟的开发板练手,别一上来就搞自己的板子
  • 多看看串口打印信息,那是你调试的第一手资料
  • 改参数前先printenv备份,改完记得saveenv
  • 烧写前确认镜像文件没问题,可以用md5sum校验一下

好了,这一章就到这里。记住,Bootloader是你和硬件对话的第一个工具,把它玩熟了,后面的路就好走了。

可可精品资料库