3. 内核源码获取与配置:从源码到menuconfig

好,咱们正式开始动手了。这一章要解决一个最基础的问题——内核源码从哪来,拿到之后怎么配置它。

我记得刚入行那会儿,觉得内核配置就是跑个 make menuconfig,点几个选项就完事了。后来做第一个裁剪项目,编译出来的内核比原版还大……嗯,从那以后我才老老实实把 Kconfig 和 Makefile 的机制摸了一遍。

说白了,不懂配置系统,你连哪些功能该砍、哪些该留都判断不准。这一章咱们就把地基打牢。

3.1 获取Linux内核源码

获取源码的方式有好几种,我按推荐程度排个序:

方式 命令/地址 适用场景
kernel.org 官方 https://www.kernel.org/ 获取稳定版、长期支持版
git 克隆主线 git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git 跟踪最新开发、做定制开发
发行版源码包 apt-get source linux-image-$(uname -r) 与当前系统内核版本对齐
镜像站加速 清华、中科大、阿里云镜像 国内网络环境,速度更快
我的习惯:做裁剪项目时,我一般从 kernel.org 下载 LTS 版本。比如 6.1.y 或 5.15.y,维护周期长,社区支持好。别追最新主线,除非你想给内核社区修 bug。

下载完成后,解压或克隆到工作目录:

# 下载 tar.xz 包
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.1.38.tar.xz
tar -xf linux-6.1.38.tar.xz
cd linux-6.1.38

# 或者用 git
git clone --depth=1 -b v6.1.38 https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
cd linux
注意:git 克隆时加 --depth=1 只拉取最新提交,能省下大量时间和磁盘空间。完整历史有 100 多万次提交,你裁剪用不上那些。

3.2 内核配置系统:Kconfig 与 Makefile

内核的配置系统由两套机制组成:Kconfig 负责定义配置选项,Makefile 负责根据配置决定编译什么。两者配合,构成了整个内核的编译开关体系。

3.2.1 Kconfig 语法速览

Kconfig 文件散布在内核各个子目录中。每个目录下的 Kconfig 文件描述了该目录下有哪些配置选项。来看一个实际例子:

# drivers/net/ethernet/Kconfig 片段
config E1000
    tristate "Intel(R) PRO/1000 Gigabit Ethernet support"
    depends on PCI
    help
      This driver supports Intel PRO/1000 gigabit ethernet adapters.
      To compile this driver as a module, choose M here.

这里的关键字我解释一下:

  • config:定义一个配置选项,名字是 E1000,最终会变成 CONFIG_E1000
  • tristate:三种状态——N(不编译)、Y(编译进内核)、M(编译成模块)。这是驱动类选项最常见的类型
  • depends on:依赖关系。只有 PCI 被选中,这个选项才会出现
  • help:帮助信息,menuconfig 里按 H 键能看到

除了 tristate,还有 bool(只有 Y/N)、int(整数)、string(字符串)等类型。比如:

config LOG_BUF_SHIFT
    int "Kernel log buffer size (16 = 64KB, 17 = 128KB)"
    range 12 25
    default 17
    help
      控制 dmesg 缓冲区大小,裁剪时调小能省内存。
裁剪要点:裁剪时重点关注 tristate 和 bool 类型的选项。把不需要的驱动设为 N,需要的驱动设为 Y 或 M。我见过有人把所有驱动都编成模块,结果内核小了,但 rootfs 里塞满了 .ko 文件,总大小反而更大。

3.2.2 Makefile 如何配合 Kconfig

Kconfig 定义完选项,Makefile 负责执行。每个子目录的 Makefile 里都有类似这样的代码:

# drivers/net/ethernet/intel/e1000/Makefile
obj-$(CONFIG_E1000) += e1000.o

e1000-objs := e1000_main.o e1000_hw.o e1000_ethtool.o

这里的 obj-$(CONFIG_E1000) 是关键:

  • 如果 CONFIG_E1000=y,展开为 obj-y,文件被编译进内核
  • 如果 CONFIG_E1000=m,展开为 obj-m,文件被编译成模块
  • 如果 CONFIG_E1000 未定义或为 n,整行消失,不编译

