升级Linux内核是一个有一定风险的操作,不过如果操作系统的内核太老了,也可能导致部分新的应用程序无法运行,此外升级内核也可能能够提升一些性能等。
下面将以Debian发行版为例,讲解两种升级内核的方法,即使用包管理器和手动编译,大家根据情况选择其一即可。
1,基于包管理器升级
使用包管理器升级内核是推荐的方法,我们可以通过下列命令搜索查看可安装的内核版本:
apt list | grep linux-image
结果如下:
可见内核软件包的命名规则为:
linux-image-版本号-类型-架构
有的内核软件包没有“类型”这一部分例如linux-image-6.1.0-11-amd64,说明是通用的内核,我们一般安装这种即可,对于有类型部分的包名,类型通常如下:
rt对实时性应用程序优化的内核,提供更快的响应时间lowlatency针对低延迟应用程序优化的内核cloud适用于云计算的内核server适用于服务器的内核generic等同于没有类型的内核,即通用内核
事实上,不同类型内核一般是配置不同,实质是一样的。
此外,往下翻我们还能发现版本号带有+bpo字样的内核包:
通常遵循下列命名方式:
linux-image-版本号+bpo-类型-架构
这种内核通常更新,但是稳定性可能不佳,来自于backports软件源。
知道了软件包及其版本,我们直接使用apt安装就可以完成内核升级工作,例如:
sudo apt install linux-image-6.1.0-31-rt-amd64
sudo apt autopurge
sudo apt clean
重启系统,在grub引导界面选择新安装的内核版本的选项即可:
启动后执行uname -r命令可查看内核版本,确定是否更新成功。
带有
dbg等其它后缀的软件包通常是包含调试符号、用于内核驱动开发的内核,这里我们先不关注。
2,源码编译安装
大多数情况下,不建议使用源码编译安装的方式升级内核,因为官方的内核通常比较新,我们现有的发行版不一定适配得很好,且绝大多数闭源驱动(例如NVIDIA、AMD显卡驱动)很可能会与最新内核不兼容,导致内核安装失败。
不过,如果你有一些定制化的需求,比如需要自定义内核编译配置等,或者单纯需要更加新的内核,则可以进行源码编译安装的方式。
(1) 下载源码包
进入Linux内核源码官方网站:传送门
选择stable或者longterm版本,点击对应版本右侧的tarball链接下载,建议下载最新的longterm版本,兼容性相对更好:
例如下载后得到linux-6.13.3.tar.xz文件,进行解压,解压后将得到linux-6.13.3文件夹,进入源码文件夹:
tar -xvf linux-6.13.3.tar.xz
cd linux-6.13.3
内核目录中文件如下:
如果要下载任意的一个不在主页显示的版本,可以先任意点击一个browse按钮进入Git仓库:
点击summary标签页,在下面Tag部分就可以看到所有版本:
点击右边Download列中对应版本项即可下载其源码压缩包。
(2) 安装依赖
编译内核需要安装一些基本依赖工具,执行下列命令:
sudo apt install cpio kmod flex bison bc build-essential libssl-dev libelf-dev libncurses-dev libdw-dev
(3) 配置内核
如果你只是单纯的升级内核,我们可以将现有的内核配置复制过来,保持当前配置即可:
cp /boot/config-$(uname -r) .config
yes '' | make oldconfig
反之,如果你对内核配置比较熟悉,且需要自定义一些配置,则可以执行下列命令进入配置界面:
make menuconfig
或者,也可以使用下列命令生成一个通用默认配置:
make defconfig
最终,出现configuration written to .config说明已完成配置:
大多数情况下,建议使用本机当前内核配置文件。
(4) 编译安装
有两种编译安装的方式:
- 直接编译源码并安装至系统
- 编译源码并打包为
deb安装包,然后使用包管理器安装
这里将分别介绍这两种方式,大家根据喜好选其一即可。
① 直接编译安装
在源码目录中,执行下列命令完成编译安装:
# 编译源码
make -j$(nproc)
# 安装内核模块
sudo make modules_install
# 安装内核
sudo make install
make的-j参数表示指定多线程编译,nproc命令是用于输出当前处理器核心数的,这里指定给了-j,也可以指定为固定线程数例如-j8表示使用8线程编译。
编译内核通常耗费的时间很长,以R5 7600X处理器为例,使用12线程编译完成大概需要35分钟左右。
如果编译失败或者中断,想要重新编译,则执行下列命令清理已编译结果:
make mrproper
清理完成后,需要重新配置内核,再进行编译步骤。
② 编译打包为deb再安装
内核源码也支持一键打包为deb包,然后使用包管理器安装,这种方式比较推荐,因为后续我们可以统一使用包管理器管理多个版本内核。
要打包为deb,我们还需要安装一些额外工具,执行下列命令:
sudo apt install git rsync debhelper
然后在内核源码目录下,我们需要先创建一个git仓库,否则无法进行后续打包工作:
git init
git add .
git commit -m "message"
提交信息可以随意编写,若commit失败则可能是没有配置提交人信息导致,执行下列命令:
# 配置提交人信息
git config --global user.name "username"
git config --global user.email "example@example.com"
然后就可以开始编译构建工作了:
make -j$(nproc) deb-pkg LOCALVERSION='' KDEB_PKGVERSION=$(make kernelversion)
可见这里有以下要点:
- 使用
deb-pkg表示编译构建为deb安装包 - 设定了构建变量
LOCALVERSION,该变量用于设定编译后内核版本号后缀,例如你设定LOCALVERSION=-custom,那么编译后得到的内核版本号就是6.13.3-custom,由于默认情况下构建deb安装包时,版本号会有一个后缀+,因此这里出于“强迫症”就设定版本号后缀为空字符串''了,确保版本号是干净的 - 设定了构建变量
KDEB_PKGVERSION,该变量用于设定打包后deb包的版本号,默认是内核版本-一串hash值,也是很不美观,因此这里直接置为内核版本号即可,使用make kernelversion命令可以输出当前编译的内核的版本号
编译完成后,deb安装包会生成在上一级目录下,通过下列命令查看:
ls ../ | grep '.deb$'
结果如下:
可见有4个安装包,它们分别是:
linux-image-xxx_xxx_amd64.deb内核镜像包,包含了编译好的内核和内核模块,这个包就是我们要安装的实际内核linux-headers-xxx_xxx_amd64.deb内核头文件包,包含了编译内核模块所需的头文件,开发者在编写或编译内核模块时需要这个包linux-image-xxx-dbg_xxx_amd64.deb内核调试信息包,包含了调试内核所需的符号和调试信息linux-libc-dev_xxx_amd64.deb内核C库开发包,提供了用户空间程序在与内核交互时所需的头文件和库函数
一般来说,我们只需要安装linux-image-xxx_xxx_amd64.deb内核镜像即可,使用dpkg -i进行安装:
# xxx替换成你的实际版本号
sudo dpkg -i linux-image-xxx_xxx_amd64.deb
如果需要安装或者编译专有驱动,则通常还需要安装linux-headers-xxx_xxx_amd64.deb这个内核头文件包。
到此,内核升级完成,重启系统并执行命令uname -r即可查看当前内核版本:
(5) 编译问题汇总
① 证书文件debian/canonical-certs.pem不存在导致编译失败
如果你在Ubuntu或Linux Mint等基于Ubuntu的发行版编译内核,那么很可能会遇到下列错误:
make[6]: *** No rule to make target 'debian/canonical-certs.pem', needed by 'certs/x509_certificate_list'. Stop.
make[5]: *** [scripts/Makefile.build:478: certs] Error 2
make[4]: *** [Makefile:1945: .] Error 2
make[3]: *** [debian/rules:76: build-arch] Error 2
dpkg-buildpackage: error: make -f debian/rules binary subprocess returned exit status 2
make[2]: *** [scripts/Makefile.package:126: deb-pkg] Error 2
make[1]: *** [/home/oge/Downloads/linux-6.12.39/Makefile:1566: deb-pkg] Error 2
make: *** [Makefile:224: __sub-make] Error 2
导致这个问题的原因是:Ubuntu等发行版的内核是使用了专有的证书用于模块签名验证和安全启动的,因此我们从系统复制来的配置文件/boot/config-xxx中也包含了生成该专有证书的配置。
然而,官方下载的内核源码并不包含生成Ubuntu专有证书的规则和资源文件,我们复制了系统配置并编译时,就会导致构建过程中仍然尝试生成该专有证书,结果生成失败。
解决这个问题,我们首先清理已构建部分,然后重新配置内核,并将证书列表设为空,重新编译即可:
# 清理已构建
make mrproper
# 重新配置
# 先复制系统配置
cp /boot/config-$(uname -r) .config
# 禁用证书强制校验并将依赖证书文件列表置为空
scripts/config --disable CONFIG_MODULE_SIG_FORCE
scripts/config --set-str CONFIG_SYSTEM_TRUSTED_KEYS ""
scripts/config --disable CONFIG_SYSTEM_REVOCATION_KEYS
# 构建配置
yes '' | make oldconfig
然后执行上述步骤(4)编译安装即可,根据实际情况直接编译或构建为deb安装包。
3,移除不要的内核
如果安装了很多内核,通常可以移除一部分,不过建议除了最新的内核之外,还是保留一个稳定版本的内核,防止启动失败。
(1) 包管理器安装的内核移除
对于包管理器安装的内核,我们直接卸载即可,执行下列命令查看我们安装了哪些版本的内核:
# 列出已安装内核镜像
apt list --installed | grep linux-image
# 此外,还可以列出已安装的内核头文件包
apt list --installed | grep linux-headers
结果:
使用sudo apt purge卸载你需要移除的内核即可。
(2) 手动安装的内核移除
如果说是使用make install手动安装的内核,那么就稍微麻烦一点了,因为手动安装的内核是不会显示在apt list里面的,就要手动删除。
我们可以先来了解一下内核相关文件存放位置,在手动安装时,内核文件安装在下列位置:
/boot目录下:存放的是内核镜像等和启动相关内核文件,执行make install时会将相关文件安装到该目录下/lib/modules目录下:内核模块,通常是驱动以及其它系统相关模块,执行make modules_install时会将相关文件安装到该目录下
首先查看/boot目录如下:
可见一个版本的内核对应了多个相关文件,以6.13.3版本为例:
config-6.13.3内核编译时的配置文件,它包含了在编译过程中使用的各种选项和参数设置initrd.img-6.13.3initrd(initial RAM disk)是一个临时磁盘镜像,它包含了必要的驱动程序和工具,用于在系统启动过程中挂载真正的根文件系统System.map-6.13.3存放了编译内核时定义的内核函数和它们的内存地址之间的关系表vmlinuz-6.13.3经过编译的内核的可执行映像文件,在Linux系统启动过程中,这个文件会被加载到内存中并由内核第一阶段程序解压和运行
删除对应版本的内核文件即可,例如:
# 删除6.12.9+bpo-rt-amd64版本内核文件
sudo rm /boot/config-6.12.9+bpo-rt-amd64 /boot/initrd.img-6.12.9+bpo-rt-amd64 /boot/System.map-6.12.9+bpo-rt-amd64 /boot/vmlinuz-6.12.9+bpo-rt-amd64
然后查看/lib/modules目录:
可见一个内核版本对应的模块文件,都以一个文件夹形式存放在该目录下,删除对应版本即可,例如:
# 删除6.12.9+bpo-rt-amd64版本内核模块文件
sudo rm -rf /lib/modules/6.12.9+bpo-rt-amd64
此外,我们发现根目录下还有符号链接:
这些符号链接指向了当前实际使用的内核文件,但如果删除了其指向就不存在了,我们删除链接,然后重新创建:
# 删除链接
sudo rm /initrd.img /initrd.img.old /vmlinuz /vmlinuz.old
# 重新链接至新内核
sudo ln -s /boot/initrd.img-6.13.3 /initrd.img
sudo ln -s /boot/vmlinuz-6.13.3 /vmlinuz
最后重新生成镜像并更新GRUB引导即可:
sudo update-initramfs -u
sudo update-grub
到此,内核的升级以及旧版本清理工作完成,在实际情况下需要谨慎升级内核。
4,多版本内核GRUB启动项配置
默认情况下以Debian操作系统为例,无论我们安装多少个内核,GRUB默认是使用最新的内核作为默认的启动项Debian GNU/Linux的,如果你需要配置对应的内核作为默认启动项,或者自定义启动项,则需要修改GRUB引导配置。
这里介绍两种自定义GRUB启动配置的方法:手动编辑配置文件,或者使用grub-customizer图形化工具进行配置,大家视情况选择其一即可。
在编辑配置之前,我们需要知道系统根路径/所挂载的分区的UUID,首先使用下列命令查看挂载情况以及分区UUID的值:
lsblk -o NAME,FSTYPE,SIZE,TYPE,MOUNTPOINTS,UUID
结果:
可见我这里的系统根路径挂载到了/dev/nvme0n1p7分区,并且知道了我的系统根路径所挂载的分区UUID如下:
cb8e5cf2-d0ab-4a25-a707-1e55d344d110
这里就先记下来,后续配置需要用到。
(1) 手动编辑配置
在服务器等无图形界面的环境下,建议手动通过编辑配置文件的方式管理GRUB启动项。
① 创建启动项
自定义配置通常以脚本形式存在,位于/etc/grub.d/目录下,该目录通常已存在部分文件:
我们可以修改40_custom这个文件直接新增启动项,也可以自己在其中创建文件编写启动项配置。这里建议自己新增一个文件编写配置,便于管理,我这里创建文件42_default_kernel在该目录下,并赋予可执行权限:
sudo touch /etc/grub.d/42_default_kernel
sudo chmod +x /etc/grub.d/42_default_kernel
需要注意的是,文件名必须是序号_名称形式,无需扩展名,且要有可执行权限,前面的序号如果越小,那么被加载的优先级会越高,当然建议自定义文件的序号还是大于已有文件的序号。
然后我们编辑该文件,在其中声明一个启动项配置,从指定内核镜像文件启动,我这里内容如下:
#!/bin/sh
exec tail -n +3 $0
menuentry 'Test' {
# 加载必要模块,包括视频和gz模块
load_video
insmod gzio
# 如果是虚拟机,则加载额外模块
if [ x$grub_platform = xxen ]; then
insmod xzio
insmod lzopio
fi
# 加载相关分区表支持模块
insmod part_gpt
insmod ext2
# 搜索设定根分区(即我们的系统分区)
search --no-floppy --fs-uuid --set=root cb8e5cf2-d0ab-4a25-a707-1e55d344d110
# 分别加载内核镜像和初始内存盘
echo 'Linux 6.12.27,启动!'
echo '加载Linux内核...'
linux /boot/vmlinuz-6.12.27 root=UUID=cb8e5cf2-d0ab-4a25-a707-1e55d344d110 ro quiet
echo '加载初始内存盘...'
initrd /boot/initrd.img-6.12.27
}
事实上,该配置文件和平时的Shell脚本并无区别,语法也是一样的,其中有很多指令,指令和指令后面的参数之间使用空格隔开,以及#开头是注释,我们来看一下其中注意点:
- 第一行的
#!/bin/sh和第二行的exec tail -n +3 $0是必须的,大家直接复制即可,其中tail命令用于GRUB加载该文件时跳过前两行,这样只读取后续部分 - 使用
menuentry声明一个启动项,后面接启动项名称,我这里是Test,字符串一般使用英文单引号'或双引号"包围 - 在
menuentry后面花括号中就是该启动项加载逻辑了,可以参考上述注释内容理解大致含义 - 如果你是物理机,可以省略上述
if [ x$grub_platform = xxen ]这个条件判断 search用于指定启动时文件查找的根目录,其后面的参数意义:--no-floppy跳过扫描软驱--fs-uuid指定用UUID来定位分区--set=root将搜索到的分区设置为GRUB的根分区,后续路径解析都基于它- 最后参数就是设定的根目录的UUID了,指定我们前面查询到的系统根路径挂载的分区的UUID即可
echo能够在启动时GRUB页面上输出字符串linux指令用于加载内核镜像文件,其后面的参数意义:- 第一个参数指定你要使用的内核镜像文件的绝对路径,例如我这里
/boot/vmlinuz-6.12.27表示使用6.12.27这个版本的内核镜像,所有内核镜像文件可以在/boot目录下找到,此外如果你的根路径下有内核镜像文件的软链接/vmlinuz,也是可以直接配置在这里的,但需要注意的是每次安装新的内核之后,根路径下的/vmlinuz也会被更新 - 第二个参数
root=UUID=...指定内核加载时根路径挂载的分区,同样地UUID=后面的值指定为我们前面查询到的系统根路径挂载的分区的UUID即可 ro表示以只读方式挂载根分区,后续系统加载后会自动切换为读写模式quiet表示减少启动时输出的信息
- 第一个参数指定你要使用的内核镜像文件的绝对路径,例如我这里
initrd指令用于加载初始内存盘文件,后面跟对应的initrd文件的绝对路径即可,通常也在/boot下面,注意其版本要和你指定的启动内核版本一致,同样地如果你的根路径下有初始内存盘文件的软链接/initrd.img,也是可以直接配置在这里的,不过每次安装新的内核之后,根路径下的/initrd.img也会被更新
到此,我们就完成了自定义启动项的配置,请确保文件保存为UTF-8编码,LF换行符。
② 配置默认启动项
修改/etc/default/grub可修改一些GRUB全局配置,例如默认启动项等,其中配置格式都是名称=值的形式,值通常使用英文双引号"包围。
编辑该文件,找到GRUB_DEFAULT这一配置项,将其后面的值改成你自定义启动项的名称(即menuentry后面接的名称),如下图:
还可以把GRUB_DEFAULT的值配置为saved,这样每次会使用上一次的启动项。
此外,还有其它配置项可以了解一下:
GRUB_TIMEOUTGRUB界面等待时间,单位为秒,在启动界面超过这个时间没有操作则会直接从默认启动项启动GRUB_DISABLE_OS_PROBER是否禁用查找其它操作系统的功能,假设你的电脑安装了双系统,那么建议这个配置可以配置为false,这样GRUB会自动查找其它操作系统,如果它们的启动项不存在,则会自动为其生成启动项GRUB_TIMEOUT_STYLE用于控制GRUB 菜单超时行为和显示方式的配置选项,可以取如下值:menuGRUB启动菜单始终显示,倒计时会在菜单下方显示,并在超时(GRUB_TIMEOUT设定秒数)后启动默认项,这也是默认行为,建议双系统或有多内核切换需求的情况下保持为该配置值hiddenGRUB菜单默认隐藏,不显示任何内容,在GRUB_TIMEOUT秒内,如果用户按下Shift(BIOS模式)或Esc(UEFI 模式),才会显示菜单,超时后自动启动默认系统countdownGRUB不显示完整菜单,只在屏幕显示一个倒计时数字,如果用户在倒计时结束前按下任意键,会中断并显示完整菜单
需要注意的是,
GRUB_TIMEOUT_STYLE的行为依赖于GRUB_TIMEOUT的值:
- 如果
GRUB_TIMEOUT=0,那么无论GRUB_TIMEOUT_STYLE是什么,通常都会直接启动默认项,不显示菜单- 如果
GRUB_TIMEOUT=-1,则无限等待,总是显示菜单
③ 更新GRUB
最后,需要执行下列命令更新GRUB使得我们的配置生效:
sudo update-grub
正常情况下,我们的启动项应该被成功加入了,可以执行下列命令验证一下:
sudo cat /boot/grub/grub.cfg | grep 'menuentry '
结果:
可见加入启动项成功。
(2) 使用grub-customizer可视化配置
如果你使用的是虚拟机或者物理机,带有图形化界面,那么用grub-customizer会更方便,这是一个允许我们使用图形化的方式配置GRUB的实用工具。
① 安装grub-customizer
在Debian 12以及绝大多数其它Debian系操作系统上,我们可以直接进行安装:
sudo apt install grub-customizer
对于Ubuntu系统,则需要添加PPA软件源才能安装,执行下列命令:
sudo add-apt-repository ppa:danielrichter2007/grub-customizer
sudo apt update
sudo apt install grub-customizer
② 添加启动项
在grub-customizer中新增启动项是很简单的,打开软件后,点击上栏菜单编辑 - 新建来新增一个启动项:
在新建界面中,Name字段填写启动项名称,类型选择其它,如下图:
然后在Boot sequence一栏填写启动项逻辑即可,也就是上述手动编写的配置文件中的menuentry的花括号里面的内容,不带menuentry和花括号,例如:
load_video
insmod gzio
if [ x$grub_platform = xxen ]; then
insmod xzio
insmod lzopio
fi
insmod part_gpt
insmod ext2
search --no-floppy --fs-uuid --set=root cb8e5cf2-d0ab-4a25-a707-1e55d344d110
echo 'Linux 6.12.42,启动!'
echo '加载内核中...'
linux /boot/vmlinuz-6.12.42 root=UUID=cb8e5cf2-d0ab-4a25-a707-1e55d344d110 ro quiet
echo '加载初始内存盘...'
initrd /boot/initrd.img-6.12.42
如下图:
最后点击确定即可,此外在列表配置中还可以修改启动项显示名称。
③ 默认启动项配置
在常规设置一栏配置即可:
这里还可以配置其它的GRUB配置。
最后,点击左上角保存按钮即可:
保存后,重启系统即可生效。
④ 使用grub变量优化启动项声明
无论是手动编写GRUB启动项,还是在grub-customizer里面声明启动项,我们不难发现一些地方是重复的,例如:
- 内核文件本身命名是规律的,例如:
vmlinuz-版本号和initrd.img-版本号 - 多次使用了系统分区的UUID
如果你经常编写脚本,就知道这些重复的内容可声明为变量。GRUB启动项脚本也一样,可声明变量,语法如下:
# set 变量名=变量值
set var=value
一般来说:
- 尽量不要用双引号或单引号包围变量值,否则在部分版本的GRUB中可能出错
- 在字符串中引用变量时,不要用单引号包围字符串,单引号中的变量不会展开
在这里,我们就可以修改上述添加启动项步骤中,启动项内容如下:
# 加载相关模块
load_video
insmod gzio
if [ x$grub_platform = xxen ]; then
insmod xzio
insmod lzopio
fi
insmod part_gpt
insmod ext2
# 声明分区UUID和内核版本作为变量
set root_uuid=e62478e9-6fbe-40cf-9e8f-35952236ddef
set kernel_version=6.17.2
# 搜索分区并加载内核
search --no-floppy --fs-uuid --set=root $root_uuid
echo "Linux $kernel_version,启动!"
echo '加载内核中...'
linux /boot/vmlinuz-$kernel_version root=UUID=$root_uuid ro quiet
echo '加载初始内存盘...'
initrd /boot/initrd.img-$kernel_version
可见我们将根分区的UUID和内核版本抽离为变量,这样启动项脚本就简洁多了!后续如要修改也只需要修改两个变量即可,对于手动在/etc/grub.d下编写的启动项脚本文件中,也可以使用上述方式在menuentry块内声明变量。