3、工具链搭建(上):RISC-V GNU 工具链的源码编译与安装、配置环境变量、验证工具链

好,咱们开始动手了。

上一章我们把 QEMU 模拟器跑起来了,但光有硬件模拟还不够——你得有编译器,才能把 C 代码变成 RISC-V 能懂的机器码。这一章,我们就来搞定 RISC-V GNU 工具链。

说白了,这套工具链就是你的「翻译官」。它把高级语言翻译成 RISC-V 的二进制指令。没有它,你写再多代码也是白搭。

核心知识点速览:

  • 从源码编译 RISC-V GNU 工具链
  • 配置环境变量,让系统能找到工具
  • 编写一个简单的 C 程序,验证工具链是否正常工作
RISC-V 工具链搭建流程 ① 下载源码 riscv-gnu-toolchain ② 配置编译 ./configure --prefix=... ③ make 编译 耗时较长,耐心等待 ④ 配置环境变量 export PATH=... ⑤ 验证工具链 riscv64-unknown-elf-gcc ⑥ 编写测试程序 hello.c → 编译 → 运行 整个流程走下来,你的 RISC-V 开发环境就基本成型了

3.1 为什么要从源码编译?

你可能会问:直接用 apt-get 安装不行吗?

嗯,我刚开始也这么想。但后来发现,发行版自带的交叉编译器版本往往比较老,而且不一定支持你想要的扩展指令集。比如你要用 RISC-V 的向量扩展(V 扩展),或者想定制一些编译参数,那就必须自己编译。

说白了,从源码编译虽然麻烦一点,但你能拿到最干净的、完全可控的工具链。我在一个项目里就吃过亏——用系统自带的工具链编译出来的程序,在 QEMU 上跑得好好的,换到真实芯片上就崩了。查了半天,发现是工具链版本不匹配。从那以后,我都是自己编译。

3.2 下载源码

首先,我们需要从 GitHub 上拉取 RISC-V GNU 工具链的源码。这个仓库是官方维护的,包含了 GCC、Binutils、GDB 等全套工具。

# 克隆仓库(建议用 --depth=1 只拉取最新版本,省时间)
git clone --depth=1 https://github.com/riscv-collab/riscv-gnu-toolchain.git
cd riscv-gnu-toolchain

小提示:这个仓库比较大,即使加了 --depth=1 也有几百 MB。如果你的网络不太好,可以考虑用国内镜像,或者提前下载好压缩包。我个人习惯用 git clone,因为后续更新方便。

3.3 安装依赖

编译工具链需要一些系统库和工具。不同发行版的包名略有不同,这里以 Ubuntu/Debian 为例:

sudo apt-get update
sudo apt-get install -y autoconf automake autotools-dev curl python3 \
    libmpc-dev libmpfr-dev libgmp-dev gawk build-essential \
    bison flex texinfo gperf libtool patchutils bc \
    zlib1g-dev libexpat-dev

如果你是 CentOS/RHEL 用户,把 apt-get 换成 yum 或 dnf,包名稍微调整一下就行。我记得有一次在 CentOS 7 上编译,缺了 libmpc,折腾了半天才发现——嗯,所以依赖这一步千万别跳过。

3.4 配置与编译

依赖装好之后,就可以配置了。这里我们选择编译「Newlib」版本——也就是面向裸机或嵌入式系统的 C 库。它体积小,适合没有操作系统的环境。

# 创建一个目录存放编译产物
mkdir build
cd build

# 配置,指定安装路径
../configure --prefix=/opt/riscv --enable-multilib

# 开始编译(-j 后面的数字是你的 CPU 核心数,可以加快速度)
make -j$(nproc)

注意:编译过程非常耗时!在我的机器上(8 核 i7),大概需要 30-40 分钟。如果你用的是虚拟机或者配置较低的电脑,可能得 1-2 小时。建议找个空闲时间,泡杯咖啡慢慢等。

这里解释一下几个关键参数:

  • --prefix=/opt/riscv:指定安装路径。我习惯放在 /opt 下,这样和系统自带的工具不会冲突。
  • --enable-multilib:支持多种 RISC-V 架构变体(比如 RV32 和 RV64)。这个选项很实用,因为你以后可能会在不同位宽的目标上做实验。

