1. 把新内核变成默认启动项,到底难在哪里
前阵子帮人排查一台业务机器,uname -r 显示 5.10,但 /boot 目录里明明躺着一个编译好的 5.15 内核,grub 菜单里也看得见,可重启之后就是进不去新内核。查了半天,问题不是内核本身,而是新版内核编完之后缺少 initramfs 镜像,引导程序加载不了根文件系统,只能回退到旧内核。这事让我意识到,很多人对"编译内核"的理解停留在 make 和 make install,真正容易翻车的恰恰是后续那几步:initramfs 生成、grub 菜单刷新、模块与内核版本匹配。
这篇文章就把整套流程讲透:从编译完内核到让它真正接管系统,中间要走的每一步、每个命令背后在干什么、哪些环节容易踩坑、以及怎么把旧内核安全地清掉。适合两类人,一类是自己编译内核做功能裁剪或性能优化的开发者,另一类是想升级内核又不敢直接换发行版内核包的运维人员。看完之后你会明白,Debian 下"替换内核"其实不是真的删除旧内核,而是让新内核按正确的顺序、完整地进入引导链。
2. 动手之前先理清楚 Debian 的引导链
2.1 内核文件在系统里到底有哪些"分身"
一个能正常启动的 Linux 内核,在 Debian 里至少包含三样东西。第一个是内核本体,也就是压缩过的 vmlinuz-xxx,放在 /boot 下,grub 加载它之后才能解压并进入内核初始化。第二个是 initramfs-xxx,它本质是一个微型的根文件系统镜像,内核启动早期、真正的根分区还没挂载时,由它负责加载磁盘控制器驱动、文件系统驱动,然后完成根目录切换。第三个是 /lib/modules/xxx-xxx 这个目录,里面是配套的驱动模块,内核启动后按需加载。
注意一点,vmlinuz 和 initramfs 的名字里那个版本号,必须和 /lib/modules 下的目录名完全一致。这个版本号由编译时候的 CONFIG_LOCALVERSION 和 Makefile 里的 VERSION、PATCHLEVEL、SUBLEVEL、EXTRAVERSION 共同决定。如果编译时开了 CONFIG_LOCALVERSION="-custom",那目录名会是 5.15.6-custom。三处名字对不上,模块加载就会失败,或者 grub 根本找不到对应的 initramfs。这个对应关系,就是引导链的"身份证"。
2.2 update-initramfs 和 update-grub 各自负责什么
理解这两个命令的分工,就理解了内核替换的核心逻辑。
update-initramfs 负责为指定版本的内核生成 initramfs 镜像,它会把内核模块目录里必要的驱动打进去,生成一个 cpio 格式的压缩包放在 /boot/initrd.img-版本号。如果你改了根文件系统类型(比如从 ext4 换成 xfs)、加了 LVM、或者新内核里把某个驱动编成了模块而不是内建,都必须重新生成 initramfs,否则内核起来之后挂不上根分区,启动就会卡在 "target filesystem doesn't have requested /sbin/init" 或者直接 panic。
update-grub 则是扫描 /boot,读取 /etc/default/grub 的配置,重新生成 grub.cfg。它会把 /boot 下所有 vmlinuz-* 和 initrd.img-* 配对,按顺序写进启动菜单。默认情况下,版本号最高的内核排在最前面,新内核自然变成默认启动项。Debian 里 make install 这个命令之所以神奇,就是因为它在结尾会调用一个 installkernel 钩子脚本,自动帮你把这两个命令都执行掉。这一点和 Arch 那种需要手动 mkinitcpio 的做法不同,但也正因为自动化程度高,很多人反而不清楚中间发生了什么,出了问题也不知道该排查哪里。
提示:make install 在 Debian 上的行为依赖 /sbin/installkernel 脚本。如果你曾经换过内核相关的工具链,或者系统里装了 kernel-package 这类包,这个脚本的行为可能有变化,建议跑完 make install 后主动检查一下 initramfs 是否真的生成了。
2.3 为什么说"替换"天然应该是"共存"
很多人第一次干这事,习惯性地想"把旧的删了,新的放上去"。这种思路在 Debian 这里非常危险。原因很简单,新内核万一起不来,你需要在 grub 菜单里还能选回旧内核。如果删得干干净净,机器变砖就只能用 live CD 去修。所以我的做法永远是:新内核先进来,确认稳定跑几天,再做清理。这在后面的章节会展开讲,这里先把这个观念立住:Debian 的引导机制天然支持多内核共存,我们应该顺应它而不是对抗它。
3. 编译安装的完整流程与参数选择
3.1 编译前的配置与检查清单
拿到内核源码之后,第一步不是直接 make,而是先把配置填好。如果你只是想升级内核版本、保留现有系统的所有特性,最快的方式是把当前运行内核的配置直接拿过来用:
cp /boot/config-$(uname -r) .config make olddefconfig第一行把当前内核的配置复制为 .config,第二行的 olddefconfig 会用新源码里默认值补齐旧配置里没有的新选项。这一步比 oldconfig 省事,因为 oldconfig 每个新选项都问你一遍,老手用旧配置时通常会选 olddefconfig 而不是 oldconfig——也就是说新选项全部采认默认值。这适合绝大多数场景,如果有特别关心的新特性,之后再 make menuconfig 单独勾选。
配置阶段一定要确认的几项:CONFIG_LOCALVERSION(决定模块目录后缀)、CONFIG_MODULES(模块支持,几乎必开)、CONFIG_DEVTMPFS(启动早期 /dev 的管理,关了很麻烦),以及你的根文件系统对应的驱动是内建(y)而不是模块(m)。比如根分区是 ext4,CONFIG_EXT4_FS 至少要编成 y。如果你拿不准,最简单的办法就是基于当前内核配置改,因为当前能跑,新配置基本不会差太远。
3.2 编译参数对安装结果的影响
配置好了就是编译:
make -j$(nproc) deb-pkg或者传统两步:
make -j$(nproc) make modules_install make install我一般在 Debian 上更推荐 deb-pkg 方式,它会直接生成 linux-image-xxx.deb 和 linux-headers-xxx.deb,装完之后 dpkg 的数据库里就有了这个内核的记录,后续卸载、清理都走包管理器,和发行版自带的包行为一致。传统三步走虽然也没问题,但它不会往 dpkg 数据库里登记,卸载时必须自己手动删文件,比较麻烦。
-j 参数后面的数字是并行编译的线程数,一般取 CPU 核数的 1.5 到 2 倍。比如 8 核机器用 -j12 或 -j16 都行。内存如果小于 4GB,建议不要开太大的并行度,编译过程中每个编译进程大概要吃几百兆内存,并行度太高容易 OOM。
3.3 分步执行时每步做了什么
如果你选择传统三步,这里格外强调一下每步的作用。
make modules_install 负责把编译出来的 .ko 模块安装到 /lib/modules/内核版本号/ 目录下。这个命令不会碰 /boot,所以它不产生引导相关文件。
make install 是最后一步,它会安装内核本体到 /boot/vmlinuz-版本号,生成 /boot/initrd.img-版本号(通过 installkernel 钩子调用 update-initramfs),然后跑 update-grub 刷新引导菜单。Debian 上这一步非常"自动化",如果你发现 make install 之后 /boot 里没有 initrd 文件,大概率是 installkernel 脚本出了问题,或者 /etc/initramfs-tools 的配置有异常。
用 deb-pkg 方式的话,dpkg -i 安装 .deb 时会执行 postinst 脚本,同样会触发 initramfs 和 grub 的更新,逻辑是一样的。
4. 实操记录:新内核如何一步步上位
4.1 一个从零到生效的完整流程实录
下面是一个在我测试机上完整跑过的流程,每一步都有实际输出,做参考很直观。
查看当前状态:
$ uname -r 5.10.0-25-amd64 $ ls /boot/ config-5.10.0-25-amd64 grub initrd.img-5.10.0-25-amd64 System.map-5.10.0-25-amd64 vmlinuz-5.10.0-25-amd64编译安装新内核,这里用 5.15.6 为例:
$ cd /usr/src/linux-5.15.6 $ cp /boot/config-5.10.0-25-amd64 .config $ make olddefconfig $ make -j$(nproc) deb-pkg ... $ ls ../*.deb linux-image-5.15.6_5.15.6-1_amd64.deb linux-headers-5.15.6_5.15.6-1_amd64.deb安装:
$ dpkg -i linux-image-5.15.6_5.15.6-1_amd64.deb ... Setting up linux-image-5.15.6 (5.15.6-1) ... update-initramfs: Generating /boot/initrd.img-5.15.6 update-grub: ... Found linux image: /boot/vmlinuz-5.15.6 Found initrd image: /boot/initrd.img-5.15.6 Found linux image: /boot/vmlinuz-5.10.0-25-amd64 Found initrd image: /boot/initrd.img-5.10.0-25-amd64此时再看 /boot:
$ ls /boot/ config-5.10.0-25-amd64 initrd.img-5.15.6 initrd.img-5.10.0-25-amd64 ... vmlinuz-5.15.6 vmlinuz-5.10.0-25-amd64重启之后 uname -r 应该显示 5.15.6。grub 菜单里两个内核都在,新内核排在第一个。
4.2 如何确认新内核真的"上位"了
重启只是第一步。我更建议进系统之后做三件事。
第一,确认内核版本:
uname -r uname -a第二,检查关键模块是否加载正常:
lsmod | grep -E "ext4|nvme|virtio"第三,检查 dmesg 有没有异常:
dmesg | grep -i error dmesg | grep -i fail尤其是如果你从 5.10 跳到 5.15,中间跨了好几个大版本,第三方驱动模块(比如 NVIDIA 闭源驱动)是否要重新编译,务必在重启前就想好。系统里有 DKMS 的话,它会在新内核安装时自动编译模块。没有 DKMS 的驱动,只能手动编一次,这个是最容易漏的环节。
4.3 不用新内核时的启动参数调整技巧
有时候新内核装上去了,但你想让它先试运行而不是固定用——比如要临时加一个启动参数排查问题。grub 菜单里选中新内核按 e 键,可以临时编辑启动项,在 linux 那一行末尾加参数,比如:
... ro quiet splash nomodeset编辑完按 Ctrl-X 或 F10 启动。这只是临时生效,下次重启又变回默认配置。如果想永久加参数,改 /etc/default/grub 里的 GRUB_CMDLINE_LINUX 或 GRUB_CMDLINE_LINUX_DEFAULT,然后 update-grub。这个技巧在处理新内核导致的黑屏、显卡驱动异常问题时非常有用。
5. 新旧内核共存的正确姿势与回滚方案
5.1 先跑几天再清旧内核的时间线建议
新内核确认没问题之后才考虑清理旧内核。我个人习惯是把新内核至少跑满一周,覆盖一到两次完整重启,确认业务进程、网络、存储都没问题,再动手清理。操作上不是直接删 /boot 下的文件,而是卸载旧的 linux-image 包:
apt remove --purge linux-image-5.10.0-25-amd64卸载过程中会触发 update-initramfs 和 update-grub,把旧内核的 initrd 和 grub 菜单项一并清理掉。如果用 make install 手动装的新内核而不是 deb 包,那你要看清楚新内核有没有对应的 dpkg 记录。
手动装的内核(没有 deb 包)在 dpkg -l 里是查不到的,卸载只能手动:删除 /boot 下的 vmlinuz、initrd.img、System.map、config,删除 /lib/modules/对应目录,然后 update-grub。这类内核我在生产环境不太推荐,因为升级容易回滚难,管理负担全在手工。
5.2 apt remove 之后 autoremove 的边界
卸载旧内核后,有些人习惯跑 autoremove 清理孤儿依赖。这个命令会移除自动安装且不再被依赖的包,但它有时候会把你想保留的东西也顺走。经验之谈:autoremove 之前先看一眼它列出的清单,里面如果出现 linux-image、linux-headers 相关字样,要多留个心眼。更稳妥的做法是不用 autoremove,而是定期手动 dpkg -l | grep linux-image 检查包列表。
5.3 回滚到旧内核的三种常见场景
场景一:新内核起不来,grub 菜单还能进。直接在菜单里选旧内核,进系统后把新内核的 deb 包 remove 掉即可回滚。这个是最幸运的情况,因为引导链是完整的,没有丢失任何环节。
场景二:新内核起不来,连 grub 菜单都看不到,但 grub 引导程序本身还在。此时在 grub 界面按 Esc 或按住 Shift(视固件类型而定),尝试调出菜单,选 Advanced options 进旧内核。Debian 默认倒计时是有的,但不显示菜单,只有手动按键才会显示,很多新手在这一步被吓到。
场景三:新内核把系统搞得完全启动不了,grub 也进不去。这时候只能用 live USB 启动,chroot 进原系统的根分区,然后执行 update-grub,或者直接卸载新内核包。chroot 的核心操作是:
mount /dev/sda1 /mnt mount --bind /dev /mnt/dev mount --bind /proc /mnt/proc mount --bind /sys /mnt/sys chroot /mnt update-grub为什么这里要强调 "新内核起不来别慌"?因为只要旧内核的镜像还在 /boot,引导链就是完整的。所以前面说的"先共存再清理"不是保守,是保命。
6. 清理旧内核的时机与方法
6.1 什么时候算"可以清理"了
判断条件再多说一句:不光是能启动就算稳,还要看几个细节。电源管理是否正常(休眠/挂起能恢复)、文件系统挂载是否所有分区都正常(特别是有网络文件系统或加密卷的机器)、关键服务是否随开机自启成功。内核升级最阴间的坑往往是那些不会立刻暴露的问题,比如数据盘挂载延迟、网络驱动性能退化、GPU 性能下降甚至温度异常。所以时间维度上多跑几天,比功能维度上多测几个场景更靠谱。
6.2 手动安装内核的清理步骤
如果你之前是手动 make install 装的内核,清理时就要全手工了。清理内容按这个顺序做:
rm /boot/vmlinuz-5.15.6 rm /boot/initrd.img-5.15.6 rm /boot/System.map-5.15.6 rm /boot/config-5.15.6 rm -rf /lib/modules/5.15.6 update-grub注意顺序,先把文件删完再刷新 grub,否则 grub 菜单里会留下指向不存在文件的坏条目。不过说实话,与其这样手动搞,不如一开始就养成用 deb-pkg 生成安装包的习惯,至少卸载时一个 remove 全搞定。
6.3 /boot 空间不足时的特殊处理
/boot 单独分区且容量只有 200MB 的话,编译安装新内核经常在 copy 镜像时因空间不足而失败。此时有两个思路:一是先卸载几个旧内核腾出空间,二是干脆不把 initramfs 放 /boot,改放在 /boot 之外的位置,在 grub 配置里用相对路径或显式路径指向它。第一个思路简单,第二个思路配置复杂,不建议新手搞。这里更推荐一个常规做法:编译前先 dpkg -l | grep linux-image 看当前有几个内核,把最老的先卸掉,给新内核腾位置。
提示:不要随手在 /boot 里手动 rm 旧内核文件而不卸包。dpkg 数据库和实际文件对不上,后面所有包管理操作都可能出问题,而且这种损坏你很难一下子就意识到。
7. 常见问题与排查记录
7.1 make install 之后没有生成 initramfs
表现是 /boot 里有新的 vmlinuz,但没有 initrd.img-新版本,重启后 grub 菜单里新内核项后面没有 initrd 行,或者直接无法启动。
排查思路:先确认 installkernel 脚本是否存在、是否可执行:
ls -l /sbin/installkernel dpkg -S /sbin/installkernelDebian 上这个脚本一般属于 initramfs-tools 包,内容就是调用 update-initramfs。脚本丢了或者被改过,就会静默失败。另一种可能是 /etc/initramfs-tools/initramfs.conf 里配置有问题,比如 MODULES=most 在解包某些特殊驱动时报错。这种情况下,手动补一步:
update-initramfs -c -k 5.15.6-c 是生成新镜像,-k 指定内核版本。如果补生成成功,再 update-grub 刷新菜单即可。
7.2 grub 菜单里找不到新内核
update-grub 扫不到 vmlinuz,最常见的原因是 grub 不认识这个文件名。比如你是手动把 vmlinuz-5.15.6 复制进 /boot 的,而不是通过 dpkg 或 make install,那么权限、文件格式可能都有隐藏问题。先检查:
file /boot/vmlinuz-5.15.6 grub-mkconfig 2>&1 | grep 5.15.6grub-mkconfig 的输出如果没有 5.15.6 相关行,说明它的扫描逻辑里没把这个文件当内核。Debian 的 os-prober 和 grub 扫描 /boot 时认的是文件名前缀 vmlinuz- 和 initrd.img-,只要命名规范、文件真实存在,一般都能扫到。扫不到的情况十有八九是文件权限不对(比如 000),或者 /boot 挂载有问题。
7.3 新内核启动后文件系统挂载不了
典型表现是启动过程中卡住,然后报 "ALERT! UUID=xxx does not exist",或者直接进入 initramfs 的救援 shell。
这种问题十有八九是根文件系统驱动没有编进 initramfs。比如你从 ext4 换成了 btrfs,但新内核的 CONFIG_BTRFS_FS=y 没有生效,或者编成了模块但 initramfs 里没带进去。进救援 shell 后可以确认一下有哪些驱动:
ls /lib/modules/$(uname -r)/kernel/fs/btrfs/没有的话,说明模块编译或者安装有问题。有的话,多半是 initramfs 生成时没纳入,重新生成一把:
update-initramfs -c -k 5.15.67.4 DKMS 模块在新内核下报错
DKMS(Dynamic Kernel Module Support)用来解决"内核升级后第三方模块需要重新编译"的问题。如果你装了 NVIDIA 这类驱动,它一般通过 DKMS 管理。新内核安装后,你在 dmesg 里看到模块加载失败,先看 DKMS 有没有自动编译:
dkms status如果状态显示 installed 的是旧内核对应版本,新内核显示 absent,那就在新内核上手动构建:
dkms install -m <module-name> -v <version> -k 5.15.67.5 新内核性能反而变差如何排查
不是所有新内核都比旧内核快,功能增多带来的是更重的调度器、更多的安全校验,某些老硬件上甚至明显变慢。性能变差的排查思路有几个方向:看是否启用了内核的省电策略,检查 cpufreq 调节器是否处于 performance 模式而不是 powersave,检查透明大页(THP)策略是否变化,看内核日志里有没有频繁的降频记录:
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor cat /sys/kernel/mm/transparent_hugepage/enabled如果确认新内核不适合当前硬件,回到旧内核是最快的解法,这就是前面强调共存的价值。
8. 最后的实际操作心法
内核替换这件事,本身不复杂,但要求每一步的"系统状态"都对得上:编译配置对应模块目录、initramfs 对应引导加载、grub 菜单对应文件真实存在。我自己玩过几次之后的最深体会是:Debian 的自动化工具有时候会让人觉得"跑完命令就完事了",但编译器修修改改、脚本环境变来变去,自动化的每一步都值得用 ls 和 grep 确认一遍输出。开机几毫秒的功夫,前面少了一个 initrd,后面就是一片救援 shell。
再分享一个能省不少事的小技巧:正式替换内核之前,先在虚拟机里把新内核的 .deb 包构建好,然后在目标机器上直接 dpkg -i。这样即使安装出问题,也能非常干净地回退,不用在目标机器上跑完整编译,时间和风险都小很多。至于编译参数,别一上来就想着最优化裁剪,先把"能跑、能启动、能挂载根分区"这三件事坐实,后续再慢慢折腾裁剪的事,不然一次把驱动的线剪错了,排查起来非常痛苦。