跳到主要内容

第 2 章 从源码构建 6.x Linux 内核(第一部分)

从源码构建 Linux 内核:一场漫长的修行

从源代码开始构建 Linux 内核,是开启内核开发之旅最硬核的方式。

别被那些花哨的图形化安装工具骗了——在那层薄薄的伪装之下,隐藏着世界上最重要的软件工程之一。这个话题大到必须分成两章来讲,这是第一章,下一章还在路上。

⚠️ 前置工作:你真的准备好了吗?

如果你还没搞定工作空间,千万别往下一步走

之前的“在线章节”(Online Chapter)我们花了大篇幅讲环境搭建——文档看哪、工具装哪、GitHub 怎么配。如果你跳过了那一步,现在去补上还来得及。这不是官僚主义,这是为了防止你在一个全是报错的终端前怀疑人生。

接下来这一章和下一章的任务非常明确:从零开始,用源码构建一个现代 Linux 内核

在这章里,我们先搞定基础:

  • 看懂内核版本号那些奇怪的时间线;
  • 理解内核是怎么一步步开发出来的(以及你如果赶不上这班车会发生什么);
  • 搞清楚“主线内核”、“LTS”、“厂商内核”这些到底都是什么鬼。

然后,我们上手干脏活:

  • 下载纯净的内核源码(Vanilla Kernel);
  • 把它解压开,看看这 3000 万行代码到底长什么样;
  • 最关键的一步:配置它。

最后,我们会花点时间黑进 Kconfig 系统——也就是那个决定哪些代码能进内核的“看门人”——甚至给它加个我们自己的菜单项。


极简系统论:Linux 是怎么跑起来的

在开始之前,我们要先达成一个共识。

任何一个能跑起来的 Linux 系统——不管是天河一号还是你手边的树莓派——都少不了三个“老三样”:

  1. Bootloader(引导程序):第一个醒来干活的家伙,负责把内核喊起来。
  2. OS Kernel(内核):就是我们要折腾的主角。
  3. Root Filesystem(根文件系统):内核启动后需要的那一大堆库、配置和工具。

还有两个“选装件”,但在 ARM 这种嵌入式平台上几乎是标配:

  • Device Tree Blob (DTB):给 ARM 准备的“硬件说明书”,告诉内核这板子上都有啥外设。
  • Initramfs:一个塞进内存里的迷你文件系统,用来挂载真正的根文件系统,或者加载那些必须得有的驱动。

在这两章里,我们只干一件事:构建那个 OS 内核。Root Filesystem 的坑留给以后,下一章我们会顺带提一下 x86 的 GRUB 引导配置,那是为了让你能真正跑起来。

对于一个完整的 x86 构建流程,大约需要 6 到 7 步。这一章我们只走前 3 步,剩下的我们放到下一章去。


Preliminaries: 别急着敲命令,先搞懂规则

在开始之前,有几件事必须得先说明白。这不只是技术细节,这是 Linux 世界的“潜规则”。

去中心化的“办公室”

Linux 内核不是某家大公司关起门来开发的,它是一个完全去中心化的、在全球协作的巨大怪兽。

