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就是连接硬件和内核的桥梁。没有它,内核连内存都用不了,更别提启动了。
4.7 写在最后
Bootloader这部分内容,说难不难,说简单也不简单。我见过太多人在这上面栽跟头了。我的建议是:
- 先拿一块成熟的开发板练手,别一上来就搞自己的板子
- 多看看串口打印信息,那是你调试的第一手资料
- 改参数前先
printenv备份,改完记得saveenv - 烧写前确认镜像文件没问题,可以用
md5sum校验一下
好了,这一章就到这里。记住,Bootloader是你和硬件对话的第一个工具,把它玩熟了,后面的路就好走了。