想学RISC-V的人,多数是从“听说它很火”开始的。我当初也没例外,以为它是个新芯片,一查才发现RISC-V是一个公开的指令集架构规范,而“架构学习”这四个字,远比背几个指令有更多东西。这篇文章是我自己从零开始啃RISC-V的阶段性记录,包括设计思想、实操路线、踩坑实录,尽量用口语讲清楚“为什么这么做”,而不是只告诉你“命令是什么”。无论你是学生、嵌入式开发者,还是刚转内核方向,应该都能从中找到可复现的学习路径。
1. 先搞清楚:RISC-V到底是“什么”
1.1 一条指令集,和一颗芯片是两回事
很多人第一次接触RISC-V,都会下意识去搜“RISC-V芯片”,然后看到一堆开发板、SoC、IP核,越看越混乱。其实RISC-V不是某块实物芯片,而是一套开放的标准指令集架构ISA,它告诉你“指令长什么样”“寄存器怎么用”“内存怎么访问”,但它不管电路怎么实现、流水线怎么排。
打个比方:指令集像语法规范,芯片像根据语法写出来的文章。同样遵循RISC-V规范,不同厂商可以做出完全不同性能、功耗、面积的产品。这也是为什么你在市场上能看到几块钱的微控制器,也能看到面向数据中心的较高性能处理器,它们很可能都自称RISC-V——指令集同源,但微架构完全自由。
所以学习RISC-V架构,本质上是学习“规则本身”,而不是学习某一颗具体芯片。认准这一点,后面看手册、读规范时就不会跑偏。
1.2 为什么值得单开一条学习线
过去我们学习指令集,基本是“别人怎么设计,我们就怎么用”。无论是桌面为主的传统指令集,还是移动端那套闭源ISA,虽然号称为开发者提供稳定支持,但想要深入定制,授权壁垒、生态壁垒都不小。RISC-V的意义在于,它是开放的、模块化的,允许任何人自由实现、自由扩展。
更重要的是它“足够简单”。基础整数指令数量远少于传统复杂指令集,这在学习上有巨大优势:一个晚上就能把核心指令过一遍,几周时间就能自己写一个最小模拟器。这种“投入产出比”在计算机体系结构领域其实很少见。学x86需要面对几万条指令的历史包袱,学ARM也有大量版本差异,而RISC-V从诞生起就在刻意做减法,减到了学习友好的程度。
1.3 和主流指令集的定位差异
不想引战,但定位差异很明显。传统桌面指令集追求兼容性和高吞吐,数十年的历史积累使得指令集非常庞大;移动端主流ISA追求能效比,在移动生态中形成了强壁垒,但扩展、授权都受到严格控制。RISC-V则从头开始设计,既可以做成极简嵌入式核,也可以扩展向量、加密、多核一致性等复杂能力。
它最强的不是某一方面性能,而是“可塑性”。业界有人把它类比成“指令集界的Linux”,虽然不完全准确,但精神很像:规则开放、社区协作、按需定制。如果你之前只接触过固定指令集,进入RISC-V后最大的冲击是:原来指令集是可以自己动手改的。
2. 设计哲学:模块化为什么让学习门槛反而更低
2.1 基础指令集与“够用”哲学
RISC-V把指令集拆成了“基础部分+扩展部分”。基础部分叫RV32I或RV64I,其中I就是基础整数指令集。以RV32I为例,它只包含最基础的整数运算、加载存储、分支跳转、系统指令等几十条指令,数量少到打印成一张A4纸就能看全。
少不等于残缺。设计者刻意把基础集做得“刚刚够用”:不追求一次覆盖所有场景,而是让扩展按需追加。这有点像装修,基础功能先保证能住,要不要地暖、中央空调,后期再按需装。这种哲学让初学者非常舒服:你不用一开始就理解整个森林,先把主干走一遍就能在模拟器上跑简单程序。
RV64I则是把寄存器和地址宽度扩展到64位,指令格式和大部分RV32I保持一致。理解RV32I后,看RV64I几乎不费力,因为设计规则是统一的,比如规范里大量使用“Same as above”式的说明。
2.2 M、A、F、D、C标准扩展
基础集之外的“积木”都是标准扩展,常用的有:
- M扩展:整数乘除法。没有它,乘法要靠循环加法或查表模拟,性能惨烈。
- A扩展:原子操作指令。多核并发时必须依赖它,比如AMO(Atomic Memory Operation)实现无锁同步。
- F扩展:单精度浮点,增加了浮点寄存器组和浮点运算指令。
- D扩展:双精度浮点,在F的基础上扩展,适合科学计算。
- C扩展:压缩指令,16位短指令,显著降低代码体积,嵌入式场景很常用。
如果把IMAFD全加上,就等于G扩展,我们常看到RV64GC之类的名字,G就是这几个标准扩展的打包。理解这些缩写后,你再看到某款芯片资料写着“RV64IMAC”,脑子里就能立刻算出它支持什么能力:64位、整数乘除、原子操作、压缩指令,但没有浮点单元。这种信息解码能力,属于RISC-V学习者的基本功。
2.3 特权架构与三种运行模式
光有指令集描述的是用户态程序,但要跑操作系统,还需要定义特权架构。RISC-V定义了三个权限模式:U模式(用户态)、S模式(监管者态,通常运行操作系统内核)、M模式(机器态,最底层,负责启动、中断、异常处理)。
M模式是必须存在的,芯片复位后先进入M模式;S模式是为了跑Linux这类操作系统而设计的;U模式运行普通应用。模式之间通过异常、中断、CSR(控制状态寄存器)切换。CSR是RISC-V中相当重要的一类寄存器,控制着各种状态设置,比如异常原因、保存返回地址、控制分页等。
这个三模式设计思路其实和经典操作系统理论高度一致,所以学RISC-V特权架构的过程,也像在同步复习操作系统中“内核态与用户态”的概念。这种“软硬结合”的反馈,是我觉得RISC-V学习最有意思的地方:它不是死板的指令罗列,而是一套能自洽支撑整个软件栈的逻辑。
3. 实操:从空手到在模拟器上跑起Linux
3.1 工具链准备
学架构不能只看文档,必须上手编译、运行。第一步是准备RISC-V工具链。最常见的方式是安装带RISC-V目标的GCC工具链,包管理器里一般能直接装,比如以riscv64-unknown-elf-或者riscv64-linux-gnu-为前缀的编译器。区别在于前者面向嵌入式裸机,后者可以链接Linux程序。
如果系统里没有现成包,可能需要从源码构建工具链。这个过程比较慢,通常在几十分钟到几小时不等。建议第一次不要自己编译全套,先用发行版提供好的二进制工具链,把流程跑通,再回头琢磨细节。另外一个特别适合起步的是在线模拟环境,几秒就能启动一个RISC-V虚拟机,省去环境配置的挫败感。
工具链装好后,用riscv64-linux-gnu-gcc -v确认版本,看到“Target: riscv64-linux-gnu”之类的输出,就说明编译器可以生成RISC-V指令了。
3.2 第一个程序:用户态Hello
写一个最普通的C程序:
#include <stdio.h> int main(void) { printf("hello riscv\n"); return 0; }编译时要注意,如果希望在模拟器里直接运行,建议使用静态链接,把依赖库直接打包进可执行文件:
riscv64-linux-gnu-gcc -static hello.c -o hello.riscv然后用用户态模拟器运行:
qemu-riscv64 ./hello.riscv看到终端打印“hello riscv”,说明从C源码到RISC-V机器码再到模拟执行的整条路径已经打通。这一步特别重要,因为它把抽象变成了可验证的事实。你甚至可以反汇编看看自己的程序长什么样:
riscv64-linux-gnu-objdump -d hello.riscv看到addi、ld、jalr这些指令里是不是很熟悉?把它们和规范里的说明对照着看,收获完全不一样。
3.3 在内核态:启动一个Linux系统
用户态跑通后,下一步是让一个完整的Linux内核在RISC-V模拟器上启动。通常使用系统模拟器模式,比如QEMU的qemu-system-riscv64,搭配开源内核源码和一个小而完整的根文件系统。
流程大致是:
- 编译Linux内核,目标架构选
riscv,配置默认的defconfig。 - 制作最小根文件系统,常用方法是使用BusyBox编译出常用命令,再打包成initramfs镜像。
- 用模拟器指定内核镜像和initramfs启动:
qemu-system-riscv64 -machine virt -kernel vmlinux -initrd initramfs.cpio.gz -nographic如果一切顺利,你会看到Linux内核日志滚动输出,最后进到shell提示符。这个过程看起来简单,但背后的知识点非常多:设备树、串口控制台、内存布局、内核启动流程。第一次跑起来的时候,那种“一个完整操作系统跑在开放指令集上”的震撼感很值得体验。
3.4 落到真实硬件前的小建议
模拟器环境干净、可重复,适合理解和调试。但如果你最终想体验硬件上的真实效果,我建议至少先清楚自己的目标:是想了解SoC集成,还是想验证自定义指令,或者只是想在开发板上跑裸机程序。
对纯学习来说,FPGA是比开发板更灵活的选择,因为你可以在硬件上实时修改硬件逻辑,配合开源软核,把一个最小RISC-V处理器跑在真实时钟上。整个调试过程类似“软件硬件联动”:先用逻辑分析仪检查总线信号,再回到RTL仿真里对照波形。这也是我这一路学到最多的地方。
如果预算和精力有限,也可以选一块现成的RISC-V开发板,跑一下最简单的GPIO点灯和串口交互。实际硬件会暴露很多模拟器不会告诉你的问题,比如电源纹波、时钟配置、烧录工具不兼容,这些都是经验里很值钱的部分。
4. 常见问题与排查技巧实录
4.1 编译与工具链问题
我遇到最多的错误,基本都集中在工具链上。比如编译器默认目标平台不对、缺少标准头文件、链接器无法找到入口。这里有一个很实用的思路:先分清楚“交叉编译”和“本地编译”的区别。如果目标是裸机开发,用-elf-工具链;如果要跑Linux用户态程序,用-linux-gnu-工具链,两者不能混用。
再常见不过的是动态链接问题:编译时如果没加-static,模拟器虽然能找到可执行文件,但运行时找不到动态链接器,直接报“No such file or directory”。解决办法就是静态链接,或者指定正确的动态链接器路径。这一步我栽过好几次,现在已经把“先试-static”当成默认操作了。
工具链版本和内核版本不匹配也可能引发奇怪错误,建议记录每个组件的版本号。出了诡异问题,先怀疑版本组合,再怀疑代码本身。
4.2 模拟器与内核启动问题
内核编译出来了,但QEMU里没有任何输出,是最让人慌张的情况。一般来说,先检查内核配置里是否打开了对应串口驱动,再检查设备树指定地址是否一致,最后留意启动参数是否需要显式加上earlycon。很多“无输出”其实只是console没配置好,内核可能在正常运行。
还有一种情况是initramfs没被正确识别,内核反复重启,卡在“Kernel panic - not syncing”。排查时优先确认initramfs是否用对了压缩格式、内核的CONFIG是否支持该格式,以及启动参数rdinit是否指定了正确的初始化程序。这些问题都不难,但要静下心来一步步查,不要一心急就重新编译所有组件。
下面这个表格是我自己整理的快速排查参考:
| 现象 | 最可能原因 | 快速操作 |
|---|---|---|
| 交叉编译找不到头文件 | 工具链类型选错 | 改用linux-gnu或elf工具链 |
| 模拟器运行报无法加载动态库 | 动态链接问题 | 加-static重编 |
| 内核无任何输出 | 串口console未配置 | 加earlycon启动参数 |
| 内核反复重启 | initramfs格式不支持 | 检查CONFIG和压缩格式 |
| 浮点段错误 | 工具链浮点ABI与硬件不符 | 改用软浮点或设置匹配ABI |
4.3 手册阅读与概念理解问题
还有一类问题不报错,但更致命:读规范读不懂,越看越困。我的经验是,千万不要从第一页线性往后读。RISC-V规范非常精确,但也非常枯燥。更有效的做法是带着问题读,比如“函数调用时返回地址怎么保存?”然后去规范里查jal、jalr的语义,再对照反汇编结果看。
如果你对指令格式不敏感,可以先用模拟器打印指令执行日志,或者用调试器单步执行,观察每条指令前后寄存器的变化。把抽象概念“可视化”之后,理解速度会大幅提升。还看不懂的地方,先标注跳过,等接触具体场景后再回来看,印象更深。
5. 我的学习路线与避坑心得
5.1 四周路线参考
如果有朋友让我推荐一条路线,我会给一个四周方案:
第一周专门学基础ISA和扩展,目标是把RV32I每条指令都能解释清楚,尤其是汇编、寄存器、内存关系。第二周进入实操,写小程序编译反汇编,尝试手写启动代码,加深对链接脚本、地址分布的理解。第三周开始碰特权架构,尝试在模拟器上运行Linux,理清U/S/M模式与异常流程。第四周选一个专题深入,比如向量扩展、多核一致性,或者实现一个小型模拟器。
这条路线不要求你每天花大量时间,但要求每阶段都做“验证性输出”,不光是看书。比如看观指令格式后,立即让编译器生成几条对应指令并观察结果,比死记硬背有效得多。
5.2 两个能让你理解加深的小项目
第一个是写一个“最小指令集模拟器”。用任何语言都行,只需要支持RV32I的一个核心子集,能执行简单程序。看起来工作量不大,但你会被迫搞清楚每条指令的编码、寄存器状态更新、内存访存边界。跑通后,你对体系结构的基础概念的掌握会远超那些刷了几遍讲义的人。
第二个是用RISC-V汇编手写一小段裸机启动代码,不依赖任何库,从设置栈指针开始,到调用C函数结束。这里面涉及的链接地址、栈布局、启动文件,都是嵌入式开发的真功夫。把这两个项目做完,RISC-V在你的知识体系里就不再是“听说过”,而是“我改过、我跑过、我调过”。
5.3 关于下一步,我想说的
很多人都问我“学完之后能干什么”。我的理解是,RISC-V最大的价值不是让你多认识一套指令集,而是提供了一种“自定义计算形态”的能力。你可以为一个专用场景增加一条专用指令,再配合编译器,让整个硬件软件栈围着问题本身转。这个视角一旦建立,很多旧观念都会被重新审视。
如果在实际操作中让我分享一个最印象深刻的技巧,那就是:在学习中刻意练习“从规范到代码再到波形”的闭环。看到一个指令,查规范,写一小段汇编,看编译后的机器码,再到模拟器或仿真波形里观察执行结果。每一步都能验证,就不会出现“感觉懂了但实际不会”的假象。架构学习真正花时间的不是记忆,而是建立起这种可验证的直觉。
过一段时间再回头补一句:别怕一开始踩坑,RISC-V生态现在还处于快速成长期,文档和工具每年都在变好。你现在踩的每个坑,都会变成未来很值钱的调试手感。