第二章 内核配置基础:源码获取与裁剪实战

说实话,很多做嵌入式Linux的朋友,一上来就make menuconfig,然后对着几千个选项发懵。我见过太多人直接把默认配置编译了,烧进去才发现——嗯,一个简单的LED驱动,内核体积却大了好几兆。

这一章,我们就来聊聊内核配置的那些基本功。从源码怎么拿,到Kconfig语法怎么看,再到怎么用menuconfig把不需要的东西统统砍掉。

2.1 内核源码获取与版本管理

先说说源码怎么搞。我个人习惯直接从kernel.org拉LTS版本。为什么?因为稳定,社区支持周期长,适合产品落地。

# 获取主线LTS版本(以6.1.y为例)
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.1.76.tar.xz
tar -xf linux-6.1.76.tar.xz
cd linux-6.1.76

如果你用的是芯片厂商的BSP,比如NXP、TI、Rockchip的SDK,它们通常会基于某个内核版本打上自己的补丁。这时候我建议你保留厂商的.patch文件,不要直接改源码。

我的习惯:在本地用git管理内核源码。即使厂商没给git仓库,我也会自己git init,每打一个补丁就commit一次。这样出了问题,git bisect能帮你快速定位是哪次改动引入的。

版本管理上,记住一个原则:产品开发用LTS,功能验证用主线。LTS版本每2-3年发布一次,维护周期长达6年,适合做产品。主线版本更新快,但API不稳定,我一般只在评估新特性时用。

版本类型 适用场景 维护周期 稳定性
LTS(长期支持) 产品量产、商业项目 6年 高
主线(Mainline) 新硬件支持、功能验证 约3个月 中
稳定(Stable) Bug修复、安全更新 随LTS或主线 较高

2.2 Kconfig语法详解

Kconfig是内核配置系统的"语法"。你看到的menuconfig界面,背后就是Kconfig文件在驱动。说白了,每个驱动目录下都有一个Kconfig文件,定义了该驱动的配置选项。

来看一个典型的Kconfig片段:

config SENSORS_LM75
    tristate "LM75 temperature sensor"
    depends on I2C
    help
      If you say yes here, support for LM75 temperature sensor
      will be included in the kernel.

      This driver can also be built as a module.  If so, the module
      will be called lm75.

这里有几个关键点:

  • tristate:三态选项——y(编译进内核)、m(编译为模块)、n(不编译)。驱动类选项几乎都用tristate。
  • bool:布尔选项,只有y和n。通常用于开关某个子系统功能。
  • depends on:依赖关系。比如这个LM75驱动依赖I2C子系统,如果I2C没开,这个选项就不会显示。
  • select:反向依赖。当某个选项被选中时,自动选中另一个选项。比如select REGMAP_I2C。
我曾经踩过的坑:有一次我裁剪内核,把I2C子系统关了,结果发现触摸屏、温度传感器、RTC全都不工作了。后来才意识到,这些设备都依赖I2C总线。所以裁剪前,一定要先梳理清楚硬件拓扑和依赖链。

Kconfig还支持if、choice、menu等结构。比如:

choice
    prompt "CPU frequency scaling governor"
    default CPU_FREQ_GOV_ONDEMAND

config CPU_FREQ_GOV_PERFORMANCE
    bool "performance"

config CPU_FREQ_GOV_ONDEMAND
    bool "ondemand"

config CPU_FREQ_GOV_CONSERVATIVE
    bool "conservative"
endchoice

这种choice结构,你只能选一个。menuconfig里会显示为单选按钮组。

2.3 基于menuconfig的裁剪策略

好了,理论讲完,来点实战。怎么用menuconfig把内核裁到最小?我总结了一套"三步走"策略。

第一步:加载默认配置

先别急着改。加载芯片厂商或架构的默认配置:

make ARCH=arm defconfig        # ARM通用默认配置
make ARCH=arm imx_v7_defconfig  # i.MX7平台默认配置
make ARCH=arm64 defconfig       # ARM64通用默认配置

然后进入menuconfig:

make menuconfig

