在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
2
$ sudo yum install -y centos-release-scl scl-utils-build
$ sudo yum install -y devtoolset-9-toolchain

安装完成后,即可在当前会话中激活:

1
2
3
4
5
6
7
$ scl enable devtoolset-9 bash
$ gcc --version

gcc (GCC) 9.3.1 20200408 (Red Hat 9.3.1-2)
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

可以看到GCC版本已经变更为GCC 9.3.1版本,但请注意,这种方式仅在当前会话中生效,用户切换或者新开会话,都需要重新激活。

RPM包管理工具

在编译之前需要安装RPM包管理工具rpmdevtools:

1
sudo yum -y install rpmdevtools

其中使用rpmdev-setuptree命令可以在当前用户下自动创建一个rpmbuild目录,用于rpm包的编译工作。其结构如下:

1
2
3
4
5
6
7
8
9
[develop@localhost rpmbuild]$ tree
.
├── BUILD
├── RPMS
├── SOURCES
├── SPECS
└── SRPMS

5 directories, 0 files

编译方式一

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
2
$ cd linux-5.15.126
$ cp /boot/config-$(uname -r) ./.config

如果需要文本图形方式配置内核,对内核加载模块进行调整,可以如下操作:

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
2
3
4
5
CONFIG_DEBUG_INFO=y

to

#CONFIG_DEBUG_INFO=y

2)INSTALL_MOD_STRIP: 另一个比较重要的是编译时传给make的参数INSTALL_MOD_STRIP,如果设置了INSTALL_MOD_STRIP, 就会对驱动做strip,减少产出物的大小。关于,内核kbuild文档中的描述是:

1
2
3
4
5
6
INSTALL_MOD_STRIP
If this variable is specified, it will cause modules to be stripped
after they are installed. If INSTALL_MOD_STRIP is '1', then the
default option --strip-debug will be used. Otherwise, the
INSTALL_MOD_STRIP value will be used as the option(s) to the strip
command.

这里可能会问,什么是strip?strip是通过删除可执行文件中ELF头的 typchk段、符号表、字符串表、行号信息、调试段、注解段、重定位信息等来实现缩减程序体积的目的。在内核编译测试中,未设置INSTALL_MOD_STRIP产出的kernel-5.15.126 RPM包大小近1G,设置后大小只有几十兆,效果十分明显。

6、在这里我们暂停一下,先通过make help看一下内核makefile提供的功能,提供的比较多,我们看几个比较常用的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Cleaning targets:
clean - Remove most generated files but keep the config and
enough build support to build external modules
mrproper - Remove all generated files + config + various backup files
distclean - mrproper + remove editor backup and patch files

Configuration targets:
menuconfig - Update current config utilising a menu based program

Kernel packaging:
rpm-pkg - Build both source and binary RPM kernel packages
binrpm-pkg - Build only the binary kernel RPM package
deb-pkg - Build both source and binary deb kernel packages
bindeb-pkg - Build only the binary kernel deb package

其中:

  • 在测试编译时make cleanmake mrproper多次使用
  • binrpm-pkg编译后没有产生kernel-devel rpm

7、编译执行:

1
$ sh -c 'yes "" | make INSTALL_MOD_STRIP=1 rpm-pkg -j 16'

make执行后会有一堆的确认项,通过yes “”即可自动处理。

我的机器上大概一次编译需要一小时左右,在看到如下内容时,就说明编译成功了:

1
2
3
4
5
6
7
8
9
10
Wrote: /home/develop/rpmbuild/SRPMS/kernel-5.15.126-1.src.rpm
Wrote: /home/develop/rpmbuild/RPMS/x86_64/kernel-5.15.126-1.x86_64.rpm
Wrote: /home/develop/rpmbuild/RPMS/x86_64/kernel-headers-5.15.126-1.x86_64.rpm
Wrote: /home/develop/rpmbuild/RPMS/x86_64/kernel-devel-5.15.126-1.x86_64.rpm
Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.1Vmnnr
+ umask 022
+ cd /home/develop/rpmbuild/BUILD
+ cd kernel-5.15.126
+ rm -rf /home/develop/rpmbuild/BUILDROOT/kernel-5.15.126-1.x86_64
+ exit 0

此时在rpmbuild/RPMS/x86_64目录下可以看到编译出的三个RPM:

1
2
3
4
total 232212
-rw-rw-r-- 1 develop develop 67109724 Aug 12 01:04 kernel-5.15.126-1.x86_64.rpm
-rw-rw-r-- 1 develop develop 1464848 Aug 12 01:04 kernel-headers-5.15.126-1.x86_64.rpm
-rw-rw-r-- 1 develop develop 169205676 Aug 12 01:05 kernel-devel-5.15.126-1.x86_64.rpm

