第2章:内核源码获取与配置系统

做嵌入式Linux开发,第一步就是拿到内核源码,然后把它配好。这事儿听起来简单,但里面坑不少。我见过不少新手,源码下载下来就急着编译,结果跑不起来,回头一看——配置压根没搞对。这一章,咱们就把源码获取和配置系统这摊事儿捋清楚。

2.1 从kernel.org获取源码

官方源码站就是 kernel.org。别去其他地方乱下,我吃过这个亏——有一次从第三方站点下了个包,编译到一半报错,查了半天发现是被篡改过的源码。

2.1.1 选择合适的内核版本

内核版本号分三部分:主版本号.次版本号.修订号。比如 5.10.120:

字段 含义 说明
5 主版本 大版本迭代,架构级变化
10 次版本 偶数=稳定版,奇数=开发版
120 修订号 bug修复、安全补丁

我个人习惯选 LTS(长期支持) 版本。比如 5.10.x、5.15.x、6.1.x。产品开发用LTS最稳,社区会持续维护好几年。

2.1.2 下载与校验

下载命令很简单:

wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.120.tar.xz

但别忘了校验。我建议你养成习惯,下载完立刻做这一步:

# 下载签名文件
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.120.tar.sign

# 导入内核维护者公钥
gpg --locate-keys torvalds@kernel.org gregkh@kernel.org

# 校验
xz -cd linux-5.10.120.tar.xz | gpg --verify linux-5.10.120.tar.sign -
注意: 如果不校验签名,你拿到的源码可能被植入了后门。我曾经在培训时遇到过学员下载了被篡改的源码,编译出来的内核在启动时偷偷开了个反向shell——这事儿真不是危言耸听。

2.2 内核配置系统初探:Kconfig与Makefile

内核的配置系统由两套机制组成:Kconfig 定义配置选项,Makefile 根据配置决定编译什么。说白了,Kconfig是菜单,Makefile是厨师。

2.2.1 Kconfig语法速览

每个驱动目录下都有 Kconfig 文件。打开看看:

# drivers/i2c/Kconfig 片段
config I2C
    tristate "I2C support"
    help
      I2C is a low-speed serial bus protocol...

config I2C_CHARDEV
    tristate "I2C device interface"
    depends on I2C
    help
      Provides /dev/i2c-N interface...

关键字段:

  • tristate:三态——不编译/编译进内核/编译为模块
  • bool:二态——不编译/编译进内核
  • depends on:依赖关系,父选项没选,子选项出不来
  • select:反向依赖,选了它会自动选上另一个
小技巧: 调试驱动时,我经常把 tristate 改成 bool 直接编进内核,省得还要处理模块加载顺序的问题。

2.2.2 Makefile如何配合Kconfig

Kconfig定义完选项,Makefile根据配置变量决定编译:

# drivers/i2c/Makefile
obj-$(CONFIG_I2C)          += i2c-core.o
obj-$(CONFIG_I2C_CHARDEV)  += i2c-dev.o
obj-$(CONFIG_I2C_GPIO)     += i2c-gpio.o

这里的 CONFIG_I2C 就是 Kconfig 里定义的配置宏。如果配置为 y,就编译进内核;配置为 m,就编译成模块;没配置就不编译。

2.3 menuconfig图形化配置

手动改 .config 文件太容易出错了。我建议你用 menuconfig,它是个基于ncurses的图形界面:

make menuconfig

界面分三栏:

  • 左侧:菜单树,按功能分类
  • 中间:选项说明
  • 下方:快捷键提示

常用操作:

按键 功能
Y 编译进内核(*)
M 编译为模块(M)
N 不编译(空)
/ 搜索选项
? 查看帮助
搜索功能特别重要。 比如你想找 "GPIO" 相关的配置,按 / 输入 GPIO,它会列出所有包含 GPIO 的选项,并告诉你路径在哪里。我在调试某款传感器驱动时,就是靠搜索找到了一个藏在 "Device Drivers → Input" 深处的配置项。

2.4 defconfig与配置文件管理

每次从头配内核太累了。实际项目中,我们都是基于 defconfig 来做的。

2.4.1 什么是defconfig

defconfig 是内核源码里预置的默认配置,放在 arch/<架构>/configs/ 目录下:

# 查看ARM64的默认配置
ls arch/arm64/configs/
defconfig  # 通用默认
# 或者针对某款开发板
ls arch/arm/configs/
imx_v7_defconfig  # i.MX7
bcm2835_defconfig # Raspberry Pi

使用方式:

make ARCH=arm64 defconfig
# 或者指定某款板子
make ARCH=arm imx_v7_defconfig

2.4.2 配置文件管理实战

我管理配置文件的流程是这样的:

  1. 基于defconfig生成初始配置
  2. 用menuconfig做定制修改
  3. 保存最终配置
# 第一步:生成基础配置
make ARCH=arm64 defconfig

# 第二步:手动调整
make ARCH=arm64 menuconfig

# 第三步:保存差异(只保存你改过的部分)
make ARCH=arm64 savedefconfig
# 生成 defconfig 文件,只包含与默认不同的选项
推荐做法: 把最终配置保存为 my_board_defconfig,放到项目目录下管理。不要直接改内核源码里的 defconfig,否则下次升级内核版本时你的配置就丢了。

2.4.3 配置文件的碎片化问题

产品开发中,经常需要管理多个配置变体。比如:

  • 开发版:开启所有调试选项
  • 量产版:关闭调试,开启优化
  • 安全版:开启加密和SELinux

我建议用 配置文件片段(fragment) 来管理:

# debug.fragment
CONFIG_DEBUG_KERNEL=y
CONFIG_DEBUG_INFO=y

# production.fragment
CONFIG_DEBUG_KERNEL=n
CONFIG_DEBUG_INFO=n

# 合并配置
scripts/kconfig/merge_config.sh -m \
    arch/arm64/configs/defconfig \
    debug.fragment

这样,基础配置和差异化配置分开管理,清晰又灵活。

2.5 本章知识体系

下面这张图把内核配置的完整流程串起来了:

内核配置系统知识体系 内核配置系统知识体系 kernel.org 源码获取 配置系统:Kconfig + Makefile Kconfig 定义配置选项 tristate / bool / depends on Makefile 条件编译 obj-$(CONFIG_XXX) += xxx.o 配置工具:menuconfig / defconfig .config 配置文件 核心流程:源码 → Kconfig/Makefile → menuconfig/defconfig → .config → 编译
一句话总结: 内核配置的本质就是通过 Kconfig 定义选项,用 menuconfig 或 defconfig 生成 .config,然后 Makefile 根据 .config 决定编译哪些代码。把这个流程刻在脑子里,后面调试配置问题就轻松多了。