什么时候个人站长真的需要自己编译内核
先说结论:绝大多数站长一辈子都不需要编译内核。发行版提供的内核经过了大量测试,稳定性、驱动兼容性、安全更新都是现成的,跟着 apt upgrade 走就行。任何时候有人跟你说"编译内核能提速 10 倍",你都可以先怀疑一下。
但有几种场景,编译内核确实是最合理的解法:第一,你需要发行版内核里没编译进去的功能,比如某个特定的文件系统、某种内核模块、或者某个实验性特性(像某些 BBR 变种、特定网卡的 offload 选项、新的 TCP 拥塞控制算法)。第二,你在极低配的机器上跑东西,发行版通用内核带了一堆你用不到的驱动和硬件支持,编译时裁掉能省下几十 MB 内存占用和更小的启动镜像。第三,你想学习——想真正理解 Linux 是怎么组装起来的。本文按"能实际跑通"的标准,把从准备到安装、从排错到回滚的完整流程写清楚。
先想清楚:为什么编译内核容易把机器搞没
内核和普通软件最大的区别是:内核更新方式不当会把机器搞成无法开机。普通软件崩了可以重装,内核装错你连终端都进不去,只能靠 VPS 面板的救援模式。所以整个流程要遵循三条铁律:
- 永远保留旧内核。 新内核装在独立文件里,引导菜单里两份都在,起不来就选旧的。
- 不要在远程 SSH 会话里直接跑安装并立即重启。 装完先确认新内核的 initramfs 和模块都生成好了,再谨慎重启。
- 提前确认你有救援通道。 VPS 一般有 VNC/救援控制台;独服要有 IPMI/KVM。没有救援通道就别在线上机器折腾。
第一步:准备编译环境
编译内核需要一整套工具链和内核头文件,缺一样后面都会报错。先把依赖装齐:
# Debian/Ubuntu
apt update
apt install -y build-essential libncurses-dev bison flex \
libssl-dev libelf-dev bc dwarves zstd cpio rsync \
kmod devscripts fakeroot
# 检查关键工具版本
gcc --version | head -1
make --version | head -1
ld --version | head -1
# 磁盘空间准备:编译过程+产物通常要 15~25 GB
df -h /usr/srclibncurses-dev 是给 menuconfig 图形化配置菜单用的,libelf-dev 和 dwarves 是给 BT F 调试信息和后期 pahole 处理用的,bc 是现代内核编译脚本的硬依赖(很多人栽在这,报错信息还很不直观),zstd 是内核模块压缩格式需要的。磁盘空间这一项务必先看:编译 Linux 内核是非常吃磁盘的操作,/usr/src 所在分区不够会导致编译到一半失败。
第二步:获取内核源码
cd /usr/src
# 方式一:从 kernel.org 下稳定版 tarball(推荐,干净)
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.6.30.tar.xz
tar -xf linux-6.6.30.tar.xz
cd linux-6.6.30
# 方式二:用发行版自己的源码包(能复用发行版的配置,更稳)
apt install linux-source-6.1
# 源码会落到 /usr/src/linux-source-6.1.tar.xz如果只是想让内核多支持几个功能、并不追求最新版本,强烈建议用发行版的源码包,因为它带了一份和当前运行内核完全一致的配置文件(/boot/config-$(uname -r)),在此基础上改,兼容性风险最低。
第三步:基于当前配置起步(关键一步)
不要从 make defconfig 的默认配置开始——那会关掉一堆你现在机器正在用的功能,装上去大概率直接起不来。正确姿势是从当前运行内核的配置复制一份:
cd /usr/src/linux-6.6.30
# 拷贝当前系统正在用的配置
cp /boot/config-$(uname -r) .config
# 用 olddefconfig 把配置里"新内核引入的、还没回答的"选项
# 全部按默认值填上,避免被交互式提问打断
make olddefconfig
# 这时 .config 已经是一份完整配置,可以在它基础上改
make olddefconfig 是这一整套流程里最有价值的一条命令。它的作用是:读取现有 .config,对于新内核版本里新增的配置项,用内核作者设定的默认值自动补全,不弹任何交互问题。如果不跑这一步直接 make,你会掉进一个要回答几百个问题的问答地狱。
第四步:进菜单改配置
# 图形化菜单,方向键导航、空格切换、/ 键搜索
make menuconfigmenuconfig 里值得关注的几个点:
- 搜功能用它自带的搜索: 按
/输入关键字(比如CONFIG_BBR),它会告诉你该选项在哪个子菜单、当前是什么状态、以及它的依赖项有没有满足。 - 三种状态要分清:
[*]编进内核(built-in,开机就有),[M]编成模块(用到才加载),[ ]不编译。驱动类通常选 M,核心功能才选 built-in。 - 砍驱动省内存: 在
Device Drivers里把明显用不到的硬件驱动(各种老式网卡、SCSI 控制器、没用到的文件系统)标成[ ]。但要格外小心"当前在用的"驱动——比如你网卡的驱动、根分区所在文件系统的驱动,这些如果被砍掉,新内核直接找不到磁盘或网卡。 - 拿不准就别动。 第一次编译,改少数几个明确的目标项即可,不要大砍。
改完保存退出,配置写回 .config。改之前建议备份:cp .config .config.bak。
第五步:编译
编译要用多核并行,不然单核编内核要好几个小时:
# 用所有 CPU 核数编译,nproc 会自动取核数
make -j$(nproc)
# 更稳妥:核数 +1,有时比纯核数更快(I/O 与计算重叠更好)
make -j$(($(nproc)+1))编译过程通常会跑 20 分钟到 2 小时,取决于机器性能。强烈建议在 screen 或 tmux 里跑,因为 SSH 一旦断线,前台编译进程就会被 SIGHUP 杀掉,前功尽弃:
apt install -y tmux
tmux new -s kernel
make -j$(nproc)
# 按 Ctrl+B 然后 D 脱离,重新连上用 tmux attach -t kernel如果编译中途报错,最常见的原因是:缺少某个 lib*-dev 依赖、磁盘空间不足、或者某个配置项组合产生冲突。错误信息里的第一条 Error 才是根因,后面成百上千行的 Error 都是连带的噪音。
第六步:安装模块与内核
编译成功后,默认的 GNU Makefile 提供了完整的安装目标,注意顺序:
# 安装内核模块到 /lib/modules/$(version)
make modules_install
# 安装内核镜像到 /boot,并自动更新引导
make install
# 更新 initramfs(Debian 系 make install 通常会自动做,
# 但手动再跑一次更保险)
update-initramfs -c -k 6.6.30
# 更新 GRUB 引导菜单
update-grub
装完先别急着重启,检查四件事:
# 1. 内核镜像在不在
ls -lh /boot/vmlinuz-6.6.30
# 2. initramfs 在不在
ls -lh /boot/initrd.img-6.6.30
# 3. 模块目录在不在
ls /lib/modules/ | grep 6.6.30
# 4. GRUB 菜单里有没有这个新条目
grep -A2 "menuentry" /boot/grub/grub.cfg | grep 6.6.30
四项齐了才能重启。uname -r 在重启后应该返回新版本号,用 uname -r 和 cat /proc/version 双重确认。
第七步:验证与回滚
重启前做一次"心理预演":如果新内核起不来,你要怎么进系统?答案是——GRUB 菜单里选择旧内核条目。所以务必确认 GRUB 显示时间不是 0 秒,否则你根本没机会选。编辑 /etc/default/grub:
GRUB_TIMEOUT=10
# 然后 update-grub 生效重启后如果一切正常,再等几天确认稳定,之后才考虑清理旧内核。清理用 apt autoremove 或者手动删 /boot 下的旧版本文件再 update-grub。但别把最后一个能工作的旧内核也删了,至少留一份。
典型报错与排查对照
No rule to make target 'debian/certs/debian-uefi-certs.pem': 用发行版配置编译上游源码时的经典错误。原因是发行版 config 里开了模块签名,指到了发行版专有的证书路径。解法:在 menuconfig 里关掉CONFIG_SYSTEM_TRUSTED_KEYS和CONFIG_SYSTEM_REVOCATION_KEYS(把值清空),或者scripts/config --set-str SYSTEM_TRUSTED_KEYS ""。BTF: .tmp_vmlinux.btf: pahole (pahole) is not available: 缺dwarves包,装上即可。或者关掉CONFIG_DEBUG_INFO_BTF。- 编译到一半
No space left on device: 磁盘满了。编译内核要留 15~25 GB,先df -h确认,不够就把源码放到大分区上编。 - 重启后卡在
Loading initial ramdisk或直接黑屏: 典型的 initramfs 没生成或驱动没编进去。用旧内核进系统,检查新内核的update-initramfs是否成功,以及根文件系统驱动、存储控制器驱动是否被误砍。没进 initramfs 的根分区驱动是最常见的黑屏原因。 - 重启后网卡不见了: 网卡驱动被标成
[ ]裁掉了。重回旧内核,把网卡驱动改回[M]或[*],重新编译安装。 - 新内核下某些模块加载失败(Unknown symbol): 通常是模块和内核版本不匹配,或者编译时用了一份
.config、安装时又换了另一份。全部删干净重来。
一份更省心的替代方案
如果你只是想"加上某个小功能",其实有一条远比全量编译内核轻松的路:把功能编译成外部模块(DKMS/树外模块)。比如加一个新版的网卡驱动、或者一个实验性的网络模块,都可以只编译那个模块,用 insmod/modprobe 加载,完全不需要碰主内核。只有当你要改的是内核核心代码(比如换调度器、改 TCP 栈、启一个需要在编译期开启的整体特性)时,才不得不走全量编译这条路。
判断标准很简单:需求能在内核配置项里用 M 解决的,就编模块;只有必须 built-in 的,才全量编译内核。 大部分"我想加个功能"的诉求,前者就够了。
小结
编译内核本身不难,难在敬畏心:留旧内核、有救援通道、改前备份配置、装完四项检查再重启。技术层面记住三根主线:用 cp /boot/config-$(uname -r) .config && make olddefconfig 从现有配置起步(而不是从默认配置)、用 tmux 挂住编译进程防止断线中断、用 GRUB 的多引导条目给自己留后路。按这个流程走,即使新内核起不来,你也能在两分钟内用旧内核把系统救回来,这就是全部的安全感来源。