Linux内核编译初尝试

Posted by breaking-wave on 2024-01-05
Estimated Reading Time 7 Minutes
Words 1.7k In Total
Viewed Times

为什么需要切换Linux内核为自定义参数版本

Linux内核是操作系统的核心,也是操作系统最基本的部分。Linux内核的体积结构是单内核的、但是他充分采用了微内核的设计思想、使得虽然是单内核、但工作在模块化的方式下、并且这个模块可以动态装载或卸载;Linux负责管理系统的进程、内存、设备驱动程序、文件和网络系统,决定着系统的性能和稳定性。如是我们在了解Linux内核的基础上根据自己的需要量身定制一个更高效,更稳定的内核,就需要我们手动去编译和配置内核里的各项相关的参数和信息了。


我的需求则是为了测试某个特定版本内核的性能,因此需要切换内核的版本。

我们有两个方法可以做到这点:

  • 基于qemu的模拟
  • 切换当前机器的内核版本

通过第一点我们可以运行起来一个简易的操作系统内核,但是它缺乏常见的库与程序,例如ls, pwd等的实现,尽管使用busybox可以帮助我们安装一些常用的库,但其在可用性上来说还是非常不足的。例如,用户无法自由地使用apt-get等包管理工具。

我们也可以彻底地更换当前机器上的内核版本,但这样可能会导致各种兼容问题,具有一定风险,在尝试之前务必备份重要文件。

Linux内核编译

下载源码

不建议通过git clone的方式从github下载Linux的源代码,文件很大,可能因为网络问题而失败,可以从一下网站下载自己需要的版本:

我选择的6.2版本,可执行如下命令:

1
wget https://ftp.sjtu.edu.cn/sites/ftp.kernel.org/pub/linux/kernel/v6.x/linux-6.2.tar.gz

安装编译所需模块

1
sudo apt-get install bison flex qemu bc build-essential gcc g++ libelf-dev libssl-dev

另外注意,在之后的编译过程中可能会出现问题,例如can’t inlcude xxx,这个时候查找对应报错并且install对应的库即可

编译

下载成功后解压,进入目录:

1
cd linux-6.2

开始编译

1
2
3
sudo make -j16       #这一步耗时最长,-j 表示多线程编译,可加快速度,具体数字视机器而定
sudo make bzImage # 编译内核,成功后会得到 arch/x86/boot/bzImage, 是压缩后的镜像
sudo make modules_install #编译模块

其中在第三步,可能会出现报错:

1
2
3
4
update-initramfs: Generating /boot/initrd.img-6.2
Error 24 : Write error : cannot write compressed block
E: mkinitramfs failure cpio 141 lz4 -9 -l 24
update-initramfs: failed for /boot/initrd.img-6.2 with 1.

原因是用来安装的boot目录没空间了,但是我们查看之后会发现空间依然充足,这其实编译过程中引入了大量不必要的内容,经过查找,可以使用如下命令:

1
sudo make INSTALL_MOD_STRIP=1 modules_install -j4

接下来需要安装内核到对应目录

1
sudo make install -j16

验证安装

1
2
cd /boot
ls

应该可以看到新安装的内核版本号相关的文件

切换版本

通过命令查看当前Linux内核版本

1
uname -r

首先使用命令查找新内核,更新grub文件

1
sudo update grub

cd/boot/grub修改grub.cfg

修改原文件:注释 set timeout_style = hidden, 修改timeout的值为10

image-20240105203030758

重启机器,就会自动弹出选择Linux内核的界面了。

需要注意的是,如果修改的是服务器的配置,重启时由于grub界面会出现在内核加载之前,我们是无法通过ssh连接到服务器进行选择的,不过根据我的经验会自动选择一个版本,且为最新的(未进行过实验)。

通过QEMU来启动编译好的Kernel

以上我们演示了如何在本机上切换kernel版本,但如果我们只是想研究kernel的某个性质则不需要如此大费周折,QEMU为我们提供了系统虚拟化的机制。

另外,之前所述的所有部署流程均可以在容器中进行,这样做的好处是

  • host无需安装大量的依赖,例如qemu
  • 编译过程中出现不可逆损伤后可以快速remake

我们接下来的讨论是基于之前的操作均在容器中进行的来展开讨论,至于容器镜像的选择,使用ubuntu即可。

但需要注意的是,容器启动时必须器授予特权,否则后续的mount将无法进行

1
docker run -it --privileged=true ubuntu:latest bash

安装qemu

1
sudo apt-get install qemu

开始模拟

1
qemu-system-x86_64 -m 512M -smp 4 -kernel ./bzImage

很快直接失败,出现了图形化界面使用错误,这里可以尝试在qemu指令后加上--curses

在启动编译后的压缩内核时,出现无法找到文件系统的错误:

image-20240112150441937

为此,需要创建磁盘镜像,制作根文件系统,且准备init程序才能启动编译后的内核,简单的做法是基于busybox构建启动程序。

编译Busybox

下载busybox压缩包之后解压,进入目录,执行以下命令:

1
2
3
4
make defconfig
make memuconfig
make
make CONFIG_PREFIX=<path_to_disk_img_mount_point> install

在memuconfig编译完成后会进入图形化界面,此时选择

1
Build BusyBox as a static binary (no shared libs)

从而将Busybox编译为静态文件,编译完成并安装后,查看<path_to_disk_img_mount_point>目录,可以看到多了很多文件,此外,还需要创建额外的文件,包括rcS,inittab,proc,sys,dev等,具体可以参考博客

以下是直接可用的脚本,需要修改相应的路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#!/bin/sh

# note: Linux kernel and busybox are in the same directory
path_to_Linux_kernel=linux-4.18
path_to_busybox=busybox-1.30.0
path_to_root=`pwd`

cd $path_to_Linux_kernel
#make clean
#make distclean
#make x86_64_defconfig
#make bzImage
#make modules

# obtain the bzImage
cp arch/x86/boot/bzImage $path_to_root

# create disk by type ext4
cd $path_to_root
if [ -d img ]; then
umount img
rm -rf img
fi

if [ -e disk.raw ]; then
rm disk.raw
fi

qemu-img create -f raw disk.raw 512M
mkfs -t ext4 disk.raw

mkdir img
sudo mount -o loop disk.raw img # note that, docker run --privileged=true

# install modules and init program
cd $path_to_Linux_kernel
sudo make modules_install INSTALL_MOD_PATH=../img

cd $path_to_root/$path_to_busybox
if [ -e busybox ]; then
make CONFIG_PREFIX=../img install
else
echo "Busybox is not built yet. Please build the busybox first!"
exit 2
fi

# update the init program
cd $path_to_root/img

mkdir -p etc/init.d
mkdir proc
mkdir sys
mkdir dev

touch etc/init.d/rcS
echo "#/bin/sh" > etc/init.d/rcS
echo "mount -t proc proc /proc" >> etc/init.d/rcS
echo "mount -t sysfs sysfs /sys" >> etc/init.d/rcS
chmod +777 etc/init.d/rcS

# start the bzImage
cd $path_to_root
qemu-system-x86_64 -curses -m 512M -smp 4 -kernel bzImage -drive format=raw,file=disk.raw -append "init=/linuxrc root=/dev/sda"

最后,在docker容器中的ubuntu就可以启动qemu了。

1
qemu-system-x86_64 -curses -m 512M -smp 2 -kernel bzImage -drive format=raw,file=disk.raw -append "init=/linuxrc root=/dev/sda"

If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. If the images used in the blog infringe your copyright, please contact the author to delete them. Thank you !