2. 内核配置基础回顾:Kconfig语法、Makefile与.config文件结构
在深入设备树驱动的模块化裁剪之前,必须牢固掌握内核配置系统的三大支柱:Kconfig(配置选项定义)、Makefile(编译规则)和 .config(配置结果存储)。这三者构成了从“配置意图”到“二进制产物”的完整链路。
2.1 三者的角色与数据流
内核配置系统的工作流程可以概括为:Kconfig 定义“有什么可配”,用户通过 make menuconfig 等工具做出选择,结果写入 .config,Makefile 根据 .config 中的变量决定编译哪些源文件。下图展示了这一数据流:
要点:设备树的模块化裁剪本质上是在 Kconfig 层面对驱动选项进行“拆分”和“条件化”,然后在 Makefile 中利用
CONFIG_* 变量控制设备树源文件(.dts)的编译与包含。
2.2 Kconfig 语法核心
Kconfig 文件位于每个驱动目录下(如 drivers/i2c/Kconfig),使用一种类脚本语言定义配置选项。以下是驱动开发中最常用的语法元素:
| 语法元素 | 作用 | 示例 |
|---|---|---|
config |
定义一个配置选项,生成 CONFIG_* 变量 |
config SENSOR_TMP117 |
tristate |
三态选项:n(不编译)、y(编译进内核)、m(编译为模块) | tristate "TMP117 temperature sensor" |
bool |
布尔选项:只能选 y 或 n | bool "Enable debug output" |
depends on |
依赖关系:只有前置选项满足时才可见 | depends on I2C |
select |
反向选择:选中本选项时自动选中另一选项 | select REGMAP_I2C |
help |
帮助文本,在 menuconfig 中显示 | help |
if / endif |
条件块:批量控制多个选项的可见性 | if EXPERT ... endif |
menu / endmenu |
创建子菜单,组织选项层次 | menu "Sensor drivers" ... endmenu |
模块化裁剪关键:使用
tristate 而非 bool 是驱动模块化的前提。只有 tristate 选项才能被编译为 .ko 文件,从而实现运行时按需加载。
一个典型的驱动 Kconfig 条目示例:
config SENSOR_TMP117
tristate "TI TMP117 temperature sensor"
depends on I2C
select REGMAP_I2C
help
Support for the TI TMP117 high-accuracy temperature sensor.
This driver can be built as a module. If unsure, say N.
If compiled as a module, it will be called tmp117.
2.3 Makefile 中的条件编译
内核 Makefile 使用 obj-$(CONFIG_*) 模式实现条件编译。当 CONFIG_* 为 y 时,目标文件编译进内核;为 m 时编译为模块;为 n 或不定义时不编译。
| .config 中的值 | Makefile 展开结果 | 编译行为 |
|---|---|---|
CONFIG_SENSOR_TMP117=y |
obj-y += tmp117.o |
编译进内核镜像 |
CONFIG_SENSOR_TMP117=m |
obj-m += tmp117.o |
编译为可加载模块 |
# CONFIG_SENSOR_TMP117 is not set |
obj-$(CONFIG_SENSOR_TMP117) 展开为空 |
不编译 |
驱动目录下的 Makefile 通常非常简洁:
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for I2C sensor drivers
#
obj-$(CONFIG_SENSOR_TMP117) += tmp117.o
obj-$(CONFIG_SENSOR_BME280) += bme280.o
obj-$(CONFIG_SENSOR_OPT3001) += opt3001.o
常见错误:初学者容易在 Makefile 中直接写
obj-y += tmp117.o,这会导致驱动始终编译进内核,无法模块化。必须使用 obj-$(CONFIG_*) 形式。
2.4 .config 文件结构
.config 是内核配置的“快照”文件,位于内核源码根目录。它由 make *config 系列工具自动生成,也是 Makefile 读取配置变量的来源。其格式为简单的键值对:
#
# Automatically generated file; DO NOT EDIT.
# Linux/arm64 6.1.0 Kernel Configuration
#
CONFIG_CC_VERSION_TEXT="aarch64-linux-gnu-gcc (GCC) 12.2.0"
CONFIG_CC_IS_GCC=y
CONFIG_GCC_VERSION=120200
# 三态选项的三种状态
CONFIG_SENSOR_TMP117=m # 编译为模块
CONFIG_SENSOR_BME280=y # 编译进内核
# CONFIG_SENSOR_OPT3001 is not set # 不编译
# 布尔选项
CONFIG_DEBUG_FS=y
# CONFIG_DEBUG_KERNEL is not set
.config 文件的关键特征:
- 自动生成:不要手动编辑
.config,应通过make menuconfig等工具修改。 - 三态表示:
=y(编译进内核)、=m(模块)、is not set(不编译)。 - 依赖自动处理:当选择
CONFIG_SENSOR_TMP117=m时,其依赖的CONFIG_REGMAP_I2C会被自动设为=y或=m。 - 顶层包含:
arch/<arch>/configs/下的defconfig是.config的模板,用于快速生成初始配置。
2.5 模块化裁剪的配置策略
基于上述基础,驱动模块化裁剪的配置策略可以归纳为以下步骤:
- 识别驱动选项:在 Kconfig 中确认目标驱动是否为
tristate,若不是则需修改。 - 拆分依赖链:将驱动依赖的通用层(如 regmap、IIO 核心)也设为
tristate,避免强制编译进内核。 - 设置模块化:在
.config中将目标驱动设为=m,其依赖自动跟随。 - 验证 Makefile:确保驱动目录的 Makefile 使用
obj-$(CONFIG_*)而非obj-y。 - 编译与测试:使用
make modules编译模块,insmod/modprobe加载测试。
实践建议:在开始设备树驱动裁剪前,先对目标驱动执行
make menuconfig 搜索其选项名(按 / 键),确认其类型和依赖关系。这是避免“配置了但编译不进去”的最有效方法。