riscv 工具链的了解和使用

  • 在 RISC-V 开发中, 交叉编译工具链允许我们在一个平台(如 x86 主机)上,为另一个平台(如RISC-V 开发板)生成可执行代码。

1. 核心概念:工具链的“三元组” (Triplet)

你经常会看到像 riscv64-unknown-linux-gnu- 这样的名称,这就是工具链的“三元组”,其标准格式为:

1
<arch>-<vendor>-<os>
  • <arch> (架构):指定目标 CPU 架构,例如 riscv64riscv32
  • <vendor> (供应商):通常是 unknown 或公司名。
  • <os> (操作系统/环境):这是最关键的部分,它决定了工具链的目标环境和使用的 C 标准库 (libc)。最常见的两个是:
    • elf: 面向裸机 (Bare-metal)嵌入式实时操作系统 (RTOS)
    • linux-gnu: 面向完整的 GNU/Linux 操作系统

2. 两大主流工具链详解

1. riscv64-unknown-elf

用于裸机和嵌入式开发的标准工具链。

  • 目标系统: 没有任何操作系统的环境(裸机),或者使用了轻量级实时操作系统(如 FreeRTOS, RT-Thread)的环境。
  • C 标准库 (Libc): 使用 Newlib
    • Newlib 是一个轻量级的 C 库,专为嵌入式系统设计。它只提供最基础的 C 语言函数(如 strcpy, printf),并且不依赖任何操作系统的系统调用(Syscall)。如果需要文件操作或内存管理,需要实现底层的“桩函数”(stubs)。
  • 应用场景:
    • 编写 Bootloader(如 U-Boot)。
    • 开发 RISC-V 的“特权二进制接口”固件(如 OpenSBI)。
    • 为微控制器 (MCU) 编写固件。
    • 开发简单的操作系统内核。

2. riscv64-linux-gnu

用于在 RISC-V 平台上开发 Linux 应用的工具链。

  • 目标系统: 运行完整 Linux 内核的系统。
  • C 标准库 (Libc): 使用 glibc (GNU C Library)。
    • glibc 是功能完备的标准 C 库,提供了丰富的 POSIX API 支持(如 fork, pthread, 文件系统操作等)。它深度依赖 Linux 内核提供的系统调用来完成工作。
  • 典型应用场景:
    • 编译一个标准的 C/C++ 应用程序(如 Nginx, Redis),让它运行在 RISC-V 架构的 Linux 发行版上(如 Ubuntu, Debian for RISC-V)。
    • 开发 Linux 用户态驱动或服务程序。

Tip: riscv64-unknown-linux-gnu-riscv64-linux-gnu- 在功能上是等价的,可以互换使用。unknown 字段在这里没有实际影响。


3. 如何获取和安装工具链

方式一:使用包管理器 (简单快捷)

对于 linux-gnu 工具链,这是最简单的方法。以 Ubuntu/Debian 为例:

1
2
3
# 安装 C 和 C++ 交叉编译器
sudo apt update
sudo apt install gcc-riscv64-linux-gnu g++-riscv64-linux-gnu
  • 优点: 安装简单
  • 缺点: 版本可能不是最新的

方式二:从源码编译 (推荐,灵活且最新)

