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 的源码
我的习惯:拿到一个新板子的 BSP,我第一件事就是看 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 驱动,选了 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。

核心逻辑:Kconfig 定义「能不能选」,Makefile 定义「选了怎么编」。

4.3 内核配置方法:从 menuconfig 到脚本化

配置内核的方法有好几种,我按使用频率排个序:

  1. make menuconfig:基于 ncurses 的文本界面,最常用
  2. make xconfig:基于 Qt 的图形界面,依赖多
  3. make defconfig:使用架构默认配置(arch/arm/configs/ 下)
  4. make olddefconfig:基于现有 .config,自动处理新选项
  5. make savedefconfig:把当前配置精简为最小配置

我个人 90% 的情况都用 menuconfig。为什么?因为它支持搜索。按 / 键输入关键字,能直接定位到选项位置,还能看到依赖链。这个功能在调试时太救命了。

一个小技巧:在 menuconfig 里按 ? 键,会显示当前选项的完整 Kconfig 定义,包括依赖、反向选择和帮助信息。我经常用它来排查「为什么这个选项是灰的」。

4.4 内核配置实战:一个嵌入式场景

假设你拿到一块 i.MX6ULL 的板子,需要配置一个支持 I2C、SPI、USB 和 NAND Flash 的内核。我会这么做:

  1. 先加载默认配置:make imx_v6_v7_defconfig
  2. 再微调:make menuconfig,按需打开 I2C、SPI、USB 驱动
  3. 保存精简配置:make savedefconfig,生成最小可复现配置
  4. 备份:把 defconfig 文件放到板级目录下,方便后续复用

嗯,这里要注意:不要直接改 .config 文件。我见过有人手动编辑 .config 导致格式错误,编译时出现各种诡异问题。老老实实用 menuconfig 或 scripts/config 脚本才是正道。

4.5 知识体系总览:一张图看懂内核源码结构

下面这张 SVG 图,我把内核源码的目录、配置、编译三者的关系画了出来。你仔细看,就能理解它们是怎么协同工作的。

Linux内核源码结构与配置编译流程 Linux 内核源码结构与配置编译流程 内核源码目录 arch/ drivers/ fs/ net/ include/ kernel/ mm/ ... 配置与编译系统 Kconfig 定义配置选项 依赖关系 帮助信息 Makefile obj-y / obj-m 条件编译规则 配置产物 .config CONFIG_XXX=y/m/n 自动生成头文件 编译标志 编译输出 vmlinux *.ko 模块 读取 Kconfig 生成 .config 配置方法 menuconfig | xconfig | defconfig | olddefconfig | savedefconfig 编译输出 zImage / uImage / dtb / *.ko Kconfig 定义菜单 Makefile 实现编译 .config 是桥梁 连接配置与编译

这张图把整个流程串起来了:源码目录提供素材,Kconfig 定义菜单,Makefile 实现编译规则,最终产出内核镜像和模块。你每次 make menuconfig 的时候,其实就是在走这个流程。

一句话总结:内核源码结构不是死记硬背的,它是你配置和编译时的导航地图。知道每个目录放什么,理解 Kconfig 和 Makefile 怎么配合,你就能在几万文件里快速找到你要改的地方。