3. 内核源码获取与结构分析:Linux内核源码下载、顶层目录结构解析、Kconfig与Makefile系统
做嵌入式Linux内核裁剪,第一步不是急着改代码,而是先把源码搞到手,搞清楚它长什么样。我见过不少新手,上来就对着某个驱动文件猛改,结果编译都过不了——因为连Kconfig和Makefile的关系都没弄明白。这一章,我们就从源码获取开始,把顶层目录、配置系统和构建系统彻底捋一遍。
3.1 内核源码的获取方式
获取Linux内核源码,主流就三种方式。我个人最推荐第一种,稳定、可控。
| 方式 | 命令/地址 | 适用场景 |
|---|---|---|
| 官方主线 | git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git |
开发、学习最新特性 |
| 稳定版 | git clone git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git |
产品开发、长期维护 |
| 发行版源码包 | apt-get source linux-image-$(uname -r) |
快速适配当前系统 |
下载完成后,记得切到目标分支:
git checkout v5.10.200
为什么要指定版本?因为不同版本的内核,驱动接口、设备树绑定、调度器行为都可能不一样。你想想看,如果团队里有人用5.4,有人用6.6,合代码的时候会疯掉的。
3.2 顶层目录结构:一张地图走遍内核
源码下载完,先别急着编译。花10分钟看看顶层目录,这比读任何文档都管用。我每次接手一个新项目,第一件事就是ls -l看一眼根目录,心里就有数了。
arch/ block/ crypto/ Documentation/
drivers/ fs/ include/ init/
ipc/ kernel/ lib/ mm/
net/ samples/ scripts/ security/
sound/ tools/ usr/ virt/
这些目录不是随便放的。它们遵循一个清晰的逻辑:架构相关 vs 架构无关、核心子系统 vs 外围驱动。
| 目录 | 核心作用 | 裁剪关注点 |
|---|---|---|
arch/ |
CPU架构相关代码(ARM、x86、RISC-V等) | 只保留目标架构子目录 |
drivers/ |
所有设备驱动 | 裁剪大头,按需保留 |
fs/ |
文件系统实现 | 只留需要的FS类型 |
net/ |
网络协议栈 | 按网络功能裁剪 |
kernel/ |
核心调度、IPC、时间管理 | 一般不动,但可调参数 |
mm/ |
内存管理 | 根据内存大小调优 |
scripts/ |
编译、配置、分析脚本 | 理解构建流程的关键 |
drivers/目录删了,结果编译报错说找不到virtio的依赖头文件。后来才明白,drivers/里有些头文件是被核心子系统引用的。裁剪驱动,要用Kconfig,不要直接删目录。
3.3 Kconfig:内核的配置菜单系统
Kconfig是什么?说白了,它就是内核的“点菜系统”。每个功能模块、每个驱动,能不能编译进内核、要不要编译成模块,都由Kconfig说了算。
Kconfig文件散布在各个目录下。顶层有一个Kconfig,然后每个子目录都有自己的Kconfig,通过source语句层层包含。
看一个典型的Kconfig条目:
config CONFIG_CMDLINE
string "Default kernel command string"
default ""
help
This option allows you to set a default command line for the kernel.
这里有几个关键字段:
- config:定义配置项名称,最终会变成
CONFIG_XXX宏 - 类型:
bool(二选一)、tristate(三态:Y/M/N)、string、int、hex - depends on:依赖关系,A依赖B,B没选A就不可见
- select:反向依赖,选A会自动选B
- help:帮助信息,告诉你这个选项是干嘛的
depends on和select是两把双刃剑。用select要小心,它可能把一堆你不需要的功能拉进来。我习惯先用make menuconfig图形界面看依赖树,再动手改。
3.4 Makefile:内核的构建引擎
Kconfig决定了“编译什么”,Makefile决定了“怎么编译”。内核的Makefile系统分为三个层级:
- 顶层Makefile:位于源码根目录,是整个构建的入口
- 子目录Makefile:每个子目录一个,定义本目录的编译目标
- Kbuild Makefile:部分目录用
Kbuild命名,作用和Makefile一样
看一个驱动目录的Makefile示例:
# drivers/i2c/Makefile
obj-$(CONFIG_I2C) += i2c-core.o
obj-$(CONFIG_I2C_CHARDEV) += i2c-dev.o
obj-$(CONFIG_I2C_GPIO) += i2c-gpio.o
这里的obj-y表示编译进内核,obj-m表示编译成模块,obj-n表示不编译。变量名CONFIG_I2C和Kconfig里定义的CONFIG_I2C是一一对应的。
编译流程大致是这样:
- 顶层Makefile读取
.config(由Kconfig生成) - 递归进入各个子目录
- 根据
obj-y和obj-m决定编译哪些文件 - 链接生成
vmlinux和模块文件(.ko)
.config,一定要执行make olddefconfig来同步依赖关系。否则编译时可能出现“undefined reference”错误。我踩过这个坑,排查了整整半天。
3.5 知识结构总览
下面这张图,把源码获取、目录结构、Kconfig和Makefile的关系串起来了。建议你保存下来,以后做裁剪时对照着看。
3.6 实战:快速定位一个驱动
光说不练假把式。假设我们要找I2C控制器的驱动,怎么快速定位?
- 看目录名:
drivers/i2c/,一目了然 - 看Kconfig:
drivers/i2c/Kconfig,找到CONFIG_I2C和CONFIG_I2C_GPIO - 看Makefile:
drivers/i2c/Makefile,确认编译规则 - 看源码:
drivers/i2c/i2c-core.c,核心实现
这个过程,其实就是把Kconfig、Makefile和源码目录串起来。你想想看,如果不懂这三者的关系,面对几万个文件,根本无从下手。