3. Linux 内核源码结构解析:内核源码目录树、Kconfig 与 Makefile 机制、内核配置系统
拿到一份 Linux 内核源码,第一反应往往是「这么大一坨,从哪看起?」。我刚开始接触内核时也懵,后来发现,只要搞懂三样东西——目录树、Kconfig 和 Makefile,整个内核的骨架就清晰了。说白了,这三者就是内核的「地图」、「菜单」和「施工图」。
3.1 内核源码目录树:一张地图走天下
解压内核源码后,顶层目录大概长这样(以 5.x 版本为例):
linux-5.x/
├── arch/ # 架构相关代码(ARM、x86、RISC-V 等)
├── block/ # 块设备层
├── crypto/ # 加密 API
├── Documentation/ # 内核文档
├── drivers/ # 设备驱动(占源码一半以上)
├── fs/ # 文件系统
├── include/ # 内核头文件
├── init/ # 内核初始化
├── ipc/ # 进程间通信
├── kernel/ # 核心子系统(调度、信号、时间等)
├── lib/ # 内核工具函数
├── mm/ # 内存管理
├── net/ # 网络协议栈
├── scripts/ # 编译脚本、工具
├── security/ # 安全模块
├── sound/ # 音频子系统
├── tools/ # 用户态工具
├── usr/ # initramfs 相关
├── virt/ # 虚拟化(KVM)
├── Kconfig # 顶层配置菜单
├── Makefile # 顶层构建文件
└── README # 你懂的
嗯,这里要注意:arch/ 和 drivers/ 是我们嵌入式开发最常打交道的两个目录。arch/ 下面按 CPU 架构分,比如 arch/arm/、arch/arm64/、arch/x86/。drivers/ 下面按设备类型分,比如 drivers/i2c/、drivers/spi/、drivers/net/。
3.2 Kconfig 机制:内核的「点菜系统」
Kconfig 是什么?说白了就是内核的配置菜单描述语言。每个目录下的 Kconfig 文件定义了该目录有哪些配置选项,以及它们之间的依赖关系。
举个例子,drivers/i2c/Kconfig 里可能有这么一段:
config I2C_CHARDEV
tristate "I2C device interface"
depends on I2C
help
Say Y here to use I2C device interface.
This is needed for i2c-dev driver.
这里的关键字我解释一下:
| 关键字 | 含义 | 取值 |
|---|---|---|
| config | 定义一个配置选项 | 选项名(如 I2C_CHARDEV) |
| tristate | 三态:不编译/编译进内核/编译为模块 | y / m / n |
| bool | 布尔:编译或不编译 | y / n |
| depends on | 依赖另一个选项 | 如 depends on I2C |
| select | 反向选择(自动选中另一个选项) | 如 select CRC32 |
| help | 帮助信息 | 文本描述 |
3.3 Makefile 机制:内核的「施工图」
每个有源码的目录下都有 Makefile,告诉内核怎么编译这些文件。内核的 Makefile 写法跟普通应用不同,它用的是 obj-y、obj-m、obj-$(CONFIG_XXX) 这种模式。
看个实际例子,drivers/i2c/Makefile:
obj-y += i2c-core.o i2c-dev.o
obj-$(CONFIG_I2C_CHARDEV) += i2c-dev.o
obj-$(CONFIG_I2C_GPIO) += i2c-gpio.o
解释一下:
- obj-y:无条件编译进内核
- obj-m:编译成可加载模块(.ko 文件)
- obj-$(CONFIG_XXX):根据配置决定是否编译
你想想看,如果 CONFIG_I2C_CHARDEV 配置为 y,那 obj-$(CONFIG_I2C_CHARDEV) 就变成 obj-y,i2c-dev.o 就编进内核。如果配置为 m,就变成 obj-m,编成模块。如果没配置,就变成 obj-n,不编译。
3.4 内核配置系统:从 menuconfig 到 .config
整个配置系统的流程是这样的:
- make menuconfig 读取各目录的 Kconfig,生成配置界面
- 你在界面里选好选项,保存为 .config 文件
- 编译时,顶层 Makefile 读取 .config,生成 include/generated/autoconf.h
- 源码通过 #include 这个头文件,使用 CONFIG_XXX 宏来控制代码分支
我画了一张图,帮你理清这个流程:
3.5 实战:如何添加一个自己的配置选项
假设你要在 drivers/misc/ 下加一个自己的驱动,叫 my_driver。步骤很简单:
- 创建 Kconfig 文件(如果已有就编辑它)
config MY_DRIVER
tristate "My custom driver"
depends on I2C
help
This is a custom driver for my project.
Say Y/M here if you need this driver.
- 编辑 Makefile
obj-$(CONFIG_MY_DRIVER) += my_driver.o
- 在上级 Kconfig 中 source 子目录
source "drivers/misc/Kconfig"
- 运行 make menuconfig,找到你的选项,选上
- 编译,搞定
3.6 避坑指南:配置系统的常见问题
- 配置选项找不到? 检查依赖链,看看 depends on 的选项开了没。我遇到过好几次,折腾半天发现是父选项没开。
- 编译报 undefined reference? 大概率是某个依赖的模块没编进去。用 make menuconfig 的搜索功能(按 / 键)查一下符号定义。
- .config 被覆盖? 别直接改 .config,用 make oldconfig 或 make menuconfig 来更新。手动改 .config 很容易被 make 的依赖检查覆盖掉。