在QEMU上运行OpenSBI+Linux+Rootfs
本文所使用的版本,如下:
- qemu-7.1.0
- 交叉编译工具:riscv64-glibc-ubuntu-20.04-nightly-2023.01.31-nightly
- opensbi v1.2
- linux 5.15.158
- busybox 1.36.1
1 编译QEMU
下载qemu-7.1.0源码
wget https://download.qemu.org/qemu-7.1.0.tar.xz
解压
tar xvJf qemu-7.1.0.tar.xz
进入目录
cd qemu-7.1.0
配置qemu
./configure --target-list=riscv64-softmmu,riscv64-linux-user --prefix=/opt/qemu --enable-debug --extra-cflags="-O0"
--enable-debug
:表示编译生成带调试信息的可执行程序。
--extra-cflags="-O0"
:表示编译时不进行优化。
若需要使用gdb调试qemu程序时,需要将上述2个编译选项打开。
riscv64-softmmu
:指定生成riscv64 system emulator。
riscv64-linux-user
:指定生成riscv64 user mode emulator。
执行编译
make -j`nproc`
在qemu-7.1.0/build/目录下,生成了可执行程序qemu-system-riscv64
。
查看qemu版本
./qemu-system-riscv64 --version
可以看到版本号为:QEMU emulator version 7.1.0
安装(可选)
sudo make install
安装时,会将可执行程序qemu-system-riscv64,以及头文件等其他文件,拷贝至/opt/qemu目录下。
到这里,模拟器已经准备好了,我们还需要准备OpenSBI(Bootloader)、Linux Kernel以及文件系统(busybox)。
接下来,我们继续。
2 安装交叉编译工具
我们先安装交叉编译工具,以便后续编译OpenSBI、Linux Kernel以及busybox。
下载编译好的RISC-V交叉编译器:
riscv64-glibc-ubuntu-20.04-nightly-2023.01.31-nightly.tar.gz
解压
tar -xvzf riscv64-glibc-ubuntu-20.04-nightly-2023.01.31-nightly.tar.gz
编辑.bashrc
vim ~/.bashrc
将编译器bin路径(/home/tools/riscv/bin替换为自己的路径),加入.bashrc文件末尾。
export PATH=/home/tools/riscv/bin:$PATH
使路径生效
source ~/.bashrc
最后,查看编译器版本,以验证安装
riscv64-unknown-linux-gnu-gcc -v
编译器环境搭建好了。
3 编译OpenSBI
下载源码
git clone https://github.com/riscv-software-src/opensbi.git
进入目录
cd opensbi/
获取V1.2版本源码
git checkout v1.2
实验时,使用最新版本,会报错,因此这里使用V1.2版本。
具体报错信息,如下:
qemu-system-riscv64: Some ROM regions are overlapping
These ROM regions might have been loaded by direct user request or by default.
They could be BIOS/firmware images, a guest kernel, initrd or some other file loaded into guest memory.
Check whether you intended to load all this guest code, and whether it has been built to load to the correct addresses.
The following two regions overlap (in the memory address space):
fw_jump.elf ELF program header segment 1 (addresses 0x0000000000000000 - 0x0000000000023940)
mrom.reset (addresses 0x0000000000001000 - 0x0000000000001028)
设置环境变量
export CROSS_COMPILE=riscv64-unknown-linux-gnu-
执行编译
make PLATFORM=generic
编译完后,在opensbi/build/platform/generic/firmware/目录下
生成的固件,如下所示:
生成的固件有三种类型:dynamic、jump和payload
- dynamic:带有动态信息的固件
- jump:指定下一级的boot地址跳转
- payload:包含下一级boot的二进制内容,通常是uboot/linux
这里我们使用jump类型固件(fw_jump.elf
),OpenSBI运行后,可以直接跳转到kernel运行。
因为opensbi本身就是一个bootloader,因此可以不使用uboot引导kernel,通过opensbi的jump固件,可以直接跳转到kernel启动。
4 编译Linux
Linux内核官网:https://www.kernel.org,进去找到linux-5.15.158版。
这里,可直接下载linux-5.15.158源码:
https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.158.tar.xz
解压
tar -xf linux-5.15.158.tar.xz
进入目录
cd linux-5.15.158
配置环境变量
export ARCH=riscv
export CROSS_COMPILE=riscv64-unknown-linux-gnu-
使用linux默认配置
make defconfig
执行编译
make -j`nproc`
编译完成后,在linux-5.15.158/arch/riscv/boot下生成可执行文件Image
。
5 创建根文件系统
5.1 编译busybox
下载busybox源码
git clone https://git.busybox.net/busybox
cd busybox
打开busybox配置界面
make ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- menuconfig
配置为生成静态二进制可执行程序,选择“Build static binary (no shared libs)”
如果是动态编译,那么在kernel中运行时,需要携带运行库。
没有运行库,就会报错: Starting init: /sbin/init exists but couldn’t execute it (error -8)
执行编译与安装
make ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- -j`nproc`
make ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- install -j`nproc`
cd ..
源码目录下,生成了二进制文件: busybox
。
5.2 创建目录结构
创建主要目录
mkdir root && cd root
mkdir -p bin etc etc/init.d dev lib proc opt sbin sys tmp usr usr/bin usr/lib usr/sbin
将busybox文件,复制到bin目录中
cp /path/to/busybox/busybox bin/busybox
生成指向init文件的符号链接
ln -s ../bin/busybox sbin/init
ln -s sbin/init init
ln -s ../bin/busybox bin/sh
接下来,解释这些命令。
ln -s ../bin/busybox sbin/init
:表示在sbin目录下创建一个名为 init 的符号链接,该链接指向父目录中的 bin 子目录下的 busybox 文件。
在标准的 Linux 发行版中,/sbin/init 是初始化系统的入口点,即这个init文件,就是init进程,它是内核启动的第一个用户级进程。
因此,需要使用 BusyBox 替代标准的 init 进程。
需要将 BusyBox 的路径链接到 /sbin/init 或 /init,以便在系统启动时能够找到并执行它。ln -s sbin/init init
:表示在当前目录下创建一个名为init的符号链接,该链接指向sbin/init文件。ln -s ../bin/busybox bin/sh
:表示在bin目录下创建一个名为sh的符号链接,该链接指向父目录中的 bin 子目录下的 busybox 文件。
许多脚本和程序都依赖于 /bin/sh 的存在。
通过创建这个链接,可以确保这些脚本能够在使用BusyBox的系统中正常运行。
比如,当 init 进程先执行,然后会调用 /etc/init.d/rcS 启动脚本,此时如果没有/bin/sh的话,就会报错:can’t run ‘/etc/init.d/rcS’: No such file or directory
若没有找到init文件时,会报以下错误:
[ 0.584936] Run /sbin/init as init process
[ 0.593521] Run /etc/init as init process
[ 0.594686] Run /bin/init as init process
[ 0.595995] Run /bin/sh as init process
[ 0.596717] Kernel panic - not syncing: No working init found.
创建必要设备节点
sudo mknod dev/sda b 8 0
sudo mknod dev/console c 5 1
创建初始化脚本
vim etc/init.d/rcS
在rcS中填入如下内容
#!/bin/sh
/bin/busybox mount -o remount rw /
/bin/busybox --install -s
mount -t proc proc /proc
mount -t sysfs sys /sys
接下来,解释这些命令。
/bin/busybox mount -o remount rw /
:使用 BusyBox 中的 mount 命令,来重新挂载(remount)根文件系统(/)为读写模式(rw)。
从Kernel日志:VFS: Mounted root (ext2 filesystem) readonly on device 254:0,发现默认按照只读挂载,因此本命令就是,以可读写重新挂载。/bin/busybox --install -s
:将 BusyBox 安装到当前系统中,包括在/bin目录下,创建大量常用命令的符号链接(可作为单独命令使用),会进行写入操作,因此需要文件系统可写。
赋予rcS文件可执行权限
chmod +x etc/init.d/rcS
此时root目录结构,如下所示:
到这里,root目录下,已经具备了一个linux文件系统的基本目录了。
5.3 制作文件系统镜像
我们,将上述root目录结构,打包成ext2镜像文件。
5.3.1 创建 ext2 文件
生成4M大小的,空白镜像文件rootfs.ext2
dd if=/dev/zero of=rootfs.ext2 bs=1M count=4
将镜像文件rootfs.ext2,格式化为ext2文件系统
mkfs.ext2 rootfs.ext2
5.3.2 将目录结构拷贝进 ext2 文件
创建目录
mkdir /mnt/ext2/
将rootfs.ext2,挂载到/mnt/ext2/目录
mount -t ext2 rootfs.ext2 /mnt/ext2/
挂载到/mnt/ext2/目录后,当你在 /mnt/ext2/ 下写入文件或修改文件时,这些更改会反映到 rootfs.ext2 文件中。
拷贝root目录内容,至/mnt/ext2/
cp -pr root/* /mnt/ext2/
也就是,将目录结构拷贝进 rootfs.ext2 文件中了。
5.3.3 取消挂载
umount /mnt/ext2/
sync # 强制内容立刻写入文件
到这里,我们已经对rootfs.ext2文件写入完毕,写入的内容,主要包括busybox、目录结构、rcS文件等。
rootfs.ext2
,即为可供 Linux kernel 挂载所用的根文件系统。
6 运行OpenSBI+Linux+Rootfs
将fw_jump.elf、Image、rootfs.ext2,拷贝到qemu-7.1.0/build/目录下。
新建start-qemu.sh脚本,输入以下内容:
#!/bin/sh
./qemu-system-riscv64 -M virt \
-bios fw_jump.elf \
-kernel Image \
-append "rootwait root=/dev/vda ro" \
-drive file=rootfs.ext2,format=raw,id=hd0 \
-device virtio-blk-device,drive=hd0 \
-netdev user,id=net0 -device virtio-net-device,netdev=net0 -nographic
- bios,指定OpenSBI可执行文件fw_jump.elf
- kernel,指定Linux镜像Image
- drive,指定根文件系统rootfs.ext2
修改权限
chmod 777 start-qemu.sh
在QEMU中,运行OpenSBI+Linux+Rootfs
./start-qemu.sh
运行效果,如下所示:
成功进入Linux!
基本命令可用,使用poweroff
命令可关机。
如果Kernel,发生panic异常时,通过Ctrl+C无法退出。
可以通过以下命令,来退出qemu:
kill $(ps aux | grep qemu | grep -v grep | awk '{print $2}')
参考链接: