跳转至

创建初始内存盘

先导知识

系统内核启动时的内存加载

上一节说到,Linux kernel 在自身初始化完成之后需要找到并运行 init 程序。用户程序存在于文件系统之中,因此,内核必须找到并挂载一个文件系统才可以成功完成系统的引导过程。

GRUB 中提供了一个选项 root= 来指定第一个文件系统,但随着硬件的发展,很多情况下这个文件系统也许是存放在 USB 设备、SCSI 设备等等多种多样的设备之上,如果需要正确引导,USB 或 SCSI 驱动模块首先需要运行起来,但这些驱动程序也是存放在文件系统里,因此会形成一个悖论。

为解决此问题,Linux kernel 提出了 RAM disk 的解决方案,把一些启动所必须的用户程序和驱动模块放在 RAM disk 中,这个 RAM disk 看上去和普通的 disk 一样,有文件系统,有 cache,内核启动时,首先把 RAM disk 挂载起来,等到 init 和一些必要模块运行起来之后,再切换到真正的文件系统之中。

不过,这种 RAM disk 的方案(下称 initrd)虽然解决了问题,但并不完美。 比如,disk 有 cache 机制,对于 RAM disk 来说,这个 cache 机制就显得很多余且浪费空间;disk 需要文件系统,那文件系统必须被编译进 kernel 而不能作为模块来使用。Linux 2.6 kernel 提出了一种新的实现机制,即 initramfs。这是一种 RAM filesystem 而不是 disk。initramfs实际是一个 cpio 归档,启动所需的用户程序和驱动模块被归档成一个文件。因此,不需要 cache,也不需要文件系统。

创建过程

Linux 在启动时,会首先加载初始内存盘(initrd)进行初始化的操作,下面我们讲解如何创建一个最小化的 initrd。

创建一个新的目录(和 linux-6.2.7 文件夹平级,比如名为 temp)用于暂存文件,再在此临时目录下创建一个 C 程序 init.c,代码如下:

#include <stdio.h>

int main() {
    printf("Hello! PB21XXXXXX\n"); // Your Student ID
    while (1) {}
}
init

init 将会作为第一个用户态进程被启动,成为所有后续进程的父进程。

编译,静态链接 为可执行程序:

gcc -static init.c -o init

打包 initrd:

find . | cpio --quiet -H newc -o | gzip -9 -n > ../initrd.cpio.gz

这会在 temp 文件夹外创建压缩后的 initrd.cpio.gz 初始内存盘。

使用 QEMU 测试效果:

qemu-system-x86_64 -kernel linux-6.2.7/arch/x86_64/boot/bzImage -initrd initrd.cpio.gz

同第一部分,你可能需要在命令末尾增添 -nographic -append console=ttyS0 选项。

当你在屏幕上看到之前 printf 的信息的时候,就成功了。

本节评分标准

要求 满分
提交 initrd.cpio.gz,保证其能够显示 "Hello! PB21XXXXXX\n" 信息(修改为真实学号) 20%
「可选」修改 init.c 使编译的内存盘会导致 kernel panic,并在 lab1/README.md 中解释原因 5%