第二步:关闭无用驱动

这是裁剪的大头。我一般按这个顺序来:

  1. 关闭不用的架构和平台:比如你用的是ARM Cortex-A7,就把ARM64、x86、MIPS等全关了。
  2. 关闭不用的总线驱动:SPI、I2C、USB、PCIe、MMC——你的板子上用不到的就关掉。
  3. 关闭不用的外设驱动:以太网PHY、音频Codec、显示控制器、摄像头接口——只留你硬件上有的。
  4. 关闭不用的文件系统:只保留你用的,比如ext4、fat、squashfs。其他如btrfs、xfs、ntfs统统关掉。
  5. 关闭网络协议栈中不需要的部分:比如IPv6、IPsec、Bridge、VLAN——如果你的产品不需要这些功能。
一个实用的技巧:在menuconfig里按/键可以搜索。比如你看到某个驱动报错说"unknown symbol",按/输入符号名,就能找到它依赖哪个配置项。这比翻代码快多了。

第三步:关闭调试选项

调试选项是内核体积的"隐形杀手"。很多开发者开发阶段开了各种debug,发布时忘了关。结果内核体积大了30%以上。

重点检查这几个地方:

  • Kernel hacking:这是重灾区。DEBUG_KERNEL、DEBUG_INFO、DEBUG_FS、DYNAMIC_DEBUG——产品发布前全部关掉。
  • Lock debugging:LOCKDEP、DEBUG_ATOMIC_SLEEP——这些只在开发阶段有用。
  • Memory debugging:SLUB_DEBUG、KMEMCHECK——关掉能省不少内存。
  • Printk:把CONSOLE_LOGLEVEL_DEFAULT调高,减少不必要的日志输出。
我的经验:一个典型的嵌入式ARM Linux内核,裁剪前大约8-10MB(压缩后),裁剪后可以做到2-3MB。如果你用XIP(片上执行),还能更小。但要注意,裁剪不是越狠越好——功能完整性是第一位的。

2.4 知识体系总览

下面这张图,是我对内核配置与裁剪整个知识体系的梳理。你可以把它当作一个"导航图":

内核配置与裁剪知识体系 内核配置与裁剪知识体系 源码获取与版本管理 Kconfig语法 menuconfig裁剪策略 LTS vs 主线 补丁管理 tristate/bool depends on/select 关闭无用驱动 关调试 wget / git clone git am / quilt choice / menu / if 依赖链分析 按硬件拓扑裁剪 关DEBUG 裁剪原则:先关架构,再关驱动,最后关调试 每关一项,都要确认硬件功能不受影响

2.5 实战:一个裁剪案例

最后,我分享一个真实的裁剪案例。之前做一个IoT网关,用的是i.MX6ULL平台,目标是把内核压缩后控制在3MB以内。

初始配置:imx_v7_defconfig,编译后内核镜像约6.8MB。

裁剪步骤:

  1. 关闭了所有不用的架构支持(ARM64、x86、MIPS等)——节省约1.2MB
  2. 关闭了不用的文件系统(btrfs、xfs、ntfs、f2fs等)——节省约0.8MB
  3. 关闭了网络子系统中不需要的部分(IPv6、IPsec、Bridge、VLAN、802.11等)——节省约0.6MB
  4. 关闭了Kernel hacking中的所有调试选项——节省约0.9MB
  5. 关闭了不用的驱动(GPU、VPU、Camera、Audio Codec等)——节省约0.5MB

最终结果:内核镜像2.6MB,功能完全正常。嗯,这个项目后来顺利量产了。

注意:裁剪后一定要做回归测试。我曾经有一次关掉了CONFIG_DEVTMPFS,结果系统启动后/dev目录是空的,所有设备节点都没了。排查了半天才发现是这个问题。所以,每关一个选项,最好编译一次、启动一次、验证一次。

好了,这一章的内容就到这里。内核配置是个细活,急不来。你只要掌握了源码获取、Kconfig语法和裁剪策略这三板斧,后面遇到再复杂的配置需求,也能从容应对。