2. 内核源码树结构:顶层目录功能说明与关键文件

拿到一份 Linux 内核源码,第一眼看到那几十个顶层目录,很多人会有点懵。我刚开始接触内核时也是这样,对着 ls 输出发呆——这从哪看起?

其实内核的顶层目录布局很有逻辑。它把不同功能的代码分门别类放好,你只要摸清每个目录的职责,后续配置、编译、调试就会顺手很多。今天我就带你把这棵树的结构理清楚。

2.1 顶层目录功能一览

内核源码顶层目录大致可以分为三类:架构相关、核心子系统、构建与工具。下面这张图能帮你快速建立全局印象:

Linux 内核顶层目录结构总览 架构相关 arch/ include/ (部分) 核心子系统 kernel/ mm/ fs/ net/ ipc/ block/ drivers/ sound/ crypto/ security/ virt/ 构建与工具 scripts/ init/ lib/ usr/ 💡 阅读建议 • 配置内核时,重点关注 arch/、drivers/、fs/、net/ 下的 Kconfig • 调试内核问题时,常需要查看 kernel/、mm/、ipc/ 下的源码 • 构建脚本出问题时,先去 scripts/ 目录找对应工具 • 新增架构支持时,arch/ 和 include/ 是主要修改对象

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/