2. 内核源码树结构:顶层目录功能说明与关键文件
拿到一份 Linux 内核源码,第一眼看到那几十个顶层目录,很多人会有点懵。我刚开始接触内核时也是这样,对着 ls 输出发呆——这从哪看起?
其实内核的顶层目录布局很有逻辑。它把不同功能的代码分门别类放好,你只要摸清每个目录的职责,后续配置、编译、调试就会顺手很多。今天我就带你把这棵树的结构理清楚。
2.1 顶层目录功能一览
内核源码顶层目录大致可以分为三类:架构相关、核心子系统、构建与工具。下面这张图能帮你快速建立全局印象:
2.2 架构相关目录
arch/ — 体系结构核心代码
这是内核里最「挑硬件」的目录。每种 CPU 架构都有自己的子目录,比如 arch/x86/、arch/arm/、arch/arm64/、arch/riscv/。里面放着启动代码、中断处理、内存页表管理、系统调用入口等跟硬件绑死的部分。
我遇到过不少新手,想移植内核到一块新板子,上来就改 kernel/sched/ 里的调度器——其实根本不用。你真正要动的是 arch/ 下的代码,尤其是 arch/你的架构/kernel/ 和 arch/你的架构/mm/。调度器、VFS 这些上层逻辑,只要架构接口实现对了,它们自己就能跑。
include/ — 头文件大本营
这个目录有点特殊。它不全是架构相关的,但有一部分是。include/linux/ 放的是通用内核头文件,include/asm-generic/ 是架构无关的汇编接口,而 include/asm-你的架构/(新版本已合并到 arch/你的架构/include/asm/)才是真正的架构相关头文件。
配置内核时,你经常会在 include/linux/kconfig.h 里看到各种 #ifdef CONFIG_XXX——这就是 Kconfig 配置项在 C 代码里的映射。
2.3 核心子系统目录
这部分是内核的「内脏」,每个目录对应一个核心模块。
| 目录 | 职责 | 我常关注的内容 |
|---|---|---|
| kernel/ | 进程管理、调度、信号、模块加载、printk 等 | kernel/sched/ 调度器策略,kernel/printk/ 日志 |
| mm/ | 内存管理:页分配、slab、VMA、交换 | mm/page_alloc.c 伙伴系统,mm/slub.c 对象缓存 |
| fs/ | 虚拟文件系统 + 具体文件系统实现 | fs/ext4/、fs/proc/、fs/sysfs/ |
| net/ | 网络协议栈:TCP/IP、socket、netfilter | net/ipv4/、net/core/ |
| ipc/ | 进程间通信:信号量、消息队列、共享内存 | ipc/sem.c、ipc/shm.c |
| block/ | 块设备层:I/O 调度、bio、分区 | block/blk-mq.c 多队列块层 |
| drivers/ | 设备驱动——内核里最大的目录 | drivers/net/、drivers/i2c/、drivers/gpu/ |
| sound/ | 音频子系统:ALSA、OSS | sound/pci/、sound/soc/ |
| crypto/ | 加密 API:对称/非对称、哈希、压缩 | crypto/aes_generic.c |
| security/ | 安全模块:SELinux、AppArmor、IMA | security/selinux/ |
| virt/ | 虚拟化:KVM 核心代码 | virt/kvm/ |
我的经验:drivers/ 占了内核源码的一半以上。配置内核时,大部分时间你都在跟 drivers/ 下的 Kconfig 打交道。别怕,按硬件类型逐级往下找就行。
2.4 构建与工具目录
init/ — 内核的「入口」
init/main.c 里的 start_kernel() 是内核启动的 C 语言入口。所有初始化——从中断到调度器到根文件系统挂载——都在这里串联。我调试启动早期崩溃时,经常在这里加 printk 定位问题。
scripts/ — 构建脚本与工具
这里面有 scripts/kconfig/(菜单配置工具)、scripts/mod/(模块处理)、scripts/dtc/(设备树编译器)等。如果你在 make menuconfig 时遇到界面问题,或者模块签名搞不定,来这找答案。
lib/ — 内核通用库函数
像 lib/sort.c、lib/string.c、lib/crc32.c 这些。内核里很多地方都会用到。我写驱动时经常直接调 lib/ 里的函数,省得自己造轮子。
usr/ — 内核镜像打包
负责把 initramfs 打包进内核镜像。你配置了 CONFIG_INITRAMFS_SOURCE 后,就是靠这里的代码把文件系统塞进内核的。
2.5 关键文件:Kconfig、Makefile、README
每个目录下都有两个文件,是配置和编译的「命根子」:Kconfig 和 Makefile。
Kconfig — 配置选项的定义
你在 make menuconfig 里看到的每一个开关,背后都是 Kconfig 文件定义的。格式大概是这样:
config CONFIG_EXT4_FS
tristate "The Extended 4 (ext4) filesystem"
depends on BLOCK
select CRC16
help
This is the next generation of the ext3 filesystem.
这里 tristate 表示可以编入内核(Y)、编译为模块(M)、或完全不编(N)。depends on 和 select 控制依赖关系——这是配置时最容易踩坑的地方。
我曾经在配置一个嵌入式内核时,选了 ext4 但没选 BLOCK 支持,结果编译报错。折腾了半天才发现是 Kconfig 依赖没满足。所以配置时一定要留意 depends on 和 select 的提示。
Makefile — 编译规则
内核的 Makefile 体系很庞大,但顶层 Makefile 和各级子目录 Makefile 的写法有固定套路。比如:
obj-y += file.o # 总是编译进内核
obj-m += module.o # 编译为可加载模块
obj-$(CONFIG_EXT4_FS) += ext4.o # 根据配置决定
你想想看,obj-$(CONFIG_XXX) 这种写法,其实就是把 Kconfig 的配置变量直接映射到编译目标。理解了这一点,你就知道为什么配置变了之后,编译哪些文件也跟着变了。
README — 快速入门文档
内核源码根目录下的 README 文件,虽然不长,但信息量很大。它告诉你:
- 如何解压和配置内核
- 编译和安装步骤
- 报告 bug 的规范
- 内核开发的基本准则
我个人的习惯是,拿到一个新版本的内核,先花 10 分钟把 README 扫一遍。有时候版本升级会带来构建步骤的变化,README 里都会写清楚。
2.6 小结
内核源码树的顶层目录,说白了就是一套「按功能分模块」的代码组织方式。你不需要记住每个目录下的每个文件,但要知道:
- 配置内核时,主要看各目录下的 Kconfig
- 编译内核时,主要看各目录下的 Makefile
- 移植或调试时,先定位到对应的 子系统目录
- 架构相关的问题,直奔 arch/