Ubuntu 修改默认启动内核攻略:GRUB 原理与避坑实操
作为常年和 Ubuntu 打交道的人我几乎每年都会碰到几次“开机进错内核”的尴尬事。系统更新把新内核装上去了结果显卡驱动没跟上开机直接黑屏或者某个内核版本在特定硬件上有 bug频繁死机。这种时候修改默认启动内核就是最直接的救命手段。这篇文章我把实际用过的几种改默认内核的方法、背后的 GRUB 原理、以及踩过的坑一次性讲清楚不管你是运维、开发者还是刚转 Linux 的爱好者都能照着操作。1. 为什么要改默认启动内核1.1 我遇到过的四种典型场景先说场景不然很多人觉得“内核能跑就行折腾它干嘛”。实际上需要改默认内核的场景非常现实场景一内核升级后驱动不兼容。Ubuntu 的自动安全更新经常会顺手升级内核但第三方驱动并不总是同步跟上最典型的就是 NVIDIA 显卡驱动、部分无线网卡驱动和虚拟化工具。新内核起来后驱动编译失败或直接没被加载表现就是黑屏、分辨率异常、Wi-Fi 消失。我遇到过连续两次更新后显卡驱动失效最后只能锁回旧内核。场景二新内核在特定硬件上有稳定性问题。有些内核版本在特定组合下会有诡异 bug比如 USB 控制器失灵、休眠唤醒后死机、高负载下重启。Ubuntu 的 HWE 系列内核在部分虚拟化主机上出问题的概率不低退回到 GA通用内核往往立刻恢复正常。内核不是越新越好稳定压倒一切。场景三核心软件与内核版本冲突。某些闭源软件、特定数据库或安全模块在较新内核上会出现兼容性问题。有些商业软件安装包明确要求内核版本范围越过之后就各种报错。场景四升级后系统直接起不来。这种最紧急。新内核 panic 或者卡在某个驱动加载机器完全进不了系统。你只能在 GRUB 菜单里手动选旧内核进去。如果不想每次开机都手动选一遍同样要把默认内核固定下来。1.2 先弄明白 GRUB 的启动逻辑在 Ubuntu 上真正决定“开机用哪个内核”的并不是 Ubuntu 本身而是 GRUB 引导器。机器通电后BIOS/UEFI 把控制权交给 GRUBGRUB 读取/boot/grub/grub.cfg里的菜单配置再把选中的内核加载进内存。关键点在于grub.cfg不是手写的而是通过update-grub内部调用grub-mkconfig根据/etc/default/grub和/etc/grub.d/目录下的脚本自动生成的。默认情况下grub.cfg里每个菜单项都有一个从 0 开始的固定序号。我们修改默认启动内核本质上就是告诉 GRUB“默认选第几个菜单项”。这个逻辑想通了后面所有操作就都顺理成章了。改配置文件本身并不难难的是搞清楚编号规则、菜单层级以及改完之后注意哪些坑。2. 动手前先摸清家底查看当前内核与菜单项2.1 查看当前内核版本和磁盘占用操作之前先确认自己正在用的是哪个内核uname -r输出类似5.15.0-91-generic。这个版本号记一下它是当前正在运行的内核也是判断“哪个内核能正常用”的基准。顺便看一眼/boot分区剩余空间内核文件不算小空间不足时升级会失败df -h /boot如果使用率接近 100%说明需要清理旧内核了这个后面专门讲。2.2 列出机器上所有已安装内核用 dpkg 查询dpkg --list | grep linux-image你会看到linux-image-5.15.0-91-generic、linux-image-5.15.0-86-generic这样的条目带installed标记的就是还在系统里的。注意有些系统还会装上linux-image-extra、linux-modules之类的包都是内核的一部分。想整体看某个内核版本装了哪些包可以这样dpkg -l | grep 5.15.0-86真正决定 GRUB 菜单长什么样的是/boot目录下实际的vmlinuz-*和initrd.img-*文件。手动删除这些文件而不清理安装包会导致系统和 dpkg 状态不一致后面会踩坑。查看 GRUB 实际会显示的菜单项grep -E menuentry /boot/grub/grub.cfg输出类似menuentry Ubuntu --class ubuntu ... menuentry Advanced options for Ubuntu ... menuentry Memory test (memtest86) ...这里只显示了三个顶级菜单项具体内核版本都被折叠在 “Advanced options for Ubuntu” 子菜单里。2.3 把 GRUB 菜单项编号彻底搞明白GRUB 的菜单项编号从 0 开始。默认配置下grub.cfg的第一个顶级菜单项是一个名为Ubuntu的项它实际指向最新的内核第二个顶级菜单项是Advanced options for Ubuntu子菜单第三个是内存测试工具如果装了 memtest86。子菜单项的编号格式是“父编号子编号”。比如Advanced options是第 1 项那它的子菜单里第 0 项就是10。常用编号对应关系如下编号菜单项说明0Ubuntu默认项自动加载最新内核1Advanced options for Ubuntu子菜单入口10Ubuntu, with Linux 5.15.0-91-generic最新内核普通模式11Ubuntu, with Linux 5.15.0-91-generic (recovery mode)最新内核恢复模式12Ubuntu, with Linux 5.15.0-86-generic旧内核普通模式13Ubuntu, with Linux 5.15.0-86-generic (recovery mode)旧内核恢复模式2Memory test (memtest86)内存测试这里有个非常容易踩的坑很多新手把GRUB_DEFAULT0理解成“我要选第二个内核”但 0 对应的顶级项Ubuntu会自动加载最新内核。想指定某个具体内核必须写子菜单编号比如12。3. 三种修改默认启动内核的实操方法3.1 方法一改 /etc/default/grub 的 GRUB_DEFAULT这是最通用、最持久的方法配置看得见、摸得着适合长期固定某个内核版本。打开配置文件sudo nano /etc/default/grub找到GRUB_DEFAULT这行默认是GRUB_DEFAULT0如果想固定选择 “Advanced options for Ubuntu” 子菜单里的旧内核5.15.0-86就改成GRUB_DEFAULT12注意引号不能丢。在 shell 里是重定向符号虽然在配置文件里不会经过 shell 展开但加引号是避免问题的最好习惯。如果你希望系统记住“上一次成功启动的项”可以用 saved 模式GRUB_DEFAULTsaved GRUB_SAVEDEFAULTtruesaved模式的逻辑是每次成功启动后GRUB 把当前启动项记录到/boot/grub/grubenv的saved_entry变量里下次启动自动用它。这个模式灵活但我个人不太建议默认使用因为它的“成功启动”判断并不严格有时候系统进了奇怪的状态也会误判为成功等你发现时默认项已经飘到一个你想不到的内核上了。机器上只有你自己这一种使用场景时倒还可以多人共用或多内核测试环境就算了。改完必须重新生成实际菜单配置sudo update-grub这一步不做修改等于白费因为 GRUB 启动时读的是grub.cfg不是/etc/default/grub。3.2 方法二用 grub-set-default 和 grub-reboot 快速切换如果你只是临时想切到旧内核验证问题不想长期改配置grub-set-default更方便sudo grub-set-default 12这个命令把默认启动项写入/boot/grub/grubenv里的saved_entry变量。注意它不会自动修改grub.cfg要在/etc/default/grub里设置了GRUB_DEFAULTsaved才有效。正确组合是先把GRUB_DEFAULT改成saved执行sudo update-grub每次切换只用sudo grub-set-default 12另一个更稳的是grub-reboot它只影响“下一次重启”重启后自动恢复正常默认项sudo grub-reboot 12 sudo reboot这个命令在调试内核时非常香。你可以在完全不改变系统长期配置的情况下让下一次重启进入指定内核。新内核没问题就继续用有问题的话重启后系统仍然是原来的默认项不会因为调试动作留下后患。我强烈建议所有做内核验证的人都养成用grub-reboot的习惯。3.3 方法三让子菜单平铺简化编号逻辑有人觉得12这种编号太绕希望菜单项平铺开。可以通过设置GRUB_DISABLE_SUBMENUnupdate-grub生成菜单时就会把所有内核项平铺在顶级菜单里不再有Advanced options子菜单。此时编号就直观多了0 是第一个内核1 是第二个内核2 是第一个内核的 recovery 模式以此类推。这种方式在桌面环境下很实用开机可以看到完整内核列表选择起来不用进二级菜单。但要注意平铺会导致 GRUB 菜单项变多如果机器上有六七个旧内核菜单会很长而且编号都变了之前写的GRUB_DEFAULT12之类也要重新计算。所以它更适合“内核数量少、编号稳定”的机器。3.4 改完必须 update-grub并验证生效更新配置后重新生成sudo update-grub正常执行时会输出类似Found linux image: /boot/vmlinuz-5.15.0-91-generic的信息借此确认系统识别到了哪些内核。不重启验证当前默认项可以先查 grubenvgrub-editenv list能看到recorded_entry、saved_entry等变量。也可以直接检查生成的配置grep set default /boot/grub/grub.cfg输出会显示set default0或set default${saved_entry}对应你设置的值。这一步很便宜几十秒的事能避免重启后一脸懵。注意update-grub只会重新生成 GRUB 菜单不会自动重启系统。很多人改完配置直接慌慌张张重启结果发现没生成配置白白浪费一次重启时间。先把配置验证这一步做扎实。4. 常见问题与避坑实录4.1 改了编号重启还是进最新的内核八成是子菜单编号没写对。前面强调过顶级菜单项和子菜单项是两回事。GRUB_DEFAULT12指的是 “Advanced options” 子菜单里的第 2 项GRUB_DEFAULT1指的是顶级菜单第 1 项 “Advanced options” 本身这会让系统停在 GRUB 菜单的二级界面而不是直接引导内核。有些 GRUB 版本还支持直接用菜单 title 字符串匹配比如GRUB_DEFAULTUbuntu, with Linux 5.15.0-86-generic。这种写法看着直观但菜单 title 里如果有特殊字符、空格或者括号匹配规则会很严格一不小心就匹配失败。我自己的习惯是先grep menuentry /boot/grub/grub.cfg把菜单全部列出来核对编号再改配置绝对不凭印象写。4.2 每次内核更新后配置就失效了这属于正常现象。update-grub生成新grub.cfg时会根据当前/boot目录下的内核文件重新计算菜单项顺序。新内核装进来后编号会变成第一个你之前写死的数字编号自然就错位了。解决办法有两个方向一是使用saved模式让系统记住上次成功启动的项二是定期清理旧内核保持菜单项数量稳定。我个人更倾向于后者。菜单干净了编号就稳定了出问题也容易排查。比如只保留当前内核和上一个内核那 “Advanced options” 子菜单里永远只有四个项两个普通 两个 recovery编号永远不变。4.3 开机总是进 recovery moderecovery 模式的菜单项一般紧挨着普通内核项编号只差 1。比如普通模式是10恢复模式就是11。如果你把GRUB_DEFAULT写成11那就稳稳进恢复模式了。判断方式很简单recovery 模式下系统会加载额外的调试服务通常不会有完整图形界面而且启动过程会看到明显的恢复菜单提示。另外/etc/default/grub里的GRUB_CMDLINE_LINUX_DEFAULT参数也值得看一眼。有些人调试时会往里面加single或emergency结果是每次启动都进单用户模式看起来和 recovery 模式很像但实际上是内核启动参数的问题和菜单编号无关。4.4 清理旧内核释放空间这也是和默认内核强相关工作。/boot分区给的比较小的服务器尤其容易爆掉内核多了 apt 升级都跑不动。清理命令很简单sudo apt purge linux-image-5.15.0-86-generic最关键的是别把当前正在用的内核或者你计划回滚的目标内核删了。删当前内核时dpkg 会给出警告提醒你 “You are trying to remove a running kernel”这时候必须停手。万一真删了系统还能靠 GRUB 里残留的旧菜单项启动但内核文件残缺很麻烦。清理后同样要执行sudo update-grub。注意如果你自己手动编译过内核/boot下会有额外的vmlinuz和initrd.imgapt purge不会清理它们。删除前务必确认对应的/lib/modules/版本号目录也删掉否则会出现“有内核映像但没有模块”的残缺状态启动后网卡、文件系统驱动可能加载不了。4.5 UEFI 与虚拟化环境的额外坑UEFI 启动的机器和传统 BIOS 启动的机器GRUB 的配置文件位置不一样。UEFI 模式下/boot/efi/EFI/ubuntu/grub.cfg会有一层薄壳配置通常引用到/boot/grub/grub.cfg。如果你手动改动过 UEFI 启动项可能出现菜单不生效的情况。先判断启动模式[ -d /sys/firmware/efi ] echo UEFI || echo BIOS虚拟化环境里还有一个小坑云主机的控制台 VNC 画面里GRUB 菜单可能显示但键盘输入不响应。这种时候建议把GRUB_TIMEOUT调大一点比如GRUB_TIMEOUT10给自己留出足够选择时间避免没来得及选就进了默认项。5. 实操心得与建议用 Ubuntu 做主力系统这几年改默认内核这个操作使用频率不算高但每次用都是关键时刻容错率很低。我有几个沉淀下来的习惯分享出来。第一改完配置一定先执行sudo update-grub再重启。GRUB 读取的是grub.cfg不是/etc/default/grub。这个链路明白了就不会漏步骤。我见过太多次改完文件忘了生成配置重启发现什么都没变然后开始怀疑人生的案例。第二重启之前先看一眼grub-editenv list。如果saved_entry指向的项不是你想要的别急着重启宁可多花一分钟核实清楚。这比重启之后发现问题再回头改配置省事得多。第三写编号之前先把菜单列出来。一条grep menuentry /boot/grub/grub.cfg命令就能把所有菜单项和顺序打出来。机器上内核版本多、装了 memtest86 这些额外菜单项的时候凭记忆写编号百分之百要出事。第四临时验证内核用grub-reboot。它只影响下一次启动调试完系统自动回归正常不留下需要回滚的配置痕迹。这个习惯能帮你避免很多不必要的“抢救现场”。有一次我在一台生产服务器上测试一个候选内核就是用grub-reboot把下一次启动指向它验证稳定后又继续跑了一段时间全程没有动过系统默认配置。最后再分享一个小技巧如果你经常在多内核环境里做测试可以写一个简单的 shell 函数把“列出菜单项 设置默认项 update-grub”串起来。比如这样function kernel_pick() { grep menuentry /boot/grub/grub.cfg | nl -v 0 echo -n Enter entry index: read idx sudo sed -i s/^GRUB_DEFAULT.*/GRUB_DEFAULT\$idx\/ /etc/default/grub sudo update-grub }把它放到~/.bashrc里以后切内核只需敲一个命令。工具的意义在于把高频操作固化下来而不是每次手动去翻配置文件。这个函数虽然简单但在多内核调试场景下确实能省不少时间。