第三章 Bootloader与系统启动:U-Boot移植与配置、内核裁剪与设备树定制、根文件系统构建
各位工程师,大家好。这一章我们聊聊嵌入式Linux系统的“三驾马车”——Bootloader、内核和根文件系统。说白了,就是让一块裸板上的CPU,从复位开始,一步步跑起你的应用程序。我做了这么多年量产,见过太多“代码在开发板跑得飞起,一到产线就翻车”的案例,问题十有八九出在这三个环节的衔接上。
3.1 U-Boot移植与配置:让板子先“活过来”
U-Boot,就是系统上电后第一个跑的程序。它的任务很简单:初始化硬件,加载内核到内存,然后跳转过去。但简单的事,往往最考验功底。
3.1.1 从参考板开始
我个人习惯,从不从零写U-Boot。那太傻了。芯片厂商通常会提供参考板(EVB)的配置。比如你用NXP的i.MX8M系列,就在configs/目录下找imx8mm_evk_defconfig。复制一份,改成你的板子名字。
make imx8mm_evk_defconfig
make menuconfig
这里要注意,menuconfig里有个大坑——DDR时序参数。我在项目中遇到过,参考板用的是DDR4-3200,我们量产时换了另一家供应商的DDR颗粒,结果U-Boot死活起不来。后来查了三天,才发现是时序参数没改。嗯,这里要提醒各位:DDR参数必须和你的硬件设计一一对应,别偷懒。
3.1.2 设备树与U-Boot的配合
U-Boot本身也使用设备树(DTB)。它需要知道你的板子上有哪些外设、GPIO怎么分配、时钟频率多少。我建议把U-Boot的设备树和内核的设备树分开维护。为什么?因为U-Boot只关心启动阶段需要的硬件,比如MMC、网络、串口。内核才关心所有外设。
关键点:U-Boot的设备树要精简。我曾经见过有人把整个内核设备树拷过来,结果U-Boot编译出来大了200KB,加载速度慢了30%。
3.1.3 量产中的U-Boot配置
量产时,U-Boot的配置要“死板”一点。比如:
- 关闭调试串口输出:产线测试不需要看U-Boot日志,省点时间。
- 固定启动参数:
bootargs不要从环境变量读,直接硬编码。防止产线工人误操作改了环境变量,导致整批板子变砖。 - 启用看门狗:如果U-Boot启动卡死,看门狗能自动复位,避免产线停线。
我的小技巧:在U-Boot里加一个“产线测试模式”。比如检测到某个GPIO拉低,就进入一个特殊循环,只做内存测试和网络ping,方便产线快速验证硬件。
3.2 内核裁剪与设备树定制:瘦身又精准
内核裁剪,说白了就是“去掉你用不到的”。但怎么知道哪些用不到?我见过有人直接删掉所有驱动模块,结果板子上的WiFi不工作,又得重新编译。所以,裁剪要讲究方法。
3.2.1 内核裁剪的“三步法”
- 先跑起来,再裁剪:第一次编译,用默认配置。确保系统能启动,所有外设正常工作。
- 用
make localmodconfig生成最小配置:这个命令会分析当前系统加载的模块,只保留这些模块的配置。我建议在目标板上跑一遍所有功能,再执行这个命令。 - 手动检查冗余项:比如你的板子没有SATA硬盘,就把
CONFIG_SATA_*全部关掉。没有PCIe,就关掉CONFIG_PCI。
# 在目标板上运行
make localmodconfig
# 然后手动检查
make menuconfig
警告:千万不要在生产环境直接使用make localmodconfig生成的配置。它可能漏掉一些“冷启动”时才需要的驱动,比如文件系统驱动。我曾经吃过这个亏,板子在产线测试时一切正常,发货后客户第一次开机,根文件系统挂载失败——因为ext4驱动没编进去。
3.2.2 设备树定制:别让内核“猜”你的硬件
设备树就是告诉内核:“我的板子上有什么,接在哪个引脚上”。定制设备树时,我建议遵循以下原则:
- 一个板子一个设备树:不要试图用一个设备树兼容多个硬件版本。我见过有人用
#ifdef在设备树里做条件编译,结果维护起来简直是噩梦。 - GPIO命名要规范:比如
gpio-led-red、gpio-btn-power。别用gpio0、gpio1这种名字,三个月后你自己都看不懂。 - 时钟和电源要写死:不要依赖内核去自动探测。量产时,每一毫秒的启动时间都很宝贵。
/ {
model = "MyMicroscope-V2";
compatible = "mycompany,imx8mm-microscope";
leds {
compatible = "gpio-leds";
led-illumination {
label = "illumination";
gpios = &gpio1 15 GPIO_ACTIVE_HIGH;
default-state = "off";
};
};
};
3.3 根文件系统构建:用Buildroot还是Yocto?
这是个老生常谈的问题。我的看法是:看团队规模和产品复杂度。
| 对比项 | Buildroot | Yocto |
|---|---|---|
| 学习曲线 | 低,一天上手 | 高,一周入门 |
| 定制灵活性 | 中等 | 极高 |
| 包管理 | 无 | 有(opkg/rpm) |
| 适合场景 | 小团队、产品固定 | 大团队、多产品线 |
3.3.1 Buildroot:快速出活
我个人偏爱Buildroot。为什么?因为它简单。你只要配置好工具链、选择需要的包(比如busybox、dropbear、libusb),然后make,一个完整的根文件系统就出来了。
make menuconfig
# 选择 Target packages -> Networking -> dropbear
# 选择 Filesystem images -> ext4
make
但要注意,Buildroot生成的根文件系统是只读的(如果你选squashfs)。量产时,我建议把配置文件放在一个单独的数据分区,挂载为可读写。这样固件升级时,只更新根文件系统分区,用户配置不会丢。
3.3.2 Yocto:当产品复杂时
如果你的医疗显微镜需要支持多种外设、多个软件版本、甚至OTA升级,那Yocto是更好的选择。它用BitBake和Layer机制,把软件包、内核、设备树、应用全部组织在一起。
我在一个多摄像头医疗设备项目里用过Yocto。当时需要同时支持USB摄像头和MIPI摄像头,而且不同批次用的传感器型号不同。Yocto的MACHINE和DISTRO机制,让我们可以轻松切换配置,而不需要维护多个分支。
核心建议:无论用Buildroot还是Yocto,根文件系统的大小要严格控制。医疗显微镜的Flash通常只有256MB或512MB。我建议根文件系统不超过100MB,剩下的留给用户数据和日志。
3.4 启动流程的“避坑指南”
最后,分享几个我在量产中踩过的坑:
- U-Boot环境变量不要放在Flash末尾:如果Flash坏块恰好在那里,整个板子就变砖了。我建议放在Flash开头,紧挨着U-Boot镜像。
- 内核和设备树要校验CRC:产线烧录时,可能因为电压不稳导致数据错误。加上CRC校验,启动时检查,能避免很多莫名其妙的问题。
- 根文件系统要预留10%空间:日志文件、临时文件会慢慢填满空间。一旦写满,系统可能崩溃。我习惯在
fstab里对数据分区设置errors=remount-ro,防止灾难性后果。
好了,这一章的内容就到这里。Bootloader、内核、根文件系统,这三者就像房子的地基、框架和装修。地基不稳,后面全白搭。下一章我们聊聊驱动开发,那又是另一番天地了。