# RISC-V-QEMU-Linux **Repository Path**: my-rsic-v/risc-v-qemu-linux ## Basic Information - **Project Name**: RISC-V-QEMU-Linux - **Description**: 自己整理的Running RISC-V Linux on QEMU文档。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2024-01-30 - **Last Updated**: 2024-01-30 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 1. Running RISC-V Linux on QEMU QEMU是纯软件实现的虚拟化模拟器,几乎可以模拟任何硬件设备,我们最熟悉的就是能够模拟一台能够独立运行操作系统的虚拟机,虚拟机认为自己和硬件打交道,但其实是和 Qemu 模拟出来的硬件打交道,QEMU将这些指令转译给真正的硬件。 Ps.所有图片都是以64bit为例的,跟32bit区别不大。 ## 1.1. 准备 安装依赖项: sudo apt install autoconf automake autotools-dev curl libmpc-dev libmpfr-dev libgmp-dev gawk \ build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev git 然后创建主工作文件夹并进入目录: - 32bit RISC-V ``` mkdir ~/riscv32-linux cd ~/riscv32-linux ``` - 64bit RISC-V ``` mkdir ~/riscv64-linux cd ~/riscv64-linux ``` ## 1.2. 下载/拉取相关资源并初步测试 下载所需的源文件: - QEMU - Linux - Busybox 通过git命令即可: git clone https://github.com/qemu/qemu git clone https://github.com/torvalds/linux git clone https://git.busybox.net/busybox 由于某些神秘力量,Github经常寄,所以考虑换镜像站,QEMU在./configure的过程中也需要连接Github服务器所以,直接wget源码: - QEMU ``` wget https://download.qemu.org/qemu-8.0.3.tar.xz tar xvJf qemu-8.0.3.tar.xz ``` - Linux ``` git clone https://mirrors.tuna.tsinghua.edu.cn/git/linux.git ``` - Busybox ``` git clone https://gitee.com/mirrors/busyboxsource.git ``` 整个编译过程还需要一个RISC-V交叉编译工具链,通过网站下载: > ![cross-compilation](./cross-compilation.png) 新建存放工具链的文件夹toolchain,在toolchain文件夹解压,查看一下目录下的文件: ls toolchain/riscv64-lp64d--glibc--bleeding-edge-2022.08-1/ ![ls](./ls.png) 通过添加环境变量安装这个工具链: - 32bit RISC-V ``` sudo vim ~/.bashrc # 在最后一行添加(此处为riscv32) ``` ``` export PATH="/home/$USER/riscv32-linux/toolchain/riscv32-ilp32d--glibc--bleeding-edge-2022.08-1/bin":$PATH ``` - 64bit RISC-V ``` sudo vim ~/.bashrc # 在最后一行添加(此处为riscv64) ``` ``` export PATH="/home/$USER/riscv64-linux/toolchain/riscv64-lp64d--glibc--bleeding-edge-2022.08-1/bin":$PATH ``` 测试一下交叉编译工具,随便写一个main.c(随便某处): ```C #include int main(int argc, char *argv[]) { int i = 1; for (i = 1; i < 16; i++) { printf("hello %d\n", i); } return 0; } ``` 输入命令编译这个文件 - 32bit RISC-V ``` riscv64-buildroot-linux-gnu-gcc main.c -o main.out ``` - 64bit RISC-V ``` riscv64-buildroot-linux-gnu-gcc main.c -o main.out ``` 查看一下现在路径下的文件: ![ls_main](./ls_main.png) 反汇编一下: - 32bit RISC-V ``` riscv32-buildroot-linux-gnu-objdump -h -D main.out > main.asm vim main.asm ``` - 64bit RISC-V ``` riscv64-buildroot-linux-gnu-objdump -h -D main.out > main.asm vim main.asm ``` 可以在下面看到,调用了printf来完成打印。可以证明该交叉编译器正常工作。 ![objdump](./objdump.png) ## 1.3. QEMU编译 - 32bit RISC-V ``` cd qemu-8.0.3 ./configure --target-list=riscv32-softmmu make -j $(nproc) sudo make install ``` - 64bit RISC-V ``` cd qemu-8.0.3 ./configure --target-list=riscv64-softmmu make -j $(nproc) sudo make install ``` - 32bit & 64bit RISC-V ``` cd qemu-8.0.3 ./configure --target-list=riscv32-softmmu,riscv64-softmmu make -j $(nproc) sudo make install ``` ## 1.4. Linux内核编译 接下来通过交叉编译器来为RISC-V编译Linux系统内核,并在QEMU上进行模拟。 编译linux内核: - 32bit RISC-V ``` cd ~/riscv32-linux/linux make ARCH=riscv CROSS_COMPILE=riscv32-buildroot-linux-gnu- defconfig make ARCH=riscv CROSS_COMPILE=riscv32-buildroot-linux-gnu- -j $(nproc) ``` - 64bit RISC-V ``` cd ~/riscv64-linux/linux make ARCH=riscv CROSS_COMPILE=riscv64-buildroot-linux-gnu- defconfig make ARCH=riscv CROSS_COMPILE=riscv64-buildroot-linux-gnu- -j $(nproc) ``` 编译完成。 ## 1.5. 添加自己的C程序 进入busybox目录: - 32bit RISC-V ``` cd ~/riscv32-linux/busyboxsource ``` - 64bit RISC-V ``` cd ~/riscv64-linux/busyboxsource ``` 创建一个目录,比如叫mine: mkdir ./mine cd ./mine 创建并编辑一个文件,hello_busybox.c: vim ./hello_busybox.c 比如这样,比如参考初步测试中: ```C #include "libbb.h" int hello_busybox_main(int argc, char *argv[]) { int i = 1; for (i = 1; i < 16; i++) { printf("hello %d\n", i); } return 0; } ``` 注意此处主函数名字,不再是`main`,而是`xxx_main`。因为BusyBox从busybox.c文件中的main函数开始执行,`int main(int argc, char *argv[])`,`argc`为参数数量,`argv[0]`为applet_name,后续执行过程如下: - 调用 applets/applets.c 文件的 return lbb_main(argv); - 在 include/applets.h 中 填充内容为xxx_main函数; - 自此跳转到 applet 中执行。 因此新增的applet函数为`int xxx_main(int argc, char*argv[])`格式。 下一步在,在*.c文件路径下添加文件Config.in: vim Config.in `Config.in:` ```{.line-numbers} menu "Mine" config HELLO_BUSYBOX bool "say hello to busybox" default y help say hello to busybox endmenu ``` 修改这里主要是使得后面执行“make menuconfig”的时候,配置界面可以出现我们新增的命令,让用户对该命令可以配置,解释如下: - 第一行是设定最外层菜单级别并设置出现在菜单界面上的文字; - 第二行是表示该命令的一个环境变量; - 第三行是出现在配置界面上的文字,是一个布尔量,取值为“Y”或者“N”; - 第四行是这个选项的默认值,这里默认是选中; - 后面两行是在配置界面的帮助信息。 与Config.in相同,接下来添加Kbuild文件并编辑: vim Kbuild `Kbuild:` lib-y:= lib-$(CONFIG_HELLO_BUSYBOX) += hello_busybox.o 简单地说,这里是添加需要链接的库。 下一步,修改include/applets.src.h文件: cd ../include/ vim ./applets.src.h 在文件中添加一行,注意,添加的新行一定要在INSERT字段之后、#endif之前进行添加,否则在后面make的时候会被自动删掉: IF_HELLO_BUSYBOX(APPLET(hello_busybox, BB_DIR_SBIN, BB_SUID_DROP)) 如图: ![applets.src.h](applets.src.h.png) 解释:第一个参数:命令的名字;第二个参数:存放的路径:第三个参数:权限 最后一步,为命令添加帮助信息,以便使用--help的时候查阅(不做这步会报错): vim ./usage.src.h 同样地,一定要在`INSERT`字段之后、`#endif`之前进行添加: #define hello_busybox_trivial_usage "None" #define hello_busybox_full_usage "None" 如图: ![usage.src.h](usage.src.h.png) 继续修改一些文件,返回上一层,修改总的`Config.in`: cd .. vim ./Config.in 在最后一行添加一句: source mine/Config.in 如图: ![bigConfig](./bigConfig.png) 还有Makefile,在libs-y下面添加一行,记得按照字母顺序: mine/ \ 如图: ![Makefile](Makefile.png) 至此,定制自己的C程序完成,接下来通过busybox生成根文件系统。 ## 1.6. 利用busybox制作rootfs 进入busybox目录: - 32bit RISC-V ``` cd ~/riscv32-linux/busyboxsource ``` - 64bit RISC-V ``` cd ~/riscv64-linux/busyboxsource ``` 然后配置busybox: - 32bit RISC-V ``` CROSS_COMPILE=riscv32-buildroot-linux-gnu- make menuconfig ``` - 64bit RISC-V ``` CROSS_COMPILE=riscv64-buildroot-linux-gnu- make menuconfig ``` 在弹出的菜单里找到Settings -> Build static binary (no shared libs),勾选: ![Build static binary](./Build_static_binary.png) Exit返回上一步,并向下,可以看到自己的程序的相关选项: ![Mine](./Mine.png) ![Mine_inside](./Mine_inside.png) 然后Exit -> Exit -> … -> Yes: 接着执行make及make install: - 32bit RISC-V ``` CROSS_COMPILE=riscv32-buildroot-linux-gnu- make -j $(nproc) CROSS_COMPILE=riscv32-buildroot-linux-gnu- make install ``` - 64bit RISC-V ``` CROSS_COMPILE=riscv64-buildroot-linux-gnu- make -j $(nproc) CROSS_COMPILE=riscv64-buildroot-linux-gnu- make install ``` 稍加等待,完成后生成_install文件夹,这个文件夹下的东西就是根文件系统要用的。 制作一个最小的文件系统,先回到主工作目录,然后使用qemu-img生成一个img文件: - 32bit RISC-V ``` cd ~/riscv32-linux qemu-img create rootfs.img 1g mkfs.ext4 rootfs.img ``` - 64bit RISC-V ``` cd ~/riscv64-linux qemu-img create rootfs.img 1g mkfs.ext4 rootfs.img ``` rootfs.img是文件系统的镜像文件名,1g是磁盘文件大小,可以根据需要修改。并将磁盘文件格式化为ext4文件格式。 挂载这个img,将_install目录下的东西拷贝到这个文件系统中,除此之外再创建一些必要的文件和目录。 mkdir rootfs sudo mount -o loop rootfs.img rootfs cd rootfs sudo cp -r ../busyboxsource/_install/* . sudo mkdir proc sys dev etc etc/init.d 然后另外再新建一个最简单的init的RC文件: cd etc/init.d/ sudo vim rcS 该文件如下: #!/bin/sh mount -t proc none /proc mount -t sysfs none /sys /sbin/mdev -s 然后修改rcS文件权限,加上可执行权限,这样当busybox的init运行起来后,就能运行这个/etc/init.d/rcS脚本: sudo chmod +x rcS 最后退出 rootfs 目录并卸载文件系统: sudo umount rootfs 至此,文件系统就制作完成了。 ## 1.7. 利用QEMU运行系统 回到主工作路径,并运行命令启动QEMU: - RISC-V 32bit ``` #!/bin/sh cd ~/riscv32-linux qemu-system-riscv32 -M virt -m 256M -nographic \ -kernel linux/arch/riscv/boot/Image \ -drive file=rootfs.img,format=raw,id=hd0 \ -device virtio-blk-device,drive=hd0 \ -append "root=/dev/vda rw console=ttyS0" ``` - RISC-V 64bit ``` #!/bin/sh cd ~/riscv64-linux qemu-system-riscv64 -M virt -m 256M -nographic \ -kernel linux/arch/riscv/boot/Image \ -drive file=rootfs.img,format=raw,id=hd0 \ -device virtio-blk-device,drive=hd0 \ -append "root=/dev/vda rw console=ttyS0" ``` 命令过长不方便,也可以编辑一个.sh文件,例如: - RISC-V 32bit ``` vim ./yuanshenqidong32.sh ``` - RISC-V 64bit ``` vim ./yuanshenqidong64.sh ``` 将上面那段命令复制粘贴到.sh文件并保存,然后运行下面的命令即可: - RISC-V 32bit ``` sh ./yuanshenqidong32.sh ``` - RISC-V 64bit ``` sh ./yuanshenqidong64.sh ``` 系统成功在QEMU上启动: ![QEMU](./QEMU.png) 再看一下自定义测试程序的情况,直接输入命令: hello_busybox 窗口打印出与代码逻辑相符的结果: ![hello_busybox](./hello_busybox.png) 要退出当前QEMU模拟器终端,只需先按下Ctrl+A,松开后按下X即可。 至此,基础的QEMU上运行64bit RISC-V Linux完成。 ## 1.8. Build static binary (no shared libs) 问题 之前勾选了Build static binary (no shared libs),所以相关的库都是静态链接的,这会导致静态编译的用户程序直接给到文件系统能运行,但动态编译的用户程序无法直接运行,而去掉这个选项在QEMU启动时又会因为根文件系统中相关的库不完备而报错。我们再做一些操作,来解决这个问题。 Ps. 对于上一节做进系统的程序命令hello_busybox其实也是静态编译的,只不过上文说的动态静态编译指的是人工地在Ubuntu上用交叉编译器进行编译(静态:riscv-buildroot-linux-gnu-gcc -static xxx.c -o xxx,不加-static则是动态),这个做进系统的命令给其实是到busybox中静态编译(因为那个选项),所以做进系统的命令与外部人工-static编译并无本质区别。 上一章中“利用busybox制作rootfs”,当执行到menuconfig时,我们不勾选Build static binary (no shared libs),勾选其子选项Build position independent executable(我觉得应该勾选这个,build到独立位置) ![Build position independent executable](./Buildiindependent.png) 然后继续原来的步骤,直到给rcS文件添加好权限后,先不要umount取消挂载,交叉编译工具链文件夹中提供了一个sysroot,参考如下: - RISC-V 32bit ``` cd home/$USER/riscv32linux/toolchain/riscv32-ilp32d--glibc--bleeding-edge-2022.08-1/riscv32-buildroot-linux-gnu/ ls cd ./sysroot ls ``` - RISC-V 64bit ``` cd /home/$USER/riscv64-linux/toolchain/riscv64-lp64d--glibc--bleeding-edge-2022.08-1/riscv64-buildroot-linux-gnu/ ls cd ./sysroot ls ``` 看看情况: ![ls_sysroot](./ls_sysroot.png) 交叉编译工具链提供了一个lib完备的根文件系统,里边有相应的lib,可以直接给到给到rootfs使用: - RISC-V 32bit ``` cd home/$USER/riscv32-linux/ sudo mount -o loop rootfs.img rootfs cd rootfs sudo cp -r ../toolchain/riscv32-ilp32d--glibc--bleeding-edge-2022.08-1/riscv32-buildroot-linux-gnu/sysroot/* . ``` - RISC-V 64bit ``` cd home/$USER/riscv64-linux/ sudo mount -o loop rootfs.img rootfs cd rootfs sudo cp -r ../toolchain/riscv64-lp64d--glibc--bleeding-edge-2022.08-1/riscv64-buildroot-linux-gnu/sysroot/* . ``` 还需要搞一下环境变量,不然正常进入系统,什么命令都没得用,甚至ls都不行: cd ./etc sudo vim profile 第一行原来的不要了,直接把改成这样: export PATH="/bin:/sbin:/usr/bin:usr/sbin:/var/bin:/var/sbin":$PATH 如图: ![export_PATH](./export_PATH.png) 还要把程序编译一下拷给根文件系统,这次的程序是这样的: ```C #include #include int main() { int i = 1; for (i = 1; i < 16; i++) { printf("i = %d\n", i); } printf("Hello World!!!\n"); return 0; } ``` 编译(动态/静态编译对比一下): - RISC-V 32bit ``` riscv32-buildroot-linux-gnu-gcc main.c -o main riscv32-buildroot-linux-gnu-gcc -static main.c -o main_static ``` - RISC-V 64bit ``` riscv64-buildroot-linux-gnu-gcc main.c -o main riscv64-buildroot-linux-gnu-gcc -static main.c -o main_static ``` 如图,注意大小: ![compareSize](./compareSize.png) 复制可执行程序 - RISC-V 32bit ``` cd home/$USER/riscv32-linux/rootfs/opt cp ~/Desktop/test/main ./ #前一个路径是编译好的程序存放的地方 ``` - RISC-V 64bit ``` cd home/$USER/riscv64-linux/rootfs/opt cp ~/Desktop/test/main ./ #前一个路径是编译好的程序存放的地方 ``` 最后取消挂载: sudo umount rootfs 然后同样地: - RISC-V 32bit ``` sh ./yuanshenqidong32.sh ``` - RISC-V 64bit ``` sh ./yuanshenqidong64.sh ``` 在QEMU模拟器窗口Terminal运行程序: /opt/main 程序顺利运行: ![dynamic_out](./dynamic_out.png) 针对动/静态编译/链接的说明: 1、静态链接与动态链接的概念 静态链接:编译系统在链接阶段将程序的输出与所欲需要的库函数链接在一起,这样生成的可执行文件可以在没有函数库的情况下运行。 动态链接:与静态链接相反,编译系统在链接阶段不将程序的输出与所欲需要的库函数链接在一起,这样生成的可执行文件不能单独运行,其所在的文件系统中必须要有所需的库。 2、静态链接与动态链接的优缺点 - 静态链接 - 优:运行效率高 - 缺:生成的可执行文件较大 - 动态链接 - 优:生成的可执行文件较小 - ​缺:运行效率低 3、在配置Busybox的选项时,选择静态链接与动态链接的区别: 在使用静态链接方式(勾选Build static binary (no shared libs)选项)进行编译时,所需的库已经与程序静态地链接在一起,这些程序不需要额外的库就可以单独运行,也就是只需要拷贝_install文件夹下的东西就够了(其实这个文件夹下只有busybox是一个程序,其他全都是指向该程序的软连接)。但是自己编写的程序在文件系统上运行必须采用静态编译,否则会报类似“-bin/sh: hello: not found”的错误。 如果想要使用动态链接方式,那就需要把riscv编译器里面的提供的库文件加进去,环境变量之类的也要改一下,然后busybox选择动态编译的。在选了动态编译之后,命令和工具集需要加载lib里面的库,所以在制作根文件系统的时候必须要添加库文件,否则根文件系统无法启动。 一般动态链接方式: 准备三个文件: > main.c ```C #include "hello.h" int main(int argc, char const *argv[]) { hello(); return 0; } ``` > hello.h ```C #ifndef HELLO_H #define HELLO_H void hello(); #endif ``` > hello.c ```C #include #include "hello.h" void hello() { printf("Hello\n"); } ``` 动态链接库命名规则是固定的,在动态库名增加前缀lib,文件扩展名为`.so`,如`libhello.so`. 执行命令: # 生成hello.o gcc -c hello.c # 生成动态链接库libhello.so gcc -shared -fPIC -o libhello.so hello.o # 使用动态链接库编译main.c gcc -o main main.c -L. -lhello `-shared`:该选项指定生成动态连接库; `-fPIC`:表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的; `-L.`:表示要连接的库在当前目录中; `-lhello`:编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.so来确定库的名称。 拷贝动态库: sudo cp libhello.so /usr/lib 然后运行: ./main 不拷贝直接运行的话会报错: ./main: error while loading shared libraries: libhello.so: cannot open shared object file: No such file or directory