4、定制Linux内核:内核配置入口、裁剪内核驱动、添加必要的硬件支持(SPI、I2C、UART)、内核模块管理

好,咱们进入第四章节。说实话,内核配置这块,是很多初学者最头疼的地方。菜单项密密麻麻,选项多到让人眼花。但别怕,我带你捋一遍,你会发现其实就那么几件事。

4.1 内核配置入口:从哪里开始?

Buildroot 里配置内核,入口其实就一个命令。你进到 Buildroot 顶层目录,执行:

make linux-menuconfig

这就打开了内核的配置界面。嗯,跟 Buildroot 本身的 make menuconfig 长得有点像,但内容完全不同。这里全是 Linux 内核的选项。

我个人习惯,第一次配置时,先跑一遍默认配置。怎么跑?

make linux-defconfig

这会加载你当前架构的默认内核配置。比如你选的 ARM 架构,它就会加载 arch/arm/configs/ 下的某个 defconfig 文件。有了这个基础,你再去做裁剪,心里就有底了。

小技巧: 如果你不确定当前用了哪个 defconfig,可以去 output/build/linux-xxx/.config 看一眼。文件头部会写明来源。

4.2 裁剪内核驱动:做减法比做加法难

很多人的误区是「功能越多越好」。其实不是。嵌入式设备资源有限,你塞一堆用不上的驱动,不仅编译慢,镜像还大。更关键的是,驱动多了,内核启动时加载的 init 过程也会变慢。

我做过一个项目,客户要求启动时间控制在 3 秒以内。我第一件事就是砍驱动。怎么砍?

  • 关掉所有不用的文件系统支持:比如你只用 ext4,那就把 ext2、ext3、btrfs、xfs 全关掉。
  • 关掉不用的网络协议:比如你的设备只用 TCP/IP,那 IPX、AppleTalk、DECnet 这些古董协议就别留了。
  • 关掉不用的硬件驱动:比如你的板子没有 SATA 接口,那 SATA 驱动就关掉。

我曾经遇到过一个坑:关掉某个驱动后,系统启动时内核 panic 了。查了半天,发现是内核里有个依赖没注意到。所以,裁剪时别太激进,每关一个选项,最好编译一次,验证一下。

注意: 内核选项之间有依赖关系。你关掉 A,可能 B 和 C 也会跟着失效。建议用 / 键搜索依赖关系,看清楚再动手。

4.3 添加必要的硬件支持:SPI、I2C、UART

网关设备,说白了就是跟各种传感器、外设打交道。SPI、I2C、UART 这三个总线,几乎是标配。下面我一个个说。

4.3.1 SPI 支持

SPI 常用于高速数据传输,比如 LCD 屏、SD 卡、RF 模块。在内核里,你需要开启:

Device Drivers  --->
  [*] SPI support  --->
    <*>   SPI controller driver for your SoC
    <M>   User mode SPI device driver support

注意那个「User mode SPI device driver support」。如果你想让用户空间程序直接操作 SPI,这个要选上。我一般选 <M>,编译成模块,按需加载。

4.3.2 I2C 支持

I2C 是低速总线,常用于传感器、RTC 时钟、EEPROM。配置路径:

Device Drivers  --->
  [*] I2C support  --->
    <*>   I2C device interface
    <*>   I2C bus driver for your SoC

这里有个小细节:I2C 的时序参数。有些传感器对时序很敏感,比如温湿度传感器 SHT30。我遇到过因为 I2C 时钟频率太高,导致通信失败的情况。解决办法是在设备树里调整 clock-frequency,一般设成 100kHz 比较稳妥。

4.3.3 UART 支持

UART 是串口,调试、通信都靠它。配置路径:

Device Drivers  --->
  [*] Character devices  --->
    [*] Serial drivers  --->
      <*>   Your SoC UART support
      [*]   Support for console on UART

记得把「Support for console on UART」勾上。否则你连串口终端都看不到。我刚开始做嵌入式时,就犯过这个错——编译完烧进去,串口没输出,还以为板子坏了。

核心要点: 这三个总线的驱动,最好都编译进内核(<*>),而不是模块。因为它们是系统基础功能,启动时就需要。

4.4 内核模块管理:什么时候用模块?

内核模块,说白了就是可以动态加载的驱动。好处是灵活,不用了可以卸载,节省内存。坏处是管理起来麻烦,你得确保根文件系统里有对应的 .ko 文件。

我一般遵循这个原则:

  • 核心驱动(SPI、I2C、UART、GPIO、中断控制器)→ 编译进内核
  • 外设驱动(Wi-Fi 模块、蓝牙、USB 设备)→ 编译成模块
  • 调试驱动(比如某些测试工具)→ 编译成模块,调试完就删掉

模块怎么管理?Buildroot 会自动把模块打包到根文件系统里。你只需要在配置内核时,把对应的驱动选成 <M> 就行。系统启动后,用 modprobe 加载:

modprobe spi-dev
modprobe i2c-dev

如果你想开机自动加载,可以把模块名加到 /etc/modules 文件里。Buildroot 的 BR2_ROOTFS_OVERLAY 可以帮你做这件事。

避坑指南: 我曾经把 Wi-Fi 驱动编译成模块,但忘了把固件文件放到根文件系统里。结果模块加载成功了,但固件加载失败,Wi-Fi 死活起不来。所以,模块依赖的固件、配置文件,一个都不能少。

4.5 实战:一个典型的网关内核配置清单

下面是我做的一个物联网网关项目的内核配置清单,供你参考:

功能 配置选项 推荐设置
SPI 控制器 CONFIG_SPI_IMX 编译进内核
I2C 控制器 CONFIG_I2C_IMX 编译进内核
UART 串口 CONFIG_SERIAL_IMX 编译进内核
Wi-Fi 模块 CONFIG_MT76x0U 编译成模块
USB 存储 CONFIG_USB_STORAGE 编译成模块
文件系统 CONFIG_EXT4_FS 编译进内核

这个清单不是死的。你根据自己板子的 SoC 型号,找到对应的驱动选项就行。比如你的 SoC 是 Allwinner 的,那就找 CONFIG_SPI_SUNXICONFIG_I2C_SUNXI 之类的。

好了,这一章就到这里。内核配置说白了就是「加加减减」——加上你需要的,砍掉你不需要的。多试几次,你就能找到感觉。下一章,咱们聊聊根文件系统的构建,那又是另一番天地。