写在前面
现在计算机的体系架构正是发展得如火如荼的时候,占领桌面端市场的x86架构、占领移动端市场的arm架构、在服务器市场仍有一定地位的mips架构、国产自研的指令集loongarch架构、还有我现在要讲到的新型开源开放的RISC-V指令集架构。
我先说一说我的学习经历,三年前我开始学习x86架构,不得不说x86架构这么多年的发展变得是真的非常复杂和难以概括,它为了兼容性,不得不保留一些早期的设计,实际上计算机硬件的发展非常迅速(摩尔定律已经保持了这么多年),很多Intel的早期设计虽然非常精彩、体现了工业的气息和Intel工程师的智慧,但是放到现在来说真的是太冗余了。2021-2022年我主要学习8086汇编,在DOS操作系统上做实验,进行实模式的编程。2022-2023我主要学习x86的保护模式,在保护模式上完成了我的第一个OS,随后进入x86_64的长模式编程,制作了一个具有现代性质的有smp支持的OS。这一路学来,体会到最多的就是Intel为了兼容性,让内核程序员体会到新旧思想的反复交替和叠加,在体系结构升级的过程中,不断弱化(但没有抛弃)分段式的内存管理,强化分页式内存管理,把用段来表示的内存逐渐变成平坦模型,为了就是分页机制可以均分内存页。除此之外就是指令集的不断增加,在很多的寄存器或者是结构里都会有res这样的保留字段,感觉坑坑洼洼的,也许是Intel为了填坑而设定的,就显得不是那么美观、符合直觉。
那么我现在要讲的是RISC-V这个新型开源开放的指令集,它是有加州大学伯克利分校的教授和研究人员开发的。它出自大学,那么和x86、arm这样出自企业的指令集形成鲜明的对比,它具有浓厚的学院的设计思想,有更好的设计美学和简洁的哲学表达语言,用人话说就是risc-v汇编写起来更加简单、不需要特别的记忆、对于程序员来说学起来比x86要简单许多。这一点非常类似于Python,简单 简洁 方便 适合初学者,成为它的一大特点。并且它作为“后来者”,在设计的时候就已经吸取了前代risc和cisc指令集的经验教训,没有那么多为了兼容性的历史包袱,更加纯粹。实际上,它不仅仅利好于程序员的汇编编程,而且对于基于RISC-V架构的芯片设计也有很多良好的帮助。
本文适合哪些人看:1、掌握c语言,内核开发c语言是基础;2、会Linux命令,我还是用我最喜欢的Fedora上实现;3、有计算机组成原理的基础,我会对比risc和cisc指令设计的不同; 4、最好是学过x86或者arm这些体系结构,我会使用x86架构来类比迁移学习risc-v。
本文尽可能少的去讲理论,而是去实践编程,也就是带大家去上手写risc-v的汇编并观察现象,把理论和实践结合起来。我主要是从一个内核开发者的角度去看,也就是从软件开发的角度着手,关于RISC-V硬件相关的功能,我只能少量的提一提,多了也不会。
以下是我当前的环境,还是那个熟悉的Fedora.
学习risc-v指令集有许多的资料,希望大家能够用起来:
《RISC-V体系结构编程与实践》
bilibili汪辰老师的视频
The RISC-V Reader: An Open Architecture Atlas
一些参考资料的合集
大家也能够看出来,risc-v指令集官方也是有提供中文资料的,这对于学习RISC-V指令集是非常方便的。希望大家能够把这些资源都利用起来,本文实际上理论来源也是官方的文档和这些参考资料。
一、RISC-V指令集简介
1、什么是ISA
ISA是Instruction Set Architecture,翻译成中文就是指令集架构的意思,可以看作是一系列指令的集合,这里的指令不仅仅是你能看见的汇编语言的指令,它还包括了这些指令对应的底层的电路设计。Instruction有指令、命令、吩咐的意思,Set就是数学上的集合,Arch这个缩写相信大家经常能够看到,是架构的意思。
2、有哪些ISA
我们知道的指令集就有x86、arm、risc-v、mips、loongarch、sparc等
实际上你在终端敲qemu然后按两下Tab,就能够看到qemu能够模拟的指令集。
既然提到了Qemu模拟器/虚拟机,那么我们就在这里安装它吧,Qemu将是我们以后学习RISC-V指令集最重要的工具之一。
sudo dnf install qemu-system-riscv qemu-user-static-riscv
前者是system mode是模拟整个计算机系统,就是它会把riscv芯片和外部设备等硬件全部模拟出来,然后你可以把它当作是一个开发板、裸机之类的概念,模拟硬件。它启动的时候是在M模式下,是要从opensbi开始运行,这个我们暂时不用管,从用户程序开始学起比较容易。
而后者是user mode,是模拟一套Linux软件,相当于是运行了一个riscv Linux系统,你可以把你写的用户程序在这个模式下跑。
为了保险起见,你可以多安装几个qemu的包,防止在运行的时候出现一些莫名其妙的错误。
我们可以给这么多的指令集进行分类。
3、CISC和RISC
CISC:Complex Instruction Set computer 复杂指令集 比如x86
在设计指令的时候是针对特定的功能设计的,也就是说它的一条指令对应了一种功能,比如x86里的mov指令,能够把数据从寄存器移动到寄存器、可以把数据从内存移动到寄存器、还可以把数据从寄存器移动到内存。这个mov指令就是针对数据移动这个功能设定的。随着计算机越来越复杂,实现的功能越来越多,那么CISC ISA就要不断地添加指令,每多一个功能就要加一个指令。优点就是你掌握好少数几条指令就可以做许多的功能,因为一条指令可以有很多用途的,而且生成的程序比较短,占用空间小;并且对兼容性和拓展性的支持也比较好,你想推出新功能,在原来的基础上添加新的指令集就好了。而缺点也很明显:每多一种就加一条指令,那么最终指令的数量会越来越多,整个指令集和电路的设计就会越来越膨胀。一开始还能接受,到了后期可能扩展到了几千条指令,据我查到的数据新x86指令集是有1468条指令,现在也许更多了,指令数量越多越难以把握。实际上x86的这种设计是针对早期计算机存储空间太小问题而设计的思路,那时候的程序员绞尽脑汁地开发就是为了自己的程序能够省一点内存。
RISC:Reduced Instruction Set computer 精简指令集 比如arm和risc-v
RISC ISA在设计指令的时候和CISC全然不同,它是只定义一些常用、必要的指令,然后你要实现更多功能,那么就需要对这些指令进行“排列组合”,用好几条指令来实现原本CISC一条指令就能实现的功能。也就是它不再按照功能来设计指令了。那么在这种设计哲学下,随着计算机功能越来越多,RISC ISA的指令条数增长的速度相比CISC就会慢很多,不会造成电路设计过于复杂、指令数量膨胀这些缺点。RISC的缺点是生成的程序比较长、占用空间大。但是由于现代计算机发展迅速,存储已经不再是已经太大的问题了,现在的个人电脑16GB内存已经是标配了,计算机只有64KB内存的时代已经一去不复返了。
但实际上,RISC和CISC这二者之间并不是水火不容的关系,而是互补的关系。RISC指令集有CISC的设计思想在里面,CISC指令集也有RISC的设计思想在里面,互相融合可以解决很多原本的缺陷,从而更加适应现实里复杂的环境。
x86系列,面向外部的指令集是CISC(复杂指令集计算机),但在内部,这些复杂的指令在执行之前会被分解成更小、更简单的操作,这些操作称为微操作(micro-operations 或 μops),这种新的设计思想就叫做Intel微架构。
而risc-v指令集为了方便程序员编程,也设计了一系列的“伪指令”,伪指令本身不翻译成机器码而是展开成好几条汇编指令,伪指令把多条汇编指令封装起来,实现类似CISC那样一条指令实现一个功能这样的设计,这样是为了方便程序员使用。
4、什么是RISC-V
终于回归主题了
RISC-V全名Reduced Instruction Set Computer ,即精简指令集计算机。V是罗马字母5,你可以认为这是伯克利大学开发的第五代精简指令集。它可以读作 risk five(/ˈrɪsk ˈfaɪv/)
1. RISC 的起源
RISC 的概念最初在 1980 年代初由加州大学伯克利分校和斯坦福大学的研究者提出。这些研究者发现,通过简化处理器中的指令集,可以提高其执行效率和速度。最初的 RISC 设计包括:
- John Hennessy 在斯坦福大学领导的 MIPS(Microprocessor without Interlocked Pipeline Stages)项目。
- David Patterson 在加州大学伯克利分校领导的 RISC-I 和 RISC-II 项目。
2. RISC-I 和 RISC-II
- RISC-I:1982 年,伯克利的 RISC-I 项目产生了第一款 RISC 处理器。这款处理器包含了约 31 条指令,实现了高度的指令流水线化。
- RISC-II:紧随 RISC-I 之后,伯克利团队开发了 RISC-II,它包含了更多的指令和优化,证明了 RISC 架构的有效性和高性能。
3. RISC 发展和商业化
80 年代中后期,RISC 架构开始被更广泛地接受和商业化。例如,Sun Microsystems 开发了基于 RISC 的 SPARC 架构,IBM 推出了 POWER 架构,同时 MIPS 和 ARM 架构也开始流行。
4. RISC-V 的诞生
- 起始:RISC-V 指令集是在 2010 年由加州大学伯克利分校的研究者,包括 David Patterson 和其他人,发起的。RISC-V 的设计目标是创建一个开放、免版税、可扩展的指令集架构,旨在支持从最小的嵌入式系统到高性能计算机系统的广泛应用。
- 开放性:RISC-V 是第一个完全开源的指令集。这意味着任何人都可以使用、修改并分发 RISC-V,而无需担心版权问题。
- 发展:自推出以来,RISC-V 逐渐成为学术界和工业界的热点。多个组织和公司,包括 Google、NVIDIA、Western Digital 等,开始投资于 RISC-V 的研发。
从以上的故事里,我们不难看出,加州大学伯克利分校对这个RISC类型的指令集真的是非常执着,一直有专门的教授和学者在研究指令集的设计思想。我目前没有搜索到关于RISC-III和RISC-IV指令集的故事,也许它们是伯克利大学内部的一些研究方案,没有公布出来。RISC-V 指令集是在 2010 年由加州大学伯克利分校的研究团队开发并以 BSD 开源协议发布的。这种开源发布方式允许任何人可以自由地使用、修改和重新分发 RISC-V,而无需支付版税或遵循严格的许可限制,这是 RISC-V 快速增长和广泛采用的一个重要因素。那么既然RISC-V指令集是伯克利大学设计和发布的,使用BSD协议开源也是很合理的吧。它最大的特点和优势就是开源,在此之前的流行指令集都是闭源的,牢牢掌握在商业公司的手里,要么就对外出售知识产权IP核心,这样虽然也算是开放了,但是不开源还是很难服众。而RISC-V是完全的开源且免费将吸引更多的企业入局,也会加强开源生态的建设。
5、RISC-V生态的特点
这里的特点讲的不是risc-v指令集本身,而是它背后的组织的特点。
RISC-V的官网
RISC-V 指令集目前由 RISC-V International 维护和推广。RISC-V International 是一个非营利性的全球性组织,负责协调 RISC-V 的技术和标准发展。这个组织由来自全球的多个企业、学术机构和个人成员组成,它们共同协作推动 RISC-V 架构的发展和应用。
RISC-V International 的总部最初设在美国,但为了强调其全球性和独立性,该组织在 2020 年将其法律和运营总部迁移到瑞士的日内瓦。这一举措旨在利用瑞士的国际中立地位,更好地服务其全球会员,并避免特定国家的政策影响。
虽然前面说了,RISC-V是加州大学伯克利分校的教授和学者设计发布的,但是它现在属于瑞士的一个非营利组织,负责指定和推动RISC-V标准的制定。
以下是一些非常重视RISC-V并且能够推动和制定RISC-V标准的企业:
西部数据(Western Digital) - 西部数据是 RISC-V 基金会的创始成员之一,并且计划在其数据存储产品中广泛使用 RISC-V 处理器。公司对 RISC-V 的推动非常积极,旨在利用 RISC-V 的可扩展性和开源优势来优化其产品。
谷歌(Google) - 谷歌对开源技术一向有着深厚的兴趣,RISC-V 也不例外。谷歌已经在多个项目中考虑使用 RISC-V,特别是在其自定义硬件和数据中心技术中。
英伟达(NVIDIA) - 又是我们的老朋友NVIDIA. NVIDIA 对 RISC-V 表示了显著的兴趣,尤其是在嵌入式处理器和辅助处理器中。英伟达是 RISC-V 基金会的高级成员,并参与了多个相关的开发项目。
高通(Qualcomm) - 作为全球领先的芯片制造商,高通对 RISC-V 的支持强调了这一架构在移动和物联网设备中的潜力。高通加入了 RISC-V International,参与标准化和技术发展工作。
SiFive - 作为一家专注于商业化 RISC-V 处理器和平台的领先公司,SiFive 是由 RISC-V 架构的创始人之一启动的。SiFive 不仅设计 RISC-V 基础的芯片,还积极参与 RISC-V 的标准化工作。
阿里巴巴 - 阿里巴巴的子公司平头哥技术有限公司已经发布了基于 RISC-V 的处理器,并在其云计算和数据中心产品中使用。阿里巴巴支持 RISC-V 的发展,以促进技术的创新和自主控制。平头哥的芯片确实不俗,有相当的实力的。
在这些企业里面,大家重点需要关注的是这个SiFIve公司,它是RISC-V创始人成立的一家公司,给RISC-V指令集的商业推广起到示范作用,它为RISC-V体系结构提供商业化的IP核心、开发工具包和芯片解决方案。
6、RISC-V指令集的特点
1. 开源
- 定义:RISC-V 是完全开源的指令集架构(ISA),意味着任何人都可以查看、使用、修改以及分发其设计,而无需支付版权费用。
- 优势:这种开源特性促进了全球性的创新和合作,这个特点前面讲过了,我这里就不重复了。
2. 社区化
- 定义:RISC-V 的发展由一个活跃的全球社区支撑,包括学术界、工业界以及个人贡献者。
- 优势:社区化确保了 RISC-V 的设计和发展方向能够响应广泛用户的需求,同时增强了技术的透明度和多样性。
3. 设计简洁
- 定义:RISC-V 保持了 RISC(Reduced Instruction Set Computer)架构的核心原则,即提供一个简洁的指令集,使得芯片设计更为简单、高效。
- 优势:简洁的设计有助于降低硬件实现的复杂性,提高处理器的执行效率和易于优化。
4. 模块化
- 定义:RISC-V 指令集支持模块化扩展,允许开发者根据具体应用需求添加或定制特定的指令模块。
- 优势:模块化设计使得 RISC-V 可以灵活适应各种应用场景,从嵌入式系统到高性能计算,都可以通过添加专门的指令扩展来优化性能。
5. 分层设计
- 定义:RISC-V 架构采用了分层设计方法,基本指令集可以通过额外的扩展层来增强功能,如浮点运算、向量处理等。
- 优势:分层设计不仅保持了核心指令集的简洁性,同时也提供了高度的可扩展性和定制性。用户可以根据需要选择性地实现这些扩展,以适应特定的应用需求,而不必在所有实现中支持所有功能。
我之所以讲这么多故事,主要是为了让大家学起来轻松一点,吸引读者的阅读兴趣,大家可以耐心看下去。
二、RISC-V的基础知识
虽然我前面说尽可能少去讲述risc-v的理论,但是必要的基础知识还是要掌握的。这里我为了简化描述,会把RISC-V简写成rv或者是RV.
RV指令集是一个现代的指令集,它已经完全抛弃了对16位的支持,因为现代计算机也没有16位的了,嵌入式设备使用32位,通用计算机使用64位。因此rv可以分为RV32和RV64,但无论是RV32还是RV64,都是32个通用寄存器,我会在后面通过例子具体讲到它们的用法。架构是多少位,那么对应的通用寄存器的大小就是多少位。
RISC-V体系结构有一套命名标准来描述当前CPU支持哪些指令集,RISC-V是模块化、可拆卸的,非常灵活。比如RV32I、RV64G、RV32IMA这样。
命名格式如下:
RV[n][I/F/D/Q/M/C/A/B/E/H/K/V/P/J/T/N]
- RV代表这是RISC-V指令集
- n代表当前指令集是几位的,32位还是64位
- 后面的是指令集模块,模块含义如下表格
指令集模块名 | 说明 |
I | 基本整数指令集,这是RV的基本指令集,是必选的,如果没有I那么这就不是RV指令集了,下面的都是扩展指令集,是可选的。(Integer) |
F | 单精度浮点数扩展指令集(Float) |
D | 双精度浮点数扩展指令集(Double) |
Q | 四倍精度浮点数扩展指令集(Qual) |
M | 整型乘除法扩展指令集(Multiple) |
C | 压缩指令集(Compress) |
A | 原子操作指令集(Atomic) |
B | 位操作指令集(Bit) |
E | 为嵌入式设计的整型指令集(Embedded) |
H | 虚拟化扩展指令集(Hyper) |
K | 密码运算扩展指令集(Key) |
V | 可伸缩矢量扩展指令集(Vector) |
P | 打包SIMD扩展指令集(packed) |
J | 动态翻译语言(JIT)扩展指令集 |
T | 事务内存指令集(Transactional Memory) |
N | 用户态中断指令集(User-Level Non-maskable Interrupts) |
所以当你拿到一块RV的CPU,那么这块CPU有多少斤两、有多少能力、有多少指令集的支持,一眼就能看出来了,你看它的命名就好了。而像x86架构的话就很麻烦,还要用CPUID一组一组去测试和判断,看是否支持对应的指令集(关于这个CPUID如果大家感兴趣的话我可以单独开一期来讲)。大家在记忆这些指令集名称的时候,就根据英文去记就好了,不难记。
在扩展指令集里有一个特殊的“稳定的指令集组合”,用G来表示(General),这不是某一个指令集,而是一组指令集IMAFD。
我们已经掌握了RV命名的标准,现在来做几个例子:
1、RV32IMAC
就意味着这是RISC-V的32位的带有通用整形支持、整形乘除法、原子指令支持、压缩指令支持的一块CPU.
2、RV64G
我们把G代进去就是,RV64IMAFD,意味着这是RISC-V的64位的带有通用整形支持、整形乘除法支持、原子指令支持、单精度浮点数支持、双精度浮点数支持的CPU.
这只是一种描述方法,这块CPU可以是一块真是的硬件CPU,也可以是一块虚拟出来的virt CPU,甚至还可以是工具链的配置,比如写在-march选项里。总之用这种方式能简洁的描述硬件提供了或者软件使用到了哪些指令集。你需要匹配这二者,否则要是执行到了不支持的指令集的的指令的话,那就要引发trap了。
那我理论就讲到这里,大家务必把上面我讲述的内容全部学会,这个东西比较重要,你在构建开发工具链和模拟器的时候需要这些参数来写配置文件的。
下面的内容大家当成是个资料简单了解一下每个指令集的实际作用:
RISC-V国际基金会将持续完善至少8种可选扩展
1、B标准扩展:位操作
B扩展提供位操作指令,包括位字段的插入、提前和测试;循环移位,漏斗移位,位和字节的排列;计算前导0和尾随0;计算置1的位数(这和x86的PF标志位有点像)
2、E标准扩展:嵌入式
为降低低端CPU的开销,RV32E减少了16个寄存器,保存寄存器和临时寄存器均被拆分称0-15和16-31号两个部分。
3、H特权态架构扩展:虚拟化的意思
H扩展向特权态架构添加超监管模式和第二级页式地址翻译机制,意思就是一台机器上开多个虚拟机的时候,有这个扩展的话,运行效率将大大提升
4、J标准扩展:JIT动态翻译语言
比如Java和Javascript这些编程语言都是基于动态翻译实现的,如果CPU支持这个扩展的话,那么运行JVM、Python解释器的效率将大大提升
5、L标准扩展:十进制浮点
支持IEEE 754-2019标准所定义的十进制浮点算术运算
6、N标准扩展:用户态中断
这个用户态也能直接执行中断程序了,而不是“陷入内核”,看起来好像很不安全,但其实这个是给嵌入式设备准备的。它就是在用户态发生中断和异常的时候,把控制权直接交给用户态(一般来说都是会陷入内核,让操作系统内核来解决这些问题)。
7、Q标准扩展:四倍精度浮点
这个是符合IEEE754-2019标准的128位四倍精度二进制浮点指令。如果有Q扩展出现,那么F和D扩展也必然出现。
8、P标准扩展:紧缩SIMD指令
加强并行计算能力,投入和细分资源提升数据的并行度,是用来做向量计算的,要有RVV扩展的前提下使用这个扩展。这个其实有点类似显卡的能力了,也许未来某一天会出现基于RISC-V架构的显卡?你说是吧,NVIDIA.
三、搭建RISC-V的学习环境
1、交叉编译工具链的安装
交叉编译是在一个平台(宿主)上生成另一个不同平台(目标)上运行的代码的过程。简单来说,交叉编译器能在一个操作系统或硬件架构上编译程序,而这些程序是为在另一个不同的操作系统或硬件架构上运行设计的。
我假设你的实验平台就是x86架构,这是个人电脑的最主流指令集架构。你在x86平台上使用gcc编译器编译的c语言程序,它是x86架构的,只能在x86环境上运行,而不是编译成RISC-V的机器码。
可以使用file命令去查看,这个编译后默认生成的a.out就是x86_64架构的,ABI也是x86的。也就是说你不可能把这个程序直接跑在arm或者RISC-V这些其他指令集的机器上。你需要使用专门的交叉编译的工具,用这个RISC-V特色的gcc去编译生成的程序,才能直接跑在RISC-V机器上。
那么我推荐的工具就是GNU的那一套:gcc、gas、GNUld.我们需要把这些工具给构建出来,不能通过dnf包管理器安装了。
下面我会放一些链接,希望大家都能点进去看看。
RISC-V开发工具链https://github.com/riscv-collab/riscv-gnu-toolchain
这是RISC-V的官方提供的工具链,当你构建好之后你就拥有了以下这些工具;
这些riscv64开头的就是RV官方的工具链。我把所有的RISC-V开发需要的工具都放置到一个目录里,便于管理和使用。就在我写文章的时候,我现在就把这个目录给删除,然后和大家一起重新构建一套RISC-V交叉编译的工具链。假如你既需要32位的工具又需要64位的工具,那么我建议你只需要编译64位的工具就好,可以往下兼容的。
在正式开始构建工具之前,我推荐最好创建一个用户组,该用户组有权限对/opt/riscv目录进行管理,然后把自己添加到这个用户组里。
sudo groupadd riscv-users
sudo mkdir /opt/riscv
sudo chown root:riscv-users /opt/riscv
sudo chmod 755 /opt/riscv
sudo usermod -a -G riscv-users april_zhao
你需要把这里的april_zhao改成你当前用户的id,april_zhao是我的id,你的计算机里没有这个用户。
这个时候你可以进入到/opt/riscv目录里试图创建一个文件,如果提示你没有权限创建,那么你可以重启一下看看,你当前这个用户登录着,会影响更新。
riscv-gnu-toolcharin项目是RISC-V官方维护的项目,是rv的开发工具链,它很特殊,它不是一个项目,而是多个项目。它使用了git的子模块管理(submodules),它允许你将一个 Git 仓库作为另一个仓库的子目录。
大家可以看到,图中有很多个@,这个意思就是这是子模块。这些子模块本身不是RISC-V官方工链的一部分,只是官方工具链需要它们、使用到了它们,于是引用它们。让它们变成子模块,然后构建的时候直接用就好了,很方便,不需要你一次一次自己去git clone去管理,自己管理很麻烦,而官方来管理更加合适、对用户也好。
大家可以看到,这里面有llvm,musl.这些大家有需要的可以去了解一下,我这里就不使用了。
git clone git@github.com:riscv-collab/riscv-gnu-toolchain.git
sudo dnf install autoconf automake python3 libmpc-devel mpfr-devel gmp-devel gawk bison flex texinfo patchutils gcc gcc-c++ zlib-devel expat-devel libslirp-devel
cd riscv-gnu-toolchain
git submodule update --init
git submodule update --remote
./configure --prefix=/opt/riscv --enable-multilib
make -j8
make linux -j8
此后你每一次想要进行开发工具链构建的时候,都需要执行一次git submodule update --remote,以更新子模块。千万不要就直接make -j8这样去运行,因为git和cc1同时执行的话,有概率导致构建的时候模块还没拉好,会出错的。
在./configure配置期间,你可以
--with-arch=rv32gc --with-abi=ilp32d
这样指定两个选项,你也可以像我一样干脆不指定,默认就是RV64GC.
--enable-multilib使用这个选项之后,它构建出来的64位的工具是兼容32位的代码。
直接make构建出来的工具是riscv64-unknown-elf-gcc等一系列命令。
make linux构建出来的工具是riscv64-unknown-linux-gnu-gcc等一系列命令。
这二者是有区别的
- 前者是一个裸机编译器,用于生成不依赖操作系统的代码,它不包括对标准C库(如 glibc)的支持,因为这些库通常依赖于操作系统级别的功能。它可能使用新lib(一个针对嵌入式系统优化的C库)或其他简化的库。
- 后者是一个为运行Linux操作系统的RISC-V系统编译程序的编译器,它支持标准的C库,如 glibc,这是大多数基于Linux的应用所需要的。这包括对系统调用、进程管理、文件操作等复杂操作的支持。
构建期间,你的CPU使用率会“拉满”,你需要等待一段时间,去喝一杯咖啡回来就好了。工程量也不算大,我在玩Gentoo整个系统都要自己去编译出来,编译KDE桌面要消耗8个小时,睡一觉醒来才能够看到结果。
如果出现构建报错的情况,那样也是很正常的,你可以尝试make clean然后重新执行configure和make.
你编译好之后,进入这个目录,这里面就存放着交叉编译的工具了。当然,你现在还不能在任意目录直接使用这些命令,需要添加到环境变量里。
如果你使用的是zsh,那么就添加到~/.zshrc里,或者是.bashrc /etc/bashrc这些目录里
export PATH=$PATH:/opt/riscv/bin
现在你就可以执行这些命令去编译出RISC-V架构的程序了。
既然已经构建好了RISC-V的工具链,我们顺便和spike模拟器和pk内核也构建一下。
以下是spike模拟器和pk内核的链接
spike模拟器https://github.com/riscv-software-src/riscv-isa-simpk内核https://github.com/riscv-software-src/riscv-pk
git clone git@github.com:riscv-software-src/riscv-isa-sim.git
cd riscv-isa-sim/
mkdir build && cd build
../configure --prefix=/opt/riscv
make
make install
git clone git@github.com:riscv-software-src/riscv-pk.git
cd riscv-pk/
mkdir build && cd build
../configure --prefix=/opt/riscv --host=riscv64-unknown-elf --with-arch=rv64gc_zifencei
make
make install
请注意以上两个工具是需要创建单独的构建目录的,而不是在原目录里进行make.
pk内核的--host选项可以是
riscv64-unknown-elf
riscv64-unknown-linux-gnu
二选一就好。
不难发现,RISC-V开发工具链、spike模拟器、pk内核这三者的--prefix都放置到了/opt/riscv这个目录。大家不需要担心,它们并不会起冲突,不会互相覆盖对方,你如果要重新构建以上工具的话,只需要再一次configure、make、make install就好了,不需要把原来的目录删除再重新创建。
好,到现在为止,咱们的环境已经搭建好了,你现在已经可以跑RISC-V程序了。我再把Qemu的用户模式给大家也配置好环境。
alias qemu-riscv64="qemu-riscv64 -L /opt/riscv/sysroot"
此时的qemu-riscv64还不能直接运行RISC-V程序,因为qemu-riscv64会尝试使用宿主机的根目录系统去找动态链接库,但是你的系统本身是x86架构的,根本是找不到riscv64的库,因此你需要使用这个-L选项去指定到一个包含有适合 RISC-V 架构的库和链接器的目录,这样才能跑起来。
如图所示.我们现在已经把RISC-V程序成功地跑起来了,现在你既可以使用spike模拟器去调试和运行c程序,你也可以使用qemu的用户模式去运行。是不是很开心?
2、RISC-V的开发板
RISC-V指令集目前主要还是用于教学和嵌入式这样的设备,虽然也有小电脑、笔记本电脑也用上了RISC-V芯片,但还是非常非常小众的。RISC-V开发板目前来说从性能上来讲很难和老牌的arm嵌入式开发板一较高下,不过我们主要是用于学习的,不需要什么太高的性能,主要是带大家去了解一下。
Sipeed Lichee Pi 4A
Sipeed Lichee Pi 4A 是一款紧凑型计算机,性能也许优于 Raspberry Pi 4。,拥有可与Raspberry Pi相比的功能。尽管它的基准测试速度在使用 Fedora 发行版时表现不佳,但这块板子使用 TH1520 芯片(这块芯片就是阿里平头哥的,从这方面来看阿里巴巴是对RISC-V是相当重视的了),理论上应有更好的性能。修复其性能可能需要更多的配置工作和系统优化。价格可以说是比较高了。
荔枝派https://wiki.sipeed.com/hardware/en/lichee/th1520/lp4a.html
VisionFive2
VisionFive2 是另一款RISC-V开发板,拥有四核处理器和8GB的RAM(不同版本配置会不同)。该板主要设计用于高级计算和多任务处理,适用于需要高性能和多线程处理的应用。VisionFive 还提供了对高速NVMe存储的支持,这是其在数据处理和操作系统性能方面的一个重要优势。
Longan Nano - 这是一款非常便宜的小型开发板,价格低廉。它是一个32位RISC-V板,具有32kB RAM和128kB Flash,包含多种GPIO引脚,2x ADC, 2x DAC, RTC, I2S, I2C, SPI, U(S)ART, CAN接口,并附带一个小型LCD屏幕。此板还可以通过USB进行编程,非常适合初学者和爱好者在面包板上进行实验。
Pine64 0x64 - 这个板配有三种不同的RISC-V核心,价格比较低廉,支持Linux操作系统,适合需要更多功能但预算有限的用户。
3、RISC-V虚拟操作系统
由于购买RISC-V开发板是有成本的,因此初学者可以在虚拟机里学习RISC-V编程,这是一个既无成本、计算机的性能还高的选择!由于你是在高性能的x86平台开启RISC-V虚拟机,那么开启的虚拟机的性能也不会太差,比“高性能开发板”的性能还可以强一些。而且开发板的硬件配置是出厂就定死的,固定的;而虚拟机是可以调节性能和配置参数的。
我还是以Fedora跑在RISC-V上为例子:
Architectures/RISC-V/Installinghttps://fedoraproject.org/wiki/Architectures/RISC-V/Installing这是Fedora官方的文档,它大致提供了两种使用Qemu跑Fedora的方法,大家听我一一道来。
①使用virsh来管理虚拟机(我不推荐这种做法)
mkdir -p ~/.config/virt-builder/repos.d/
cat <<EOF >~/.config/virt-builder/repos.d/fedora-riscv.conf
[fedora-riscv]
uri=https://dl.fedoraproject.org/pub/alt/risc-v/repo/virt-builder-images/images/index
EOF
你可以直接复制粘贴去执行,本质就是创建一个目录,然后在这个目录里创建一个配置文件,把Fedora RV的镜像源给添加进来,这样方便管理。
virt-builder --list | grep riscv64
这是你添加的镜像源里有的镜像,你可以挑选其中一个。不建议你选择最后两个。
选择好你要的镜像之后,复制它的名字,比如fedora-rawhide-minimal-20200108.n.0,把它记录下来,我们一会儿要用到。其中developer的是“开发者”镜像,它包含更多的rpm包,也自带一些图形桌面的包,而minimal是“最小化”的镜像,它只会自带必要的rpm包,开发工具什么的你也必须自己单独安装。
virt-builder \
--arch riscv64 \
--size 50G \
--format qcow2 \
--output fedora-rawhide-minimal-20200108.n.0.qcow2 \
fedora-rawhide-minimal-20200108.n.0
通过virt-builder命令可以自动下载镜像并进行配置,--arch是指定指令集;--size是把下载下来的镜像扩展到的尺寸,这里的50GB是我自己设置的,实际上根本不需要这么大;--format是把下载好的镜像转换成什么样的格式(我这里选择了qcow2是因为我喜欢这个格式,它具有更多良好性质;你可以换成其他格式,默认使用的raw裸盘也可以);--output是最终输出的文件,你需要把拓展名跟上去。最后的是我之前让你复制的镜像名字。第一次下载会消耗一点时间,请大家耐心等待,第一次下载好之后它会缓存起来,当你第二次执行的时候就不会重复去下载了。
实际上你第一次执行后就会在这个目录里放置缓存文件,所以你第二次执行的的时候就不会真的去下载了,而是在这个目录里找。如果你需要使用了,你可以把这个目录里的缓存文件给删除掉,防止占用过多的磁盘空间。你也可以看见,这个镜像的大小也就是424MB而已,我上面给它50GB属实是有点过多了,你自己看磁盘空闲情况去调节这个大小吧。
这就是执行后的样子,这里大家一定要注意一下,在构建结束的时候,它会自动生成一个root密码然后告诉你,你千万不要直接clear把屏幕清理掉,而是要把这个root密码记下来,否则的话你的镜像都白下载和构建了。
最终出现镜像fedora-rawhide-minimal-20200108.n.0.qcow2之后,我们还需要去下载一个elf文件,这个 .elf
文件是一个预先编译好的 U-Boot 启动加载器,用于在仿真环境中初始化并启动操作系统。
elf文件下载地址https://dl.fedoraproject.org/pub/alt/risc-v/repo/virt-builder-images/images/
你需要下载你所选择的镜像对应的elf,你去对比一下文件名就好了。比如我们这个例子你就需要下载红色框里的elf,上面的是elf文件,下面的是校验文件,校验文件你也可以不下载。
virt-install \
--qemu-commandline='-bios none' \
--name fedora-riscv \
--arch riscv64 \
--vcpus 8 \
--memory 4096 \
--os-variant fedora33 \
--boot kernel=Fedora-Minimal-Rawhide-20200108.n.0-fw_payload-uboot-qemu-virt-smode.elf \
--import --disk path=fedora-rawhide-minimal-20200108.n.0.qcow2 \
--graphics none
根据你的实际情况,使用virt-install命令去安装这个虚拟机。你只需要把--boos kernel=和--import --disk path=这两个参数给填写成你的实际情况就好了。
当你执行这条命令之后,就会自动启动和注册虚拟机,然后你把虚拟机关闭之后,如果想要再次开启,就不需要重复执行virt-install了,你可以使用virsh命令去管理(开启、关闭、中断虚拟机等操作),你还可以使用Red Hat开发的Cockpit工具更加方便地管理虚拟机。
当你注册好虚拟机之后,以后的操作就可以在Web页面进行控制,而不需要再使用命令行,当然很多时候命令行也是非常高效的。
虚拟机启动之后你会进入到如下页面。
它要求你进行登陆,用户就是root ,密码就是它自动生成的那个字符串(之前让你复制下来了)。
我们现在已经正式进入了RISC-V的时代,大家还记得之前学的命名方式吧,这是一个RISC-V的64位的有基础整形支持、整形乘除法支持、原子指令支持、单双精度浮点数支持、压缩等一系列指令集支持的这么一个环境。
但是大家千万不要高兴的太早,由于Fedora官方提供的这个镜像是2020年的,距离现在2024已经过去很久了,这里面原装的rpm包是非常古老的,你需要一个“大换血”,这样才能正常使用。
此时,如果你觉得自动生成的root密码太麻烦、太复杂,你可以直接使用passwd命令换一个密码。
rpm --rebuilddb
dnf install neofetch
现在这个镜像的rpm包环境是非常恶劣(正是因为这么恶劣,所以我更加推荐你使用下面的方法2)的,依赖也许都已经破坏掉了,你需要重建这个数据库,否则的话你一更新,就滚挂了。现在你千万不要、千万不要直接运行dnf update. 在成功安装neofetch之后,你还需要按照dnf命令给你的提示再一次执行rpm --rebuilddb,总之这里面情况复杂,你需要根据输出的日志,再做判断。根据我的实践来说,只要你不去更新内核,想保住这个虚拟机,还是很有希望的。
不管你是否能够解决这个依赖问题,都千万不要去更新kernel版本,否则的话这个虚拟机你再也进不去了,除非你重新创建。如果你在意kernel版本请查看第二种方法。
在关闭虚拟机的时候,你可以在虚拟机的命令行里使用init 0/shutdown now/halt命令关机,你也可以用virsh或者cockpit来关机。
开机的时候你可以在cockpit里直接点击
也可以
virsh start fedora-riscv
开机之后你打开终端,然后
virsh console fedora-riscv
这样就能重新进入虚拟机环境了。其中fedora-riscv是你在virt-install的时候指定的虚拟机的名字。
无论是第一种还是第二种方法创建的虚拟机里,客户机系统都是可以直接联网的(只要你的主机能联网),不需要你做什么配置。
②直接使用Qemu命令直接创建虚拟机
我更加推荐这种方法,这样可以随意更新内核,而且它发布的镜像离现在更近,rpm包依赖也没有损坏。这就涉及到我前面提到过的SIfive公司了。
它貌似是SIfive公司提供的,原来是为开发板准备的Fedora镜像,现在移植到了Qemu平台,也许刚反过来,在Qemu调试好之后再移植到开发板上。不管是怎样,我们都能通过这个镜像直接使用较为新版本的Fedora RV了。
镜像下载地址https://dl.fedoraproject.org/pub/alt/risc-v/disk_images/Fedora-Developer-38-20230519.n.0.SiFive.Unmatched.and.QEMU/
你需要下载如图所示的这几个文件,然后xz -d解压它们。
也许是冷门资源的原因,这个镜像的下载速度是非常非常慢的,请大家耐心等待亿点点时间。这段时间内你可以去看看RISC-V的中文手册了。虚拟机配置好之后,就要实际去做RISC-V的编程了。
qemu-system-riscv64 \
-bios Fedora-Developer-38-20230519.n.0-u-boot-spl.bin \
-nographic \
-machine virt \
-smp 8 \
-m 4G \
-device loader,file=Fedora-Developer-38-20230519.n.0-u-boot.itb,addr=0x80200000 \
-object rng-random,filename=/dev/urandom,id=rng0 \
-device virtio-rng-device,rng=rng0 \
-device virtio-blk-device,drive=hd0 \
-drive file=Fedora-Developer-38-20230519.n.0-qemu.raw.img,format=raw,id=hd0 \
-device virtio-net-device,netdev=usernet \
-netdev user,id=usernet,hostfwd=tcp::10000-:22
然后使用Qemu的system mode运行
- -bios Fedora-Developer-38-20230519.n.0-u-boot-spl.bin
指定 BIOS 文件,这是虚拟机启动时首先加载的基本输入输出系统,这里是u-boot。
- -nographic
这个选项告诉 QEMU 不使用图形界面,所有的输出都通过控制台进行。 但实际上这个镜像本身是有桌面环境GNOME的,你想使用的话也行,不过你会觉得很卡就是了,除非你的宿主机CPU很强。
- -machine virt
指定虚拟机使用的机器类型。在这里,virt
是一个适用于 QEMU 的通用虚拟化平台,适合于各种客户端和服务端。
- -smp 8
设置虚拟 CPU 的数量,这里是 8 个虚拟 Hart。Hart是硬件线程的意思
- -m 4G
为虚拟机分配的内存大小,这里是 4 GB。实际上不带图形界面的话,占用的内存是很小的,4GB完全足够了
- -device loader,file=Fedora-Developer-38-20230519.n.0-u-boot.itb,addr=0x80200000
这个选项用于加载 U-Boot Image Tree Blob(ITB)文件到虚拟机的指定内存地址(0x80200000)。
- -object rng-random,filename=/dev/urandom,id=rng0
创建一个随机数生成器对象,使用 Linux 系统的 /dev/urandom
设备作为源,为虚拟机提供高质量的随机数。
- -device virtio-rng-device,rng=rng0
将上面创建的随机数生成器对象添加到虚拟机中,作为一个设备。
- -device virtio-blk-device,drive=hd0
添加一个 virtio 块设备到虚拟机,这通常用于硬盘驱动。
- -drive file=Fedora-Developer-38-20230519.n.0-qemu.raw.img,format=raw,id=hd0
指定虚拟硬盘的映像文件和格式。这里使用的是 raw 格式的 Fedora 镜像,id 为 hd0,与上一个命令中的块设备关联。
- -device virtio-net-device,netdev=usernet
添加一个 virtio 网络设备,连接到名为 usernet
的网络设备。
- -netdev user,id=usernet,hostfwd=tcp::10000-:22
创建一个用户模式的网络后端,名为 usernet
,并设置端口转发,将宿主机的 10000 端口转发到虚拟机的 22 端口(通常是 SSH 服务端口)。
执行后会输出一大堆Qemu的日志,虚拟机启动成功之后会进入这个界面,和方法1一样也是直接用root登陆,只是密码不是随机生成的字符串,而是一个固定的密码“fedora_rocks!”,就在前面的日志里。
进去之后你可以直接修改root密码,然后执行rpm --rebuilddb
你可以进入/etc/yum.repo.d 这里面有一个仓库是fedora-riscv-koji.repo,默认情况下是关闭的,你可以打开它,其他仓库不建议你打开,有可能会404。
这里面通常带有最新构建的rpm包。
至此,跑在RISC-V架构上的虚拟机已经安装好了。
这里你需要注意的是:当你在虚拟机里执行clear命令的时候,字符并不是直接删除掉了,而仅仅是把屏幕往上推,当你用鼠标滚轮往上面滚动的时候,前面的东西还是会出现的。
虚拟机对宿主机的CPU占用通常都是小于200%,基本上是不会导致你的主机卡顿的,如果你的主机卡顿了,并且Qemu的CPU占用率超过了600%,那你直接在宿主机上使用kill命令强制结束Qemu进程吧,这时候已经不正常了。如果你在虚拟机里不执行任何命令,那么主机上的Qemu进程的CPU占用率甚至不会超过20%,你一直挂着也是没问题的。
通常来说都是网络和IO的消耗,在虚拟机里进行磁盘读写会比较慢,大家需要忍耐一下。在安装好系统的第一次dnf update需要比较久的时候,你可以把主机一直挂在那里,可能要个把小时。
同样地,你使用第二种方式创建的虚拟机在虚拟机内部使用关机命令就好,最好不要直接kill掉qemu进程,除非长久地无法正常启动。
大家可以看见,这就是用Qemu system mode直接跑的好处。它非常地类似x86平台的grub,你更新内核之后,他会提供给你用哪一个内核进入的选项,而不是直接无法进入OS.默认是选用最新的内核进入。
qemu-system-riscv64 \
-bios Fedora-Developer-38-20230519.n.0-u-boot-spl.bin \
-display gtk \
-machine virt \
-smp 8 \
-m 4G \
-device loader,file=Fedora-Developer-38-20230519.n.0-u-boot.itb,addr=0x80200000 \
-object rng-random,filename=/dev/urandom,id=rng0 \
-device virtio-rng-device,rng=rng0 \
-device virtio-blk-device,drive=hd0 \
-drive file=Fedora-Developer-38-20230519.n.0-qemu.raw.img,format=raw,id=hd0 \
-device virtio-net-device,netdev=usernet \
-netdev user,id=usernet,hostfwd=tcp::10000-:22
如果你想试试开启图形界面,你可以这样写配置,然后就会出现一个gtk风格的qemu窗口,默认还是命令行进入,你可以试图修改配置以启动xinit.
关机方法和前面一样,也是可以在虚拟机里使用init 0/shutdown now/halt/poweroff等命令
在输出日志后会把终端归还给运行qemu命令的shell.
经过观察,根目录所处的磁盘就是你下载下来的那个文件,在虚拟机里抽象成了一个虚拟磁盘/dev/vda. 其中/boot分区有2.1GB,大家不需要担心更新内核的时候/boot分区容量会不够的问题。使用Qemu方法直接启动的Fedora RV是可以更新和删除内核的,你更新内核之后重启并进入新内核,然后就可以是用dnf包管理器把旧内核给remove ,就和你在x86平台使用Fedora是一样的。
现在我们已经处于虚拟环境了
我们再一次写这个最简单的hello.c程序,使用RISC-V的原生gcc编译器编译hello,c
执行后成功打印了Hello World!那么这就代表着我们在RV的“原生”机器上跑了第一个RISC-V程序。是不是和第一个写c语言程序在屏幕输出一行开心?
三、RISC-V编程初体验
好了,我们可以进入正题了,现在我们有四种方法运行RISC-V的程序了。
- 在x86设备上使用Spike模拟器+pk内核
- 在x86设备上使用qemu-riscv64
- 在RISC-V架构的开发板上直接运行程序
- 在x86设备上使用Qemu的system mode虚拟出一个Linux,在虚拟环境里直接运行RV程序
我们之前已经运行了第一个RV程序,只是这个c程序实在是简单,我们好像看不出花样来,那么先体验一下真正的RV汇编,然后我再把更多的基础知识教给大家。
gcc -S hello.c -o hello.S
这就是RV汇编的初体验了,从上图不难注意到,在main函数里有很多的汇编指令,有addi,sd,mv,ld这么多呢;除了这些汇编指令,我们还能注意到s0,a0,ra,这些就是RISC-V的通用寄存器了。不过不要着急,我会在后面一一讲到。
我们先来完成在RISC-V设备上的第一个RV汇编程序。新建一个文本文件stop.S
.global _start
_start:
stop:
j stop
as stop.S -o stop.o
ld stop.o -o stop
./stop
这是最简单的一个汇编程序,就是不停地执行j stop循环下去,它不会退出程序。
文章篇幅比较长,我会把对RV的新的思考放在我的下一篇文章里。