之前的篇幅中我们已经将 Linux 内核 bringup 起来了,不知道大家有没有去尝试将根文件系统运行起来,今天我就带领大家完成这个事情,可以跟着下面的步骤一步步来完成:
在这里我们使用 busybox 构建 rootfs:
- 下载 busybox:
wget https://github.com/mirror/busybox/archive/refs/tags/1_35_0.tar.gz
- 解压并配置:
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig
这里需要使用静态编译获取 busybox,这样一来我们就不需要拷贝各种动态库了:
Busybox Settings ---> Build Options ---> Build BusyBox as a static binary (no shared libs) ---> yes
- 编译 busybox:
make
make install
- 添加 init 和其他文件夹:
mkdir dev proc mnt sys tmp root
mkdir etc && mkdir etc/init.d
在rootfs下,新建init,添加:
#!/bin/sh
# devtmpfs does not get automounted for initramfs
echo "------> I am a VM on X-Hyper <------"
/bin/mount -t devtmpfs devtmpfs /dev
exec 0</dev/console
exec 1>/dev/console
exec 2>/dev/console
exec /sbin/init "$@"
然后执行:chmod +x init
- 创建/etc/init.d/rcS:
#!/bin/sh
PATH=/sbin:/bin:/usr/sbin:/usr/bin
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/lib:/usr/lib
export PATH LD_LIBRARY_PATH runlevel
/bin/hostname megvii
mount -a
mkdir /dev/pts
mount -t devpts devpts /dev/pts
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s
然后:chmod +x etc/init.d/rcS
- 创建/etc/fstab:
proc /proc proc defaults 0 0
tmpfs /tmp tmpfs defaults 0 0
sysfs /sys sysfs defaults 0 0
tmpfs /dev tmpfs defaults 0 0
- 创建/etc/inittab:
#etc/inittab
::sysinit:/etc/init.d/rcS
console::askfirst:-/bin/sh
::restart:/sbin/init
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
::shutdown:/sbin/swapoff -a
- 创建/etc/profile:
USER="`id -un`"
LOGNAME=$USER
HOSTNAME=`/bin/hostname`
HOME=/root
PS1="[$USER@$HOSTNAME \W]\# "
PATH=$PATH
export USER LOGNAME HOSTNAME HOME PS1 PATH PATH LD_LIBRARY_PATH
- 打包:
find ./* | cpio -H newc -o > rootfs.cpio
gzip rootfs.cpio
- 将 rootfs.cpio.gz 转换为.o,为了后续使用 ld 将其打包进 X-Hyper.elf 做准备:
aarch64-linux-gnu-ld -r -b binary rootfs.cpio -o rootfs.cpio.o
上述过程我们已经将 rootfs 准备好了,那么 Linux 如何加载这个 rootfs 呢,我们使用设备树 Chosen 来指定 initrd 的起始地址和结束地址:
由于 Linux 的启动需要足够的内存,所以我们在 X-Hyper 中我们已经将物理内存扩展到 256M,然后在 chosen 中指定 initrd 的信息:
chosen {
stdout-path = "/pl011@9000000";
linux,initrd-start = <0x0 0x84000000>;
linux,initrd-end = <0x0 0x85000000>;
};
同时在 X-Hyper 中我们需要将 rootfs 的内容拷贝到上述指定的 IPA 对应的物理内存中(注意这里是 IPA 哦):
- 我们首先在 vm_config 中指定 rootfs 的地址,这里的地址要和设备树中的信息一致:
vm_config_t guest_vm_cfg = {
.guest_image = &guest_vm_image,
.guest_dtb = &guest_virt_dtb,
.guest_initrd = &guest_rootfs,
.entry_addr = 0x80600000,
.dtb_addr = 0x80000000, /* virt dtb ipa */
.rootfs_addr = 0x84000000, /* rootfs ipa */
.ram_size = 0x8000000, /* 128M */
.ncpu = 2,
};
- 然后我们把 rootfs 的内容拷贝到这段 IPA 对应的物理内存中,由于这段 IPA 之前已经被映射了,所以直接拷贝就可以:
copy_to_ipa(pgt, vm_config->rootfs_addr, (char *)vm_config->guest_initrd->start_addr, vm_config->guest_initrd->image_size);
完成上述操作后,整个 IPA 地址空间如下所示(当然 IPA 还包括设备的地址,这里没有显示出来):
其实在 bringup 整个 Linux 和 rootfs 的过程中会遇到各种问题,我觉得大家可以不看上述内容,自己先去尝试一下,完成上述所有操作后,我们就可以将 Linux 虚拟机运行起来了:
我已经将 Image/image.o/rootfs.cpio/rootfs.cpio.o/virt.dts/virt.dtb 放到 linux 的文件夹下了。
项目构建:
- clone 源代码到本地:git clone GitCode - 全球开发者的开源社区,开源代码托管平台;
- 编译生成 u-boot 的 bin 文件:sh build_uboot.sh;
- 编译虚拟机 Guest OS 镜像:cd ./guest; sh build_vm.sh;
- 编译虚拟机管理器代码,生成虚拟机管理器镜像:sh run_build.sh;
- 运行 qemu 并加载镜像:sh run_qemu.sh (直接运行);