1. 课程导入与学习地图
很多初学者拿到内核源码,第一反应就是懵。几万个文件,上百个目录,从哪看起?
我刚开始接触内核时也这样。那时候编译一次内核要等半小时,改个驱动还得满世界找头文件。后来慢慢摸清了门道——内核源码的目录结构,其实是有逻辑的。你只要搞懂每个顶层目录的职责,就能像查字典一样快速定位代码。
下面这张图,是我自己整理的内核源码顶层目录关系。建议你先看一遍,再往下读。
1.1 核心子系统:内核的"五脏六腑"
先看最核心的几个目录。它们构成了操作系统的骨架。
| 目录 | 职责 | 典型内容 |
|---|---|---|
| kernel/ | 进程管理、调度、信号、系统调用 | sched.c, fork.c, signal.c |
| mm/ | 内存管理(虚拟内存、页分配、SLAB) | mmap.c, page_alloc.c, slab.c |
| fs/ | 文件系统(VFS + 具体文件系统) | ext4/, btrfs/, nfs/, sysfs/ |
| ipc/ | 进程间通信(信号量、消息队列、共享内存) | sem.c, msg.c, shm.c |
| net/ | 网络协议栈(TCP/IP、套接字、路由) | ipv4/, ipv6/, bridge/, netfilter/ |
| block/ | 块设备层(I/O 调度、bio、分区) | blk-core.c, elevator.c, partitions/ |
| crypto/ | 加密 API(对称/非对称、哈希、AEAD) | aes.c, sha256.c, algapi.c |
| lib/ | 内核通用库(字符串、CRC、红黑树、KASAN) | string.c, rbtree.c, kobject.c |
我的习惯: 调试驱动时,我经常在
kernel/ 里翻 printk.c 和 panic.c。遇到内核崩溃,先看 kernel/panic.c 里的 panic() 函数——它是一切"死机"的终点。
1.2 硬件相关目录:arch/ 和 drivers/
这两个目录,是驱动开发者的主战场。
arch/ —— 体系结构相关代码
每种 CPU 架构都有自己的子目录。比如:
- arch/x86/ —— 桌面、服务器最常见的 x86/x86_64
- arch/arm/ —— 嵌入式、手机用的 ARM 架构
- arch/arm64/ —— 64 位 ARM(AArch64)
- arch/riscv/ —— 开源指令集 RISC-V
- arch/mips/ —— MIPS 架构(路由器、嵌入式)
每个 arch 目录下,通常包含:
- boot/ —— 启动代码(head.S、压缩解压)
- kernel/ —— 架构相关的核心代码(中断、时钟、信号)
- mm/ —— 架构相关的内存管理(页表、TLB)
- include/ —— 架构相关的头文件
- configs/ —— 默认内核配置文件
注意: 移植内核到新板子时,
arch/ 是改动最大的地方。我曾经在移植 RISC-V 内核时,光 arch/riscv/boot/ 里的设备树和启动汇编就调了一周。
drivers/ —— 设备驱动的大本营
这是内核源码中最大的目录,没有之一。按设备类型划分子目录:
- drivers/gpio/ —— GPIO 控制器驱动
- drivers/i2c/ —— I2C 总线及设备驱动
- drivers/spi/ —— SPI 总线驱动
- drivers/pci/ —— PCI/PCIe 子系统
- drivers/usb/ —— USB 主机/设备/控制器驱动
- drivers/net/ —— 网络设备驱动(以太网、无线)
- drivers/mmc/ —— SD/MMC/eMMC 卡驱动
- drivers/input/ —— 输入设备(键盘、触摸、鼠标)
- drivers/video/ —— 显示/帧缓冲驱动
- drivers/char/ —— 字符设备(串口、随机数、看门狗)
- drivers/mtd/ —— 存储类设备(NAND Flash、NOR Flash)
- drivers/staging/ —— 暂存区(未完全成熟的驱动)
避坑指南: 我曾经在调试一个 SPI 触摸屏驱动时,死活找不到对应的代码。后来发现它藏在
drivers/input/touchscreen/ 里,而不是 drivers/spi/。记住:驱动按"功能类型"归类,不是按"总线类型"。
1.3 构建与辅助目录
这些目录不直接参与内核运行,但开发过程中离不开它们。
| 目录 | 作用 | 我常用的场景 |
|---|---|---|
| scripts/ | 构建脚本、配置工具、分析工具 | scripts/checkpatch.pl 检查代码风格 |
| init/ | 内核初始化流程(main.c、initcall) | 调试启动卡住时,看 init/main.c |
| include/ | 内核头文件(linux/, asm-generic/, uapi/) |
写驱动时,#include <linux/module.h> 就在这 |
| Documentation/ | 内核文档(RST 格式、Kconfig 说明) | 查 API 用法、设备树绑定、驱动编写指南 |
小技巧: 写驱动时不确定某个函数怎么用?先搜
Documentation/ 里有没有相关文档。比如 Documentation/driver-api/ 里就有 GPIO、I2C、SPI 的编程指南。比看源码快多了。
1.4 特殊功能目录
这几个目录,平时用得少,但关键时刻很有用。
- security/ —— 安全模块(SELinux、AppArmor、IMA)
- virt/ —— 虚拟化(KVM 核心代码)
- sound/ —— ALSA 音频子系统(驱动 + 核心)
- samples/ —— 示例代码(写驱动时参考的好地方)
- usr/ —— initramfs 打包相关
- firmware/ —— 固件文件(部分驱动需要)
1.5 目录之间的关联:一个例子
光说理论太枯燥。我们看一个实际场景:写一个 I2C 温度传感器驱动。
这个驱动会涉及哪些目录?
- drivers/i2c/ —— I2C 核心框架(注册设备、收发数据)
- drivers/hwmon/ —— 硬件监控子系统(温度传感器的"家")
- include/linux/ —— 头文件(
i2c.h,hwmon.h) - Documentation/ —— 查 I2C 驱动编写规范
- arch/arm/boot/dts/ —— 设备树文件(描述硬件连接)
- scripts/ —— 用
checkpatch.pl检查代码格式
你看,一个简单的驱动,就串起了 6 个目录。这就是内核源码的"网状"结构——每个目录都不是孤岛。
核心观点: 学内核源码,不要试图记住每个文件。先记住目录的"职责边界"。遇到问题,先想"这个问题属于哪个子系统",再去对应目录里找。效率能翻倍。
1.6 我的学习建议
最后,分享几个我自己的经验:
- 先看
init/main.c—— 这是内核的入口,从start_kernel()开始,一路看它调用了哪些初始化函数。看完你就知道内核启动的"骨架"了。 - 别怕
include/—— 很多初学者觉得头文件太多。其实include/linux/里就是内核的"API 手册"。常用的module.h,fs.h,device.h都在那。 - 善用
Documentation/—— 内核文档质量参差不齐,但Documentation/driver-api/和Documentation/devicetree/写得不错。遇到不懂的,先搜文档,再读源码。 - 用
scripts/里的工具 ——checkpatch.pl能帮你检查代码风格,kernel-doc能生成文档。这些工具能省不少事。
嗯,这一章就到这里。目录结构是内核源码的"地图",先记住地图,后面我们才能在里面"开车"。