构建并运行最小 Linux 内核
大家好!我是大聪明-PLUS!
有一天,一位技术主管建议我读一读 Bove 和 Cesati 合著的《理解 Linux 内核》 。这本书涵盖了 Linux 2.6,比更现代的 6.0 版本落后了不少。但显然,它仍然包含许多有价值的信息。这本书很厚,所以我花了很长时间才读完。在学习的过程中,我决定搭建一个开发环境,以便查看和修改最新版本的 Linux 内核——这会让学习过程更加有趣。
还有其他文章讲解了如何构建 Linux 内核。但在本文中,我将以稍微不同的方式组织和呈现这些信息。
❯ 阶段
这项工作将分两个主要阶段进行:
在 qemu 上构建和运行 Linux
在 Busybox 用户空间支持下在 qemu 上构建和运行 Linux
此外,qemu 允许您将调试器直接连接到正在运行的 Linux 内核。我原本计划研究这个过程并写一篇文章,但发现篇幅太长后就改变了主意。您可以亲自体验一下——我以前从未使用过它。
❯ 安装 qemu
我们将使用 Qemu 模拟器来模拟硬件。我们正在构建的 Linux 系统将由它来运行。要安装,请运行:
~ sudo apt install qemu qemu-system
起初我尝试从源代码构建 qemu,但它有很多依赖项,我很快意识到我在浪费时间。
❯ 克隆 Linux,在本地机器上建立分支
首先,让我们克隆 Linux 存储库。
~ git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/
这是测试下载速度的一项重要任务。
~ du --max-depth=1 --block-size=GB | grep linux
6GB ./linux
然后,选中我们感兴趣的版本旁边的框。我选择了 5.19。
~ cd linux
# alias gco="git checkout"
~ gco v5.19
~ git branch -M 5.19
❯ 构建 Linux
如您所见,整个构建过程直接记录在 Linux 源代码树中;您也可以输入命令 make help
,它将显示可用的选项。下面,我将逐步描述我所做的工作。
清洁(第一次不需要)
清除 .o
之前尝试留下的所有过时文件。由于这是第一次构建,我们现在不需要这么做,但提前养成良好的习惯总是好的。
~ make mrproper
构建内核映像
内核有很多功能——选择你喜欢的功能。例如,内核有很多驱动程序,但你可能只需要其中几个。一次性编译包含所有驱动程序的内核甚至可能导致某些功能无法正常工作。驱动程序只是一个例子。虚拟化、文件系统等也存在类似的功能。有很多配置需要配置!因此,在编译内核时,有一个单独的步骤,你需要明确指定所需的所有功能,然后才能开始实际的编译。
输出是一个内核映像--文件-- bzImage
,我们确信它的大小只有 1.5 MB。
❯ 第二步:在 qemu 上运行 Linux
现在让我们尝试使用 qemu 运行我们获得的内核。
按下ctrl + a
,然后 x
退出控制台屏幕。按下 ctrl + a
,然后 h
打开帮助菜单和其他选项。
无论如何,如果通过 qemu 运行编译后的内核,内核就会崩溃:
Warning: unable to open an initial console.
List of all partitions:
No filesystem could mount root, tried:Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)
下一步我们将摆脱这种恐慌。
❯ 获取 Busybox
现在我们有了 Linux 内核,但还没有用户空间或文件系统。让我们使用一个支持内存的文件系统(initramfs;您可以参考这个 Gentoo 链接作为参考)。我们需要在文件系统中写入一些内容,因为我们不希望用户空间空着。
这时 Busybox 就派上用场了。它通过一个小型二进制文件提供了诸如ls
、 cd
、 cp
、 mv
、 vim
、 tar
、 grep
、 (Linux 设备的热插拔事件)和 (网络通道/接口监控)等命令。这些命令dhcp
的 功能可能不如 Busybox 之外的其他命令那么丰富且易于配置,但对于我们的目的来说,它们已经足够了。
❯ 配置和构建 Busybox
该过程与使用 Linux 内核时的过程相同。
下载完成后,请检查 busybox 源中的 INSTALL 文件。
打开配置界面后,选择“设置”(按 Enter 键),然后选择“将 Busybox 构建为静态二进制文件”(按 Space 键)。这是因为我们的裸内核在用户空间文件系统中没有任何共享库,所以我们可以立即开始。
现在我们退出配置菜单并保存所做的更改。
我们已准备好开始组装!
所以,
~ ls -la $OUTPUT_DIR --block-size=KB | grep busybox
-rw-r--r-- 1 yangwenli yangwenli 2kB Aug 23 15:33 .busybox_unstripped.cmd
-rwxr-xr-x 1 yangwenli yangwenli 2694kB Aug 23 15:33 busybox
-rwxr-xr-x 1 yangwenli yangwenli 2987kB Aug 23 15:33 busybox_unstripped
-rw-r--r-- 1 yangwenli yangwenli 2340kB Aug 23 15:33 busybox_unstripped.map
-rw-r--r-- 1 yangwenli yangwenli 105kB Aug 23 15:33 busybox_unstripped.out
busybox
我们希望获取的二进制文件 确实大约有 2.7 MB,比我们构建的内核还要大。busybox_unstripped
我们对这个变体不感兴趣。它稍微大一点,显然是用来使用分析工具进行分析的。
❯ 创建初始目录结构
接下来的两节很大程度上受到了 Gentoo wiki 中有关自定义 Initramfs 的参考资料的启发 。
现在我们需要为我们的 Linux 用户空间构建初始文件结构。
我们需要确保二进制文件busybox
已到位并提供初始化进程/脚本来设置我们的用户空间。
❯ 创建 init 进程
现在让我们创建 init 进程。在目录中, initramfs
创建一个名为 init
~ touch init && chmod +x init
❯ 创建 initramfs cpio
cpio 是一个归档工具。本质上,它能将一组文件和目录可逆地转换为单个文件。类似于 tar。我不明白为什么,但是 initramfs 是通过 cpio 指定的,所以我们必须用它来打包所有内容。我们将使用 gzip 进行压缩。
~ find . -print0 | cpio --null --create --verbose --format=newc | gzip --best > ./custom-initramfs.cpio.gz
.
./etc
./root
./sys
./dev
./bin
./bin/busybox
./init
./proc
cpio: File ./custom-initramfs.cpio.gz grew, 1310720 new bytes not copied
./custom-initramfs.cpio.gz
7824 blocks
所以,我们已经准备好要使用的 initramfs!
❯ 步骤:使用 Busybox 在 qemu 上运行 Linux(使用 initramfs)
现在让我们启动启用 initramfs 的 Linux 内核!
让我们从上面获取 qemu 命令,并从上面添加命令标志并添加一个 initrd,从而指定 initramfs。
您可能对内核崩溃仍然发生感到沮丧。这是因为我们仍然没有在内核中启用 initramfs 支持,并且我们还没有处理用户空间正常运行所需的一些其他细节。
让我们简单介绍一下内核如何启动用户空间,以及它如何知道在哪里找到进程init
。为了启动用户空间,内核会依次查找 /init、 /sbin/init 、 /etc/init 、 /bin/init ,最后是 /bin/sh 。我留下了源代码链接。我将 init 文件放在了 /bin/init 。
现在我们已经建立了 initramfs 支持,让我们构建另一个内核。重复上述配置步骤并构建内核:
~ cd /home/$USER/linux
~ make O=$LINUX_BUILD_DIR menuconfig
转到 General Setup
那里找到文件系统 Initial RAM
,以及具有随机存取存储器(RAM)的磁盘,然后按“空格”。
在配置文件的最顶部,还要启用 64 位内核。如果您在使用 Busybox 二进制文件时使用 file 命令,您会看到它是为 x86_64 架构构建的。您还会注意到它是 elf 格式的,因此我们也需要为这种格式提供内核支持。由于我们在 init file 中使用了 .elf 符号!#
,因此我们也需要提供对它的支持。
最后,从文件顶部向下滚动到 Device Drivers > Character devices > Serial drivers
和 8250/16550
和compatible serial support
和 Console on 8250/16550
。compatible serial port
需要这些配置设置才能将串行端口用作控制台。如果没有这些更改,init 将无法工作。我认为这就是需要最后一行的原因 exec /bin/sh
。
现在让我们编译内核:
~ make O=$LINUX_BUILD_DIR -j8
然后我们再次运行 qemu:
~ qemu-system-x86_64 -kernel $LINUX_BUILD_DIR/arch/x86/boot/bzImage -nographic -append "earlyprintk=serial,ttyS0 console=ttyS0 debug" --initrd $INITRAMFS_DIR
至此,我们创建了一个可以运行的 Linux 发行版。如果您有足够的空闲时间,可以继续这个实验,并基于此处的工作构建您自己的发行版。