如果你非要找一个“办公室”,那可能是 Linux Foundation(Linux 基金会)。它负责搞定法律和资金问题,而 Kernel Organization 负责把代码免费发到全世界(https://www.kernel.org)。

这里有一个常见的误区:GPL 协议是不允许赚钱的? 大错特错。GPL 从来不阻止开发者收费。Linus 和基金会选择免费发给别人,但这不妨碍 Red Hat、SUSE 这些公司拿着内核卖支持、卖订阅、卖 SaaS。只要源代码最终公开,怎么商业化那是你的本事。


理解 Linux 内核版本命名规范

看一眼你的终端,输入 uname -r,输出的一串字符是什么意思?

在我们的 Ubuntu 22.04 虚拟机上,它大概是这个样子的:

$ uname -r
5.19.0-40-generic

这串字符背后有一套严格的语法。现代 Linux 内核的版本号格式如下:

major#.minor#[.patchlevel][-EXTRAVERSION]

或者用更简单的代数表示:w.x[.y][-z]

方括号里的部分是可选的。让我们拆解一下这串 5.19.0-40-generic

  • Major # (w): 5 —— 主版本号,现在是 6.x 时代了。
  • Minor # (x): 19 —— 次版本号。
  • [patchlevel] (y): 0 —— 补丁级别(也叫修订版)。
  • [-EXTRAVERSION] (-z): -40-generic —— 特定版本标识。generic 表示这是 Ubuntu 通用版。

⚠️ 注意:发型版内核(比如 Ubuntu 的)可能会自己加料,不一定完全遵循这个命名法。但在 https://www.kernel.org/ 上发布的纯净内核 是严格遵守这个规矩的。

历史的回响:偶数奇数传说

在 2.6 内核之前(也就是上古时代),次版本号是有玄机的:

  • 偶数:稳定版(比如 2.4、2.6)。
  • 奇数:开发版/测试版(比如 2.5)。

但现在?这个规矩已经没了。现在的版本号只和时间有关,和功能无关。

“手脚模型” (Fingers-and-toes releases)

Linus 曾经开玩笑说,他的版本号策略是“数手指和脚趾”。

当一个次版本号(x)数到 20 左右(手指 10 个 + 脚趾 10 个),他就会把主版本号(w)加 1。

  • 3.0 到 3.19(20 个小版本)
  • 4.0 到 4.19(20 个小版本)
  • 5.0 到 5.19(20 个小版本)
  • 6.0 ……

每两个次版本之间大概间隔 6 到 10 周。这不是功能驱动的,这是有机演化


内核开发工作流:从合并窗口到发布版

很多人以为内核开发是“想到哪写到哪”,完全乱来。这是天大的误解。

内核开发有着极其严格的节奏感。为了看懂这个节奏,我们可以去看看内核的 Git 记录。

查看 Git 历史记录

假设你 Clone 下来了最新的 Linus 主线树。用下面这条命令,你可以看到一条清晰的时间线:

$ git log --date-order --tags --simplify-by-decoration \
--pretty=format:'%ai %h %d'

2023-04-23 12:02:52 -0700 457391b03803 (tag: v6.3)
2023-04-16 15:23:53 -0700 6a8f57ae2eb0 (tag: v6.3-rc7)
2023-04-09 11:15:57 -0700 09a9639e56c0 (tag: v6.3-rc6)
[...]
2022-10-16 15:36:24 -0700 9abf2313adc1 (tag: v6.1-rc1)
2022-10-02 14:09:07 -0700 4fe89d07dcc2 (tag: v6.0)

这行命令只会在 Git 树上工作。这里我们纯粹是为了演示演化过程。

在这个输出里,你能看到一个完整的生命周期:

  • v6.0 发布:也就是上一个稳定版。
  • v6.1-rc1:合并窗口关闭,开始修 Bug。
  • v6.1-rc7 / -rc8:发布候选版,不断的测试和修补。
  • v6.1:最终稳定版发布。

时间轴上的两个关键阶段

1. Merge Window (合并窗口) —— 只有 2 周

当 v6.0 发布后(2022 年 10 月 2 日),为期 两周 的合并窗口随即开启。

这是整个社区最疯狂的时刻。子系统维护者们会把他们手里攒了很久的新代码一股脑合并进主线。如果你想加新功能,这是唯一的机会。

2. Bugfix Window (修复窗口) —— 6 到 10 周

两周后(2022 年 10 月 16 日),合并窗口关闭。v6.1-rc1 诞生。

接下来的日子里,任何新功能都被拒之门外。只能修 Bug,只能回退有问题的代码,只能解决 Regressions(倒退)。

这个过程会持续 6 到 10 周不等,直到 Linus 和核心维护者觉得“这就够了”,于是 v6.1 正式发布。

赶不上合并窗口怎么办?

如果你错过了一个窗口(比如没来得及把代码合进去),你只有一个选择:

等下一个窗口,大概 2.5 到 3 个月后。这就是内核开发者的“断舍离”。


探索内核源码树的类型

内核不是只有一个版本。在这个庞大的生态里,有几种不同的源码树,每一种都有它的用途。

1. Mainline / Stable (主线 / 稳定版)

这是正宫娘娘。所有的 Feature 最终都会汇聚到 Mainline,然后 Mainline 会被打上补丁变成 Stable 版本。通常也是企业发行版的基础。

2. Long Term Support (LTS)

这是“特别稳定”的版本。维护者承诺在很长一段时间内(通常是 2 年起步,甚至更长)持续为它打补丁。

按照惯例,每年 12 月发布的最后一个内核会被选为 LTS。我们这本书用的 6.1 LTS 就是这么个角色,它的预定寿命是 4 年(2022.12 - 2026.12)。

3. -next Trees (前沿树)

这是“ bleeding edge ”。如果你打算向内核提交代码,你必须在 linux-next 树上工作。这是所有准备进入下一个合并窗口的补丁的集合体。这里非常不稳定,不适合生产环境,但是贡献者的必经之路。

4. Distribution Kernels (发行版内核)

Red Hat、Ubuntu、Debian 他们用的内核。通常基于某个 Stable/LTS,然后加上自己的一大堆补丁和驱动。

这也是为什么有时候你看到某家云厂商还在用 4.x 内核——别急着嘲笑老旧,他们可能把数万个最新的安全补丁都已经 backport 回去了。

5. SoC Vendor Kernels (芯片厂商内核)

ARM、嵌入式厂商维护的内核。这是一个重灾区

芯片厂通常会基于一个(往往很旧的)LTS 内核,然后往里面塞自己的 BSP、私有驱动。这导致他们的内核和主线差异巨大,维护起来简直是噩梦。

最佳实践:如果你做嵌入式,尽量用 Yocto 这种现代构建系统,它们会帮你把这些差异抹平。

6. Super LTS (SLTS)

这是给“民用基础设施”准备的(比如电网、大坝)。CIP 项目维护的 SLTS 内核,支持周期长达 10 年甚至更久。如果你做的设备要在那里跑 20 年不动窝,你就得关注这个。

⚠️ 重要更新:LTS 缩水了

在 2023 年 9 月,Linus 他们宣布了一个大消息:以后的 LTS 维护周期可能会缩短到 2 年

为什么?

  1. 真的没人用那么老的内核:很多厂商自己都在维护自己的分支。
  2. 维护者累挂了:同时维护 7 个 LTS 版本(4.14, 4.19... 6.6),补丁要不停地往后移植,工作量指数级增长。

不过,我们这本用的 6.1 LTS 应该能苟到 2026 年 12 月,足够你折腾了。


到底该跑哪个内核?

这是个送命题,但内核社区的大佬们给出了标准答案:

“跑最新的稳定版。” —— Jon Corbet, 2023

“你得一路升级上来。如果你试图挑挑拣拣地打补丁,你只会得到一个既不安全又有已知 Bug 的怪胎。” —— Greg Kroah-Hartman

别试图自己当“发行版”,让大佬们替你把关吧。


开始构建:前 3 步实战

好了,理论讲够了。现在我们正式开始构建 6.x 内核的旅程。

我们把整个构建过程拆成了 6-7 步。这一章我们只做前三步:

  1. 获取 源码。
  2. 解压 源码。
  3. 配置 内核(这是重头戏)。

接下来的 4-7 步(编译、安装模块、配置 Bootloader、安装内核)留到下一章。


Step 1 – 获取 Linux 内核源码

你有两个选择:

  1. 下载压缩包:适合大部分只想学习或者使用特定版本的人。
  2. Clone Git 仓库:适合想贡献代码或者追到最新版的人。

对于本书,我们选择第一种:下载 6.1.25 LTS 的压缩包

方法 A:直接下载

最简单的方法是用 wget 或者 curl

wget --https-only -O ~/Downloads/linux-6.1.25.tar.xz \
https://mirrors.edge.kernel.org/pub/linux/kernel/v6.x/linux-6.1.25.tar.xz

这会把大概 130MB 的源码包扔进你的 ~/Downloads

方法 B:Clone Git 仓库(可选)

如果你想体验 bleeding edge,或者想看看 linux-next 长啥样:

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

⚠️ 注意:完整的内核 Git 历史记录非常巨大,Clone 需要很长时间和大量磁盘空间(几个 GB)。如果你只是想看一眼,可以用 --depth 1 来浅克隆。

如果是想搞稳定版分支:

git clone git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git
cd linux-stable

对于本书,我们还是推荐用第一种方法,下载确定版本的 6.1.25,这样大家的环境一致,不容易出幺蛾子。


Step 2 – 解压内核源码树

假设你现在手里已经有了那个 linux-6.1.25.tar.xz 文件。

解压它:

# 简单粗暴法:解压到当前目录
tar xf ~/Downloads/linux-6.1.25.tar.xz

# 推荐法:解压到一个专门的工作目录
mkdir -p ~/kernels
tar xf ~/Downloads/linux-6.1.25.tar.xz --directory=~/kernels/

现在,你的 ~/kernels/linux-6.1.25/ 下面就是那个传说中的 3000 万行代码宇宙了。

为了方便后续操作,我们设个环境变量:

export LKP_KSRC=~/kernels/linux-6.1.25

⚠️ 别用 GUI 解压工具:真的,练练 tar 命令。将来你在服务器上或者是纯终端环境下(比如通过 SSH 连接的虚拟机),你会感谢这个习惯。


📖 额外赠送:源码树结构速览

在正式配置之前,让我们花一分钟(真的是一分钟)来看看这个庞然大物里面都有啥。

这就像是你刚买了一套乐高,拆开盒子先看说明书上的零件清单。

$ du -h .
1.5G .

大概 1.5 GB。

那个传说中的版本号在哪里定义?看一眼根目录的 Makefile:

$ head Makefile
# SPDX-License-Identifier: GPL-2.0
VERSION = 6
PATCHLEVEL = 1
SUBLEVEL = 25
EXTRAVERSION =
NAME = Hurr durr I'ma ninja sloth

你看,6.1.25 就在这里。那个 NAME 是每个内核版本的代号(Linus 的恶趣味)。

下面是一个极简的“地图”,告诉你东西大概都在哪:

目录/文件是干嘛的
kernel/核心核心:调度器、信号、cgroups、模块管理。
mm/内存管理。
fs/文件系统:VFS 虚拟层、ext4、btrfs、nfs 都在这里。
drivers/驱动程序。这里是最大的一块,也是变化最快的地方。
net/网络协议栈。TCP/IP 的灵魂就在这。
block/块设备 I/O 层和缓存。
crypto/加密算法库。
init/内核初始化代码。那个传说中的 start_kernel() 就在 init/main.c 里。
usr/用来生成 initramfs 的代码。
arch/架构相关代码x86, arm, arm64, riscv... 都在这里。
Documentation/官方文档。别去百度了,答案多半在这里。

看一眼就够了,不用死记硬背。你以后会在这些目录里迷路很多次的,那是常事。


Step 3 – 配置 Linux 内核

这是最关键的一步。

Linux 内核之所以能跑在手机上,也能跑在超级计算机上,全靠这一步:配置

如果不配置,你需要面对几千个配置选项。如果配置错了,内核可能起不来,或者缺了某些关键功能(比如网络、USB)。

配置的核心是一个叫 Kconfig 的系统,它负责生成一个叫 .config 的隐藏文件。

Kconfig / Kbuild 极简入门

这里有一套基础设施在默默工作:

  • Kconfig: 负责那个“菜单界面”。它读取 Kconfig 文件,显示选项,处理依赖关系(比如选了 A 才能选 B)。
  • Kbuild: 负责编译。它读取 .config 文件,根据里面的内容决定编译哪些文件。

所有的配置最终都汇总到一个文件里: .config (就在源码树的根目录下)。

在这个文件里,所有的配置项都长这样:

CONFIG_FOO=y # y = 编进内核镜像
CONFIG_BAR=m # m = 做成模块
CONFIG_BAZ=n # n = 不要这个功能

y (yes) 意味着这个功能会被硬塞进内核镜像里,开机就有,但体积会变大。 m (module) 意味着做成一个 .ko 文件,需要的时候再加载。 n (no) 意味着这玩意对你来说是隐形的。

获取初始配置:三条路

对于新手,直接手写 .config 是不可能的。我们通常从三个起点出发:

  1. 厂家配置:直接复制当前系统正在用的配置。
  2. 默认配置:让内核给你一个通用的配置(通常很大)。
  3. 精简配置:基于当前系统已加载的模块来生成。
策略 1:厂家的配置(安全但臃肿)

如果你在 Ubuntu 上想自己编一个内核,最稳妥的办法就是先用现在的:

make mrproper
cp /boot/config-$(uname -r) ${LKP_KSRC}/.config

这会生成一个巨大的 .config,里面啥都有。这能保证内核能跑起来,但编译时间会很长,体积也会很大。

策略 2:默认配置
make defconfig

这会根据你的 CPU 架构(比如 x86_64)给出一套标准配置。

策略 3:精简配置 —— 推荐给学习者

如果你想练手,或者想要一个比较苗条的内核,可以用 localmodconfig

它的逻辑是:看看我现在系统里加载了哪些模块(lsmod),然后只把这些功能编译进去

# 先把当前加载的模块列表存下来
lsmod > /tmp/lsmod.now

# 基于这个列表生成配置
cd ${LKP_KSRC}
make LSMOD=/tmp/lsmod.now localmodconfig

在运行过程中,如果新内核(6.1.25)有旧内核(5.19)没有的新选项,它会问你。这时候一般狂按 Enter(选默认值)就行。

最终,你会得到一个 .config 文件。这就是你的定制配方。


Step 4(其实是 Step 3 的延续)—— 调整配置

拿到 .config 之后,我们通常还需要微调一下。

这时候就需要用那个传说中的神器了:make menuconfig

启动图形化菜单

cd ${LKP_KSRC}
make menuconfig

你会看到一个蓝色的、基于 ncurses 的图形界面(在 SSH 下也能用)。

导航与操作

  • 方向键:移动。
  • Enter:进入子菜单。
  • Space (空格):切换状态(在 y, m, n 之间循环)。
  • / (斜杠):搜索(比如搜你想找的驱动)。
  • Esc Esc:退出当前菜单。

符号说明

  • [ ] : 不编译。
  • [*] : 编进内核。
  • <M> : 编成模块。
  • --- : 被强制选为编译进内核(通常是依赖项)。

实战演示:开启 .config 查看支持

我们来做一个小实验,开启一个很有用的功能:让内核运行时能查看自己的配置

  1. 进入菜单。
  2. 找到 General setup -> Kernel .config support
  3. 把它从 <M> (模块) 改成 [*] (内置)。
  4. 选中它下面的 Enable access to .config through /proc/config.gz,把它选上 [*]
  5. 退出并保存。

这会启用两个配置宏:

  • CONFIG_IKCONFIG=y
  • CONFIG_IKCONFIG_PROC=y

以后内核跑起来的时候,你只要 zcat /proc/config.gz 就能看到它当时的编译选项了,这对调试非常有帮助。


配置进阶:脚本化与差异化

如果你是搞自动化构建的,menuconfig 那套交互界面可能不适合你。

脚本修改配置

内核源码里自带了一个脚本:scripts/config

你可以直接在命令行里修改 .config

# 禁用某个选项
./scripts/config --disable IKCONFIG

# 启用某个选项
./scripts/config --enable IKCONFIG

# 查看某个选项的状态
./scripts/config --state IKCONFIG
# 输出: y

⚠️ 注意:用脚本改配置就像在黑暗里挥刀——它不管依赖关系。改完记得跑一下编译测试一下。

查看配置差异

你改了一堆配置,想看看到底动了哪里?用 diffconfig

./scripts/diffconfig .config.old .config

它会列出哪些选项变了,从什么值变成了什么值,一目了然。


安全加固:Kconfig Hardened Check

如果你是搞安全或者做产品的,内核配置的安全性至关重要。

有一个叫 kconfig-hardened-check 的工具,专门用来检查你的内核配置是否符合 KSPP (Kernel Self Protection Project) 的安全建议。

# 安装
git clone https://github.com/a13xp0p0v/kconfig-hardened-check
cd kconfig-hardened-check

# 检查你的 .config
python3 kconfig-hardened-check.py --config ${LKP_KSRC}/.config

它会告诉你哪里开启了弱项,哪里应该加固。比如 CONFIG_BUG_ON_DATA_CORRUPTION 之类的。


定制 Kconfig 菜单:加上你自己的名字

假设你写了一个牛逼的驱动或者功能,想把它加进内核菜单里。怎么做?

这其实分为两步:

  1. Kconfig: 告诉菜单系统有个新选项。
  2. Makefile: 告诉编译系统对应的代码在哪。

实验:加个“LKP 测试选项”

我们来玩个真的。在 General setup 菜单里加一个我们自己的选项。

  1. 备份文件

    cp init/Kconfig init/Kconfig.orig
  2. 编辑文件: 找到 init/Kconfig。找个空地(比如 LOCALVERSION_AUTO 附近),插进去这段:

    config LKP_OPTION1
    bool "Test case for LKP 2e book/Ch 2: creating ..."
    default n
    help
    This is a dummy test option added for learning purposes.
    Selecting this does absolutely nothing except prove
    that you know how to edit Kconfig files.
    • config: 定义了一个宏叫 CONFIG_LKP_OPTION1
    • bool: 这是一个布尔选项(开/关)。
    • help: 下面那几行就是帮助文档。
  3. 验证修改

    make menuconfig

    进入 General setup,往下翻,你应该能看到你的新选项了!

  4. 启用它: 选中它,按空格把它变成 [*]。保存退出。

  5. 检查结果

    grep "LKP_OPTION1" .config
    # CONFIG_LKP_OPTION1=y

    恭喜,你刚刚黑进了 Linux 内核的构建系统。

  6. 关联代码(高级): 如果你真的写了代码(比如 lkp_test.c),你还得修改 Makefile:

    init/Makefile(或者你代码所在目录的 Makefile)里加上:

    obj-$(CONFIG_LKP_OPTION1) += lkp_test.o

    这样,当你选 Y 时,它会被编译进内核;选 N 时,编译器会无视它。


本章小结

这一章我们干了挺多事。

首先,我们搞清楚了 6.x 内核的版本号是怎么来的——那是 Linus 数手指头数出来的,代表了时间,而不是功能。我们也知道了 LTS 是个什么东西,以及为什么你要选它。

然后,我们上手了:下载了源码,解压了它,还花了一分钟认了认这 3000 万行代码的布局。

最重要的是,我们花了大篇幅去理解 KconfigKbuild——这是构建内核的基石。我们学会了怎么获取一个基础配置(defconfiglocalmodconfig),怎么通过 make menuconfig 调整它,甚至怎么写自己的 Kconfig 脚本给内核加个菜单项。

现在,你的工作目录里应该有一个配置好的内核源码树,里面躺着一个写满了你选择的 .config 文件。

别急着休息。下一章,我们要按下那个红色的按钮了。

我们要运行 make。 我们要把这 3000 万行代码变成一个二进制文件。 我们要把它装进系统里,重启,看着屏幕上滚动的 log 变成你自己编译的内核名字。

那才是真正的“Hello World”。


练习题

练习 1:版本号考古 ⭐(理解)

找一台你手边能接触到的 Linux 机器(云服务器、虚拟机、WSL 都行),运行 uname -r

  1. 它的版本号是多少?
  2. 它是 Stable 版本吗?还是发行版魔改版?怎么判断的?
  3. 如果它是 5.x 的,你能猜到它大概是哪年发布的吗?(提示:数手指)

练习 2:配置项狩猎 ⭐⭐(应用)

使用 / 搜索功能在 make menuconfig 中找到以下选项,并记录它们的位置和依赖关系:

  1. CONFIG_KASAN (Kernel Address Sanitizer)
  2. CONFIG_DEBUG_INFO_BTF (eBPF 相关)
  3. 找一个和你电脑硬件相关的驱动(比如 WiFi 或者显卡驱动),看看它依赖什么?

练习 3:自定义模块构建 ⭐⭐⭐(实战)

这题有难度,如果你做出来了,说明你真的懂了。

  1. 编写一个极其简单的内核模块(只要 initexit 函数打印 Hello World)。
  2. 把这个文件放到内核源码树的 drivers/char/ 目录下。
  3. 修改该目录下的 MakefileKconfig,使得你可以通过 make menuconfig 选中你的模块。
  4. 编译成模块(.ko),并成功加载。

提示:你需要参考该目录下其他驱动的写法。


附录:常用命令速查

# 1. 准备环境(清理旧的配置)
make mrproper

# 2. 获取当前系统的模块列表(用于精简配置)
lsmod > /tmp/lsmod.now

# 3. 生成本地化配置(仅包含当前硬件需要的驱动)
make LSMOD=/tmp/lsmod.now localmodconfig

# 4. 打开图形化配置界面
make menuconfig

# 5. 查看配置差异(改完配置后)
./scripts/diffconfig .config.old .config

# 6. 脚本化修改配置(例如开启 IKCONFIG)
./scripts/config --enable IKCONFIG
./scripts/config --enable IKCONFIG_PROC

# 7. 检查内核配置安全性(需安装工具)
kconfig-hardened-check --config .config

练习题

练习 1:understanding

题目:根据 Linux 内核版本命名规范 'w.x[.y][-z]',内核版本号 '6.1.25-custom' 中的各个数字和字符分别代表什么含义?请结合“手脚模型”解释为什么主版本号从 5 变成了 6。

答案与解析

答案:w=6 (主版本号), x=1 (次版本号), y=25 (补丁级别/修订版本), -z=-custom (额外版本/本地版本)。

根据“Fingers-and-toes”模型,当次版本号(x)大约累积到 20 个(即从 0 到 19,手指加脚趾的总数)时,主版本号(w)就会增加。因此,内核从 5.19 演进到了 6.0。

解析:本题考察对内核版本号结构和演进规则的理解。

  1. 版本号结构:遵循 major.minor.patchlevel-extraversion 格式。
  2. 手脚模型:这是一种非严格时间基础的版本演进策略。Linus 形象地比喻为用完手脚(20个)计数后就进一位。历史上,3.x、4.x、5.x 系列都在第 20 个次版本结束时结束了该主版本周期(例如 5.19 之后是 6.0),这并不意味着巨大的架构变更,而是有机的演进。

练习 2:application

题目:在内核构建系统的 Kconfig 语言中,depends onselect 是两种常见的依赖关系机制。请简述这两种机制的区别,并解释为什么在编写配置选项时通常建议优先使用 depends on 而不是 select

答案与解析

答案区别

  • depends on (正向依赖):当前选项依赖于指定选项。如果依赖项未满足(为 n),当前选项将不可见或不可选。
  • select (反向依赖/自动选择):当前选项被选中时,自动强制选中另一个选项(无论用户是否意识到)。

建议原因select 会强制开启隐含依赖,可能导致用户无意中开启了不需要或不兼容的代码,增加内核大小或引发冲突。depends on 则是向用户展示依赖关系,由用户主动解决,更符合模块化设计原则。

解析:本题考察在实际编写内核配置(BSP 或驱动开发)时的最佳实践。

  • depends on 是一种“可见性限制”,例如 depends on NET 表示只有在网络子系统开启时,此选项才会在菜单中显示。
  • select 是一种“强制的传递依赖”,例如选择了 USB 存储驱动可能会自动 select SCSI 支持。
  • 风险在于,如果被 select 的选项有它自己的依赖冲突,用户可能会面临复杂的配置错误。因此,除非必须开启某些底层基础设施(如总线或核心库),否则应尽量避免使用 select

练习 3:thinking

题目:假设你是一家嵌入式公司的系统工程师,正在规划一款基于新型 ARM SoC 的工业控制设备,预计产品生命周期为 10 年。你需要选择合适的 Linux 内核源树作为基础。

选项 A:使用 SoC 厂商提供的基于旧版 LTS 的厂商内核(含 BSP 驱动)。 选项 B:使用最新的主线 LTS 内核(如 6.1 LTS),并通过 Yocto 等工具集成 BSP 层。

请结合内核维护周期、技术债务和安全性,分析选择选项 B 的利弊。

答案与解析

答案结论:应选择选项 B(最新 LTS 内核 + Yocto/社区集成)。

利(理由)

  1. 长期支持与可维护性:厂商内核通常基于特定旧版本并分叉,长期维护会面临“技术债务”,难以跟上上游安全补丁。而主线 LTS(如 6.1)有社区支持,且 Yocto 等项目能提供持续的元数据更新。
  2. 安全性:工业设备需长期运行,旧内核容易积累未修复的 CVE 漏洞。主线 LTS 能更快获得安全修复。
  3. 驱动更新:利用设备树机制,可以更容易地将厂商的硬件支持移植到新内核,而不依赖于厂商修改的旧内核源码。

弊(挑战)

  1. 初期开发成本:如果 SoC 厂商未将驱动提交到主线,你可能需要自己移植驱动或编写设备树。
  2. 兼容性风险:新内核的 API 可能发生变化,需要验证所有 BSP 组件的兼容性。

解析:本题考察对内核生态、供应链安全和工程架构的综合思考能力。

  • SoC 厂商内核:虽然“开箱即用”,但往往是一次性的,一旦厂商停止支持该旧内核,产品将面临巨大的安全风险和升级困难。
  • 主线 LTS 策略:符合“上游优先”原则。虽然门槛较高,但利用 Device Tree(硬件描述与代码解耦)和现代构建系统(Yocto/Buildroot),可以实现长期、可持续的维护,这对于 10 年寿命周期的工业设备至关重要。
  • Super LTS (SLTS):题目中提到的 CIP SLTS 也是解决此问题的方案之一,通常也是基于主线 LTS 的超长维护分支,选项 B 更符合这一思路。

要点提炼

理解 Linux 内核严格的开发节奏和版本规则是构建前的首要必修课。现代内核开发遵循固定的“合并窗口”与“修复窗口”周期,新功能仅在合并窗口开启的两周内被接受,其余时间仅修补 Bug。版本号(如 6.1)不再沿用旧时的奇偶数稳定/开发区分法,而是基于时间的“有机演化”;对于构建者而言,选择 LTS(长期支持) 版本(如本书使用的 6.1)通常是平衡稳定性与维护周期的最佳实践,这能确保你在数年的使用中获得持续的安全补丁支持。

内核构建的基石在于掌握 Kconfig/Kbuild 配置系统,它决定了哪些代码被编译以及如何编译。内核之所以能适配从手机到超级计算机的各种硬件,全靠这一步进行定制。核心配置最终汇总为根目录下的 .config 文件,其中的 y(编译进内核)、m(编译为模块)和 n(不编译)状态决定了内核的功能集与体积。盲目手动编写该文件不可行,最佳实践是利用当前系统的配置(如 /boot/config-xxx)或默认配置作为起点。

获取源码并正确解压是动手实战的第一步,推荐直接从 Kernel.org 下载确定版本(如 linux-6.1.25.tar.xz)的压缩包,以避免完整 Git 仓库克隆带来的巨大时间与空间开销。解压后,源码树呈现高度结构化的特征,核心代码如调度器(kernel/)、内存管理(mm/)、驱动(drivers/)和架构相关代码(arch/)各司其职。理解这一目录结构就像查看乐高零件清单,是后续进行针对性修改或驱动开发的基础。

对于学习者或开发者,使用 make menuconfig 是调整配置最直观且有效的方法。这个基于 ncurses 的图形化界面允许用户通过搜索、导航来微调内核选项,例如开启 Kernel .config support 以便在系统运行时通过 /proc/config.gz 查看当前配置。若要进行自动化构建,则需掌握 scripts/config 等脚本工具来修改 .config,并使用 diffconfig 来检查配置变更,确保只启用了必要的功能以减小内核体积。

真正的内核定制能力体现在能够“黑进”构建系统,即通过修改 KconfigMakefile 将自定义代码或选项集成进内核菜单。这一过程不仅涉及编写配置语法(定义 config 选项、依赖关系和帮助文档),还需要在构建脚本中正确关联对象文件(如 obj-$(CONFIG_FOO) += bar.o)。这种深度的干预是将内核开发从单纯的“编译”提升到“系统级编程”的关键,也是完成后续编译与安装模块的必要前提。