learn-tech/专栏/计算机基础实战课/12QEMU:支持RISC-V的QEMU如何构建?.md
2024-10-16 10:18:29 +08:00

13 KiB
Raw Blame History

                        因收到Google相关通知网站将会择期关闭。相关通知内容
                        
                        
                        12 QEMU支持RISC-V的QEMU如何构建
                        你好我是LMOS。

工欲善其事,必先利其器。作为开发者,学习过程中我们尤其要重视动手实践,不断巩固和验证自己学到的知识点。而动手实践的前提,就是要建立一个开发环境,这个环境具体包括编译环境、执行环境,以及各种常用的工具软件。

我会用两节课带你动手搭好环境,今天这节课咱们先热个身,搞清楚什么是主环境,还有怎么基于它生成交叉编译工具。

代码你可以从这里下载。

主环境

主环境有时也叫作HOST环境也就是我们使用的计算机环境即使用什么样的操作系统、什么架构的计算机作为开发环境。

比方说我们经常用PC机作为开发机使用它实际就是一个基于x86架构或其他架构的硬件平台再加上Windows或者Linux等操作系统共同组成的开发环境。

普通用户的电脑上经常安装的操作系统是Windows因为界面友好方便、操作简单且娱乐影音、游戏办公等应用软件也是不胜枚举。

Windows对普通用户来说的确非常友好。但是作为软件开发者对于志存高远、想要精研技术的我们而言更喜欢用的是Linux系统。

它虽然没有漂亮的GUI却暴露了更多的计算机底层接口也生产了更多的开发工具和各种各样的工具软件。比如大名鼎鼎的编译器GCC、声名远扬的编辑器EMACS、VIM还有自动化的脚本工具shell、make等。这些工具对开发者非常友好配合使用可以让我们的工作事半功倍后面你会逐渐体会到这点。

当然Linux只是一个内核我们不能直接使用还需要各种工具、库和桌面GUI把这些和Linux打包在一起发行这就构成了我们常说的Linux发行版。

我最喜欢的Linux发行版是Deepin和Ubuntu。为了统一我建议你使用Deepin最新版你也可以使用Ubuntu它们是差不多的。只是操作界面稍有不同。我先给你展示下我的Deepin如下图刚装上它的时候我就觉得它颇为惊艳。

这里最基础的安装我就不讲了因为安装Deepin十分简单无论是虚拟机还是在物理机上安装我相信你通过互联网都可以自行解决搞不定也可以看看这里。

这两种方式我也替你对比过虚拟机中的Linux较物理机上的Linux性能稍差一点但并不影响我们实验操作和结果。

为什么需要交叉编译

虽然主环境搞定了,但现在我们还不能直接跑代码。为什么呢?

先回想一下平时我们正常开发软件需要什么我猜哪怕你不能抢答也会知道个大概需要电脑PC、特定的操作系统比如Windows或Linux等在这个操作系统上还能运行相应的编辑器和编译器。编辑器用来编写源代码而编译器用来把源代码编译成可执行程序。

似乎不需要更多东西了毕竟我们日常开发的软件宿主平台和目标平台是相同的。如果我们把限制条件变一变情况就不同了。如果我们想尝试在RISC-V平台上跑程序要怎么办呢

你或许会说这简单买一台RISC-V的机器不就行了。可是先不说购买硬件的经济成本实际上很多RISC-V平台硬件资源如内存、SD卡容量有限不足以运行复杂的编译器软件有的甚至没有操作系统更别说在上面运行编译器或者编辑器软件了。

面对这样的困境,就要用到交叉编译了。什么是交叉编译呢?简单来说,就是在一个硬件平台上,生成另一个硬件平台的可执行程序。

举个例子我们在x86平台上编译生成ARM平台的可执行程序再比如说之后的课里我们将在x86平台上生成RISC-V的可执行程序。这些都属于交叉编译在这个过程中编译生成可执行程序的平台称为宿主机或者主机执行特定程序的平台如ARM或者RISC-V平台称为目标机。

我特意准备了图解为你展示在x86平台上交叉编译生成RISC-V平台可执行程序的过程你可以仔细看看

如何构建RISC-V交叉编译器

前面说了交叉编译的本质就是生成其他平台体系上的可执行程序这个体系又不同于我们宿主平台。我们的目的很简单就是要在x86平台上编写源代码然后编译出RISC-V平台的可执行程序最后放在RISC-V平台上去运行。

