上回说到了我们如何用 QEMU 搭建一个包含最新内核的开发环境。但试过一段时间后,我却感觉这样并不方便。

更多时候,我只需要运行单个程序(比如,单元测试)即可,而上文中所构建的完整环境却让这个过程便复杂了。本文介绍了一个更简单的方法。只需要一个简单的脚本即可构建完整运行环境。

initrd

上一篇文章中已经提到了使用 initrd 启动操作系统的方法。initrd 是一个只读文件系统镜像,内核启动时,会将镜像内容加载到内存中。程序可以像普通文件系统一样访问。

加载文件系统之后,内核会从其中寻找可用的 init 程序。因此我们只要编写合理的 init 程序,将其打包到一个 initrd 中即可。

busybox

在这种从零开始构建文件系统的场景下,使用动态库太过繁琐。因此我们直接用宿主机上的 busybox 来提供基本的程序。

准备阶段

首先创建一个 WORK_DIR 目录。此路径下的文件会被打包成文件系统镜像。

WORK_DIR=$(mktemp -d)
trap "rm -rf $WORK_DIR" EXIT

之后寻找宿主机上的 busybox,将其复制到 $WORK_DIR/bin/busybox

mkdir -p "$WORK_DIR/bin"

# Copy busybox into the work directory
BUSYBOX_PATH=$(which busybox)
if [ -z "$BUSYBOX_PATH" ]; then
    echo "Error: busybox not found in PATH."
    exit 1
fi
cp "$BUSYBOX_PATH" "$WORK_DIR/bin/busybox"

创建 $WORK_DIR/init 脚本,赋予其可执行权限。内核在启动中会识别并执行该脚本。

# Create init script
cat << 'EOF' > "$WORK_DIR/init"
#!/bin/busybox sh

# ...

EOF
chmod +x "$WORK_DIR/init"

init 脚本内容

接下来查看 $WORK_DIR/init 脚本的内容。首先,指明该脚本由 /bin/busybox sh 执行。

#!/bin/busybox sh

之后,创建通常的文件系统路径;并挂载 procsysdev 三个特殊的文件系统。

# Initialize minimal directories
busybox mkdir -p /etc /proc /root /sbin /sys /usr/bin /usr/sbin

# Mount necessary filesystems
busybox mount -t proc proc /proc
busybox mount -t sysfs sys /sys
busybox mdev -s

安装 busybox 所提供的各项工具。这一过程会创建工具名到 busybox 的符号链接。

# Install busybox applets
busybox --install -s

设定用户。root 用户,uid=0,gid=0,无需密码,home 为 /root,shell 为 /bin/bash

# Create a minimal passwd file
echo root::0:0:root:/root:/bin/sh > /etc/passwd

启动本地回环

# Set up loopback interface
hostname localhost
ip link set lo up

减少 kernel 日志的可见性。登陆 root 用户。如果退出的话,使用 poweroff 关机。

# Reduce kernel printk verbosity
echo 5 > /proc/sys/kernel/printk

# Start an interactive shell
login root

# Power off
poweroff -f

构建 initrd 镜像

接下来,构建 initrd 镜像。

$FILES 中定义了一系列额外需要放入镜像的文件。注意,如果需要执行该程序的话,应该采用静态链接。

# Copy user-specified files into /root
mkdir -p "$WORK_DIR/root"
for FILE in $FILES; do
    BASENAME=$(basename "$FILE")
    cp "$FILE" "$WORK_DIR/$DEST_DIR/$BASENAME"
done

最后,通过 cpio 命令将 WORK_DIR 转换为 initrd 文件系统。所有文件的所有者都为 root 用户。

# Create the initrd image
OUTPUT_FILE_REAL=$(realpath "$OUTPUT_FILE")
cd "$WORK_DIR"
find . | cpio -o -H newc -R 0:0 | gzip -9 > "$OUTPUT_FILE_REAL"
echo "Created initrd image: $OUTPUT_FILE"

运行

使用如下命令运行

qemu-system-x86_64 \
    -m 512M \
    -kernel /path/to/bzImage \
    -initrd /path/to/initrd \
    -append "console=ttyS0" \
    -nographic

再次宣传一下自己的新项目 Condy :)