3. 内核源码获取与配置:从 kernel.org 下载源码、使用 git 管理内核树、内核配置系统(Kconfig/Makefile)

做内核开发,第一步就是拿到源码。这事听起来简单,但里面坑不少。我见过不少新手直接从网盘下个压缩包就开始搞,结果编译到一半发现版本不对,或者 git 管理混乱,改了什么自己都记不清。今天咱们就把这第一步走扎实。

3.1 从 kernel.org 获取源码

官方源头只有一个:kernel.org。别去其他地方下,容易被植入后门或者拿到被修改过的代码。

我个人习惯用 curl 或者 wget 直接拉取主线内核。比如要获取 6.1 LTS 版本:

# 下载完整源码包
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.1.tar.xz

# 验证签名(强烈建议做)
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.1.tar.sign
xz -cd linux-6.1.tar.xz | gpg --verify linux-6.1.tar.sign -
注意: 下载后一定要验证 GPG 签名。我曾经在一个内部项目中用过未验证的源码,编译出来的内核在特定硬件上会随机死机。查了两天才发现是源码被篡改过。从那以后,我每次下载都先导入 Linus Torvalds 的 GPG 公钥再验证。

内核源码包主要有三种格式:

格式 压缩率 解压速度 适用场景
.tar.gz 中等 快 快速下载、临时使用
.tar.xz 高(体积小 30-40%) 较慢 长期保存、带宽有限
.tar.bz2 较高 中等 兼容旧系统

我建议用 .tar.xz,体积最小,省带宽。解压命令:

tar -xf linux-6.1.tar.xz
cd linux-6.1

3.2 使用 Git 管理内核树

下载压缩包只是临时方案。真正做开发,必须用 git。为什么?因为内核社区每天都在提交新代码,你拿到的压缩包在下载那一刻就已经过时了。

克隆主线仓库:

git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
cd linux

这一下会拉取整个历史,大概 3-5GB。如果你只想看最新代码,可以加 --depth=1:

git clone --depth=1 git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
小技巧: 我习惯先做浅克隆,等需要看历史时再用 git fetch --unshallow 补全。这样既快又灵活。

内核的 git 分支管理很有讲究。主线(master/main)是 Linus 维护的,每天都有新提交。稳定分支(如 linux-6.1.y)由 Greg Kroah-Hartman 维护,只修 bug 不加功能。我建议初学者用 稳定分支,问题少,文档全。

# 查看所有远程分支
git branch -r

# 切换到 6.1 稳定分支
git checkout --track origin/linux-6.1.y

为什么要用 git?我给你列几个实际好处:

  • 回滚方便:改坏了直接 git checkout -- . 恢复
  • 打补丁容易:git format-patch 生成标准补丁
  • 追踪修改:git blame 看每行代码是谁改的
  • 管理多个版本:一个仓库可以同时维护 5.10、6.1、6.6 三个分支

我在做嵌入式项目时,经常需要同时维护多个内核版本。用 git 分支管理,每个客户一个分支,改了什么一目了然。要是用压缩包,早就乱成一锅粥了。

3.3 内核配置系统:Kconfig 与 Makefile

拿到源码后,下一步就是配置。内核的配置系统非常庞大,但核心只有两个东西:Kconfig 和 Makefile。

先看一张整体流程图:

内核配置系统工作流程 内核配置系统工作流程 Kconfig 文件 定义配置选项与依赖 配置工具 make menuconfig / defconfig .config 文件 保存最终配置结果 Kbuild Makefile 根据 .config 条件编译 编译过程 obj-y / obj-m 控制 输出文件 vmlinux / .ko 模块 核心逻辑: Kconfig 定义「有什么选项」→ 配置工具生成「选了什么」→ .config 保存结果 Makefile 读取 .config → 根据 CONFIG_XXX 决定编译哪些文件 → 输出内核或模块

3.3.1 Kconfig:配置选项的定义

