Linux 之旅 24:内核编译
图源:pexels
认识内核与获取源码
什么是系统内核
内核(Kernel)是整个操作系统的最底层,负责所有硬件的驱动,以及提供系统所需的内核功能。
所有需要让计算机完成的功能,都需要内核支持才行。
内核的本质是一个大型程序,可以在编译后加载到内存中执行,进而加载各种驱动和模块,驱动主机的相关硬件,然后对系统中的其它软件提供相应的功能。
为了节省空间,编译后的内核通常会被压缩成一个内核文件,使用的时候再被解压到内存中。一个Linux主机上可以有多个内核文件,可以在启动的时候选择其中一个用于加载系统。当然也可以通过grub2等boot loader进行多重引导。
前面说过了,内核需要编译后才能使用(执行),而编译自然需要相应的源代码,一般称为内核源码。
默认情况下我们安装的Linux发行版是不会附带内核源码的,如果要安装源码就要去Linux发行版的官网下载安装对应的内核SRMP文件。而如果想安装其它版本的内核,可以直接从网上下载相应的内核源码Tarball,解压后进行内核编译,然后通过grub2进行引导就可以使用了。
更新内核的目的
通常来说,我们不太需要对内核进行频繁编译,而且一般来说Linux发行版自带的编译好的内核就能满足我们日常的需要。只有当出现以下几种情况的时候你可能才需要编译内核:
-
新功能的需求
内核是频繁更新的,随着更新会逐渐加入一些新功能和特性,如果你想使用新版本内核加入的功能,或者是你有新的硬件要使用,但新硬件无法被旧版本的内核识别或加载,那你就可能需要进行内核编译。
-
需要精简内核
Linux发行版附带的默认内核是针对大部分人的使用情况来设置的,所以在极个别特殊情况下可能会显得很“臃肿”,比如你要安装的目标硬件是嵌入式设备,硬件水平有限,需要节省内存或者磁盘,那你可能需要重新编译内核,以去除不需要的功能来精简内核。
-
与硬件搭配的稳定性
Linux发行版附带的默认内核大多数是针对Intel的CPU设置的,如果你的CPU是其它型号,比如AMD或ARM,有可能会运行的不是很完美,此时你可以对内核源码的设置进行调整,专门对你的特异CPU进行优化后重新编译内核。
内核版本
如要要查看当前使用的内核版本:
[icexmoon@xyz ~]$ uname -r
3.10.0-1160.el7.x86_64
CentOS 7使用3.10.x作为长期维护版本,所以升级内核的话最好使用3.10.x版本的内核,比如3.10.89。
当然你也可以不遵守上边的限制,直接使用最新的5.x.x版本内核,但是跨越大版本升级内核可能出现一些无法解决的问题。
获取内核源码
通过Linux发行版官网获取
如果要获取当前使用的Linux发行版的默认内核对应的源码,最好的方式是通过其官网获取:
CentOS官网提供源码的页面是:https://vault.centos.org/。
寻找对应的CentOS版本(我这里是7.9.2009),然后查找到updates目录,然后往下找.src.rpm
文件,最终我找到的是这个页面:https://vault.centos.org/7.9.2009/updates/Source/SPackages/:
有多个SRPM文件,其中kernel.xxx
就是内核的SRMP包,kernel-3.10.0-1160.el7.src.rpm应该就是我当前使用的内核对应的SRPM包,其它类似kernel-3.10.0-1160.42.2.el7.src.rpm应该是小版本更新后的SRPM包。
下载SRPM包后进行解包,就能获取相应的内核源码。
获取最新的内核源码
Linux内核一直都是由Linux创始人Linus所属的团队在负责开发维护,其发布内核的官网是:
具体提供所有Linux内核源码的地址是:https://mirrors.edge.kernel.org/pub/linux/kernel/
如果访问速度不快,可以使用其它镜像站,这里找到一个中科大的Linux用户协会网站:
该网站提供Linux相关资源的镜像站,内核的下载网址是:
除了内核,还有其它很多Linux资源镜像。
使用patch升级
之前在在Linux 之旅 21:编译安装软件中介绍过怎么使用.patch
文件来升级软件。如果你已经有某个版本的内核源码了,也可以使用这种方式进行升级,不过需要注意的是需要按照版本顺序依次使用.path
文件升级,比如你当前内核版本是5.8.1
,使用patch-5.8.2
可以升级到5.8.2
内核,再使用patch-5.8.3
就可以升级到5.8.3
版本的内核,以此类推。
内核源码的解压、安装和查看
先下载需要的源码,这里我选择linux-3.10.99
版本的源码:
[icexmoon@xyz tmp]$ wget https://mirrors.edge.kernel.org/pub/linux/kernel/v3.x/linux-3.10.99.tar.xz
--2021-09-17 16:33:01-- https://mirrors.edge.kernel.org/pub/linux/kernel/v3.x/linux-3.10.99.tar.xz
正在解析主机 mirrors.edge.kernel.org (mirrors.edge.kernel.org)... 2604:1380:3000:1500::1, 147.75.95.133
正在连接 mirrors.edge.kernel.org (mirrors.edge.kernel.org)|2604:1380:3000:1500::1|:443... 失败:连接超时。
正在连接 mirrors.edge.kernel.org (mirrors.edge.kernel.org)|147.75.95.133|:443... 已连接。
已发出 HTTP 请求,正在等待回应... 200 OK
长度:73312148 (70M) [application/x-xz]
正在保存至: “linux-3.10.99.tar.xz”
100%[===========================================================================================>] 73,312,148 153KB/s 用时 6m 51s
2021-09-17 16:42:00 (174 KB/s) - 已保存 “linux-3.10.99.tar.xz” [73312148/73312148])
- 速度比较慢,原因是
wget
会先尝试通过IPV6进行连接,如果服务器不支持就一直要等待,建议使用wget -4
指定通过IPV4进行连接,建立连接的所读会快得多。- 官方镜像下载速度会很慢,建议使用上边提供的国内镜像,能慢速下载。
一般来说,内核源码应该安装到/usr/src/kernels/
目录:
[root@xyz tmp]# tar -Jxvf linux-3.10.99.tar.xz -C /usr/src/kernels/
...省略
linux-3.10.99/virt/kvm/iodev.h
linux-3.10.99/virt/kvm/iommu.c
linux-3.10.99/virt/kvm/irq_comm.c
linux-3.10.99/virt/kvm/irqchip.c
linux-3.10.99/virt/kvm/kvm_main.c
[root@xyz tmp]# du -sh /usr/src/kernels/linux-3.10.99/
566M /usr/src/kernels/linux-3.10.99/
居然解压出来要566M,恐怖如斯。
解压出的内核源码包含以下目录:
[root@xyz tmp]# ll /usr/src/kernels/linux-3.10.99/ | grep '^d'
drwxrwxr-x. 32 root root 4096 3月 4 2016 arch
drwxrwxr-x. 3 root root 4096 3月 4 2016 block
drwxrwxr-x. 4 root root 4096 3月 4 2016 crypto
drwxrwxr-x. 100 root root 8192 3月 4 2016 Documentation
drwxrwxr-x. 112 root root 4096 3月 4 2016 drivers
drwxrwxr-x. 36 root root 4096 3月 4 2016 firmware
drwxrwxr-x. 73 root root 4096 3月 4 2016 fs
drwxrwxr-x. 26 root root 4096 3月 4 2016 include
drwxrwxr-x. 2 root root 254 3月 4 2016 init
drwxrwxr-x. 2 root root 256 3月 4 2016 ipc
drwxrwxr-x. 11 root root 4096 3月 4 2016 kernel
drwxrwxr-x. 9 root root 8192 3月 4 2016 lib
drwxrwxr-x. 2 root root 4096 3月 4 2016 mm
drwxrwxr-x. 55 root root 4096 3月 4 2016 net
drwxrwxr-x. 12 root root 186 3月 4 2016 samples
drwxrwxr-x. 13 root root 4096 3月 4 2016 scripts
drwxrwxr-x. 9 root root 268 3月 4 2016 security
drwxrwxr-x. 22 root root 4096 3月 4 2016 sound
drwxrwxr-x. 17 root root 215 3月 4 2016 tools
drwxrwxr-x. 2 root root 102 3月 4 2016 usr
drwxrwxr-x. 3 root root 17 3月 4 2016 virt
这些目录的用途为:
- arch :與硬體平台有關的項目,大部分指的是 CPU 的類別,例如 x86, x86_64, Xen 虛擬支援等;
- block :與區塊裝置較相關的設定資料,區塊資料通常指的是大量儲存媒體!還包括類似 ext3 等檔案系統的支援是否允許等。
- crypto :核心所支援的加密的技術,例如 md5 或者是 des 等等;
- Documentation :與核心有關的一堆說明文件,若對核心有極大的興趣,要瞧瞧這裡!
- drivers :一些硬體的驅動程式,例如顯示卡、網路卡、PCI 相關硬體等等;
- firmware :一些舊式硬體的微指令碼 (韌體) 資料;
- fs :核心所支援的 filesystems ,例如 vfat, reiserfs, nfs 等等;
- include :一些可讓其他程序呼叫的標頭 (header) 定義資料;
- init :一些核心初始化的定義功能,包括掛載與 init 程式的呼叫等;
- ipc :定義 Linux 作業系統內各程序的溝通;
- kernel :定義核心的程序、核心狀態、執行緒、程序的排程 (schedule)、程序的訊號 (signle) 等
- lib :一些函式庫;
- mm :與記憶體單元有關的各項資料,包括 swap 與虛擬記憶體等;
- net :與網路有關的各項協定資料,還有防火牆模組 (net/ipv4/netfilter/*) 等等;
- security :包括 selinux 等在內的安全性設定;
- sound :與音效有關的各項模組;
- virt :與虛擬化機器有關的資訊,目前核心支援的是 KVM (Kernel base Virtual Machine)
摘抄自鸟哥的Linux私房菜
内核编译前的预处理与功能选择
清理源码环境
在编译之前应当清理源码目录,将可能存在的之前编译产生配置文件和目标文件(object file)清除:
[root@xyz tmp]# cd /usr/src/kernels/linux-3.10.99/
[root@xyz linux-3.10.99]# make mrproper
需要注意的是,make mrproper
命令会将内核功能选择文件一并删除,如果仅需要清理之前编译残留的文件,可以:
[root@xyz linux-3.10.99]# make clean
make clean
仅会删除编译产生的中间文件,不会删除相关配置文件。
选择内核功能
在开始编译内核前,需要先选择内核功能,即为内核设定好哪些功能直接编译进内核,哪些功能是编译成模块外挂,哪些功能是完全不需要的。
具体可以通过以下几种方式进行设置:
-
make menuconfig
命令行下以纯文本的方式进行设置。
-
make oldconfig
使用已存在的
./.config
文件作为默认设置,在此基础上仅将新功能列出让用户选择。 -
make xconfig
通过
Qt
图形接口实现的界面进行设置,比如支持Qt
图形接口的KDE下就可以使用此功能进行设置。 -
make gconfig
通过
Gtk
图形接口实现的界面进行设置,比如支持Gtk
图形接口的GNOME就可以使用此功能进行设置。 -
make config
最原始的设置方式,选择错误后将无法修改。
通过既有设置来完成内核功能选择
这里以默认内核的配置文件作为参考来设置新内核的功能:
[root@xyz linux-3.10.99]# ll /boot/config-3.10.0-1160.el7.x86_64
-rw-r--r--. 1 root root 153591 10月 20 2020 /boot/config-3.10.0-1160.el7.x86_64
[root@xyz linux-3.10.99]# cp /boot/config-3.10.0-1160.el7.x86_64 .config
[root@xyz linux-3.10.99]# ll .config
-rw-r--r--. 1 root root 153591 9月 17 17:13 .config
下面进行内核选择:
[root@xyz linux-3.10.99]# make menuconfig
会出现这样的界面:
有点像是BIOS的设置界面。
操作方式很简单,上边是具体的设置项,下边是菜单,选中不同的菜单项有不同的功能:
-
Select
:当选中该菜单项时:- 按下
Enter
会进入当前设置项(有子设置项的菜单尾部会有-->
符号) - 按下
Space
(空格)会修改当前配置项(头部有[]
或<>
标识的才能修改)
- 按下
-
Exit
:选中时按下Enter
会返回上一级菜单,如果已经是顶部菜单,将退出设置程序。 -
Help
:选中时按下Enter
会显示选中设置项的帮助信息。 -
Save
:选中时按下Enter
会保留当前的设置到配置文件。 -
Load
:从配置文件加载内核设置。
内核功能项的设置值主要分以下几种:
-
-*-
:默认编译到内核,无法修改。 -
[*]
:编译到内核。 -
[M]
:编译到内核模块。 -
[ ]
:不使用该功能。
内核功能选择
General setup
这部分设置是与Linux最相关的程序交互、内核版本说明、是否使用开发中的程序代码等相关的。
这里仅做了上面的修改,其余均保持默认值。
其中Local version
和Automatically append version...
的用途是可以在Linux内核版本后添加上一段自定义字符,比如上边设置后最终编译出的内核版本显示的时候就会显示3.10.99icexmoon
之类的字样。
Kernel compression mode
是内核编译后的压缩模式,使用Bzip2
可以让内核拥有较高的压缩比。
Kernel .config support
可以让内核的配置文件直接写入内核文件。
其余相关配置的说明可以阅读鸟哥的Linux私房菜。
Enable loadable module support
这里是内核加载模块的相关设置,如果内核要能加载模块,该功能必须写入内核。
这里仅增加了一个Forced module unloading
功能,该功能可以让内核强制卸载模块。
Enable the block layer
其下的自配置项Patition Types
涉及分区的相关功能:
这里均保持默认值。
子配置项I/O scheduler
可以设置I/O调度算法:
这里建议设置为Deadline
。
Processor type and features
这一部分设置包括CPU的类型与功能选择。
子选项Linux guest support
提供Linux虚拟化相关功能:
这里保持默认选项。
选项Preemption Model
可以调整内核抢占模式:
具体分为三种:
- No Forced Preemption (Server)
- Voluntary Kernel Preemption (Desktop)
- Preemptible Kernel (Low-Latency Desktop)
如果Linux主机的用途是服务器,可以修改选项为No Forced Preemption (Server)
。
内核抢占模式的更多内容可以阅读Linux Preemption模式。
选项Timer frequency
可以调整CPU的时钟频率,时钟频率会影响CPU的调度和执行,如果Linux主机用于服务器,可以适当调低时钟频率,比如300 HZ。这里使用默认值1000HZ:
Power management and ACPI options
这里是电源管理的相关功能。如果硬件支持,可以通过这里的设置降低系统的能耗。
子选项CPU Frequency scaling
中可以设置CPU频率脉冲相关的功能:
其中Default CPUFreq governor
可以设置CPU频率调节模式,这里设置为ondemand
。
更多的CPU频率调节模式可以阅读cpufreq 五种模式。
Bus options (PCI etc.)
这里是总线相关的功能。
这里保留默认值,其中PCI Stub driver
选项为虚拟化需要的功能。
Executable file formats / Emulations
这里是文件格式相关的功能。
虽然现在都是64位系统环境,但可以打开IA32 a.out support
和x32 ABI for 64-bit mode
,以兼容32位程序的运行。
Networking support
这里是网络相关的选项,包括防火墙等。
子选项Networking options
中包含防火墙的功能:
在这里打开Transparent proxying support
功能。
其它选项均保持默认值,如果想了解更多相关功能说明,请阅读鸟哥的Linux私房菜。
Device Drivers
这里是驱动相关的设置。这里不做修改,直接使用默认值。
File systems
这里是文件系统相关的设置。
CentOS 7默认是不支持ext2
和ext3
的,这里开启。
增加一个xfs realtime subvolume support
,该功能可以支持“事实子卷”,"实时子卷"是专门存储文件数据的卷,可以允许将日志与数据分开在不同的磁盘上。
子选项DOS/FAT/NT Filesystems
可以添加对Windows
相关文件系统的支持。
这里将default codepage for FAT
修改为936,将Default iocharset
修改为utf8
,以支持简体中文。
此外,添加NTFS write support
以支持NTFS
文件系统的写入功能。
更多关于
codepage
的相关内容请阅读内核中VFAT文件系统codepage和iocharset设置。
子选项Network File Systems
中包含网络文件系统的相关设置:
这里添加上对SMB2网络文件系统的支持。
子选项Native language support
可以设置语言支持:
简体中文以模块的方式支持。
Kernel hacking
这里是Linux核心开发者会用到的相关功能,如果你不是内核开发人员,应该是用不到的。
Security options
这里是安全相关的选项,包括SELinux的相关设置:
Virtualization
这里包含虚拟化相关的设置。
这里添加一个虚拟化相关的功能KVM legacy PCI device assignment support
。
到这里所有设置就都选择好了,选择下方的save
进行保存:
确认保存到.config
,点ok
。
保存好后一路选择exit
退出程序即可。
内核的编译和安装
编译内核和模块
可以通过make help
命令查看内核编译的相关命令:
[root@xyz linux-3.10.99]# make help
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:
config - Update current config utilising a line-oriented program
nconfig - Update current config utilising a ncurses menu based program
menuconfig - Update current config utilising a menu based program
xconfig - Update current config utilising a QT based front-end
gconfig - Update current config utilising a GTK based front-end
oldconfig - Update current config utilising a provided .config as base
...省略
主要的命令有:
-
make vmlinux
:将内核编译成未压缩的内核文件。 -
make modules
:仅编译内核模块。 -
make bzImage
:将内核编译成压缩后的内核文件。 -
make all
:执行上边所有的三个操作。
通常编译内核时的步骤是:
-
make clean
:清理上次编译的残留文件。 -
make bzImage
:编译内核。 -
make modules
:编译模块。
或者可以使用组合命令:make clean bzImage modules
。
此外,为了更高效地编译内核,可以使用-j n
命令使用多线程编译内核,其中n
可以设置为CPU最大支持的线程数,比如双核四线程的CPU编译内核时可以使用make -j 4 bzImage
。
现在开始实际编译内核:
[root@xyz linux-3.10.99]# make clean
[root@xyz linux-3.10.99]# make bzImage
...省略
OBJCOPY arch/x86/boot/vmlinux.bin
HOSTCC arch/x86/boot/tools/build
BUILD arch/x86/boot/bzImage
Setup is 16720 bytes (padded to 16896 bytes).
System is 4469 kB
CRC d8aafb6f
Kernel: arch/x86/boot/bzImage is ready (#1)
出现xxx is ready
字样就说明编译好了。
下面编译模块:
[root@xyz linux-3.10.99]# make modules
- 编译前请确保有足够的空闲空间,我因为空间不够编译失败了。
- 模块编译非常耗时,我几年前的小破笔记本上的虚拟机用单线程跑了1小时左右。
安装模块
内核模块都安装在/lib/modules/$(uname -r)
目录下。也就是说以内核版本进行区分,一般来说不会有什么问题,但如果我们要安装的内核模块对应的版本已经存在了,就会产生冲突。此时解决的方式有:
- 将旧的内核模块所在目录名修改后进行安装。
- 在内核设置中将
General setup
中的Local version
中添加一个额外的版本标识。
如果不存在版本冲突的问题就可以直接安装:
[root@xyz linux-3.10.99]# make modules_install
...省略
INSTALL /lib/firmware/edgeport/down2.fw
INSTALL /lib/firmware/edgeport/down3.bin
INSTALL /lib/firmware/whiteheat_loader.fw
INSTALL /lib/firmware/whiteheat.fw
INSTALL /lib/firmware/keyspan_pda/keyspan_pda.fw
INSTALL /lib/firmware/keyspan_pda/xircom_pgs.fw
DEPMOD 3.10.99icexmoon
[root@xyz linux-3.10.99]# ll /lib/modules
总用量 8
drwxr-xr-x. 7 root root 4096 7月 24 14:46 3.10.0-1160.el7.x86_64
drwxr-xr-x. 3 root root 4096 9月 17 21:59 3.10.99icexmoon
安装内核与实现多重引导
安装内核
安装内核实际上就是将编译好的内核文件部署到/boot
目录:
[root@xyz linux-3.10.99]# cp ./arch/x86/boot/bzImage /boot/vmlinuz-3.10.99icexmoon
[root@xyz linux-3.10.99]# cp .config /boot/config-3.10.99icexmoon
[root@xyz linux-3.10.99]# chmod a+x /boot/vmlinuz-3.10.99icexmoon
[root@xyz linux-3.10.99]# cp ./System.map /boot/System.map-3.10.99icexmoon
[root@xyz linux-3.10.99]# gzip -c Module.symvers > /boot/symvers-3.10.99icexmoon.gz
[root@xyz linux-3.10.99]# restorecon -Rv /boot
restorecon reset /boot/System.map-3.10.99icexmoon context unconfined_u:object_r:boot_t:s0->unconfined_u:object_r:system_map_t:s0
这里除了内核文件,还拷贝了其它关键文件到/boot
目录。
创建对应的虚拟文件系统
我们之前说过,内核在加载到内存中后,要依靠虚拟文件系统才能读取到模块,所以还要创建新内核的虚拟文件系统:
[root@xyz linux-3.10.99]# dracut -v /boot/initramfs-3.10.99icexmoon.img 3.10.99icexmoon
Executing: /sbin/dracut -v /boot/initramfs-3.10.99icexmoon.img 3.10.99icexmoon
dracut module 'busybox' will not be installed, because command 'busybox' could not be found!
dracut module 'dmsquash-live-ntfs' will not be installed, because command 'ntfs-3g' could not be found!
dracut module 'cifs' will not be installed, because command 'mount.cifs' could not be found!
...省略
*** Creating image file ***
*** Creating image file done ***
*** Creating initramfs image file '/boot/initramfs-3.10.99icexmoon.img' done ***
多重引导
要让新内核能使用还需要让其出现在boot loader的引导菜单上才行,所以这里需要重建grub2
的配置文件,以让grub2
自动识别到新内核:
[root@xyz linux-3.10.99]# grub2-mkconfig -o /boot/grub2/grub.cfg
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-3.10.99icexmoon
Found initrd image: /boot/initramfs-3.10.99icexmoon.img
Found linux image: /boot/vmlinuz-3.10.0-1160.el7.x86_64
Found initrd image: /boot/initramfs-3.10.0-1160.el7.x86_64.img
Found linux image: /boot/vmlinuz-0-rescue-f5a85e92d51e4d40975fd956fd775f9c
Found initrd image: /boot/initramfs-0-rescue-f5a85e92d51e4d40975fd956fd775f9c.img
done
可以看到识别到了我们的新内核。
测试
现在新内核已经就绪,重启来看看:
第一个选项就是新内核,选择新内核进入系统。
[icexmoon@xyz ~]$ uname -r
4.1.16
内核更新成功。
- 花了两天时间总算成功安装了一个官方内核,不过兼容性依然有点问题,GNOME窗口管理器就很卡。安装过程中我同样参考了CentOS7编译内核 详细步骤。
- 遇到的其它坑包括:
- 安装最新的内核
5.9.9
时,各种编译工具均版本过老,无法编译,如果强行要编译可能还需要找其它的软件源更新编译工具,我这里没有继续折腾。- 安装内核
3.10.99
后,无法正常进入系统,显示不能加载/dev/dm-0
(centos-root
),猜测应该是因为/root
所在分区使用的是LVM
,且因为空间不够我进行过扩若,导致出现了xfs
的一个BUG,该BUG影响3.10
~3.12
的所有内核版本,详情请见xfs.org。- 安装内核
3.13.1
和3.19.8
后,无法正常进入系统,VMware虚拟机直接报错:操作系统拒绝CPU。网上有说法是内存太小,但我遇到的问题并不完全一致,是完全没有内核的加载过程输出,修改虚拟机内存大小后问题也依然存在。
以上就是Linux内核编译的全部内容了,谢谢阅读。
到这里,《鸟哥的Linux私房菜》这本书就全部学习完毕了,可能还有一两篇文章作为番外以及该书的书评。