4. 内核配置:Linux Kernel版本选择、内核配置片段(defconfig)的使用、设备树(Device Tree)的集成
内核配置这块,说难不难,说简单也容易踩坑。我见过不少新手,Buildroot 编译都过了,板子启动到一半就卡死,最后发现是内核版本选错了,或者设备树没对上。今天咱们就把这几个关键点捋清楚。
4.1 Linux Kernel 版本选择:别盲目追新
选内核版本,我个人习惯是「稳」字当头。你想想看,嵌入式产品要的是长期稳定,不是跑分。
| 场景 | 推荐策略 | 我踩过的坑 |
|---|---|---|
| 量产产品 | 选 LTS(长期支持)版本,比如 5.10.x、6.1.x | 曾经追新用了 5.15-rc,结果某个 SPI 驱动在正式版改了 API,项目延期两周 |
| 原型验证 | 可以用最新稳定版,但别用 -rc 或 -next | — |
| BSP 强制要求 | 优先用芯片厂商提供的内核分支 | 某次用主线内核跑全志芯片,MIPI DSI 怎么调都不亮,换回厂商 4.9 分支就好了 |
在 Buildroot 里,内核版本在 BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE 里设置。我个人建议:先查芯片厂商的官方 BSP 用了哪个内核版本,然后选对应的 LTS。别问我为什么强调这个——我曾经为了省事,给 i.MX6 平台选了 5.4 主线内核,结果 GPU 驱动死活起不来,最后发现 NXP 官方 BSP 还在用 4.19。
linux/Config.in 里,你可以看到所有预置的内核版本。如果列表里没有你想要的,用 BR2_LINUX_KERNEL_CUSTOM_VERSION 选项手动指定 Git 标签或 commit hash。
4.2 内核配置片段(defconfig)的使用
说白了,内核配置片段就是你的「配置补丁」。Buildroot 本身会先加载一个基础 defconfig,然后你用片段往里加东西、改东西。这样做的好处是:你不用维护一份完整的内核 .config,只需要记录你的改动点。
4.2.1 配置片段的加载机制
Buildroot 处理内核配置的顺序是这样的:
- 加载
BR2_LINUX_KERNEL_DEFCONFIG指定的基础 defconfig - 依次应用
BR2_LINUX_KERNEL_CONFIG_FRAGMENT_FILES里列出的所有片段文件 - 如果启用了
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG,则直接用自定义 .config 覆盖
嗯,这里要注意:片段文件是增量叠加的。如果你在片段里写了 CONFIG_A=y,它就会把基础配置里的 CONFIG_A 改成 y。但如果基础配置里没有 CONFIG_A,它就新增一行。
4.2.2 片段文件的写法规范
片段文件本质上就是内核的 .config 片段,但有几个硬性要求:
- 每行一个配置项,格式为
CONFIG_XXX=y或CONFIG_XXX=m - 可以用
# CONFIG_XXX is not set来禁用某个选项 - 注释行以
#开头 - 不要包含
# Automatically generated file; DO NOT EDIT.这种头部
# 我的片段文件:enable_qt5_fb.frag
# 启用帧缓冲控制台
CONFIG_FRAMEBUFFER_CONSOLE=y
CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY=y
# 禁用 HDMI 音频(板子上没接喇叭)
# CONFIG_DRM_I915 is not set
# 启用 Qt5 需要的 evdev 输入
CONFIG_INPUT_EVDEV=y
CONFIG_LOCALVERSION 或 CONFIG_LOCALVERSION_AUTO,这些应该在 Buildroot 的 BR2_LINUX_KERNEL_LOCALVERSION 里统一管理。我曾经在片段里改了 LOCALVERSION,结果每次 make linux-reconfigure 都触发完整重编,浪费了半小时排查。
4.2.3 多平台共享配置的技巧
我在项目中遇到过需要维护 3 款硬件变体的场景。我的做法是:
- 一个公共片段
common.frag,放所有平台都需要的配置(比如 ext4、网络、串口) - 每个平台一个专属片段,比如
board_a.frag、board_b.frag - 在 Buildroot 的
board/xxx/目录下放一个readme.txt,说明每个片段的作用
然后在 buildroot.config 里这样引用:
BR2_LINUX_KERNEL_CONFIG_FRAGMENT_FILES="board/common/common.frag board/board_a/board_a.frag"
4.3 设备树(Device Tree)的集成
设备树,说白了就是硬件的「说明书」。内核通过它知道你的板子上有哪些外设、接在哪个引脚上、用哪个驱动。没有设备树,内核就是个瞎子。
4.3.1 Buildroot 中的设备树配置
在 Buildroot 里,设备树的配置主要涉及这几个变量:
| 配置项 | 作用 | 典型值 |
|---|---|---|
BR2_LINUX_KERNEL_DTS_SUPPORT |
启用设备树支持 | y |
BR2_LINUX_KERNEL_INTREE_DTS_NAME |
使用内核源码树内的 .dts 文件 | imx6ull-14x14-evk |
BR2_LINUX_KERNEL_CUSTOM_DTS_PATH |
使用自定义的 .dts/.dtsi 文件 | board/myboard/myboard.dts |
BR2_LINUX_KERNEL_DTB_OVERLAY_SUPPORT |
启用设备树叠加层 | 按需启用 |
我个人强烈建议:除非你的板子和官方评估板完全一样,否则一定要用自定义设备树。为什么?因为官方 .dts 里可能启用了你板子上没有的硬件(比如 HDMI、WiFi 模块),内核在初始化时如果找不到对应硬件,可能会卡住或报错。
4.3.2 自定义设备树的集成步骤
- 在
board/<company>/<boardname>/下创建你的 .dts 文件 - 在 .dts 里
#include芯片厂商提供的 .dtsi 文件(通常在内核源码的arch/arm/boot/dts/下) - 在 Buildroot 配置中设置
BR2_LINUX_KERNEL_CUSTOM_DTS_PATH指向你的 .dts - 设置
BR2_LINUX_KERNEL_INTREE_DTS_NAME为你的 .dts 文件名(不含路径和扩展名)
# 假设你的板子叫 myboard,基于 i.MX6ULL
# 1. 创建 board/mycompany/myboard/myboard.dts
/dts-v1/;
#include "imx6ull.dtsi"
/ {
model = "MyCompany MyBoard";
compatible = "mycompany,myboard", "fsl,imx6ull";
chosen {
stdout-path = &uart1;
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>; /* 512 MB */
};
};
&uart1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart1>;
status = "okay";
};
# 2. 在 Buildroot 配置中设置
# BR2_LINUX_KERNEL_CUSTOM_DTS_PATH="board/mycompany/myboard/myboard.dts"
# BR2_LINUX_KERNEL_INTREE_DTS_NAME="myboard"
4.3.3 设备树编译的常见问题
我遇到过最坑的一个问题:.dts 里引用了不存在的 .dtsi 文件。编译时 DTC(设备树编译器)会报错,但 Buildroot 的日志里可能只显示一行 Error: ...,很容易被忽略。我的排查方法是:
- 先手动编译一次:
make linux-dirclean && make linux,然后看完整输出 - 检查
output/build/linux-xxx/arch/arm/boot/dts/下是否生成了你的 .dtb 文件 - 用
dtc -I dtb -O dts xxx.dtb反编译生成的 .dtb,确认内容正确
make linux-rebuild 单独编译内核和设备树,确认无误后再 make 完整构建。
4.4 本章知识体系
下面这张图把内核配置、defconfig 片段、设备树三者的关系串起来了。你可以看到,从 Buildroot 配置到最终生成内核镜像和 .dtb 文件的完整流程。
4.5 避坑指南:我踩过的三个坑
最后分享几个我实际遇到过的教训,希望能帮你省点时间:
- 坑一:内核版本与设备树不匹配。我曾经把 5.10 内核的设备树拿到 5.4 内核上用,结果 GPIO 编号全乱了。后来我养成了习惯:每次换内核版本,都重新检查
arch/arm/boot/dts/下的 .dtsi 文件有没有变化。 - 坑二:配置片段里写了冲突的选项。比如一个片段里
CONFIG_FOO=y,另一个片段里# CONFIG_FOO is not set。Buildroot 不会报错,但最终生效的是最后一个片段里的值。我排查了整整一天才发现是两个片段顺序搞反了。 - 坑三:忘了把 .dts 文件加入版本控制。有一次我重装了开发环境,从 Git 拉下来直接编译,结果板子起不来。查了半天才发现自定义 .dts 文件根本没提交到仓库。从那以后,我把所有板级文件都放在
board/目录下,并且写了个脚本检查是否有未跟踪的文件。
make linux-dirclean && make linux 来确保干净编译。然后检查 output/images/ 下是否生成了新的 zImage 和 .dtb 文件。最后用 file 命令确认文件类型正确——这个习惯帮我避免过好几次「编译成功但文件没更新」的尴尬。