RISC-V学习笔记

1.RISC ISA=1个基本整数指令集+多个可选的扩展指令集,如RV32I表示支持32位整数指令集。I表示基本指令集,M表示整数乘法与除法指令集,A表示存储器原子指令集,F表示单精度浮点指令集,D表示双精度浮点指令集等,C表示压缩指令集,G(通用)表示指令集模块组合"IMAFD,Zicsr,Zifencei"(Zicsr指令集扩展用于增强对控制和状态寄存器(CSR)的操作,Zifencei指令集扩展用于优化指令缓存(I-Cache)的操作)。ISA的命名格式为RV[###][abc...xyz],其中RV位RISC-V的缩写,[###]为32、64、128等,表示处理器字宽,[abc...xyz]标识该处理器支持的指令集模块集合。

2.RISC-V的Unprivileged Specification定义了32个通用寄存器(X0-X31)以及一个PC,PC寄存器是无法被直接访问的。RV32寄存器位宽为32位,RV64寄存器位宽为64位,依此类推。寄存器宽度与指令的宽度无关,RISC-V大部分指令都是32位的。

3.RISC-V定义了三个特权级,分别是User(U)、Supervisor(S)和Machine(M),M是最高级别,所有的实现都需要支持,还提供了一个可选的Debug级别。不同特权级别下分别对应各自的一套Registers(CSR),用于控制和获取相应Level下的处理器工作状态,高级别的特权级别下可以访问低级别的CSR,反之不行。

4.RSIC-V支持物理内存保护和虚拟内存,物理内存保护(PMP)允许M模式指定U模式可以访问的内存地址,即可以指定不同内存区域的读写权限;虚拟内存需要支持S特权级,用于实现高级的操作系统特性。

5.硬件线程(Hardware Thread,Hart)是指由处理器硬件直接支持的线程执行单元。硬件线程是多线程处理的基础,用于提高计算资源的利用率和整体系统性能。RSIV-V中Hart的概念可以简单理解为一个CPU,Hart在执行算术逻辑运算时所操作的数据必须直接来自于寄存器,Hart可以执行在寄存器和内存之间的数据读写操作,读写操作使用字节为基本单位进行寻址。

6.参与和运行的机器根据其角色可以分成以下三类:构建系统(build):生成编译器可执行程序的计算机;主机系统(host):运行编译器可执行程序,编译链接应用程序的计算机系统;目标系统(target)系统:运行应用程序的计算机系统。根据build/host/target的不同组合我们可以得到如下的编译方式分类:本地编译(native):build==host==target;交叉编译(cross):build==host!=target。

7.平时使用的gcc命令其实是一个符号链接,比如在ubuntu22.04中gcc指向/usr/bin/gcc-11,而/usr/bin/gcc-11又指向/usr/bin/x86_64-linux-gnu-gcc-11,/usr/bin/x86_64-linux-gnu-gcc-11才是真正的编译程序,这个名称含义如下:x86_64指明目标架构,表示该编译器生成适用于64位x86架构;linux指明目标操作系统,表示该编译器生成适用于 Linux 操作系统的代码;gnu指明目标 ABI(应用二进制接口),表示该编译器生成符合 GNU 操作系统和库的代码;gcc是GNU编译器集合(GNU Compiler Collection)的缩写,是一个支持多种编程语言的编译器系统;11是 GCC 的版本号,表示这是 GCC 的第11版。

8..s(小写)文件是纯汇编文件,不再需要预处理器处理,可直接由汇编器处理。.S(大写)文件是汇编文件,需要经过预处理器处理后再由汇编器处理,允许使用宏定义和条件编译指令。.s适用于简单汇编代码,.S适用于需要预处理的复杂汇编代码。

9.RISC-V的汇编语法格式:[label:] [operation] [comment]

  • label(标号):GNU汇编中,任何以冒号结尾的标识符都被认为是一个标号;
  • operation可以有以下多种类型:instruction(指令):直接对应二进制机器指令的字符串;pseudo-instruction(伪指令):为了提高编写代码的效率,可以用一条伪指令指示汇编器产生多条实际的指令;directive(指示/伪操作):通过类似指令的形式(以"."开头),通知汇编器如何控制代码的产生等,不对应具体的指令;macro:采用.macro/.endm自定义的宏。
  • comment(注释):"#"开始到当前行结束。

10.在RISC-V架构中,中断和异常都是处理器执行流的非正常转移,但它们有不同的触发方式和处理特点。中断是由外部事件(如硬件定时器或外部设备)触发的异步信号,通常在指令之间发生,并会暂停当前指令的执行,转而执行中断服务程序。中断处理完毕后,返回的指令通常是继续执行被中断前的下一条指令。异常是由指令本身引发的同步事件,如非法指令或访问错误等。异常在指令执行时立即发生,并触发异常处理程序处理错误。异常处理完毕后,返回的指令通常是重试引发异常的那条指令,或者根据具体情况跳过该指令。

11.QEMU有两种运行模式:用户模式和系统模式。在用户模式下,QEMU只模拟单个进程,使得为一种架构编译的程序可以在另一种架构上运行,它适用于在不同架构的Linux二进制文件上运行,而无需完整的系统支持。在系统模式下,QEMU模拟整个系统,包括CPU、内存和外围设备,使得可以运行完整的操作系统或模拟硬件平台,此模式常用于操作系统开发和在不同架构上进行测试。通常情况下,系统中的QEMU命令是不同QEMU可执行文件的软链接。QEMU提供多个二进制文件,每个文件针对不同的架构或操作模式(如qemu-system-x86_64、qemu-system-arm等)。

12.RSIC-V的x0寄存器是一个zero寄存器,无法写入数据,读出的数据永远为0。

13.RSIC-V指令编码如下图所示,具体参考官方非特权指令手册。指令长度为32位,指令按照32位对齐,opcode、funct3和funct7共同决定最终的指令类型,指令在内存中按照小端排序。共有R、I、S、B、U、J六类指令。

常见指令与伪指令如下图所示:

RISC-V中的伪指令,如:neg rd,rs(等价于sub rd,x0,rs)、mv rd,rs(等价于addi rd,rs,0)、nop(等价于addi x0,x0,0)等都是通过等价的指令来实现的。

14.QEMU的-s和-S选项常用于调试虚拟机。-s选项等效于-gdb tcp::1234,启动一个gdb服务器并将其绑定到TCP端口1234,允许使用gdb连接到虚拟机进行调试。-S选项则会在启动QEMU后暂停虚拟机的CPU执行,确保虚拟机在启动时不立即运行,而是等待进一步的调试操作。通常,这两个选项会一起使用,启动虚拟机后立即暂停,然后通过gdb进行连接调试,这样可以在虚拟机运行前设置断点,便于调试操作系统或嵌入式系统。

15.常见RISC-V指令使用方法示例:

对于上图中的addi指令,它是属于I-type的,但由于立即数只有12位,所以可表示的数值范围为[-2048,2047),并且在参与计算前该指令中的立即数还会被符号扩展。RISC-V并没有提供subi指令,因为可以用addi指令代替。对于addi指令,要实现赋值一个大数(32位),可以通过lui和addi命令共同实现,lui是一个U-type命令该指令可以构造一个32位的立即数并存放到寄存器中,这个32位的数的高20位为imm,低12位为0,例如lui x5,0x12345等价于x5=0x12345<<12。下图是这两个指令结合使用的例子:

但由于addi指令会让立即数进行符号扩展,所以可能出错,如下图:

正确如下:

RISC-V提供了伪指令li来给寄存器赋值,如:li x5,0x12345678。这个li指令会根据后面立即数的具体大小,将其转换为addi指令或addi和lui指令的组合,如下图:

auipc指令常用来构造相对于当前pc值的地址,并赋值给寄存器,如下图,auipc指令为U-type:

la伪指令可以将label对应的地址加载到寄存器中,编译器会根据实际情况利用auipc和其他指令自动生成la指令,如:la x5,foo(即将foo对应的地址加载到x5寄存器中)。上述内容总结如下:

对于逻辑运算指令,RISC-V提供了逻辑与and(and rd,rs1,rs2)、逻辑或or(or rd,rs1,rs2)、异或xor(xor rd,rs1,rs2),RISC-V并没有提供取反的指令,而是通过伪指令not来实现:not rd,rs(实际是通过xori rd,rs,-1来实现的)。如下图:

移位指令分为逻辑移位和算术移位,逻辑移位又分为逻辑左移和逻辑右移,如下图所示:

对于内存读写指令,在load从内存读取数据到寄存器时,由于寄存器是32位的,所以lb、lh指令存在符号扩展和零扩展的区别,load和store类型的指令中的imm都是12位的,范围为[-2048,2047],但是有相应的伪指令可以实现全局的load和store。如下图:

常规的条件跳转指令如下图所示,立即数imm只有12位,所以跳转是有范围限制的,但实际使用时,直接写标号即可:

以上图中的指令衍生出的伪指令如下图所示:

无条件跳转指令JAL如下图所示,该指令基于PC进行相对跳转,一般用在函数调用时,会保存返回地址,方便函数返回:

基于寄存器的无条件跳转指令JALR如下图所示:

对于更远的,超过+/-1MB的地址,可以将jalr与auipc指令结合使用实现跳转。伪指令j和jr可以在不需要返回时使用,这两个伪指令实际是利用jal和jalr指令和x0寄存器实现的,对于需要返回时可以使用伪指令jal和jalr。

16.RISC-V中寄存器的默认使用规则如下图所示:

注意上图中没有x3和x4寄存器,x3寄存器通常用于存储全局数据的基地址,它是一个全局指针,用于在程序中访问全局变量或数据结构,特别是在使用位置无关代码(PIC)时,x3寄存器可以提高对全局数据的访问效率,x3又被称作gp;x4寄存器通常在多线程编程中用于指向每个线程的线程本地存储(Thread-Local Storage, TLS)。这允许每个线程快速访问与自身相关的数据,而无需查找复杂的结构或全局变量。x4通常用于存储当前硬件线程(hart)的 ID,这需要由软件赋值,x4寄存器又被称作tp。一般在函数调用过程中使用下图中的伪指令:

17.C语言内嵌汇编形式如下:

18.QEMU-virt的内存布局如下图所示:

左侧为起始物理地址,右侧为空间大小。启动 QEMU 可以发现,virt 平台在reset时,会将PC置为 0x1000。0x1000处的内存块名为VIRT_MROM,这部分相当于bootloader。内核加载的地址就是VIRT_DRAM对应的0x80000000。

19.RISC-V系统上电后处于Machine特权级,Machine特权级对应的CSRs寄存器如下图所示,如寄存器mhartid上电初始化后会存储Hart的ID号(多个hart的ID必须是唯一的,且必须有一个hart的ID值为0)。

Zicsr扩展指令集中的指令可用来读取CSR寄存器,如下图中的CSRRW指令可用来读取CSR寄存器的值并写入新值:

伪指令csrw csr,rs(实际为csrrw x0,csr,rs)可以向CSR寄存器中写入新值。下图中的csrrs指令可以读取CSR寄存器的值,并设置相应位为1,伪指令csrr rd,csr(实际为csrrs rd,csr,x0)可用来读取CSR寄存器的值。

20..ld文件是链接脚本文件,用于指导链接器如何将编译后的代码和数据链接成最终的可执行文件,可以在gcc编译时用-T选项指定要使用的链接脚本文件(如gcc -T os.ld)。链接脚本文件常见的语法有:

  • ENTRY(symbol) 用来告诉链接器在生成最终的可执行文件时,程序的入口点应该是指定的符号(symbol)的位置,如ENTRY( _start );

  • OUTPUT_ARCH(bfdarch)命令指定输出文件所适用的计算机体系架构,如OUTPUT_ARCH( "riscv" )(这里并没有说明具体是32位架构还是64位架构,也没有指定目标RISC-V架构具体实现了哪些模块,需要在使用gcc使通过-mabi和-march选项进一步说明);

  • MEMORY 命令描述目标中内存块的位置和大小。可以使用它来描述链接器可以使用哪些内存区域,以及链接器必须避免使用哪些内存区域,可以把段放到特定的内存区域里。链接器将会基于内存区域设置段地址,如果区域趋于饱和将会产生警告信息,链接器不会为了把段更好的放入内存区域而打乱段的顺序。其语法如下图所示:


    name 是链接器脚本中用于引用内存区域的名称。区域名称在链接器脚本之外没有任何意义。区域名称存储在单独的名称空间中,不会与符号名、文件名或段冲突。每个内存区域在 MEMORY 命令中必须有一个不同的名称。attr 字符是一个可选的属性列表,用于指定是否对链接器脚本中未显式映射的输入段使用特定的内存区域。

  • SECTIONS 命令来描述输出文件的内存布局。如下图所示,符号.表示当前位置计数器,如果未通过其他方式指定输出段的地址,地址就会被设置为位置计数器的当前值,在'SECTIONS '命令的开头,位置计数器的值为' 0 '。

  • PROVIDE命令用来定义符号(这就是一个如上图中提到的符号赋值命令),每个符号包括一个名字和一个对应的地址值,在代码中访问这些符号等同于访问一个地址,如:PROVIDE(_text_start=.)表示将当前所在地址值赋值给_text_start。

21.mscratch(属于CSRs)是一个关键的临时寄存器,用于在机器模式下处理中断和异常时保存关键状态信息。它帮助确保在处理完中断或异常之后,系统能够正确地恢复并继续执行原来的程序。

22.RISC-V中与Trap(包括异常和中断)相关的寄存器(下图为Machine模式下,三个模式分别有自己的CSRs寄存器,具体可参考RISC-V特权指令集手册)如下图所示:

mtvec寄存器用来存储异常处理函数入口地址,Direct模式表明只有一个异常处理函数,Vectored模式表示异常处理函数以向量表的形式给出:

mcause记录了中断或者异常的具体类型,WLRL表示只有符合要求的值才能被写入:

mtvaul寄存器可以提供更多的信息:

mstatus寄存器记录了相关状态信息,如是否开中断等(这里的中断控制位控制的是全局中断,一旦关闭,后续对mie寄存器的设置将无效),mstatus寄存器上电默认值为全0:

当发生异常或中断时,处理器会将当前的PC值或下一条指令的PC值保存到mepc(Machine Exception Program Counter)寄存器中。mret指令会从mepc寄存器中恢复保存的PC值,并将其加载到PC中,从而使程序跳转回异常发生前的位置继续执行。mret指令如下图所示:

23.Trap发生时,Hart(硬件)自动执行如下图所示的状态装换(下图对应的是异常,保存的是当前指令的PC值):

异常和中断会分为上部分和下部分,上部分就是上图所示的硬件自动执行的部分,在这个过程中会关中断(使用mret命令后才会恢复到之前的中断打开/关闭状态),下半部分就是执行设置的处理函数,这个过程中,主要是:保存当前控制流的上下文信息、调用C语言的trap handler、从trap handler返回(mepc的值可能需要调整)、恢复上下文信息、执行mret指令返回到trap之前的状态。要注意中断和异常返回地址的区别,中断返回后执行下一条指令,而异常返回后会重新执行之前的那条指令。

24.RISC-V将中断分为三类:软中断、定时器中断和外部中断,每类中断又分为U/S/M三种模式,可以通过mie寄存器进一步设置各类中断的打开/关闭状态,如下图:

RISC-V共定义了53个外部中断源,其中UART中断源编号为10。

25.PLIC(Platform-Level Interrupt Controller)是 RISC-V 架构中的一个平台级中断控制器,负责处理和管理系统中的外部中断。PLIC 的设计使得它可以在多核系统中协调多个硬件外部中断,并将这些中断分配给适当的核心进行处理。PLIC相关寄存器如下图:

26.CLINT(Core-Local Interruptor)是 RISC-V 架构中用于管理核心本地中断的组件,它主要负责定时器中断和软件中断,这些中断用于核心之间的通信和定时调度。CLINT相关寄存器如下图所示:

27.可睡眠锁(如互斥量、读写锁)允许线程在锁被占用时进入休眠状态,直到锁变为可用,适用于持锁时间较长的场景,它们的开销较大但适合复杂或长时间的操作。不可睡眠锁(如自旋锁)则不会使线程休眠,而是通过忙等待方式尝试获取锁,适用于持锁时间非常短的场景,它们避免了线程上下文切换的开销,但在锁被长时间占用时可能会浪费CPU资源。

28.ecall是RISC-V指令集中的系统调用指令,用于触发操作系统服务。在执行ecall时,处理器根据寄存器a7中的值(系统调用号)来识别请求的服务类型,而其他寄存器(如a0到a6)传递系统调用的参数。执行ecall后,控制权转交给操作系统,操作系统根据系统调用号和参数执行相应操作。系统调用处理完成后,控制权返回到用户程序。如下图所示:

29.在 RISC-V 架构中,U模式(用户模式)下的程序无法直接访问某些系统控制寄存器(CSR)。这些寄存器通常包括机器模式(M模式)和监督模式(S模式)专用的寄存器,用户模式下的程序在访问这些寄存器时会引发异常。

相关推荐
超自然祈祷27 分钟前
pyTorch笔记
人工智能·pytorch·笔记·神经网络
PyAIGCMaster32 分钟前
docker学习记录:部署es+kibana
学习·elasticsearch·docker
蜗牛_snail1 小时前
学习笔记 : MySQL进阶篇一之架构和日志文件
笔记·学习·mysql
小大力1 小时前
简单的jmeter数据请求学习
java·学习·jmeter
要加油哦~2 小时前
尚硅谷· vue3+ts 知识点学习整理 |14h的课程(持续更ing)
javascript·vue.js·学习
sensen_kiss3 小时前
CAN201 Introduction to Networking(计算机网络)Pt.5 网络安全
学习·计算机网络·web安全·智能路由器
DO your like4 小时前
Linux系统操作笔记
linux·服务器·笔记
网络安全King4 小时前
网络安全系统学习实验1:RDP远程登录配置
学习·安全·web安全
LiuIleCPP_Golang5 小时前
【2025 Rust学习 --- 10 运算符重载】
学习·rust
杂货铺的小掌柜7 小时前
spring mvc源码学习笔记之六
学习·spring·mvc