12、Kconfig 与内核编译:内核编译集成、Kconfig 配置、内核模块编译
说到 Android 内核编译,很多人第一反应就是「make menuconfig」然后一顿操作。但说实话,真正把 Kconfig 玩明白的人不多。我刚开始接触内核时,也踩过不少坑——比如改了配置发现没生效,或者编译出来的内核模块死活加载不上。今天咱们就把这块彻底捋清楚。
12.1 内核编译的整体流程
先看一张图,把整个流程串起来。我个人习惯把内核编译分成三个阶段:配置、构建、打包。
你看,配置阶段是入口。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-y 或 obj-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