12、Kconfig 与内核编译:内核编译集成、Kconfig 配置、内核模块编译

说到 Android 内核编译,很多人第一反应就是「make menuconfig」然后一顿操作。但说实话,真正把 Kconfig 玩明白的人不多。我刚开始接触内核时,也踩过不少坑——比如改了配置发现没生效,或者编译出来的内核模块死活加载不上。今天咱们就把这块彻底捋清楚。

12.1 内核编译的整体流程

先看一张图,把整个流程串起来。我个人习惯把内核编译分成三个阶段:配置、构建、打包。

阶段一:配置 Kconfig 解析 生成 .config 阶段二:构建 make 编译内核 编译内核模块 阶段三:打包 生成 boot.img 生成 dtbo.img 核心文件流转 Kconfig → .config → autoconf.h → C 源码 Makefile → 读取 .config → 条件编译 模块 .ko → 打包进 ramdisk 或独立分区 dtc 编译 .dts → dtbo.img(设备树覆盖)

你看,配置阶段是入口。Kconfig 定义了所有可选项,你通过 menuconfig 选完后,系统会生成一个 .config 文件。这个文件就是整个编译的「宪法」——所有 Makefile 都听它的。

12.2 Kconfig 语法速览

Kconfig 文件长什么样?说白了就是一套「菜单描述语言」。每个选项都是一个条目,有类型、有依赖、有默认值。

config MY_DRIVER
    bool "Enable My Driver"
    depends on ARM
    default y
    help
      This is a sample driver for demonstration.
      Say Y here if you want to use it.

这里我解释一下几个关键字段:

  • bool:开关类型,要么 Y 要么 N。还有 tristate(三态:Y/N/M),M 表示编译成模块。
  • depends on:依赖关系。比如这个驱动只能在 ARM 架构下可见。
  • default y:默认开启。很多厂商会把自己平台的驱动默认设为 y。
  • help:帮助信息,menuconfig 里按 ? 能看到。

我的小技巧:调试时如果发现某个选项在 menuconfig 里消失了,先检查它的 depends on 条件是否满足。我曾经花了一下午找原因,最后发现是依赖的 ARCH 没选对。

12.3 内核模块编译的两种方式

内核模块编译,说白了就两种路子:内联编译独立编译

方式 配置值 产物 适用场景
内联编译 CONFIG_XXX=y 直接链接进 zImage 必须加载的驱动(如串口、存储)
模块编译 CONFIG_XXX=m 生成 .ko 文件 可热插拔的驱动(如 USB、WiFi)
不编译 CONFIG_XXX=n 不需要的功能

嗯,这里要注意:模块编译出来的 .ko 文件,需要跟内核版本严格匹配。你想想看,如果内核升级了,旧模块直接 insmod 大概率会报错——version magic 不一致。

12.4 实战:添加一个自定义内核模块

咱们动手走一遍。假设你要在 Android 内核里加一个自己的驱动模块。

第一步:写 Kconfig

# drivers/misc/my_driver/Kconfig
config MY_DRIVER
    tristate "My Custom Driver"
    depends on HAS_IOMEM
    help
      A custom driver for learning purpose.
      Choose M to build as module.

第二步:写 Makefile

# drivers/misc/my_driver/Makefile
obj-$(CONFIG_MY_DRIVER) += my_driver.o

注意这个写法:obj-$(CONFIG_MY_DRIVER) 会自动展开成 obj-yobj-m,取决于你在 .config 里怎么配的。

第三步:在上级 Kconfig 和 Makefile 里引用

# drivers/misc/Kconfig 里加一行
source "drivers/misc/my_driver/Kconfig"

# drivers/misc/Makefile 里加一行
obj-y += my_driver/

避坑指南:我曾经犯过一个低级错误——在 Makefile 里写了 obj-y += my_driver.o,但 Kconfig 里配的是 tristate。结果模块死活编不进去。后来发现,obj-y 是强制编译,跟 Kconfig 的 tristate 冲突了。正确的做法是用 obj-$(CONFIG_MY_DRIVER) 这种条件编译。

12.5 编译与部署

配置好之后,编译就简单了:

# 配置内核
make ARCH=arm64 menuconfig
# 找到你的驱动,设为 M
# 编译内核和模块
make ARCH=arm64 -j8
make ARCH=arm64 modules_install INSTALL_MOD_PATH=./modules

编译出来的 .ko 文件在 drivers/misc/my_driver/ 目录下。你可以用 insmod 加载,或者把它打包进 ramdisk。

我个人习惯在开发阶段用 adb push 直接推到设备上测试:

adb push my_driver.ko /data/local/tmp/
adb shell insmod /data/local/tmp/my_driver.ko

如果一切顺利,lsmod 应该能看到你的模块。如果报错,dmesg 会告诉你原因——多半是依赖没满足或者版本不匹配。

12.6 关于设备树的一点补充

现在 Android 内核基本都用了设备树(Device Tree)。你的驱动如果想读取硬件信息,得在 .dts 文件里定义节点。Kconfig 只管「编不编译」,设备树管「硬件怎么配」。

举个例子:

// arch/arm64/boot/dts/qcom/my-board.dts
&i2c0 {
    my_driver@50 {
        compatible = "my,driver";
        reg = <0x50>;
        my,param = <100>;
    };
};

然后在驱动代码里用 of_match_table 来匹配。这部分内容比较多,咱们后面章节再细讲。

核心要点回顾

  • Kconfig 是配置入口,.config 是编译依据
  • tristate 支持 Y/N/M 三种状态,M 表示模块
  • Makefile 用 obj-$(CONFIG_XXX) 做条件编译
  • 模块编译后要匹配内核版本,否则加载失败
  • 设备树和 Kconfig 是两套体系,各管各的

好了,这一章就到这里。Kconfig 这块内容其实不难,关键是理解它的「菜单-配置-编译」这条链路。你只要动手配一次、编一次、加载一次,就全明白了。


公众号:蓝海资料掘金营,微信deep3321