Ubuntu && LLVM && RISC-V
主要参考:
https://risc-v-getting-started-guide.readthedocs.io/en/latest/linux-qemu.html;
https://zhuanlan.zhihu.com/p/263550372;
https://zhuanlan.zhihu.com/p/258394849
1 LLVM构建
1.1 LLVM 源码下载
建议下载LLVM release版本:https://releases.llvm.org/
当前版本:11.0.0
下载:Sources --> llvm-project monorepo source code (.sig)选项
1.2 编译前配置 & 编译安装
创建build,然后使用cmake配置;或者使用clion等IDE配置,参考https://blog.csdn.net/qq_21746331/article/details/110198265#t8
推荐先了解cmake,再使用IDE
$ cmake -G <generator> [options] SRC_ROOT
其中:
-
generator 表示用于最终驱动 gcc 执行编译生成 llvm 的工具,是用双引号括起来的字符串,cmake 支持跨平台开发,有以下四种选项:
-
Unix Makefiles
: 即采用 Unix 上传统的 Make,指定该选项后 cmake 负责生成用于 Make 的 makefile 文件。Ninja
: 采用 Ninja,指定该选项后 cmake 负责生成用于 Ninja 的 build.ninja 文件。这是 LLVM 的开发社区推荐采用的方式,因为对于像 LLVM 这样的大型软件来说,采用 Ninja 会大大加速编译的速度。Visual Studio
: 指示 cmake 产生用于 Visual Studio 的项目构造文件。Xcode
: 指示 cmake 产生用于 Xcode 的项目构造文件, Xcode 是运行在操作系统 MacOS X 上的集成开发工具(IDE)。
-
SRC_ROOT
: LLVM 的官方定义是the top level directory of the LLVM source tree
, 在这里指的就是我们下载的仓库根目录llvm-project
下的llvm
子目录,在 LLVM 项目中 llvm 子目录存放的是这个项目的主框架代码,是必须要编译的对象。 -
options,以
-D
开头定义的选项宏,如果超过一个则用空格分隔。这些选项会影响 cmake 生成的构造配置文件并进而影响整个编译构造过程,针对 LLVM 常用的有以下这些,更多选项请参阅 【参考 1】: -
CMAKE_BUILD_TYPE=type
: 指定生成的应用程序(这里当然指的是 LLVM)的类型,type
包括Debug
、Release
、RelWithDebInfo
或者MinSizeRel
。如果不指定缺省为Debug
。CMAKE_INSTALL_PREFIX=directory
: 用于指定编译完后安装 LLVM 工具和库的路径,如果不指定,默认安装在/usr/local
。LLVM_TARGETS_TO_BUILD
: 用于指定生成的 LLVM 可以支持的体系架构(这里称为 target),LLVM 和 GCC 有个很大的不同点是, GCC 需要为每个特定的体系架构,譬如 arm/x86 独立生成一套交叉工具链套件,而 LLVM 是在一个工具链套件中就可以支持多个体系架构。如果不指定,默认会编译所有的 targets,具体的 targets 有哪些,可以看源码llvm-project/llvm/CMakeLists.txt
中LLVM_ALL_TARGETS
的定义。具体制作时可以自己指定需要的 targets,通过以分号(semicolon)分隔方式给出,譬如-DLLVM_TARGETS_TO_BUILD="ARM;PowerPC;X86"
。LLVM_DEFAULT_TARGET_TRIPLE
: 可以通过该选项修改默认的 target 的 triple 组合,不指定默认是x86_64-unknown-linux-gnu
。LLVM_ENABLE_PROJECTS='...'
: LLVM 是整个工具链套件的总称,LLVM 下包括了很多个子项目,譬如 clang, clang-tools-extra, libcxx, libcxxabi, libunwind, lldb, compiler-rt, lld, polly, or debuginfo-tests 等。如果不指定该选项,默认只编译 llvm 这个主框架。如果要选择并指定编译哪些子项目,可以通过分号分隔方式给出,譬如我们在编译 llvm 之外还想编译 Clang, libcxx, 和 libcxxabi, 那么可以写成这样:-DLLVM_ENABLE_PROJECTS="clang;libcxx;libcxxabi"
。
$ cmake -G "Unix Makefiles" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=../install \
-DLLVM_TARGETS_TO_BUILD="RISCV" \
-DLLVM_ENABLE_PROJECTS="clang;libcxx;libcxxabi" \
-DLLVM_DEFAULT_TARGET_TRIPLE="riscv64-unknown-linux-gnu" \
../llvm
$ make -j $(nproc)
$ make install
$ ../install/bin/clang -v
clang version 10.0.1 (https://gitee.com/mirrors/llvm-project.git ef32c611aa214dea855364efd7ba451ec5ec3f74)
Target: riscv64-unknown-linux-gnu
Thread model: posix
InstalledDir: /home/u/ws/llvm-project/test/../install/bin
2 构建RISCV交叉编译工具链
2.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 \
libglib2.0-dev libfdt-dev libpixman-1-dev \
libncurses5-dev libncursesw5-dev
$ mkdir riscv64-linux
$ cd riscv64-linux
2.2 从国内镜像git riscv-gnu-toolchain 并移除qemu并下载其他子库
$ git clone https://gitee.com/mirrors/riscv-gnu-toolchain
$ cd riscv-gnu-toolchain
$ git rm qemu
$ git submodule update --init --recursive
2.3 编译交叉工具链
$ ./configure --prefix=/opt/riscv64
$ sudo make linux -j $(nproc)
2.4 export
export PATH="$PATH:/opt/riscv64/bin"
2.5 验证
$ riscv64-unknown-linux-gnu-gcc -v
Using built-in specs.
COLLECT_GCC=riscv64-unknown-linux-gnu-gcc
COLLECT_LTO_WRAPPER=/opt/riscv64/libexec/gcc/riscv64-unknown-linux-gnu/10.1.0/lto-wrapper
Target: riscv64-unknown-linux-gnu
Configured with: /home/u/ws/riscv64-linux/riscv-gnu-toolchain/riscv-gcc/configure --target=riscv64-unknown-linux-gnu --prefix=/opt/riscv64 --with-sysroot=/opt/riscv64/sysroot --with-system-zlib --enable-shared --enable-tls --enable-languages=c,c++,fortran --disable-libmudflap --disable-libssp --disable-libquadmath --disable-libsanitizer --disable-nls --disable-bootstrap --src=.././riscv-gcc --disable-multilib --with-abi=lp64d --with-arch=rv64imafdc --with-tune=rocket 'CFLAGS_FOR_TARGET=-O2 -mcmodel=medlow' 'CXXFLAGS_FOR_TARGET=-O2 -mcmodel=medlow'
Thread model: posix
Supported LTO compression algorithms: zlib
gcc version 10.1.0 (GCC)
3 构建RISCV运行环境:QEMU
3.1 下载qemu源码
$ cd riscv64-linux
#回到根目录
$ wget https://download.qemu.org/qemu-5.2.0.tar.xz
$ tar xvJf qemu-5.2.0.tar.xz
3.2 编译和安装qemu
$ cd qemu-5.1.0/
$ ./configure --target-list=riscv64-softmmu --prefix=/opt/qemu
$ make -j $(nproc)
$ sudo make install
3.3 export
export PATH=$PATH:/opt/riscv64/bin:/opt/qemu/bin
3.4 验证qemu
$ qemu-system-riscv64 --version
QEMU emulator version 5.1.0
Copyright (c) 2003-2020 Fabrice Bellard and the QEMU Project developers
3.5 下载内核源码
$ cd riscv64-linux
#回到根目录
$ git clone https://gitee.com/mirrors/linux.git
如果出现“过早的文件结束符”,尝试扩大缓存
3.6 配置和编译内核
$ git checkout v5.4
$ make ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- defconfig
$ make ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- -j $(nproc)
3.7 下载busybox源码
$ cd riscv64-linux
#回到根目录
$ git clone https://gitee.com/mirrors/busyboxsource.git
$ cd busyboxsource
3.8 配置编译安装busybox
$ CROSS_COMPILE=riscv64-unknown-linux-gnu- make menuconfig
#打开配置菜单后进入第一行的 "Settings",在"Build Options"节中,选中 “Build static binary (no shared libs)”,设置好后退出保存配置。
$ CROSS_COMPILE=riscv64-unknown-linux-gnu- make -j $(nproc)
$ CROSS_COMPILE=riscv64-unknown-linux-gnu- make install
$ ls _install
bin linuxrc sbin usr
3.9 制作一个最小的文件系统
$ cd riscv64-linux
#回到根目录
$ qemu-img create rootfs.img 1g
$ mkfs.ext4 rootfs.img
$ 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
$ cd etc/init.d/
$ sudo touch rcS
$ sudo vi rcS
#编辑该文件如下
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
/sbin/mdev -s
$ sudo chmod +x rcS
$ sudo umount rootfs
4 编译并启动运行
4.1 编译
编辑一个简单的 c 文件 test.c
$ vim test.c
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("Hello, world!\n");
return 0;
}
受限于 LLVM 自身链接器和 C 库的不完善,clang 目前需要使用 GNU 的链接器和 C 库来生成 RISC-V 的可执行程序。
运行 clang 编译程序,通过 --sysroot
选项来指定 gnu 工具链的 sysroot,通过 --gcc-toolchain
来指定 gcc 工具链的位置。
使用 2 中制作的交叉编译工具链,生成riscv上的可执行文件
$ clang --target=riscv64-unknown-linux-gnu --gcc-toolchain=/opt/riscv64 --sysroot=/opt/riscv64/sysroot/ --static test.c
验证文件格式
$ file a.out
a.out: ELF 64-bit LSB executable, UCB RISC-V, version 1 (SYSV), statically linked, for GNU/Linux 4.15.0, with debug_info, not stripped
4.2 启动qemu并运行riscv可执行文件
将文件复制到3中构建的文件系统中
$ cd riscv64-linux
#回到根目录
$ sudo mount -o loop rootfs.img rootfs
$ sudo cp <the path of the a.out> rootfs/root/
$ sudo umount rootfs
启动qemu并运行
$ 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"
/ # cd root/
/root # ./a.out
Hello, world!