2. 设备树基础

做嵌入式开发,环境搭建是第一道坎。很多人觉得这事简单,照着文档敲命令就行。但我在实际项目中见过太多人卡在这一步——不是版本冲突,就是路径不对,折腾半天连个Hello World都跑不起来。

这一章,我带你一步步把环境搭好。咱们的目标很明确:装好QEMU、配好交叉编译工具链、装好GDB,最后跑一个程序验证全部正常。整个过程大概需要30分钟,请确保你的电脑能联网。

本章核心产出:一个能编译RISC-V程序、能在QEMU上运行、能用GDB调试的完整开发环境。

2.1 整体架构:这些工具怎么配合工作?

先别急着敲命令。咱们花两分钟搞清楚这些工具之间的关系,后面遇到问题你才知道怎么排查。

RISC-V 开发环境工具链协作关系 开发机 (x86/ARM) Ubuntu / macOS / Windows riscv64-unknown-elf-gcc 交叉编译工具链 riscv64-unknown-elf-gdb 调试器 *.elf (RISC-V 二进制) 目标平台可执行文件 QEMU (riscv64) RISC-V 虚拟机 GDB 远程调试 通过 TCP 1234 端口 工作流程:源码 → 交叉编译 → ELF → QEMU运行 / GDB远程调试 所有工具运行在开发机上,只有ELF在QEMU中执行

说白了,你的开发机是x86或ARM架构,但目标芯片是RISC-V。你不能直接在开发机上编译运行——得用交叉编译器生成RISC-V的二进制文件,然后扔给QEMU去模拟执行。GDB则通过远程调试协议连上QEMU,让你能单步调试。

2.2 安装QEMU

QEMU的安装方式取决于你的操作系统。我个人习惯用包管理器,干净省事。

2.2.1 Ubuntu/Debian 系

sudo apt update
sudo apt install qemu-system-misc

装完验证一下:

qemu-system-riscv64 --version

如果看到版本号,说明装好了。我遇到过有人装成了 qemu-system-riscv(少了个64),那是32位的,别搞混。

2.2.2 macOS 系

brew install qemu

2.2.3 从源码编译(可选)

如果你需要最新特性,或者想自己加patch,那就从源码编译:

git clone https://gitlab.com/qemu-project/qemu.git
cd qemu
./configure --target-list=riscv64-softmmu
make -j$(nproc)
sudo make install

我的建议:初学者先用包管理器装。等后面学到设备模拟、自定义机器时,再考虑源码编译。我曾经为了调试一个RISC-V的MMU bug,不得不自己编译QEMU加调试日志——那是后话了。

2.3 配置RISC-V交叉编译工具链

交叉编译工具链,就是一套能在你的开发机上生成RISC-V代码的编译器、汇编器、链接器。最常用的是 riscv64-unknown-elf-gcc。

2.3.1 方案一:使用发行版包管理器(推荐)

Ubuntu 22.04+ 直接有现成的包:

sudo apt install gcc-riscv64-unknown-elf

装完检查:

riscv64-unknown-elf-gcc --version

2.3.2 方案二:从SiFive官网下载预编译包

如果包管理器版本太老,或者你用的不是Ubuntu,可以去SiFive的GitHub Releases页面下载预编译的工具链。下载后解压,把 bin 目录加到PATH里:

export PATH=$PATH:/path/to/riscv64-unknown-elf-toolchain/bin

记得把这个加到 ~/.bashrc 或 ~/.zshrc 里,不然每次开终端都要重新export。

2.3.3 方案三:自己编译(不推荐初学者)

从源码编译RISC-V工具链大概需要2小时,而且容易踩坑。除非你有特殊需求,否则别折腾。

注意:交叉编译工具链的前缀是 riscv64-unknown-elf-,不是 riscv64-linux-gnu-。前者是裸机工具链(没有操作系统),后者是Linux工具链(依赖内核)。咱们做固件开发,用裸机工具链。

2.4 安装GDB调试器

GDB用于调试RISC-V程序。同样需要交叉编译版本的GDB。

sudo apt install gdb-multiarch

或者安装RISC-V专用版本:

sudo apt install gdb-riscv64-unknown-elf

我个人更推荐 gdb-multiarch,因为它支持多种架构,后面调试ARM或其他芯片也能用。

验证安装:

gdb-multiarch --version
# 或者
riscv64-unknown-elf-gdb --version

2.5 验证环境:写一个Hello World跑起来

光装不验等于白装。咱们写个最简单的程序,编译、运行、调试,全套走一遍。

2.5.1 编写启动代码和链接脚本

RISC-V裸机程序需要一点启动代码。新建一个文件 start.S:

.section .text.init
.globl _start
_start:
    # 设置栈指针
    lui sp, 0x80000
    # 跳转到main
    jal main
    # 死循环
loop:
    j loop

再写个链接脚本 link.ld:

OUTPUT_ARCH(riscv)
ENTRY(_start)

SECTIONS
{
    . = 0x80000000;
    .text : { *(.text.init) *(.text) }
    .data : { *(.data) }
    .bss : { *(.bss) }
}

2.5.2 编写C程序

main.c:

volatile unsigned int *uart = (unsigned int *)0x10000000;

void putchar(char c) {
    *uart = c;
}

void print(const char *s) {
    while (*s) {
        putchar(*s++);
    }
}

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

2.5.3 编译

riscv64-unknown-elf-gcc -march=rv64gc -mabi=lp64d \
    -nostdlib -nostartfiles -ffreestanding \
    -T link.ld start.S main.c -o hello.elf

解释一下关键参数:

参数含义
-march=rv64gc64位RISC-V,带通用扩展(I、M、A、F、D、C)
-mabi=lp64dLP64数据模型,双精度浮点
-nostdlib不使用标准库(裸机环境没有libc)
-ffreestanding独立环境编译

2.5.4 在QEMU上运行

qemu-system-riscv64 -machine virt -nographic \
    -bios none -kernel hello.elf

如果看到输出 Hello, RISC-V World!,恭喜你,环境搭建成功!

常见问题:如果没输出,检查UART地址。virt机器的串口在0x10000000,其他机器可能不同。我曾经在sifive_u机器上折腾了半天,才发现它的UART地址是0x10010000。

2.5.5 用GDB调试

先启动QEMU,让它等待GDB连接:

qemu-system-riscv64 -machine virt -nographic \
    -bios none -kernel hello.elf \
    -s -S

-s 是 -gdb tcp::1234 的简写,-S 让CPU在启动时暂停。

另开一个终端,启动GDB:

gdb-multiarch hello.elf
(gdb) target remote :1234
(gdb) break main
(gdb) continue
(gdb) step
(gdb) info registers

你会看到程序停在 main 函数入口,可以单步执行、查看寄存器。嗯,这种感觉很爽。

2.6 环境验证清单

最后,给你一个检查清单。每完成一项就打勾:

#检查项验证命令
1QEMU安装成功qemu-system-riscv64 --version
2交叉编译器可用riscv64-unknown-elf-gcc --version
3GDB可用gdb-multiarch --version
4编译生成ELFfile hello.elf 显示RISC-V架构
5QEMU运行输出看到 "Hello, RISC-V World!"
6GDB远程调试能打断点、单步、看寄存器

我的习惯:我会把上面这些验证命令写成一个shell脚本,每次换电脑或重装系统后跑一遍,5分钟就能确认环境是否正常。你也试试。