3、开发环境搭建:内核源码树准备、编译工具链配置、内核编译选项、QEMU/KVM虚拟机环境搭建

说实话,做内核热补丁开发,环境搭建这一步最容易被忽视。很多人一上来就急着写补丁,结果编译不过、加载失败,折腾半天才发现是工具链版本不对,或者内核选项没开。我当年就吃过这个亏,白白浪费了两天时间。

这一节,咱们把环境搭得稳稳当当的。你跟着我的步骤来,保证后面开发顺风顺水。

3.1 内核源码树准备

首先得有一份内核源码。我个人习惯用主线内核,因为文档全、社区支持好。但如果你在生产环境做热补丁,建议用发行版自带的内核源码,比如 CentOS 的 kernel-devel 包。

这里我推荐两种方式:

  • 方式一:直接下载主线内核
    去 kernel.org 下载最新稳定版。我一般用 5.10 LTS,生命周期长,适合做产品。
  • 方式二:用发行版源码包
    Ubuntu 下执行 apt-get source linux-image-$(uname -r),CentOS 下用 yumdownloader --source kernel

下载完后,解压到 /usr/src/ 目录。我个人习惯建个软链接:

ln -s /usr/src/linux-5.10.210 /lib/modules/$(uname -r)/build

这样后面编译模块时,系统能自动找到源码路径。

小技巧: 如果你像我一样经常切换内核版本,建议用 git clone 拉取 Linus 的树,然后 git checkout 到目标版本。这样切换分支非常快,而且能保留你的修改记录。

3.2 编译工具链配置

工具链这块,说白了就是 gcc、make、elfutils 这几样。但版本有讲究——太新的 gcc 可能编译老内核报错,太旧的又缺特性。

我建议的版本组合:

工具 推荐版本 备注
gcc 9.x ~ 11.x 太新(如13.x)可能遇到内联汇编兼容问题
make 4.3+ 低版本 make 解析内核 Kbuild 会卡住
elfutils 0.186+ 热补丁需要 libelf 解析符号表
openssl-devel 1.1.1+ 内核模块签名需要

安装命令很简单:

# Ubuntu/Debian
sudo apt-get install build-essential libelf-dev libssl-dev

# CentOS/RHEL
sudo yum groupinstall "Development Tools"
sudo yum install elfutils-libelf-devel openssl-devel

嗯,这里要注意:如果你用 CentOS 7,默认 gcc 是 4.8.5,太老了。我曾经在这个坑里爬了半天——编译出来的内核模块加载时直接 panic。解决办法是用 devtoolset 升级 gcc:

sudo yum install centos-release-scl
sudo yum install devtoolset-9-gcc*
scl enable devtoolset-9 bash

3.3 内核编译选项配置

热补丁依赖三个核心内核选项:CONFIG_FTRACECONFIG_KPROBESCONFIG_LIVEPATCH。缺一个,你的补丁就加载不了。

进入内核源码目录,执行:

make menuconfig

然后按下面的路径打开选项:

  • CONFIG_FTRACE
    Kernel hacking → Tracers → Enable Ftrace(必选)
    子选项里建议把 FUNCTION_TRACERFUNCTION_GRAPH_TRACER 也打开,调试时很有用。
  • CONFIG_KPROBES
    General setup → Kprobes(必选)
    同时打开 OPTPROBES,能提升性能。
  • CONFIG_LIVEPATCH
    General setup → Live Patching(必选)
    这个选项在 4.0+ 内核才有,老内核就别想了。
核心配置速查: 你可以直接修改 .config 文件,确保以下三行存在:
CONFIG_FTRACE=y
CONFIG_KPROBES=y
CONFIG_LIVEPATCH=y
然后执行 make olddefconfig 自动补全依赖项。

配置完后,编译内核:

make -j$(nproc) bzImage
make -j$(nproc) modules
sudo make modules_install
sudo make install

编译时间取决于你的机器。我笔记本 8 核,大概 15 分钟。第一次编译建议别开 -j 太大,容易 OOM。

注意: 如果你用的是发行版内核(比如 Ubuntu 的 linux-image-generic),这些选项默认就是打开的。但定制内核时很容易漏掉。我曾经有一次忘了开 CONFIG_LIVEPATCH,结果补丁编译通过但加载时报 ENOSYS,查了半天才发现是内核不支持。

3.4 QEMU/KVM 虚拟机环境搭建

说实话,我不建议在物理机上直接调试热补丁。万一补丁写崩了,系统直接挂掉,你连抢救的机会都没有。用虚拟机最安全,快照一恢复,又是一条好汉。

我推荐用 QEMU/KVM 方案,性能接近原生,而且支持嵌套虚拟化——你可以在虚拟机里再跑一个虚拟机来测试。

安装步骤:

# Ubuntu
sudo apt-get install qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils virt-manager

# CentOS
sudo yum install qemu-kvm libvirt libvirt-python libguestfs-tools virt-install

启动 libvirtd 服务:

sudo systemctl enable libvirtd
sudo systemctl start libvirtd

然后创建一台虚拟机。我习惯用 virt-install 命令行,比图形界面快:

virt-install \
  --name kpatch-dev \
  --ram 4096 \
  --vcpus 4 \
  --disk path=/var/lib/libvirt/images/kpatch-dev.qcow2,size=40 \
  --os-variant centos7.0 \
  --network bridge=virbr0 \
  --location /path/to/CentOS-7-x86_64-Minimal-2009.iso

装好系统后,把刚才编译好的内核拷贝进去:

scp /boot/vmlinuz-5.10.210-custom root@vm-ip:/boot/
scp /boot/System.map-5.10.210-custom root@vm-ip:/boot/

然后在虚拟机里更新 grub,重启就能用新内核了。

我的经验: 虚拟机磁盘用 qcow2 格式,支持快照。每次调试前打个快照,补丁崩了直接 virsh snapshot-revert,10 秒恢复。比重新装系统快多了。

3.5 验证环境是否就绪

环境搭好了,得验证一下。写个简单的内核模块,看看能不能正常编译和加载:

// test_module.c
#include <linux/module.h>
#include <linux/kernel.h>

static int __init test_init(void)
{
    printk(KERN_INFO "Hello from kpatch dev env!\n");
    return 0;
}

static void __exit test_exit(void)
{
    printk(KERN_INFO "Goodbye!\n");
}

module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");

Makefile:

obj-m := test_module.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

all:
    $(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
    $(MAKE) -C $(KDIR) M=$(PWD) clean

编译后加载:

sudo insmod test_module.ko
sudo rmmod test_module.ko
dmesg | tail -2

如果看到 "Hello" 和 "Goodbye",说明环境没问题了。

最后,检查内核选项是否真的生效:

cat /boot/config-$(uname -r) | grep -E "CONFIG_FTRACE|CONFIG_KPROBES|CONFIG_LIVEPATCH"

三个选项都应该是 =y=m。如果哪个是 is not set,回去重新配置吧。

环境搭建就到这里。说白了,这一步花点时间把基础打牢,后面写补丁、调试才能顺风顺水。我见过太多人跳过这一步直接写代码,最后在环境问题上浪费的时间比写代码还多。


公众号:蓝海资料掘金营,微信deep3321