跳到主要内容

1.7 打造你的定制生产内核

到了这一步,我必须假设你对从源码构建 Linux 内核的这套「基本操作」已经不算陌生了:拉取源码树、配置、编译。如果你觉得有点生疏,或者想复习一下细节,强烈推荐去翻翻《Linux Kernel Programming》这本书,或者参考本章末尾的「延伸阅读」部分。

既然说是「生产内核」,我们的目标就很明确:它得稳,得快,得安全。但就像我们在上一节讨论的,不能从零开始瞎猜配置。最聪明的办法是「站在巨人的肩膀上」——基于现有系统的配置进行调优(这就是所谓的 localmodconfig 策略)。一旦有了这个靠谱的起点,我们再通过「加固」配置来提升安全性。

让我们开始第一步,先把地基打好。

1.7.1 获取源码与基础配置

目标:建立工作目录,拉取并解压 LTS 内核源码。

为什么这样做: 上一节我们选定了 5.10.60 这个 LTS 版本作为基准。为了保持环境整洁,我们需要一个独立的工作目录来存放源码和编译产物。tar--directory 参数能让我们在解压的同时把文件归位,少敲几行 mv

操作位置:用户主目录

命令与输出

首先,咱们建个专门的「车间」:

mkdir –p ~/lkd_kernels/productionk
cd ~/lkd_kernels

接下来把源码拉下来。这里我们用的是 wget 直接抓取压缩包,当然你也可以用 git(如果你不介意等待的时间稍微长一点):

wget https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/linux-5.10.60.tar.xz

解压它:

tar xf linux-5.10.60.tar.xz --directory=productionk/

进入解压后的目录,顺手验证一下版本信息。这看起来是个无聊的步骤,但有一件事挺有意思:

每个版本的 Linux 内核都有一个「代号」—— 5.10.60 的代号是 "Dare mighty things"(敢于成就大事)。我挺喜欢这个名字的,它放在这里非常应景,毕竟我们正在做的事,就是在操作系统的核心地带动刀子。

1.7.2 生成精简配置(localmodconfig)

目标:利用当前系统正在运行的模块列表,生成一个量身定制的初始内核配置文件。

为什么这样做: 全量默认配置包含了几千个你用不到的驱动和模块,编译出来的内核既臃肿又慢。localmodconfig 的魔法在于,它会扫描当前系统加载了哪些模块(lsmod 的结果),然后把没选中的东西统统关掉。这就好比给内核做了一次抽脂手术。

操作位置:内核源码根目录 (~/lkd_kernels/productionk/linux-5.10.60/)

命令与输出

先生成一份当前系统的模块快照:

lsmod > /tmp/lsmod.now

然后把这个快照喂给 make

make LSMOD=/tmp/lsmod.now localmodconfig

⚠️ 踩坑预警 这个过程中,终端可能会停下来问你几个问题(交互式界面)。别慌,如果你不懂某个选项的具体含义,直接按 Enter 键选默认值通常是最安全的。我们的目标只是获得一个「够用」的起点,而不是在这里这就把所有细节都敲定。

这步做完之后,源码树根目录下就多了一个 .config 文件。这可是宝贝,它是内核的「灵魂」。

先把它备份起来,以防后面改坏了回不来:

cp –af .config ~/lkd_kernels/kconfig_prod01

小贴士:随时可以敲 make help 查看所有可用的配置命令。如果你是个老手,完全可以用 make menuconfig 进去把界面改成你熟悉的风格。

1.7.3 内核加固——安全的代价

目标:通过自动化工具检查并修改内核配置,开启安全加固选项。

现在我们有一个「能跑」的配置了。但在把它投入生产之前,我们要解决一个两难问题:安全 vs 便利

很多加固特性在 Linux 内核里默认是关闭的。为什么?因为它们要么会牺牲性能,要么会破坏某些旧软件的兼容性。但在生产环境中,尤其是对于专门用来调试或运行敏感服务的系统,我们倾向于选择「安全」。

问题是:哪怕你打开 make menuconfig,面对成千上万条选项,你也不知道哪条是管安全的。

好在有人帮我们干了这个脏活。

引入工具:kconfig-hardened-check

这其实是一个验证器——但它能反向告诉你:你的配置里缺了哪些业界公认的安全最佳实践。它就像一个严格的安全审计员,拿着一张检查表(Checklist)对你的 .config 挨个打勾。

安装并运行它(这里以 Python 脚本为例):

# 假设你已经通过 pip 安装了该脚本,或者克隆了仓库
python3 kconfig-hardened-check.py --config .config

你会看到一大堆输出,像这样(截取了一部分):

[+] Check: CONFIG_BUG is required: set to y
[+] Check: CONFIG_STRICT_KERNEL_RWX is required: set to y
[-] Check: CONFIG_DEBUG_LIST is suggested: NOT set (should be y)
[...]