因此我们需要用宿主机编译器A编译出一个编译器B这个编译器B是本地平台上的可执行程序。

说得再具体点你可以把编译器B看作是 x86 Linux上的一个应用。但它的特殊之处就是能根据源代码生成RISC-V平台上的可执行程序。补充一句这里的编译器A和B都是C语言编译器。

下面我们开始构造编译器B。编译器B不仅仅是C语言编译器还有很多额外的程序。比如RISC-V平台上使用的二进制文件分析objcopy、反汇编objdump、elf结构分析工具readelf、静态库归档ar、汇编器as、链接器ld、GDB、C语言库Newlib、Glib、Multlib等。

为了简单、便于区分我们把这些对应于RISC-V平台的编译器相关的软件统称为 RISC-V工具链。

构建RISC-V工具链的主要步骤如下

安装依赖工具在宿主平台上安装编译器A以及相应的工具和库。- 下载RISC-V工具链的源代码- 配置RISC-V工具链- 编译RISC-V工具链并安装在宿主平台上。

第一步:安装依赖工具

我们先从第一步开始编译器A主要是宿主平台上的GCC工具主要是Make、Git、Autoconf、Automake、CURL、Python3、Bison、Flex等。这里GCC主要在build-essential包中我们只要在Linux终端中输入如下指令就可以了

sudo apt-get install git autoconf automake autotools-dev curl python3 libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf patchutils bc libexpat-dev libglib2.0-dev ninja-build zlib1g-dev pkg-config libboost-all-dev libtool libssl-dev libpixman-1-dev libpython-dev virtualenv libmount-dev libsdl2-dev

如果不出意外这些工具和库会通过网络由Linux的apt包管理器全自动地给你安装完毕。

第二步:下载工具链源代码

接着进入第二步下载RISC-V工具链源代码。通常来说我们只要用Git克隆一个riscv-gnu-toolchain仓库即可其它的由riscv-gnu-toolchain仓库中的仓库子模块自动处理。

手动配置环节

由于众所周知的网络原因你可能连riscv-gnu-toolchain仓库都下载不下来更别说自动下载仓库子模块了。为了照顾卡壳的人我把手动处理的情况也顺便讲一下能够直接自动安装的同学可以跳过这部分直接翻到7条指令之后的最终截图对一下结果就行。

子模块如下:

riscv-qemu虚拟机 riscv-newlib (用嵌入式的轻量级C库) riscv-binutils(包含一些二进制工具集合如objcopy等) riscv-gdb(用于调试代码的调试器) riscv-dejagnu(用于测试其它程序的框架) riscv-glibc(GNU的C库) riscv-gcc (C语言编译器)

这些子模块我们需要手动从Gitee网站上下载。下载前我们先在终端上输入后面的指令建立一个目录并切换到该目录中

mkdir RISCV_TOOLS cd RISCV_TOOLS

把RISC-V工具链的源代码手动下载好步骤稍微多了一些我在后面分步骤列出方便你跟上节奏。

其实也就是7条指令的事儿并不复杂。先统一说明下后面这些命令都是切换到riscv-gnu-toolchain目录的终端下输入我给你列出的指令即可。

开始下载riscv-gnu-toolchain命令如下

git clone https://gitee.com/mirrors/riscv-gnu-toolchain
cd riscv-gnu-toolchain

下载RISC-V平台的C语言编译器源代码仓库输入如下指令

git clone -b riscv-gcc-10.2.0 https://gitee.com/mirrors/riscv-gcc

下载测试框架源代码仓库即riscv-dejagnu。输入如下指令

git clone https://gitee.com/mirrors/riscv-dejagnu

下载GNU的C库源代码仓库也就是riscv-glibc输入如下指令

git clone -b riscv-glibc-2.29 https://gitee.com/mirrors/riscv-glibc

下载用于嵌入式的轻量级C库源代码仓库即riscv-newlib。输入如下指令

git clone https://gitee.com/mirrors/riscv-newlib

下载二进制工具集合源代码仓库riscv-binutils输入如下指令

git clone -b riscv-binutils-2.35 https://gitee.com/mirrors/riscv-binutils-gdb riscv-binutils

最后下载GDB软件调试器源代码仓库riscv-gdb输入如下指令

