4. Linux 内核裁剪基础:源码结构、Kconfig 与 Makefile 机制、内核配置方法
说到内核裁剪,很多人一上来就开干——直接 make menuconfig,然后一顿乱选。结果呢?要么编译不过,要么跑起来各种崩溃。我早年也干过这种蠢事,后来才明白:不懂内核的骨架,裁剪就是瞎蒙。
这一章,咱们把内核的底裤扒开看看。你搞清楚了三件事——源码怎么组织的、Kconfig 怎么玩的、Makefile 怎么编译的——后面裁剪才能有的放矢。
4.1 内核源码结构:先认路,再动手
Linux 内核源码虽然大(几万个文件),但目录结构其实很清晰。我习惯把它分成三大块:架构相关、驱动、核心子系统。
下面这张图,是我每次带新人必画的。你把它记住,找文件至少快一倍。
几个关键目录,我单独拎出来说说:
- arch/:你用的 ARM、RISC-V、x86,都在这里。裁剪时先确定架构,然后进去把不用的 SoC 平台、不用的板级文件删掉。我做过一个项目,光
arch/arm/mach-*就砍掉了 80% 的板级支持。 - drivers/:这是裁剪的主战场。内核 70% 的代码量都在这里。你想想看,你的板子上只有 I2C、SPI、GPIO、MMC,那什么 FireWire、ISDN、红外遥控,统统可以关掉。
- fs/:文件系统。嵌入式常用 ext4、squashfs、ubifs、fat。NFS 调试时可以开,产品阶段关掉。至于什么 XFS、Btrfs、NTFS,基本用不上。
- net/:网络协议栈。如果你不需要 IPv6、不需要蓝牙、不需要 802.11,果断关掉。我见过有人把整个
net/关掉,内核小了 30%。
ls arch/ drivers/ fs/ net/ 扫一遍目录。心里有个数,哪些目录是“肯定要的”,哪些是“肯定不要的”。这比直接进 menuconfig 瞎翻快得多。
4.2 Kconfig 机制:内核的“菜单系统”
Kconfig 是什么?说白了,它就是内核的菜单配置文件。每个功能模块都有一个 Kconfig 文件,里面定义了:这个功能叫什么、依赖什么、有没有子选项。
你执行 make menuconfig 时看到的那些树形菜单,就是内核把成千上万个 Kconfig 文件解析后拼出来的。
4.2.1 Kconfig 语法速览
我不打算把 Kconfig 语法全讲一遍(那够写一本书了),但几个核心关键字你必须认识:
| 关键字 | 作用 | 示例 |
|---|---|---|
config |
定义一个配置选项 | config CONFIG_FOO |
tristate |
三态:不编译/编译进内核/编译为模块 | tristate "Foo support" |
bool |
二态:编译或不编译 | bool "Enable Foo feature" |
depends on |
依赖另一个选项 | depends on CONFIG_I2C |
select |
反向选择(自动开启依赖项) | select CONFIG_CRC32 |
help |
帮助信息 | help\n Say Y here if you need Foo. |
if / endif |
条件块 | if CONFIG_FOO\n ... \nendif |
举个例子,你打开 drivers/i2c/Kconfig,会看到类似这样的内容:
config I2C
tristate "I2C support"
select CRC32
help
I2C is a low-speed bus used by many sensors and EEPROMs.
if I2C
config I2C_BOARDINFO
bool "Provide device info from board files"
default y
config I2C_CHARDEV
tristate "I2C device interface"
help
Say Y here to use /dev/i2c-N interface.
endif
看到没?I2C 是 tristate,可以编成模块。I2C_BOARDINFO 是 bool,要么编要么不编。而且 I2C_CHARDEV 只有在 I2C 开启后才可见——这就是依赖链。
select 了这个被关掉的驱动。记住:select 是强制依赖,你关不掉。遇到这种情况,要么把依赖它的驱动也关掉,要么保留。
4.2.2 配置选项的三种状态
在 menuconfig 里,你看到的选项有三种状态:
- Y:编译进内核(built-in)。内核启动时直接加载,速度快,但体积大。
- M:编译为模块(module)。按需加载,体积小,但需要文件系统和
modprobe支持。 - N:不编译。彻底砍掉。
裁剪时我的原则是:能 M 不 Y,能 N 不 M。但要注意,有些功能(比如根文件系统驱动、内存管理)必须 Y,不能 M。你想想看,内核都还没挂载文件系统,怎么加载模块?
4.3 Makefile 机制:内核怎么编译的
Kconfig 决定了“编译什么”,Makefile 决定了“怎么编译”。每个目录下都有一个 Makefile,里面用 obj-y、obj-m、obj-$(CONFIG_XXX) 来控制哪些文件参与编译。
举个例子,drivers/i2c/Makefile:
obj-y += i2c-core.o i2c-dev.o
obj-$(CONFIG_I2C_CHARDEV) += i2c-dev.o
obj-$(CONFIG_I2C_GPIO) += i2c-gpio.o
obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o
这里的关键是:
obj-y:无条件编译。比如i2c-core.o,只要 I2C 子系统开启,就必须编译。obj-$(CONFIG_XXX):条件编译。如果CONFIG_I2C_GPIO=y,就编译i2c-gpio.o并链接进内核;如果是=m,就编译成独立的.ko模块。
裁剪时,你不需要手动改 Makefile——那是 Kconfig 的事。但理解这个机制,能帮你快速定位:某个功能对应的源文件在哪里,依赖链是怎样的。
grep -r "CONFIG_I2C_GPIO" drivers/结果会告诉你,哪些 Makefile 引用了这个配置,哪些源文件有条件编译。我在调试裁剪问题时,这招屡试不爽。
4.4 内核配置方法:menuconfig 实战
好了,理论讲完了,咱们上手。配置内核的方法有好几种,但嵌入式开发最常用的就是 menuconfig。
4.4.1 启动 menuconfig
在源码根目录下执行:
make ARCH=arm menuconfig
如果你是交叉编译,记得指定 CROSS_COMPILE:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
界面出来后,你会看到一个蓝底灰字的菜单。上下键移动,回车进入子菜单,空格切换选项状态(Y/M/N),? 查看帮助,/ 搜索。
4.4.2 搜索功能:救命用的
内核配置项成千上万,你不可能一个个翻。我每次裁剪,用得最多的就是 / 搜索。比如我想找 GPIO 相关的配置:
- 按
/,输入GPIO,回车。 - 结果会列出所有包含 "GPIO" 的配置项,以及它们的路径、当前值、依赖关系。
- 按数字键可以直接跳转到对应位置。
举个例子,搜索 I2C_GPIO 的结果可能是:
Symbol: I2C_GPIO [=n]
Type : tristate
Prompt: GPIO-based bitbanging I2C
Location:
-> Device Drivers
-> I2C support (I2C [=y])
-> I2C Hardware Bus support
Defined at drivers/i2c/busses/Kconfig:123
Depends on: I2C [=y] && GPIOLIB [=y]
看到没?它告诉你这个选项在哪个 Kconfig 文件的哪一行,依赖什么。这对排查“为什么这个选项灰了”特别有用。
4.4.3 裁剪实战:一个最小配置的诞生
我带你走一遍典型的裁剪流程。假设我们有一个 ARM Cortex-A7 的板子,只需要跑 Linux,挂载 squashfs 根文件系统,支持 I2C、SPI、GPIO、MMC、串口。
步骤是这样的:
- 加载默认配置:
make ARCH=arm defconfig(或者用厂商提供的xxx_defconfig)。 - 进入 menuconfig:
make ARCH=arm menuconfig。 - 关闭不需要的架构:
General setup→ 关掉不用的架构支持。 - 裁剪驱动:进入
Device Drivers,把下面这些统统设为 N:Graphics support(没有显示)Sound card support(没有音频)USB support(没有 USB)MMC/SD/SDIO card support(如果有就保留)Network device support(如果不需要网卡就关掉)
- 裁剪文件系统:
File systems→ 只保留squashfs、proc、sysfs、devtmpfs,其他全关。 - 裁剪网络:
Networking support→ 如果不需要网络,直接关掉Networking support顶层选项。 - 保存退出:按
Exit直到提示保存,选Yes。
这样一轮下来,内核能从默认的十几 MB 瘦身到 2-3 MB(压缩后)。
GPIOLIB 关了,而 MMC 驱动依赖它。
4.4.4 保存与复用配置
配置完成后,内核会自动保存到 .config 文件。你可以把它备份起来:
cp .config my_board_defconfig
下次编译时,直接加载:
make ARCH=arm my_board_defconfig
或者,你也可以把它放到 arch/arm/configs/ 目录下,这样就能用 make ARCH=arm my_board_defconfig 直接加载了。我习惯每个项目都保留一份 defconfig,方便后续复用和版本管理。
.config 文件里有很多自动生成的选项(比如 CONFIG_GCC_VERSION),不同内核版本之间不要直接复制。正确的做法是用 make savedefconfig 生成最小化的 defconfig,然后再复制。
4.5 本章小结
这一章的内容,说白了就是三件事:
- 源码结构:知道东西在哪,才能知道砍什么。
- Kconfig 与 Makefile:理解配置和编译的机制,才能避免“关了这个那个报错”的尴尬。
- menuconfig 操作:工具用得溜,裁剪效率翻倍。