4. Linux 内核源码结构:从目录到配置的实战地图
说实话,我刚接触内核源码那会儿,第一反应就是「这玩意儿也太大了」。几万个文件,几百个目录,光看名字都头晕。但干这行十几年下来,我慢慢发现,内核的目录结构其实很有章法。今天我就带你把它拆开看看。
4.1 内核源码顶层目录:一张功能分区图
你下载一份 Linux 内核源码,解压后第一眼看到的顶层目录,大概有 20 来个。我习惯把它们分成三类:核心代码、架构相关、工具与文档。
| 目录名 | 功能 | 我的备注 |
|---|---|---|
arch/ |
体系结构相关代码(ARM、x86、RISC-V 等) | 嵌入式开发最常改的地方 |
block/ |
块设备层(IO 调度、电梯算法) | 调存储性能时进来 |
crypto/ |
加密 API 与算法实现 | 安全启动会用到 |
drivers/ |
设备驱动(占源码 50% 以上) | 嵌入式工程师的主战场 |
fs/ |
文件系统(ext4、proc、sysfs 等) | 根文件系统定制时必看 |
include/ |
内核头文件 | 写驱动时经常 #include 这里 |
init/ |
内核初始化流程(main.c) | 启动顺序调试的关键 |
kernel/ |
核心调度、进程管理、信号 | 一般不动,但得知道在哪 |
mm/ |
内存管理(页分配、SLAB、VMA) | 调内存碎片时进来翻 |
net/ |
网络协议栈(TCP/IP、Netfilter) | 做物联网网关时经常改 |
scripts/ |
编译、配置、分析脚本 | Kconfig 解析器就在这 |
tools/ |
用户态调试与性能工具 | 比如 perf 的源码 |
arch/arm/boot/dts/ 和 drivers/ 下对应厂商的目录。这两个地方基本决定了你的板子能不能跑起来。
4.2 Kconfig 与 Makefile:内核的「菜单」和「菜谱」
内核的配置和编译,靠的就是这两个文件。你可以把 Kconfig 理解成菜单——它定义了「有哪些菜可以点」。Makefile 就是菜谱——它告诉你「每个菜怎么做」。
4.2.1 Kconfig:配置选项的声明语言
每个支持配置的功能,都会在 Kconfig 里用一套语法描述。我挑几个最常用的关键字说说:
- config:定义一个配置选项,比如
config CONFIG_XXX - tristate:三态选项(Y=编译进内核,M=编译为模块,N=不编译)
- bool:二态选项(Y/N)
- depends on:依赖关系,A 依赖 B,B 没选 A 就不可见
- select:反向依赖,选 A 会自动选 B
- help:帮助信息,告诉你这个选项干嘛用的
举个例子,这是 drivers/i2c/Kconfig 里的一段:
config I2C_CHARDEV
tristate "I2C device interface"
depends on I2C
help
Say Y here to enable I2C character device interface.
This is needed for i2c-dev userspace access.
SPI_MASTER 但没选它依赖的 GPIO 控制器。结果编译通过,加载时直接 oops。后来我养成了习惯——每次改完配置,先跑一遍 make olddefconfig 检查依赖。
4.2.2 Makefile:编译规则的编织者
内核的 Makefile 分三层:顶层 Makefile、子目录 Makefile、以及 .config 生成的编译标志。你写驱动时,最常打交道的是子目录 Makefile。
它的写法非常简洁:
# 编译进内核
obj-y += mydriver.o
# 编译为模块
obj-m += mydriver.o
# 条件编译
obj-$(CONFIG_I2C_CHARDEV) += i2c-dev.o
看到 obj-$(CONFIG_XXX) 了吗?这就是 Kconfig 和 Makefile 的桥梁。你在 menuconfig 里选了 Y,CONFIG_XXX 就被赋值为 y,对应的 .o 文件就进内核。选了 M,就编译成 .ko。
4.3 内核配置方法:从 menuconfig 到脚本化
配置内核的方法有好几种,我按使用频率排个序:
- make menuconfig:基于 ncurses 的文本界面,最常用
- make xconfig:基于 Qt 的图形界面,依赖多
- make defconfig:使用架构默认配置(arch/arm/configs/ 下)
- make olddefconfig:基于现有 .config,自动处理新选项
- make savedefconfig:把当前配置精简为最小配置
我个人 90% 的情况都用 menuconfig。为什么?因为它支持搜索。按 / 键输入关键字,能直接定位到选项位置,还能看到依赖链。这个功能在调试时太救命了。
? 键,会显示当前选项的完整 Kconfig 定义,包括依赖、反向选择和帮助信息。我经常用它来排查「为什么这个选项是灰的」。
4.4 内核配置实战:一个嵌入式场景
假设你拿到一块 i.MX6ULL 的板子,需要配置一个支持 I2C、SPI、USB 和 NAND Flash 的内核。我会这么做:
- 先加载默认配置:
make imx_v6_v7_defconfig - 再微调:
make menuconfig,按需打开 I2C、SPI、USB 驱动 - 保存精简配置:
make savedefconfig,生成最小可复现配置 - 备份:把
defconfig文件放到板级目录下,方便后续复用
嗯,这里要注意:不要直接改 .config 文件。我见过有人手动编辑 .config 导致格式错误,编译时出现各种诡异问题。老老实实用 menuconfig 或 scripts/config 脚本才是正道。
4.5 知识体系总览:一张图看懂内核源码结构
下面这张 SVG 图,我把内核源码的目录、配置、编译三者的关系画了出来。你仔细看,就能理解它们是怎么协同工作的。
这张图把整个流程串起来了:源码目录提供素材,Kconfig 定义菜单,Makefile 实现编译规则,最终产出内核镜像和模块。你每次 make menuconfig 的时候,其实就是在走这个流程。