4、内核配置:Linux Kernel配置入口、内核版本选择、内核模块管理、内核补丁管理
内核配置这件事,说白了就是给Linux内核「剪裁」和「定制」。我刚开始接触Buildroot时,总觉得默认配置就能用,结果做出来的系统要么太大,要么缺驱动。后来踩过几次坑才明白——内核配置是嵌入式系统的灵魂,配好了事半功倍,配砸了寸步难行。
这一章,咱们就聊聊内核配置的四个核心环节:怎么进配置界面、怎么选版本、怎么管模块、怎么打补丁。嗯,都是实战中绕不开的硬骨头。
核心要点:内核配置不是一次性的工作。随着硬件迭代、功能增减,你至少要在每个大版本发布前重新审视一次配置。我见过不少团队因为「上次配好了就不动了」,结果换颗Flash芯片就翻车。
4.1 内核配置入口:从哪里开始?
Buildroot里配内核,入口其实有好几个。我个人最常用的是make linux-menuconfig,直接在终端里调出内核的图形化配置界面。你想想看,几百个选项靠手写Kconfig?那不得疯掉。
具体操作很简单:
# 进入Buildroot顶层目录后
make linux-menuconfig
这个命令会先检查内核源码是否已下载和解压,然后自动弹出内核的配置菜单。嗯,这里要注意——它改的是output/build/linux-xxx/.config,而不是Buildroot的.config。很多人第一次用会搞混,以为改了Buildroot的配置。
除了menuconfig,还有几个入口:
| 命令 | 界面类型 | 适用场景 |
|---|---|---|
make linux-menuconfig |
终端图形(ncurses) | SSH远程、无桌面环境 |
make linux-xconfig |
Qt图形界面 | 本地开发、需要搜索功能 |
make linux-gconfig |
GTK图形界面 | GNOME桌面用户 |
make linux-nconfig |
终端新式图形 | 习惯nconfig快捷键 |
我的习惯:远程开发时用menuconfig,本地用xconfig。xconfig的搜索框特别好用——按Ctrl+F输入"USB",所有USB相关选项瞬间列出来。省得在菜单树里一层层翻。
配置完成后,记得把内核配置保存回Buildroot。方法是在make linux-menuconfig界面里选"Save",或者手动复制:
cp output/build/linux-xxx/.config board/yourcompany/linux.config
然后在Buildroot配置里指定这个文件:
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="board/yourcompany/linux.config"
4.2 内核版本选择:LTS还是主线?
选内核版本,是我见过最纠结的事。有人追新,非主线不用;有人求稳,死守LTS。我的建议是——看产品阶段。
Buildroot里选版本,通过这几个配置项控制:
BR2_LINUX_KERNEL_VERSION="5.15.32"
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="5.15.32"
或者用Git仓库:
BR2_LINUX_KERNEL_CUSTOM_GIT=y
BR2_LINUX_KERNEL_CUSTOM_REPO_URL="https://github.com/yourcompany/linux.git"
BR2_LINUX_KERNEL_CUSTOM_REPO_VERSION="v5.15.32-custom"
选版本时,我一般遵循这个原则:
- 原型验证阶段:用主线最新稳定版。驱动支持全,踩坑了也好搜答案。
- 产品开发阶段:切到LTS版本。比如5.10.x、5.15.x、6.1.x。LTS有长期维护,安全补丁跟得上。
- 量产维护阶段:锁定某个LTS小版本。除非有严重安全漏洞,否则不轻易升级。
我曾经踩过的坑:有一回为了用上某个新驱动,直接上了主线内核。结果WiFi模块的固件接口变了,用户态工具不兼容。折腾了两周才回退到LTS。所以——新内核虽好,别忘了用户态程序的兼容性。
Buildroot还支持从本地源码构建:
BR2_LINUX_KERNEL_CUSTOM_LOCAL=y
BR2_LINUX_KERNEL_CUSTOM_LOCAL_PATH="/home/me/my-kernel"
这个选项在调试自定义驱动时特别有用。改一行代码,make一下就能验证,不用反复提交和拉取。
4.3 内核模块管理:编进去还是外挂?
内核模块有三种状态:=y(编进内核)、=m(编译成模块)、=n(不编译)。这个选择直接影响系统大小和启动速度。
我的经验法则:
| 场景 | 推荐方式 | 理由 |
|---|---|---|
| 根文件系统必需的驱动(存储、串口) | =y | 启动时就要用,不能等加载模块 |
| 外设驱动(USB、音频、网络) | =m | 按需加载,节省内存 |
| 调试/测试用驱动 | =m | 不用时就不加载,减少攻击面 |
| 硬件不存在的驱动 | =n | 省空间、省编译时间 |
Buildroot里管理模块,有几个关键点:
# 确保内核配置里开启了模块支持
CONFIG_MODULES=y
# 编译后,模块会被安装到目标文件系统
# 路径:output/target/lib/modules/<内核版本>/
模块的加载顺序由modules.dep控制。Buildroot在编译时会自动运行depmod生成依赖关系。你只需要在启动脚本里用modprobe加载就行:
# /etc/init.d/S99modules
modprobe usb-storage
modprobe ath9k_htc
省空间技巧:如果Flash空间吃紧,可以只保留需要的模块。在Buildroot配置里设置BR2_LINUX_KERNEL_EXTRA_MODULES,只安装你指定的模块列表。我有个项目,这么一搞,内核模块从12MB降到了2MB。
4.4 内核补丁管理:不改源码怎么定制?
说实话,纯主线内核很少能直接满足嵌入式需求。要么加驱动,要么改行为,要么修Bug。Buildroot的补丁管理机制,就是为这个场景设计的。
补丁的放置位置很灵活:
# 全局补丁目录(对所有内核版本生效)
board/yourcompany/patches/linux/
# 版本特定补丁目录(只对指定版本生效)
board/yourcompany/patches/linux/5.15.32/
Buildroot会自动扫描这些目录,按文件名顺序打补丁。命名规范很重要:
0001-add-my-usb-driver.patch
0002-fix-spi-bug.patch
0003-enable-debug-print.patch
数字前缀控制顺序。我习惯用四位数字,从0001开始。为什么?因为补丁之间有依赖关系时,顺序错了会打不上。
补丁的生成方式:
# 基于Git
git format-patch -1 HEAD
# 基于diff
diff -uprN original/ modified/ > my-patch.patch
我曾经翻过的车:有一次直接在生产分支上改内核源码,忘了生成补丁。后来升级内核版本,所有修改全丢了。从那以后,我强制团队遵守一条铁律——永远不改源码树,所有修改必须走补丁。
Buildroot还支持KERNEL_EXTRA方式添加外部驱动:
# 在board目录下放一个外部驱动包
board/yourcompany/kernel-extras/
├── mydriver/
│ ├── Makefile
│ ├── Kconfig
│ └── mydriver.c
└── mydriver.mk
然后在Buildroot配置里启用:
BR2_LINUX_KERNEL_EXTRA="board/yourcompany/kernel-extras/mydriver"
这种方式比打补丁更干净——驱动源码独立维护,升级内核时基本不受影响。
4.5 实战:一次完整的内核配置流程
说了这么多,咱们串起来走一遍。假设你要为一块新板子配内核:
- 选版本:在Buildroot配置里设置
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="5.15.32" - 配入口:运行
make linux-menuconfig,按硬件需求勾选驱动 - 管模块:把不常用的驱动设为
=m,核心驱动设为=y - 打补丁:把硬件厂商提供的BSP补丁放到
board/yourcompany/patches/linux/ - 保存配置:把
.config复制到board/yourcompany/linux.config - 编译验证:
make,然后检查output/images/zImage和模块目录
关键提醒:每次修改内核配置后,最好执行一次make linux-dirclean再重新编译。否则Buildroot可能用缓存,导致配置没生效。我因为这个浪费过半天时间——改了半天配置,编译出来的内核还是老样子。
嗯,内核配置这块儿,说白了就是「理解硬件需求 + 熟悉内核选项 + 规范管理补丁」。刚开始会觉得选项太多、无从下手。但只要你按这套流程走,配过两三个板子之后,就会形成自己的套路。