拿出一台全新服务器,跑着某个你不熟悉的发行版,内核版本跟手里文档对不上;或者接手一个驱动移植的活儿,遇到一个诡异的行为,网上搜了一圈,所有回答都指向同一句话:“去看源码”。这时候你深吸一口气,打开 /usr/src/linux,看着里面上万个C文件、几千万行代码,忽然不知道该从哪下手。
这不是我编的场景,是我自己踩过的坑,也是很多刚接触 Linux kernel 的人共同的困境。内核源码不是一本小说,没法从头到尾按顺序读,它更像一座城市——有清晰的功能分区,有主干道,也有大量需要门牌号才能找到的小巷子。这篇博文我想从一个“常年翻代码的人”的角度,聊聊怎么高效地查看和分析内核代码:源码目录怎么布局、本地和在线工具有哪些、一条实际读码路径长什么样,以及那些文档里不会写的坑。目标读者是刚入门内核开发的人、做驱动和系统集成的工程师,以及那些需要在生产环境排查内核相关问题的运维朋友。
1. 先看懂内核目录布局,才不会在源码里迷路
下载下来的内核源码解压后,通常有1GB以上,约7万个文件。如果你直接一头扎进去看,大概率半小时后就退出来了。我的习惯是:动代码之前,先花半小时把目录结构过一遍。内核工程的目录划分有很强的逻辑性,它是按“功能域”而不是“调用顺序”组织的,理解了这层逻辑,后面每一次跳转都有方向感。
1.1 核心目录各自的职责,一张表说清
| 目录 | 职责 | 典型内容 |
|---|---|---|
| arch | 体系结构相关代码 | x86、arm64、riscv、loongarch等,每个架构下还有子目录 |
| kernel | 核心内核机制 | 调度器(sched)、进程管理(fork)、时间(time)、panic等 |
| mm | 内存管理 | 页分配、slab、vmalloc、内存回收等 |
| fs | 文件系统框架 | VFS层和各种具体文件系统,如ext4、btrfs、proc、sysfs |
| drivers | 设备驱动 | 整个内核中体量最大的目录,按子系统分:net、gpio、i2c、drm等 |
| include | 公共头文件 | 内核数据结构的“字典”,task_struct、sk_buff等大结构体都在这里 |
| net | 网络协议栈 | 从socket层到TCP/IP具体实现 |
| init | 内核启动与初始化 | main.c是入口,start_kernel函数就在这调用 |
| ipc | 进程间通信 | 信号量、消息队列、共享内存等 |
| lib | 内核工具库 | 字符串处理、crc校验、rbtree等通用实现 |
| scripts | 构建与辅助脚本 | Kconfig解析、checkpatch、tag生成等 |
| Documentation | 内核文档 | 子系统设计说明、admin-guide、driver-api等 |
这个表你不需要背,但要建立起“找什么东西往哪个目录去”的习惯。比如你怀疑某个驱动程序有问题,先去 drivers 对应子系统;你要看进程是怎么创建的,就去 kernel/fork.c;你想知道某个数据结构长什么样,大概率在 include/linux 下的同名头文件里。
1.2 拿到源码后,我建议按什么顺序读
如果你是第一次接触内核,我不建议从 drivers 开始,那里代码量太大,而且多数驱动是“注册回调 + 等待事件”的套路,读多了容易条件反射。比较适合入门的顺序是:
- 先读
init/main.c里的start_kernel,它会让你对内核启动早期做什么有个整体印象。不需要每个函数都追进去,看到名字知道是干嘛的就行。 - 再挑一个你熟悉的功能域进去。比如你做网络,就顺着
net/ipv4/tcp.c读;你做存储,就从fs/namei.c看路径查找。基于自己熟悉的场景切入,比漫无目的扫目录高效得多。 - 之后才是按需查
drivers下的具体硬件代码,这时候你已经有内核的基本语言体系了。
有一点容易被忽略:include/linux 里面那些头文件,才是很多“全局观”的来源。举个例子,task_struct 的定义在 `include/linux
