在第二节,我们一起下载和编译了 linux 内核源码,并且使用 qemu 模拟运行了编译好的内核,但是因为没有文件系统,内核在启动后期陷入了“kernel panic”。所以,上一节我们创建了 disk.img 文件作为“虚拟磁盘”,然后将其格式化为 linux 经典的 ext2 文件系统。
但是虽然有了 ext2 文件系统的 disk.img “虚拟磁盘”,里面却是空的,还没有 bin ,etc,proc 等基本目录,所以虽然解决了内核因缺少文件系统引起的“kernel panic”,最后还是报错了。而且,我们自己编写的 init 程序并没有实际意义,因此linux内核没有完全启动也是意料之中的事。
本节的目标就是建立能够启动linux内核的文件系统,也即往 disk.img 里填充 linux 启动需要的基本文件,当然,也包含可以启动 linux 的真正 init 程序。能够实现目标的方法有多种,这里选用 busybox。
busybox 是一个开源项目,通常用在嵌入式等小型系统中。除了 init 程序外,busybox 还包含了很多常用的命令工具,比如 ls、cat 等。busybox 非常轻量级,可以编译出完全独立无依赖的 busybox 套件。
要使用 busybox,第一步当然是下载它的源码:
wget -c https://busybox.net/downloads/busybox-1.27.2.tar.bz2
这里选用的是 busybox 的 1.27.2 版本。下载以后,解压之,然后按照默认配置,编译。
tar xf busybox-1.27.2.tar.bz2 cd busybox-1.27.2 make defconfig make menuconfig
上面这几条命令的意义,第一节已经介绍的比较清楚。最后一条命令执行完毕后,会弹出图形配置界面,依次选择:
Busybox Settings ---> --- Build Options [*] Build BusyBox as a static binary (no shared libs)
勾选这一项的原因是因为我们的磁盘镜像中没有任何其它库,所以 busybox 需要被静态编译成一个独立、无依赖的可执行文件,以免运行时发生链接错误。然后就可以编译了:
make -j4
编译完成后,就可以安装了,我们新建一个目录 install,将 busybox 安装到该目录下:
mkdir install make CONFIG_PREFIX=<你的install绝对路径> install
执行完毕以后,busybox 就安装到 install 目录了,如下图:
busybox 提供的命令就在 bin 目录里,能够看出这些命令其实都是指向 busybox 的快捷方式:
busybox 的命令可以直接通过 busybox 执行,例如:
./busybox ls # 相当于 ls ./busybox ps # 相当于 ps
现在有了 busybox,我们把它们塞入 disk.img “虚拟磁盘”。先把 disk.img 挂载到 rootfs,然后直接将编译好的 busybox 复制进去即可:
sudo mount -o loop disk.img rootfs cp -a busybox-1.27.2/install/* rootfs
现在我们的虚拟磁盘越来越像 linux 的文件系统了:
init 是我们在上一节编写的应用程序,它不能用作启动 linux。而 linuxrc 则是从 busybox 编译出的 init 程序,我们用 qemu 模拟运行 linux 内核时,可以把 init 程序指定为 linuxrc。复制完 busybox 之后,不要忘记卸载 disk.img,以把改动刷新。
sudo umount rootfs
好了,现在再来使用 qemu 模拟运行一下 linux 内核试试:
qemu-system-x86_64 \ -m 512M \ -smp 1 \ -kernel bzImage \ -drive format=raw,file=disk.img \ -append "init=/linuxrc root=/dev/hda" \ -curses
内核终于不再恐慌了,只不过在 linux 内核启动到最后阶段,还是有一些不正常的信息:
配置
这是因为还有一些配置项没有配置,事实上 busybox 官方也给出文档了:
... # Note: BusyBox init works just fine without an inittab. If no inittab is # found, it has the following default behavior: # ::sysinit:/etc/init.d/rcS # ::askfirst:/bin/sh # ::ctrlaltdel:/sbin/reboot # ::shutdown:/sbin/swapoff -a # ::shutdown:/bin/umount -a -r # ::restart:/sbin/init # tty2::askfirst:/bin/sh # tty3::askfirst:/bin/sh # tty4::askfirst:/bin/sh # # Boot-time system configuration/initialization script. # This is run first except when booting in single-user mode. # ::sysinit:/etc/init.d/rcS ...
init 程序(即上面我们指定的 linuxrc)启动后会扫描 /etc/inittab 配置文件,这个配置文件决定了 init 程序的行为。而 busybox init 在没有 /etc/inittab 文件的情况下也能工作,因为它有默认行为,它的默认行为相当于如上配置。我们暂时不需要打开 tty2 tty3等,所以我们在 disk.img 中建立 /etc/inittab 文件:
sudo mount -o loop disk.img rootfs cd rootfs mkdir etc vim etc/inittab
填入如下内容:
::sysinit:/etc/init.d/rcS ::askfirst:/bin/ash ::ctrlaltdel:/sbin/reboot ::shutdown:/sbin/swapoff -a ::shutdown:/bin/umount -a -r ::restart:/sbin/init
并且根据配置,我们需要创建可执行文件 /etc/init.d/rcS
mkdir etc/init.d touch etc/init.d/rcS chmod x etc/init.d/rcS # 给 rcS 执行权限
linux 在启动后,会执行这个脚本,执行一些启动工作。查看当前系统环境,会发现当前文件系统结构是不完整的。比如没有 /proc 以及 /sys 挂载点。如果执行 df 命令也会因为没有 /proc 挂载点而报错:
df: /proc/mounts: No such file or directory
创建 linux 基本目录
因此,我们需要手工创建 /proc, /sys 目录。 /proc 和 /sys 需要执行挂载才可工作,可以将 /proc 和 /sys 的挂载动作放到 /etc/init.d/rcS 中,每次系统启动时自动挂载。
vim etc/init.d/rcS
修改 /etc/init.d/rcS 内容如下:
#!/bin/sh mount -t proc proc /proc mount -t sysfs sysfs /sys
做完这些改动,要卸载 disk.img 以把改动刷新到磁盘:
cd ../ sudo umount rootfs
好了,现在我们再次使用 qemu 模拟运行编译好的 linux 内核,和修改好的文件系统。这次终于没有报错了,提示我们按回车激活 console:
Please press Enter to activate this console.
到这里,我们终于将 linux 操作系统完全跑起来了,可以执行 linux 的基本命令了。还记得 init 这个应用程序吗?现在也正常执行了。
欢迎在评论区一起讨论,质疑。文章都是手打原创,每天最浅显的介绍C语言、linux等嵌入式开发,喜欢我的文章就关注一波吧,可以看到最新更新和之前的文章哦。
,