Kconfig 文件分散在内核各个子目录中。每个驱动、每个功能模块都有自己的 Kconfig。它的语法其实不复杂,核心就是 config、menuconfig、choice 这几个关键字。

举个例子,看一个简单的 Kconfig 片段:

config SAMPLE_DRIVER
    tristate "Sample driver support"
    depends on PCI
    select MISC_DEVICES
    help
      This is a sample driver for demonstration.
      Say Y here to compile it into the kernel.
      Say M to build it as a module.

这里的关键点:

  • tristate:三态选项(Y=编译进内核,M=编译为模块,N=不编译)
  • depends on:依赖关系,比如这个驱动依赖 PCI 子系统
  • select:反向依赖,选中它会自动选上 MISC_DEVICES
经验之谈: 写 Kconfig 时最容易犯的错就是依赖关系搞错。我曾经写过一个网卡驱动,忘了加 depends on NETDEVICES,结果用户在配置时能看到选项,但编译永远报错。排查了半天才发现是 Kconfig 没写对。

3.3.2 Makefile:条件编译的执行

Kconfig 定义完选项,Makefile 负责执行。内核的 Makefile 体系叫 Kbuild,和普通的 Makefile 不太一样。

看一个典型的驱动 Makefile:

obj-$(CONFIG_SAMPLE_DRIVER) += sample_driver.o

sample_driver-objs := main.o ioctl.o probe.o

这里 obj-$(CONFIG_SAMPLE_DRIVER) 是关键。如果 CONFIG_SAMPLE_DRIVER=y,就变成 obj-y,编译进内核。如果是 m,就变成 obj-m,编译成模块。如果是空,就不编译。

常用的 Makefile 变量:

变量 含义 示例
obj-y 编译进内核 obj-y += usb/
obj-m 编译为模块 obj-m += my_driver.o
obj-n 不编译 默认值
ccflags-y 附加编译选项 ccflags-y += -DDEBUG
subdir-ccflags-y 子目录编译选项 subdir-ccflags-y += -Wall

3.3.3 配置方法实战

实际配置内核,我推荐用 make menuconfig。它基于 ncurses,在终端里就能操作,比图形界面快得多。

# 先加载默认配置(以 x86 为例)
make defconfig

# 然后手动调整
make menuconfig

进入 menuconfig 后,你会看到树形结构。按 / 可以搜索选项,比如搜 SAMPLE_DRIVER。按 Y 编译进内核,按 M 编译为模块,按 N 排除。

我的习惯: 先用 make defconfig 生成一个基础配置,再用 make localmodconfig 精简。这个命令会分析当前系统加载的模块,只保留需要的驱动,能大幅减少编译时间。我在笔记本上编译内核,从 40 分钟降到了 8 分钟。

配置完成后,生成 .config 文件。你可以直接编辑它,但不建议手动改,容易搞乱依赖关系。正确的做法是:

# 保存当前配置
cp .config my_custom_config

# 下次用这个配置
cp my_custom_config .config
make olddefconfig   # 自动处理新选项的默认值

make olddefconfig 这个命令很实用。当你升级内核版本后,新内核可能新增了一些配置选项。这个命令会用默认值填充新选项,保证你的配置能继续用。

3.4 避坑指南

最后,分享几个我踩过的坑:

  • 不要用 root 编译:用普通用户编译,出问题不会搞坏系统。我见过有人用 root 编译,一个 Makefile 错误把 /lib/modules 清空了。
  • 保存 .config 到版本控制:把 .config 提交到 git,方便回溯。我每个项目都会在仓库里保留一份 config_xxx 文件。
  • 交叉编译时注意 ARCH:给 ARM 板子编译,要设置 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-。忘了设 ARCH 的话,编译出来的还是 x86 的内核。
  • 模块签名:如果开启了 CONFIG_MODULE_SIG,编译出来的模块只能加载到同一台机器上。换台机器就报 Required key not available。调试阶段建议关掉这个选项。

嗯,源码获取和配置这部分就讲到这里。这些基础打牢了,后面编译和调试才能顺风顺水。