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。有时候,你需要让这个特工开口说话,或者让它把每一步心里活动都记下来。
那时候,光靠这个「生产内核」是不够的。
我们还需要另一个更话痨、甚至有点臃肿的角色——调试内核。