2. 内核源码结构解析:Linux内核源码目录结构、Kconfig与Makefile机制、内核配置系统(menuconfig)
说实话,很多做嵌入式Linux的朋友,拿到内核源码第一反应就是直接 make menuconfig,然后一顿操作猛如虎。但你要问他某个配置项到底在哪层目录、Kconfig 和 Makefile 是怎么配合的,他可能就含糊了。
我当年刚入行时也踩过这个坑。有一次给某款车机裁剪内核,明明把某个驱动模块在 menuconfig 里勾掉了,编译时却还是报符号冲突。折腾了大半天,最后发现是 Kconfig 的依赖关系没理清,那个模块被别的子系统强制选上了。从那以后,我养成了一个习惯:改配置之前,先看 Kconfig。
这一章,我们就来把内核源码的骨架摸清楚。你想想看,内核几万文件,要是没有一套清晰的目录结构和配置机制,根本没法维护。Linux 社区能搞定,靠的就是 Kconfig + Makefile 这套组合拳。
2.1 内核源码顶层目录结构
先看顶层。你解压一个 Linux 内核源码包,会看到这些目录:
| 目录/文件 | 作用 | 车载裁剪关注点 |
|---|---|---|
arch/ |
体系结构相关代码,如 x86、arm64、riscv | 车载常用 arm64,重点关注 arch/arm64/ |
block/ |
块设备层核心代码 | eMMC、UFS 相关调度器可裁剪 |
crypto/ |
加密算法库 | 若不用磁盘加密或安全启动,可大幅裁剪 |
drivers/ |
设备驱动,最大目录 | 车载重点:i2c、spi、gpio、pinctrl、drm、net、mmc、usb |
fs/ |
文件系统实现 | 保留 ext4、squashfs、proc、sysfs,其余可砍 |
kernel/ |
核心调度、进程管理、irq | 基本不动,但可关掉 cgroup 等非必要特性 |
net/ |
网络协议栈 | 车载保留 can、tcp/ip、ipv6(可选),砍掉无线、bridge 等 |
sound/ |
音频子系统 | ALSA 核心保留,codec 驱动按硬件选配 |
include/ |
全局头文件 | 一般不动,但裁剪时注意 include/config/ 自动生成 |
scripts/ |
编译脚本、Kconfig 解析工具 | 不用动,但理解其逻辑有助于调试 |
init/ |
内核初始化流程 | 可定制启动参数默认值 |
mm/ |
内存管理 | 车载建议保留 CMA、SLUB,关掉不用的页面回收策略 |
ls -d */ 扫一遍顶层目录,然后重点关注 arch/arm64/ 和 drivers/ 下的子目录。这两个地方占了裁剪工作量的 70% 以上。
2.2 Kconfig 机制:配置项的骨架
Kconfig 是什么?说白了,它就是内核配置项的「说明书」。每个目录下都有一个 Kconfig 文件,定义了该目录下有哪些配置选项、它们的依赖关系、默认值等。
来看一个车载场景下常见的例子。假设我们要裁剪掉蓝牙驱动,先找到 drivers/bluetooth/Kconfig:
config BT_HCIUART
tristate "UART (HCI) protocol support"
depends on BT
select SERIAL_DEV_BUS
help
This driver provides support for Bluetooth HCI UART transport layer.
Say Y here to compile it into the kernel, or M to compile as module.
这里有几个关键字段:
- tristate:三态,Y(编入内核)、M(模块)、N(不选)。车载裁剪时,能用模块就别编入内核。
- depends on:依赖关系。如果
BT没选,这个选项根本不会出现。 - select:反向选择。选了它,会自动选上
SERIAL_DEV_BUS。这个要小心,容易引入不必要的依赖。
NET 核心选项改成了 N,结果整个 drivers/ 下大半驱动都消失了——因为它们都 depends on NET。所以,改核心选项前,先用 grep -r "depends on" 查一下影响面。
2.3 Makefile 机制:编译的指挥棒
Kconfig 决定了「选什么」,Makefile 决定了「怎么编译」。每个目录下的 Makefile 会根据 Kconfig 生成的 .config 文件,决定哪些 .c 文件参与编译。
看一个典型的 Makefile 片段:
# drivers/bluetooth/Makefile
obj-$(CONFIG_BT_HCIUART) += hci_uart.o
obj-$(CONFIG_BT_HCIVHCI) += hci_vhci.o
obj-$(CONFIG_BT_HCIBCM2038) += bcm2038.o
这里的 obj-$(CONFIG_BT_HCIUART) 会被展开为:
- 如果
CONFIG_BT_HCIUART=y,则展开为obj-y,编译进内核 - 如果
CONFIG_BT_HCIUART=m,则展开为obj-m,编译为模块 - 如果
CONFIG_BT_HCIUART未定义,则整行消失,不编译
嗯,这里要注意:obj-y 和 obj-m 的编译顺序。内核链接时,obj-y 会按目录顺序链接,如果两个驱动有依赖关系,必须保证先编译被依赖的一方。车载平台上,我见过因为 Makefile 顺序问题导致驱动 probe 失败的情况。
2.4 内核配置系统:menuconfig 实战
menuconfig 是基于 ncurses 的图形化配置界面。你执行 make menuconfig 后,它会:
- 读取
arch/arm64/Kconfig(或其他架构),递归解析所有子目录的 Kconfig - 生成一个树形菜单,让你勾选
- 保存结果到
.config文件
车载裁剪时,我常用的几个操作:
- / 搜索:按
/键,输入关键字,快速定位配置项。比如搜 "CAN",就能找到所有 CAN 相关选项。 - Z 查看依赖:按
Z键,显示当前选项的完整依赖链。这个在排查「为什么选项灰掉」时特别有用。 - 差异对比:用
make savedefconfig生成精简配置,再用diff对比两个版本的差异。
2.5 知识结构图:Kconfig + Makefile 协作流程
下面这张图,把 Kconfig 和 Makefile 的协作关系画清楚了。我建议你把它存下来,每次裁剪时对照着看。
2.6 车载裁剪实战:从 Kconfig 入手
最后,分享一个我实际用过的裁剪流程。假设我们要为某款车机裁剪内核:
- 先跑默认配置:
make defconfig或使用厂商提供的defconfig文件 - 搜索关键功能:用
make menuconfig的/搜索 CAN、I2C、SPI、DRM、ALSA、MMC 等 - 逐层关闭:从
Device Drivers开始,关掉不用的硬件驱动(如 WiFi、Bluetooth、NFC、TV tuner 等) - 检查依赖:每次关闭后,用
make olddefconfig自动处理依赖,再用diff对比变化 - 验证编译:
make -j$(nproc),确保无编译错误 - 启动测试:在目标板上启动,检查 dmesg 是否有驱动加载失败
make listnewconfig 可以列出所有新增的配置项,适合在升级内核版本时快速了解变化。用 make help 能看到所有配置相关的目标。