这种设计非常优雅。你想想看,整个内核成千上万个文件,就是靠这种机制决定哪些进内核、哪些不进。

3.3 menuconfig 图形化配置入门

好了,理论说完了,咱们实际操作一下。menuconfig 是基于 ncurses 的文本界面配置工具,也是我用的最多的配置方式。

3.3.1 启动 menuconfig

在源码根目录执行:

make menuconfig

如果提示缺少 ncurses 库,安装一下:

# Ubuntu/Debian
sudo apt-get install libncurses-dev

# CentOS/RHEL
sudo yum install ncurses-devel

启动后你会看到一个分层的菜单界面。操作键很简单:

按键 功能
↑ ↓ 移动光标
Enter 进入子菜单 / 选中选项
Space 切换选项状态(N → Y → M → N)
Y / N / M 直接设置状态
? 查看帮助、依赖关系、反向依赖
/ 搜索选项
Esc Esc 返回上一级 / 退出
搜索功能一定要会用:按 / 输入关键词,比如搜 USB,会列出所有包含 USB 的选项,还能看到它们当前的状态和路径。我在裁剪时几乎全程靠搜索定位选项。

3.3.2 选项的三种状态

在 menuconfig 里,每个选项前面有个括号,显示当前状态:

  • [ ] — 未选中(N),不编译
  • [*] — 编译进内核(Y)
  • <M> — 编译成模块(M)

按 Space 键循环切换。注意,有些选项是 bool 类型,只有 [ ] 和 [*] 两种状态,没有 M。

3.3.3 依赖与反向依赖

这是新手最容易踩坑的地方。你选中一个选项,可能自动选上了其他选项。比如:

config USB_STORAGE
    tristate "USB Mass Storage support"
    depends on USB && SCSI

如果你选 USB_STORAGE,系统会自动把 USB 和 SCSI 也选上。这就是依赖传播。

反过来,你取消一个选项,也可能导致其他选项消失。我曾经在裁剪时把 SCSI 关了,结果硬盘驱动全没了,系统起不来……

避坑指南:修改选项后,按 ? 查看依赖链。特别是裁剪时,别只盯着你要砍的选项,看看它是不是被其他功能依赖着。我习惯在改完一批选项后,先 make savedefconfig 生成精简配置,再检查一遍。

3.4 知识体系总览

这一章的内容比较多,我画了一张图帮你理清关系:

内核源码获取与配置知识体系 内核源码获取与配置知识体系 ① 源码获取 kernel.org 官方包 | git 克隆主线 | 发行版源码包 | 镜像站加速 ② 内核配置系统 Kconfig 定义配置选项、依赖、帮助信息 Makefile 根据 CONFIG_xxx 决定编译对象 ③ 配置工具 menuconfig(文本界面) | xconfig(Qt界面) | gconfig(GTK界面) ④ 核心操作 选项搜索(/) | 状态切换(Space/Y/M/N) | 依赖查看(?) | 配置保存 裁剪核心:理解 Kconfig/Makefile 配合机制 → 熟练使用 menuconfig 搜索与依赖分析 → 精准控制每个选项

3.5 配置保存与后续操作

配置完成后,按 Esc Esc 退出,menuconfig 会询问是否保存。选择 Yes,配置会写入 .config 文件。

我建议你额外做一步:

# 生成精简的 defconfig
make savedefconfig

# 查看当前配置中所有被选中的选项
grep '=y' .config | wc -l
grep '=m' .config | wc -l

savedefconfig 会生成一个 defconfig 文件,只包含与默认值不同的选项。这个文件非常适合版本管理,也方便你后续对比不同裁剪方案的差异。

一个小技巧:每次修改配置前,备份 .config 文件。我习惯用 cp .config config-$(date +%Y%m%d_%H%M) 打时间戳。裁剪过程中来回试错,有个备份能省很多时间。