在CentOS 7上编译和升级Linux内核
遇到CentOS 7环境上因为某个原因需要升级内核的场景,一番搜索在网上找到了可以直接使用的RPM,极大的减轻了工作量,但对如何编译内核产生了兴趣。那么就动手实验下吧。
一些内核知识
Linux内核代号分为Prepatch、Mainline、Stable、Longterm(Long Term Support)等,其中:
- Prepatch: Prepatch或“RC”内核是主要针对其他内核开发人员和Linux爱好者的主线内核预发布版。它们必须从源代码编译,并且通常包含必须在放入稳定版本之前进行测试的新功能。Prepatch内核由Linus Torvalds维护和发布。
- Mainline: 主线树由Linus Torvalds维护。这是一个树,所有的新特性都在这里被引入,所有令人兴奋的新开发都在这里发生。新的主线内核每9-10周发布一次。
- Stable: 在每个主线内核发布后,它被认为是“稳定的”。稳定内核的任何bug修复都是从主线树中反向移植的,并由指定的稳定内核维护者应用。在下一个主线内核可用之前,通常只有几个bug修复内核版本–除非它被指定为“长期维护内核”。稳定的内核更新是根据需要发布的,通常每周一次。
- Longterm(Long Term Support): 通常有几个“长期维护”内核版本,用于对旧内核树进行反向移植修复。只有重要的bug修复应用于这样的内核,它们通常不会看到非常频繁的发布,特别是对于较老的树。当前LTS内核版本有6.1、5.15、5.10、5.4、4.19、4.14。
此前提到因为某个原因需要升级内核解决问题,经过仔细的代码比对,最终选择了5.15内核版本进行版本升级,在内核升级完成后,问题得到解决。
内核编译准备
编译环境
- OS: CentOS 7.6.1810
- 磁盘大小: 60G,可用45G
注意: 编译中间过程的产出物非常大,一定保证编译目录所在磁盘空余大小有足够的空间,我的测试过程开始仅有25G可用磁盘,导致编译因空间不足失败,后面扩展了20G,可用45G才保证顺利编译。
GCC准备
CentOS 7系统自带GCC版本为4.8.5,已经不满足5.15内核的编译条件,这里我们使用SCL软件集的方式来使用高版本GCC:
1 | sudo yum install -y centos-release-scl scl-utils-build |
安装完成后,即可在当前会话中激活:
1 | scl enable devtoolset-9 bash |
可以看到GCC版本已经变更为GCC 9.3.1版本,但请注意,这种方式仅在当前会话中生效,用户切换或者新开会话,都需要重新激活。
RPM包管理工具
在编译之前需要安装RPM包管理工具rpmdevtools:
1 | sudo yum -y install rpmdevtools |
其中使用rpmdev-setuptree
命令可以在当前用户下自动创建一个rpmbuild目录,用于rpm包的编译工作。其结构如下:
1 | [develop@localhost rpmbuild]$ tree |
编译方式一
1、安装依赖包:
1 | sudo yum install -y gcc make ncurses-devel flex bison openssl-devel elfutils-libelf-devel perl bc |
2、下载代码包:
1 | wget https://www.kernel.org/pub/linux/kernel/v5.x/linux-5.15.126.tar.xz |
3、解压代码:
1 | tar -xvf linux-5.15.126.tar.xz |
4、拷贝当前内核编译config到新的内核源码中,主要是对内核编译选项进行设置。先把当前用来启动系统的config拷贝过来是最稳妥的方式。
1 | cd linux-5.15.126 |
如果需要文本图形方式配置内核,对内核加载模块进行调整,可以如下操作:
1 | make menuconfig |
比如在文本图形界面设置一个本地的版本号:
1 | General setup —>Select Local version – append to kernel release |
5、减少RPM包大小:
1)CONFIG_DEBUG_INFO
: 如果我们内核开启了CONFIG_DEBUG_INFO
选项, 那么我们编译的二进制会带上很多调试信息,内核镜像Image都是经过裁剪和优化的,但是驱动没有,所以导致单个KO的大小就很大。为了减少RPM的大小,需要编辑.config
文件,注释下面一行:
1 | CONFIG_DEBUG_INFO=y |
2)INSTALL_MOD_STRIP
: 另一个比较重要的是编译时传给make的参数INSTALL_MOD_STRIP
,如果设置了INSTALL_MOD_STRIP
, 就会对驱动做strip,减少产出物的大小。关于,内核kbuild文档中的描述是:
1 | INSTALL_MOD_STRIP |
这里可能会问,什么是strip?strip是通过删除可执行文件中ELF头的 typchk段、符号表、字符串表、行号信息、调试段、注解段、重定位信息等来实现缩减程序体积的目的。在内核编译测试中,未设置INSTALL_MOD_STRIP
产出的kernel-5.15.126 RPM包大小近1G,设置后大小只有几十兆,效果十分明显。
6、在这里我们暂停一下,先通过make help
看一下内核makefile提供的功能,提供的比较多,我们看几个比较常用的:
1 | Cleaning targets: |
其中:
- 在测试编译时
make clean
和make mrproper
多次使用 - binrpm-pkg编译后没有产生kernel-devel rpm
7、编译执行:
1 | sh -c 'yes "" | make INSTALL_MOD_STRIP=1 rpm-pkg -j 16' |
make执行后会有一堆的确认项,通过yes “”即可自动处理。
我的机器上大概一次编译需要一小时左右,在看到如下内容时,就说明编译成功了:
1 | Wrote: /home/develop/rpmbuild/SRPMS/kernel-5.15.126-1.src.rpm |
此时在rpmbuild/RPMS/x86_64目录下可以看到编译出的三个RPM:
1 | total 232212 |
这里编译完成后的RPM就可以进行升级测试了,升级测试我们放后面说。
编译方式二
ELRepo Project
在常见的内核升级方案中,有一种比较常用的方式是采用ELRepo Project提供的内核版本,同时ELRepo Project将内核名称变更为kernel-ml,以免与RHEL内核冲突。其kernel-ml一直提供最新版内核的rpm安装包。但新的大版本出来以后就不再提供旧的版本,在各个镜像站可以看到,目前提供的是kernel-ml-6.4.9、kernel-ml-6.4.8版本的RPM包。
在这次的问题解决时需要5.15 LTS版本,那么怎么编译一个5.15版本的kernel-ml的包呢?
经过资料查找,kernel-ml分支5.15到5.15.13以后就不再更新了,幸运的是找到了5.15.13的SRPM包kernel-ml-5.15.13-1.el7.elrepo.nosrc.rpm
。
SRPM
什么是SRPM?
SRPM包,比RPM包多了一个“S”,是“Source”的首字母,所以SRPM可直译为“源代码形式的RPM 包”。也就是说,SRPM包中不再是经过编译的二进制文件,都是源代码文件。可以这样理解,SRPM包是软件以源码形式发布后直接封装成RPM包的产物。
通过rpm2cpio将kernel-ml-5.15.13-1.el7.elrepo.nosrc.rpm
拆解:
1 | rpm2cpio kernel-ml-5.15.13-1.el7.elrepo.nosrc.rpm | cpio -idv |
可以看到其中包含四个文件:
- config-5.15.13-x86_64与编译方式一中提到的
.config
功能相同 - kernel-ml-5.15.spec是提供RPM包制作的spec文件
- cpupower是一组为辅助CPU调频而设计的用户空间工具。提供了方便的命令行实用程序,并且内置systemd服务,可在启动时更改调频器。
编译准备
1、已经获取了kernel-ml-5.15.13的文件,那么基于此我们可以制作5.15.126版本所需的内容:
1)将config-5.15.13-x86_64变更名字为config-5.15.126-x86_64,即可直接使用
2)kernel-ml-5.15.spec中修改LKAver的版本号:
1 | %define LKAver 5.15.13 |
3)如果执行过编译方式一,需要删除rpmbuild目录后,再执行rpmdev-setuptree
命令,生成一个新的rpmbuild目录。
拷贝以下几个文件到rpmbuild/SOURCES目录中:
1 | config-5.15.126-x86_64 |
2、安装依赖,ELRepo的方式较于编译方式一需要多安装几个依赖项:
1 | sudo yum install asciidoc newt-devel xmlto audit-libs-devel binutils-devel elfutils-devel java-1.8.0-openjdk-devel libcap-devel numactl-devel perl python-devel python3 slang-devel xz-devel pciutils-devel perl-ExtUtils-Embed |
3、执行编译
进行spec所在的目录执行:
1 | rpmbuild -v -bb --clean ./kernel-ml-5.15.spec |
执行一次同样需要一小时,当看到如下内容时,即说明制作成功:
1 | Wrote: /home/develop/rpmbuild/RPMS/x86_64/kernel-ml-5.15.126-1.el7.x86_64.rpm |
此时在rpmbuild/RPMS/x86_64目录下可以看到编译出的几个RPM:
1 | total 78164 |
可以到出,较于方式一,产生的RPM数量多,且devel-5.15.126的rpm大小比较小。
实际devel-5.15.126解包对比,内眼可见的差距在于:
- 编译方式一产生的devel-5.15.126中包含的各文件夹所占大小与源码近似
- 编译方式二产生的devel-5.15.126远小于源码大小,且存在部分link文件
也找了许久的差异原因,但并未有结果,如果有大佬知晓原因,希望能给予指点,在此提前感谢。
两种方式都能正常完成内核版本升级,但从占用空间大小和产出RPM对比下来,更倾向于使用编译方式二,即基于ELRepo Project的SRPM做修改后,编译最新的kernel rpm。
内核升级
内核升级就比较简单了,实际升级中仅需要这两个RPM文件(对于编译方式二):
1 | kernel-ml-5.15.126-1.el7.x86_64.rpm |
如果是编译方式一,就是这两个:
1 | kernel-5.15.126-1.x86_64.rpm |
其中编译产生的kernel-headers rpm在很多文章中说需要安装,但是在我实际测试中,如果带上会遇到冲突。elrepo官方文档也有说明kernel-headers是不需要升级的,重新构建glibc以及整个操作系统时才需要:
There is no need to install the kernel-ml-headers package. It is only necessary if you intend to rebuild glibc and, thus, the entire operating system. If there is a need to have the kernel headers installed, you should use the current distributed kernel-headers package as that is related to the current version of glibc.
1、安装内核:
1 | yum localinstall kernel-ml-* |
2、安装成功后,查看本机存在的内核列表:
1 | awk -F\' '$1=="menuentry " {print i++ " : " $2}' /boot/grub2/grub.cfg |
3、设置启动最新内核(一般最新安装的内核启动顺序为 0)
1 | grub2-set-default 0 |
4、reboot重启机器
参考
1、http://elrepo.org/tiki/kernel-ml
2、https://www.mail-archive.com/elrepo@lists.elrepo.org/msg03172.html
3、https://www.kernel.org/category/releases.html
4、https://linux.cc.iitk.ac.in/mirror/centos/elrepo/kernel/el7/SRPMS/
5、https://blog.csdn.net/avatar_2009/article/details/126303420
6、https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?h=linux-5.15.y
7、https://wiki.archlinuxcn.org/wiki/CPU_%E8%B0%83%E9%A2%91
8、https://www.kernel.org/doc/html/next/process/changes.html
9、https://superuser.com/questions/705121/why-is-install-mod-strip-not-on-by-default
10、https://elixir.bootlin.com/linux/v5.3.6/source/Documentation/kbuild/makefiles.rst#L1481
11、https://superuser.com/questions/370586/how-can-a-linux-kernel-be-so-small
12、https://wiki.centos.org/HowTos/Custom_Kernel
13、https://www.geeksforgeeks.org/how-to-compile-linux-kernel-on-centos-7/