我们不需要在这里深入解释这个脚本的每一个细节(那值得单独写一篇文章),核心逻辑是:它告诉你缺什么,你就补什么

我根据它的建议,调整出了生产内核的最终配置。这个配置文件我已经放到了本书的 GitHub 仓库里(ch1/kconfig_prod01),你可以直接拿去用,或者作为参考。

1.7.4 编译与安装

目标:编译内核,安装模块,并更新引导加载程序。

现在,配置终于定稿了。是时候把这些源代码变成那个著名的 bzImage 了。

操作位置:内核源码根目录

首先,确认一下你的 CPU 核心数,这决定了我们能多快干完这活:

$ nproc
4

这里我有 4 个核心。作为经验法则,make -j 后面的数字通常是核心数的 2 倍。这样可以最大化利用 CPU,同时避免过载。

$ make -j8
[ ... 一大段滚动的编译日志 ... ]
BUILD arch/x86/boot/bzImage
Kernel: arch/x86/boot/bzImage is ready (#1)

看到 is ready 这行,你就成功了。

但这里有两个文件,我们需要稍微理清一下关系:

$ ls -lh arch/x86/boot/bzImage vmlinux
-rw-r--r-- 1 letsdebug letsdebug 9.1M Aug 19 17:21 arch/x86/boot/bzImage
-rwxr-xr-x 1 letsdebug letsdebug 65M Aug 19 17:21 vmlinux
  • bzImage (9.1MB):这是压缩后的内核镜像。这是 GRUB 实际读取并启动的文件。别被名字里的 b 迷惑,它不代表 Big,而是代表 "boot zImage"(大概)。
  • vmlinux (65MB):这是未压缩的 ELF 可执行文件。别删它。虽然启动不用它,但它包含了所有的符号信息——那是我们后面调试时的救命稻草。

SICP 式旁白:这里有一个微妙的分工。bzImage 是为了生存——它要足够小,能被加载进内存并快速解压;vmlinux 是为了理解——它保留了所有的细节,供我们在系统崩溃时尸检。

接下来,安装内核模块。这会把编译好的 .ko 文件拷贝到系统的标准目录 /lib/modules/$(uname -r) 下:

$ sudo make modules_install
[ ... ]
DEPMOD 5.10.60-prod01
$ ls /lib/modules/
5.10.60-prod01/ 5.11.0-27-generic/ 5.8.0-43-generic/

注意看那个 5.10.60-prod01 目录,这就是我们的新地盘。

最后一步,让系统「知道」这个新内核的存在。我们需要生成 initramfs(初始内存文件系统),并更新 GRUB 配置:

sudo make install

这行命令背后其实运行了好几个脚本。initramfs 非常关键,它包含了一组最基本的驱动和脚本,负责在内核真正挂载硬盘上的根文件系统之前,先把硬件环境准备好。没有它,现代系统通常起不来。

1.7.5 第一次点火——切换到新内核

目标:重启系统,从 GRUB 菜单选择新内核,验证系统正常运行。

万事俱备,只欠重启。

但在按下重启键之前,先停一下。想一想:如果这一步成功了,你预期会看到什么?

你应该会看到 GRUB 的启动菜单。如果你的菜单平时是隐藏的(或者跳过得太快),你可以在启动时狂按 Shift 键(如果是传统 BIOS)或 Esc 键(如果是 UEFI),把它叫出来。

你应该能看到名为 "5.10.60-prod01" 的新选项。

选中它,回车。

如果你是在虚拟机里操作,比如像我这台 Oracle VirtualBox 一样,可能还会发现一个问题:分辨率不对,或者鼠标不能自动捕获。别担心,那只是因为 VirtualBox 的 Guest Additions(客户机附加组件)还没针对新内核编译。这完全不影响内核功能,只是体验上差点意思。为了避免干扰,我通常这时候会直接用 SSH 连进去干活。

如果一切顺利,你会看到熟悉的登录提示符。

进系统后,敲下这个命令,确认我们已经坐在了新的引擎上:

$ uname -a
Linux dbg-LKD 5.10.60-prod01 #1 SMP PREEMPT Thu Aug 19 17:10:00 IST 2021 x86_64 x86_64 x86_64 GNU/Linux

那个 5.10.60-prod01 的后缀,就是我们亲手打下的烙印。

阶段性小结 我们现在有了一个跑在自家定制内核上的系统。它精简、加固、随时待命。但这只是「生产内核」——它就像一个身手敏捷但不爱说话的特工。

在接下来的旅程中,你会遇到各种棘手的内核级 Bug。有时候,你需要让这个特工开口说话,或者让它把每一步心里活动都记下来。

那时候,光靠这个「生产内核」是不够的。

我们还需要另一个更话痨、甚至有点臃肿的角色——调试内核