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) 快速适配当前系统
我的习惯:做嵌入式项目,我一般从稳定版拉一个LTS分支。比如v5.10.y或v6.1.y,社区支持周期长,遇到坑也好查资料。

下载完成后,记得切到目标分支:

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是一一对应的。

编译流程大致是这样:

  1. 顶层Makefile读取.config(由Kconfig生成)
  2. 递归进入各个子目录
  3. 根据obj-y和obj-m决定编译哪些文件
  4. 链接生成vmlinux和模块文件(.ko)
注意:如果你手动改了.config,一定要执行make olddefconfig来同步依赖关系。否则编译时可能出现“undefined reference”错误。我踩过这个坑,排查了整整半天。

3.5 知识结构总览

下面这张图,把源码获取、目录结构、Kconfig和Makefile的关系串起来了。建议你保存下来,以后做裁剪时对照着看。

嵌入式Linux内核裁剪知识体系 源码获取 顶层目录结构 Kconfig配置系统 Makefile构建系统 vmlinux / zImage 内核模块 (*.ko) 核心逻辑: 1. 从官方仓库获取源码 → 确定目标架构和版本 2. 通过Kconfig生成 .config 配置文件(make menuconfig) 3. Makefile读取 .config,按 obj-y/obj-m 规则编译链接 4. 输出内核镜像和模块,用于嵌入式系统部署

3.6 实战:快速定位一个驱动

光说不练假把式。假设我们要找I2C控制器的驱动,怎么快速定位?

  1. 看目录名:drivers/i2c/,一目了然
  2. 看Kconfig:drivers/i2c/Kconfig,找到CONFIG_I2C和CONFIG_I2C_GPIO
  3. 看Makefile:drivers/i2c/Makefile,确认编译规则
  4. 看源码:drivers/i2c/i2c-core.c,核心实现

这个过程,其实就是把Kconfig、Makefile和源码目录串起来。你想想看,如果不懂这三者的关系,面对几万个文件,根本无从下手。

核心要点:Kconfig是“配置层”,Makefile是“构建层”,目录结构是“组织层”。三层合一,才是完整的内核源码视图。裁剪时,改Kconfig,不要改Makefile;调优时,改Makefile的编译选项,不要动Kconfig的逻辑。