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) |
与当前系统内核版本对齐 |
| 镜像站加速 | 清华、中科大、阿里云镜像 | 国内网络环境,速度更快 |
下载完成后,解压或克隆到工作目录:
# 下载 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
--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 知识体系总览
这一章的内容比较多,我画了一张图帮你理清关系:
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) 打时间戳。裁剪过程中来回试错,有个备份能省很多时间。