这里编译完成后的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
2
3
4
5
6
$ rpm2cpio kernel-ml-5.15.13-1.el7.elrepo.nosrc.rpm | cpio -idv
config-5.15.13-x86_64
cpupower.config
cpupower.service
kernel-ml-5.15.spec
744 blocks

可以看到其中包含四个文件:

  • 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
2
3
4
5
%define LKAver 5.15.13

to

%define LKAver 5.15.126

3)如果执行过编译方式一,需要删除rpmbuild目录后,再执行rpmdev-setuptree命令,生成一个新的rpmbuild目录。

拷贝以下几个文件到rpmbuild/SOURCES目录中:

1
2
3
4
config-5.15.126-x86_64
cpupower.config
cpupower.service
linux-5.15.126.tar.xz

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Wrote: /home/develop/rpmbuild/RPMS/x86_64/kernel-ml-5.15.126-1.el7.x86_64.rpm
Wrote: /home/develop/rpmbuild/RPMS/x86_64/kernel-ml-devel-5.15.126-1.el7.x86_64.rpm
Wrote: /home/develop/rpmbuild/RPMS/x86_64/kernel-ml-headers-5.15.126-1.el7.x86_64.rpm
Wrote: /home/develop/rpmbuild/RPMS/x86_64/perf-5.15.126-1.el7.x86_64.rpm
Wrote: /home/develop/rpmbuild/RPMS/x86_64/python-perf-5.15.126-1.el7.x86_64.rpm
Wrote: /home/develop/rpmbuild/RPMS/x86_64/kernel-ml-tools-5.15.126-1.el7.x86_64.rpm
Wrote: /home/develop/rpmbuild/RPMS/x86_64/kernel-ml-tools-libs-5.15.126-1.el7.x86_64.rpm
Wrote: /home/develop/rpmbuild/RPMS/x86_64/kernel-ml-tools-libs-devel-5.15.126-1.el7.x86_64.rpm
Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.vF4Ure
+ umask 022
+ cd /home/develop/rpmbuild/BUILD
+ cd kernel-ml-5.15.126
+ /usr/bin/rm -rf /home/develop/rpmbuild/BUILDROOT/kernel-ml-5.15.126-1.el7.x86_64
+ exit 0
Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.7Fu6Tu
+ umask 022
+ cd /home/develop/rpmbuild/BUILD
+ rm -rf kernel-ml-5.15.126
+ exit 0

此时在rpmbuild/RPMS/x86_64目录下可以看到编译出的几个RPM:

1
2
3
4
5
6
7
8
9
total 78164
-rw-rw-r-- 1 develop develop 59928964 Aug 12 10:30 kernel-ml-5.15.126-1.el7.x86_64.rpm
-rw-rw-r-- 1 develop develop 14431016 Aug 12 10:30 kernel-ml-devel-5.15.126-1.el7.x86_64.rpm
-rw-rw-r-- 1 develop develop 1601944 Aug 12 10:30 kernel-ml-headers-5.15.126-1.el7.x86_64.rpm
-rw-rw-r-- 1 develop develop 2715444 Aug 12 10:30 perf-5.15.126-1.el7.x86_64.rpm
-rw-rw-r-- 1 develop develop 762024 Aug 12 10:30 python-perf-5.15.126-1.el7.x86_64.rpm
-rw-rw-r-- 1 develop develop 279844 Aug 12 10:30 kernel-ml-tools-5.15.126-1.el7.x86_64.rpm
-rw-rw-r-- 1 develop develop 161168 Aug 12 10:30 kernel-ml-tools-libs-5.15.126-1.el7.x86_64.rpm
-rw-rw-r-- 1 develop develop 138332 Aug 12 10:30 kernel-ml-tools-libs-devel-5.15.126-1.el7.x86_64.rpm

可以到出,较于方式一,产生的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
2
kernel-ml-5.15.126-1.el7.x86_64.rpm
kernel-ml-devel-5.15.126-1.el7.x86_64.rpm

如果是编译方式一,就是这两个:

1
2
kernel-5.15.126-1.x86_64.rpm
kernel-devel-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
2
3
4
5
awk -F\' '$1=="menuentry " {print i++ " : " $2}' /boot/grub2/grub.cfg

0 : CentOS Linux (5.15.126-1.el7.x86_64) 7 (Core)
1 : CentOS Linux (3.10.0-862.el7.x86_64) 7 (Core)
2 : CentOS Linux (0-rescue-2acea53cb12b4a61b90296add99afcc1) 7 (Core)

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/

0%