git clone -b fsf-gdb-10.1-with-sim https://gitee.com/mirrors/riscv-binutils-gdb riscv-gdb

现在所有的RISC-V工具链的源代码我们已经下载完了。我们一起来同步一下确保你我的riscv-gnu-toolchain目录下的目录和文件完全一致。

在riscv-gnu-toolchain目录的终端下输入ls指令你应该得到和后面这张图一样的结果。

第三步:配置工具链

在我们用宿主编译器编译所有的RISC-V工具链的源代码之前还有最重要的一步那就是配置RISC-V工具链的功能。

RISC-V工具链有很多配置选项不同的配置操作会生成具有特定功能的RISC-V工具链。此外配置操作还有一个功能就是检查编译RISC-V工具链所依赖的工具和库。检查通过就会生成相应的配置选项文件还有用于编译操作的Makefile文件。

下面我们开始配置操作。为了不污染源代码目录我们可以在riscv-gnu-toolchain目录下建立一个build目录用于存放编译RISC-V工具链所产生的文件。还是在切换到riscv-gnu-toolchain目录的终端下输入如下指令

mkdir build  #建立build目录
#配置操作终端一定要切换到build目录下再执行如下指令
../configure --prefix=/opt/riscv/gcc --enable-multilib --target=riscv64-multlib-elf

我给你解释一下指令里的关键内容。

prefix表示RISC-V的工具链的安装目录我们一起约定为“/opt/riscv/gcc”这个目录。

enable-multilib表示使用multlib库使用该库编译出的RISC-V工具链既可以生成RISCV32的可执行程序也可以生成RISCV64的可执行程序而默认的Newlib库则不行它只能生成RISCV32/64其中之一的可执行程序。

target表示生成的RISC-V工具链中软件名称的前缀是riscv64-multlib-elf-xxxx。若配置操作执行成功了build目录中会出现如下所示的文件

第四步:编译工具链

最后我们来完成第四步编译RISC-V工具链。只要配置操作成功了就已经成功了90%。其实编译操作是简单且高度自动化的我们只要在切换到build目录的终端下输入如下指令即可

sudo make -j8

这个指令在编译完成后会自动安装到“/opt/riscv/gcc”目录由于要操作“/opt/riscv/gcc”目录需要超级管理员权限所以我们要记得加上sudo。

另外如果你的宿主机的CPU有n个核心就在make 后面加-jn*2这样才能使用多线程加速编译。

好了,一通操作猛如虎,现在最重要的事情是等待计算机“搬砖”了。你不妨播放音乐,泡上一杯新鲜的热茶,一边听歌,一边喝茶……估计要喝很多杯茶,才会编译完成。最最重要的是这期间不能断电,否则几个小时就白费了。

如果终端中不出现任何错误,就说明编译成功了。我们在终端中切换到“/opt/riscv/gcc/bin”目录下执行如下指令

riscv64-unknown-elf-gcc -v

上述指令执行以后会输出riscv64-unknown-elf-gcc的版本信息这证明RISC-V工具链构建成功了。如下所示

到这里我们环境已经成功了一半有了交叉编译器并且这种交叉编译器能生成32位的RISC-V平台的可执行程序也能生成64位的RISC-V平台的可执行程序。

你可能会好奇,成功了一半,那另一半呢?这需要我们接着干另一件事。什么事呢?容我先在这里卖个关子,下节课再揭秘。

重点回顾

通过这节课的学习我们成功构建了RISC-V工具链这样就能在X86平台上生成RISC-V平台的可执行程序了。下面让我们一起回顾一下这节课中都做了些什么。

我们首先约定了宿主环境需要用到Ubuntu或者Deepin的Linux发行版无论你是将它们安装在物理PC上还是安装在虚拟机上。

然后我们了解了什么是交叉编译。为了方便后面课程学习动手实践我们要在x86平台的宿主机上编译生成RISC-V平台的可执行程序。

明确了目标我们一起动手开始构建了一个RISC-V交叉编译器。你会发现其中不只是C/C++编译器还有很多处理二进制可执行程序的工具我们把这些统称为RISC-V工具链。

思考题

请你说一说交叉编译的过程?

期待你再留言区分享自己的实验笔记,或者与我交流讨论。也推荐你把这节课分享给更多朋友,我们一起玩转交叉编译。