获取最新版本工具链(包括 elflinux-gnu)的最佳方式

  1. 安装相关依赖

    1
    2
    sudo apt install libncurses-dev libncursesw5-dev pkg-config autoconf automake bison flex gawk gcc g++ libtool make patch python3-dev texinfo wget
    sudo apt-get 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 make bison flex texinfo gawk libncurses5-dev libexpat1-dev libgmp-dev libmpfr-dev libmpc-dev libgmp-dev libmpfr-dev libmpc-dev
  2. 克隆官方仓库

    1
    2
    3
    #`--recursive` 参数至关重要,它会同时下载 `gcc`, `binutils` 等所有子模块。
    git clone --recursive https://github.com/riscv-collab/riscv-gnu-toolchain
    cd riscv-gnu-toolchain
    1
    2
    3
    4
    5
    6
    7

    * 检查当前子模块情况。
    git submodule status

    * 拉取子模块(init: 子模块未初始化时初始化,recursive: 嵌套子模块也一起拉取)
    * 主仓库换分支时同步子模块
    git submodule update --init --recursive
  3. 配置与编译
    需要指定安装路径 (--prefix) 和目标架构 (--with-arch, --with-abi)。

    • 编译 linux-gnu 工具链 (用于Linux):
    1
    2
    3
    4
    5
    6
    7
    8
    # 创建安装目录
    mkdir -p /opt/riscv-linux
    # 配置: 目录,目标是为linux构建工具链
    ./configure --prefix=/opt/riscv-linux --enable-linux
    # `make linux` 会自动处理多阶段编译的复杂流程(构建临时gcc->构建glibc->构建最终gcc)
    time make -j$(nproc) linux
    # 安装
    sudo make install
    • 编译 elf 工具链 (用于裸机):riscv64-unknown-elf-
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 创建一个安装目录
    mkdir -p /opt/riscv-elf
    # 配置: 其中 `rv64gc` 指支持 64 位基础整数指令集(I)、乘除法(M)、原子(A)、浮点(F、D)、压缩(C)等扩展;
    # `lp64d` 表示 long 和 pointer 为 64 位,使用 double 精度浮点。
    ./configure --prefix=/opt/riscv-elf --with-arch=rv64gc --with-abi=lp64d
    # 编译 (-j`nproc` 使用所有CPU核心加速)
    time make -j$(nproc)
    # 安装
    sudo make install
  4. 添加到环境变量
    为了方便使用,将工具链的 bin 目录添加到 PATH。编辑 ~/.bashrc~/.zshrc 文件:

    1
    2
    3
    4
    5
    6
    # 添加这行到文件末尾 (根据编译的类型选择)
    export PATH="/opt/riscv-elf/bin:$PATH" # For elf toolchain
    export PATH="/opt/riscv-linux/bin:$PATH" # For linux toolchain

    # 使配置生效
    source ~/.bashrc

4. 简单使用

1
2
3
4
5
6
7
// hello.c
#include <stdio.h>

int main() {
printf("Hello, RISC-V World!\n");
return 0;
}

使用 elf 工具链编译

1
2
3
4
5
6
7
# 编译
riscv64-unknown-elf-gcc -o hello.elf hello.c

# 查看文件类型
file hello.elf
# 输出会类似:
# hello.elf: ELF 64-bit LSB executable, UCB RISC-V, version 1 (SYSV), statically linked, not stripped

这个 hello.elf 是一个静态链接的裸机程序。它不能直接在 x86 Linux 主机上运行,也不能在 RISC-V Linux 系统上直接运行,因为它缺少操作系统加载器所需的信息。它需要被烧录到裸机环境或通过模拟器(如 QEMU-system)加载执行。

这个 hello.elf 文件虽然是标准的 ELF 格式,但它与 Linux 可执行文件有本质区别:

  • 不含 INTERP:它不指定动态链接器,因为它不依赖任何动态库。
  • 静态链接: 它静态链接了轻量级的 newlib C 库,而非 glibc
  • 无系统调用: 其中的 printf 函数最终依赖开发者实现的底层 I/O 桩函数(如通过 UART 发送字符),而不是 Linux 的 write 系统调用。
  • 不同的程序入口: 它的启动代码 (_start) 负责初始化 C 运行环境后调用 main,但 main 返回后程序通常会进入死循环,因为它没有“退出”到操作系统的概念。

使用 linux-gnu 工具链编译

1
2
3
4
5
6
7
# 编译
riscv64-linux-gnu-gcc -o hello.linux hello.c

# 查看文件类型
file hello.linux
# 输出会类似:
# hello.linux: ELF 64-bit LSB executable, UCB RISC-V, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-riscv64-lp64d.so.1, for GNU/Linux 4.15.0, not stripped

这个 hello.linux 是一个动态链接的 Linux 程序。它需要一个 RISC-V Linux 环境来运行,因为它依赖于该环境中的动态链接器 (ld-linux-riscv64-lp64d.so.1) 和 glibc 库。


总结:

特性 riscv64-unknown-elf riscv64-linux-gnu
目标平台 裸机 (Bare-metal)、RTOS GNU/Linux 系统
C 库 newlib (轻量级,无 OS 依赖) glibc (功能完备,依赖 Linux 内核)
核心用途 固件、Bootloader、RTOS 应用、简单操作系统内核 编译可在 RISC-V Linux 上运行的应用程序
选择场景 “为一块开发板从零开始写程序。” “在启动的 Linux 上面运行软件。”
作者

GoKo Mell

发布于

2024-01-18

更新于

2025-09-11

许可协议

评论

:D 一言句子获取中...