编译完成后,你会看到 /opt/riscv 目录下多了一堆东西:bin、lib、share 等。核心的可执行文件都在 bin 目录里。

ls /opt/riscv/bin/
# 你应该能看到类似这样的输出:
# riscv64-unknown-elf-addr2line  riscv64-unknown-elf-gcc
# riscv64-unknown-elf-ar         riscv64-unknown-elf-gcc-12.2.0
# riscv64-unknown-elf-as         riscv64-unknown-elf-gcc-ar
# ... 等等

3.5 配置环境变量

工具链装好了,但系统还不知道它在哪。你需要把它的路径加到 PATH 环境变量里。

我个人习惯把环境变量配置写到 ~/.bashrc~/.zshrc 里,这样每次打开终端都能直接用。

# 编辑你的 shell 配置文件
vim ~/.bashrc

# 在文件末尾添加以下内容
export PATH=$PATH:/opt/riscv/bin

保存退出后,执行 source ~/.bashrc 让配置生效。或者直接关掉终端重新打开也行。

一个小技巧:我还会额外加一个环境变量 RISCV,指向工具链的根目录。这样在写 Makefile 或者脚本时,引用起来更方便:

export RISCV=/opt/riscv
export PATH=$PATH:$RISCV/bin

3.6 验证工具链

环境配好了,我们来验证一下工具链能不能正常工作。

先检查版本信息:

riscv64-unknown-elf-gcc --version
# 输出类似:
# riscv64-unknown-elf-gcc (GCC) 12.2.0
# Copyright (C) 2022 Free Software Foundation, Inc.
# ...

看到版本号,说明工具链已经就绪了。接下来,我们写一个最简单的 C 程序,编译成 RISC-V 的可执行文件。

// hello.c
#include <stdio.h>

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

编译命令:

riscv64-unknown-elf-gcc -o hello hello.c

编译完成后,用 file 命令看看生成的文件:

file hello
# 输出:
# hello: ELF 64-bit LSB executable, UCB RISC-V, version 1 (SYSV), ...

看到「UCB RISC-V」这几个字,说明编译成功了。这是一个货真价实的 RISC-V 64 位可执行文件。

你还可以用 riscv64-unknown-elf-objdump 反汇编看看:

riscv64-unknown-elf-objdump -d hello | head -20

你会看到一堆 RISC-V 指令,比如 addiluijal 等等。嗯,这就是你的 C 代码被翻译成的机器语言。

验证清单:

  1. riscv64-unknown-elf-gcc --version 能正常输出版本号
  2. ✅ 能编译 hello.c 生成 ELF 文件
  3. file 命令确认是 RISC-V 架构
  4. objdump 能看到 RISC-V 指令

3.7 避坑指南

编译过程中可能会遇到一些问题,我把自己踩过的坑列出来,你遇到了可以参考:

  • 编译到一半报错,提示缺少某个头文件——多半是依赖没装全。回头检查一下 3.3 节的依赖列表,看看是不是漏了某个包。
  • make 执行到 90% 左右卡住不动——可能是内存不够。编译 GCC 很吃内存,建议至少 4GB 可用内存。如果不够,可以关掉一些其他程序,或者增加 swap 空间。
  • 编译完成后,执行 gcc 提示「找不到命令」——环境变量没配好。检查一下 PATH 里是否包含了 /opt/riscv/bin,以及是否执行了 source。
  • 我曾经在 macOS 上编译过一次——嗯,那真是噩梦。GNU 工具链对 macOS 的支持不太好,各种依赖问题。如果你用的是 Mac,建议用 Docker 或者直接装 Linux 虚拟机。

3.8 本章小结

这一章我们完成了工具链的搭建。你学会了:

  • 从 GitHub 拉取 RISC-V GNU 工具链源码
  • 配置并编译出适合嵌入式开发的交叉编译器
  • 配置环境变量,让系统能找到工具链
  • 编写并编译了一个简单的 C 程序,验证了工具链的正确性

现在,你的开发环境已经具备了「编译」的能力。下一章,我们会把编译好的程序放到 QEMU 上运行,真正看到 RISC-V 的世界里跑起代码来。


专注资料整理