第1章:内核编译与构建系统——Kconfig与Kbuild详解

做车规级驱动开发,内核编译是基本功。说实话,我见过不少工程师,驱动写得挺溜,但一问到内核怎么编译、Kconfig怎么配,就有点含糊了。这不行。车规级开发对内核的定制要求很高,你得知道怎么裁剪、怎么配置、怎么把驱动编进内核或者编成模块。

这一章,我就带你把这些东西彻底搞明白。

1.1 Kconfig——内核的配置菜单

Kconfig是什么?说白了,它就是内核的配置系统。你运行make menuconfig时看到的那个图形界面,背后就是Kconfig在驱动。

每个驱动目录下,都会有一个Kconfig文件。比如你写了一个SPI驱动,那就在drivers/spi/Kconfig里加上你的配置项。格式大概是这样的:

config MY_SPI_DRIVER
    tristate "My SPI Driver Support"
    depends on SPI
    help
      This is a sample SPI driver for automotive use.

这里有个关键点——tristate。它表示三种状态:不编译、编译进内核、编译成模块。车规级开发中,我建议核心驱动直接编进内核,不要用模块。为什么?因为模块加载有延迟,而且万一文件系统挂了,模块就加载不上了。我在一个项目中就吃过这个亏,后来全改成了built-in。

重要:车规级驱动,能编进内核就别用模块。安全第一。

再看depends on,这是依赖关系。你的驱动依赖SPI子系统,那就写上。否则用户配了你的驱动却没配SPI,编译会报错。嗯,这里要注意,依赖链别搞太复杂,否则配置时会让人抓狂。

1.2 Kbuild——内核的编译系统

Kconfig管配置,Kbuild管编译。每个目录下还有一个Makefile,告诉内核怎么编译你的代码。

举个例子,你的驱动就一个文件my_spi.c,那Makefile里写:

obj-$(CONFIG_MY_SPI_DRIVER) += my_spi.o

如果驱动有多个文件,比如my_spi_main.cmy_spi_utils.c,那就这样:

my_spi_driver-objs := my_spi_main.o my_spi_utils.o
obj-$(CONFIG_MY_SPI_DRIVER) += my_spi_driver.o

我个人习惯把功能拆成多个文件,这样维护起来清晰。车规级代码尤其如此——你想想看,一个文件几千行,后面的人怎么改?

小技巧:编译时加V=1可以看到详细的编译命令,调试问题很有用。

1.3 交叉编译工具链配置

车规级芯片大多是ARM、RISC-V这类架构,你得用交叉编译工具链。配置方式很简单:

export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-

然后执行:

make menuconfig
make -j4

这里有个坑——工具链版本要和内核匹配。我曾经用了一个太新的gcc编译老内核,结果一堆警告,最后链接失败。后来我学乖了,每个项目都固定工具链版本,写在文档里。

另外,车规级开发经常要用到Linaro的工具链,稳定可靠。我个人推荐gcc-arm-9.2-2019.12-x86_64-arm-none-linux-gnueabihf这个版本,用了很久没出过问题。

1.4 内核模块的编译与加载

虽然我说核心驱动要编进内核,但有些调试用的驱动,或者不关键的驱动,用模块更方便。编译模块很简单:

make modules

然后安装到目标板:

make modules_install INSTALL_MOD_PATH=/path/to/rootfs

加载模块用insmodmodprobe。区别在哪?insmod简单粗暴,直接加载;modprobe会处理依赖关系。我建议用modprobe,省心。

modprobe my_spi_driver

卸载用rmmod

rmmod my_spi_driver

警告:车规级产品中,不要在行驶状态下动态加载或卸载驱动。这可能导致系统不稳定。所有驱动必须在启动阶段加载完毕。

1.5 设备树(DTS)的编译与使用

设备树是车规级开发的重头戏。它描述了硬件信息,内核通过它来知道板上有什么设备。

DTS文件是文本格式,编译后生成DTB二进制文件。编译命令:

dtc -I dts -O dtb -o my_board.dtb my_board.dts

在车规级项目中,设备树经常要改。比如换了一个GPIO口,或者调整了时钟频率。改完之后重新编译DTB,烧到板子上就行。

我遇到过一个问题——设备树里reg属性写错了地址,结果驱动死活不工作。查了两天才发现是地址偏移了一位。从那以后,我每次改设备树都会用dtc -I dtb -O dts反编译回来检查一遍。

设备树里常用的写法:

spi@ff110000 {
    compatible = "my,spi-controller";
    reg = <0xff110000 0x1000>;
    interrupts = <0 45 4>;
    status = "okay";
    
    my_spi_device@0 {
        compatible = "my,spi-device";
        reg = <0>;
        spi-max-frequency = <10000000>;
    };
};

注意status属性。调试时设为"disabled"可以临时关闭设备,不用改驱动代码。这个技巧在车规级开发中很实用。

1.6 实战建议

好了,理论讲完了,说点实际的。车规级内核编译,我建议你这样做:

  • 版本管理:内核源码、工具链、设备树,全部用Git管理。每次改动都要有记录。
  • 自动化编译:写一个脚本,一键完成配置、编译、打包。别手动敲命令,容易出错。
  • 持续集成:每次提交代码,自动编译并跑一遍基本测试。我在上一个项目中就是这么做的,省了不少事。

最后,记住一点——车规级开发,稳定压倒一切。不要追求最新的内核版本,选一个长期支持版(LTS),然后深耕细作。

下一章,我们会深入内核模块的编写技巧,包括如何注册设备、如何实现文件操作接口。到时候见。