当前位置: 首页 > article >正文

RISC-V特权模式与寄存器

1.特权模式

RISC-V架构定义了4种特权模式,目前实现了三种,剩下一种保留,便于后续扩展。实现的三种特权模式分别为User/Application(用户模式,U Mode)、Supervisor(监管者模式,S Mode)和Machine(机器模式,M Mode),特权模式级别由低到高。三种特权模式对应不同的操作权限,区别主要体现在对寄存器的访问、特权指令的使用、对内存空间的访问。处理器复位后在机器模式下执行程序。
特权模式
在特权级架构实现中,机器模式为必选模式,另外两种为可选模式。通过不同的模式组合可以实现不同用途的系统。

  • 机器模式:通常为简单的嵌入式系统
  • 机器模式 + 用户模式:该系统可以实现用户和机器模式的区分,从而实现资源的保护
  • 机器模式 + 用户模式 + 监管者模式:该系统可以实现类Unix操作系统
    特权模式实现

1.1.用户模式

用户模式是最低的特权模式,可以运行应用程序,只能访问用户级的指令和寄存器,不能直接操作硬件,需要通过监管者模式或机器模式的服务。

1.2.监管者模式

监管者模式是一种保护模式,可以运行操作系统内核(如Linux内核),管理虚拟内存和外设,不能访问机器模式的控制寄存器,受到机器模式的监督和控制。

1.3.机器模式

机器模式是最高的特权模式,可以访问所有的硬件资源和状态寄存器,负责处理异常和中断(异常和中断没有被委托到监管者模式处理),以及启动和管理其他的特权模式。

1.4.调试模式

在具体的架构实现中可能还会包含一种调试模式(Debug Mode,D mode),以支持片外调试和生产测试。调试模式可以被视为一种额外的特权模式,其权限甚至比机器模式更高。调试模式保留了一些仅在D mode下可访问的控制状态寄存器(CSR)地址,并且可能还会保留平台上物理地址空间的某些部分。

1.5.模式切换

当异常或者中断发生时,处理器会自动切换到对应的特权模式。其中异常同步产生,如软件使用特权指令(ECALL、SRET、MRET)主动切换特权模式;中断异步产生,如外设中断产生后,处理器自动切换到对应的特权模式。所有的异常和中断默认在机器模式处理,软件可以将部分异常和中断委托到监管者模式处理。
异常或者中断发生时,处理器将当时的特权模式自动保存到SSTATUS.SPP/MSTATUS.MPP中。下面只介绍特权指令引起的特权模式切换。

  1. 用户模式调用特权指令
    软件使用ECALL指令产生环境调用异常,默认切换到机器模式处理(MEDELEG.bit[8]=0);若将环境调用异常委托到监管者模式(MEDELEG.bit[8]=1),则会切换到监管者模式处理。
    若环境调用异常在监管者模式处理,则使用SRET指令返回到用户模式(SSTATUS.SPP = U)。若环境调用异常在机器模式处理,则使用MRET指令返回到用户模式(MSTATUS.MPP = U)。
  2. 监管者模式调用特权指令
    软件使用ECALL指令产生环境调用异常,默认切换到机器模式处理(MEDELEG.bit[9]=0);若将环境调用异常委托到监管者模式(MEDELEG.bit[9]=1),则会保持在监管者模式处理。
    若环境调用异常在监管者模式处理,则使用SRET指令返回,特权模式仍为监管者模式(SSTATUS.SPP = S)。若环境调用异常在机器模式处理,则使用MRET指令返回到监管者模式(MSTATUS.MPP = S)。
  3. 机器模式调用特权指令
    软件使用ECALL指令产生环境调用异常,固定在机器模式处理。处理完成后使用MRET指令仍然返回到机器模式。
    模式切换

1.6.异常和中断委托

在机器模式中,软件可通过MEDELEG和MIDELEG寄存器,将部分异常和中断委托到监管者模式处理。
MEDELEG
MIDELEG
两个寄存器的位域如下图所示。Interrupt为1表示中断,对应于MIDELEG寄存器,Interrupt为0表示异常,对应于MEDELEG寄存器。
异常和中断分类

  1. 异常只能在大于或等于当前特权级别的特权模式中处理,不会降低特权级别。比如在机器模式中产生异常,只能在机器模式中处理,无论该异常是否被委托到监管者模式。
  2. 机器模式中发生了中断,且该中断被委托到监管者模式,此时机器模式不响应该中断(相当于在机器模式屏蔽了该中断),等返回到监管者模式才会响应并处理。
  3. 机器模式软件中断、机器模式定时器中断、机器模式外部中断无法被委托到监管者模式,只能在机器模式中处理。

2.寄存器

2.1.通用寄存器

RISC-V架构定义了32个通用整数寄存器,其中X0也称为zero寄存器,硬件保持为常量0。PC保存了执行指令的地址,软件无法直接修改。上述的寄存器在用户模式都可见。RV32(RISC-V32位架构)架构寄存器宽度为32位,RV64(RISC-V64位架构)架构寄存器宽度为64位。
通用寄存器
RISC-V定义了一套ABI名称来描述通用寄存器,具体如下。RISC-V的汇编指令使用通用寄存器的ABI名称。
RISC-V ABI

2.2.C920寄存器

下面是玄铁C920 CPU的寄存器视图,不仅包括RISC-V架构定义的寄存器,还包括C920扩展的寄存器。用户模式只能访问矢量寄存器、通用寄存器、浮点寄存器、性能事件计数器、浮点控制寄存器和矢量扩展寄存器。监管者模式只能访问异常配置寄存器、异常处理寄存器、地址转换寄存器、信息寄存器、异常配置寄存器、异常处理寄存器和内存保护寄存器。机器模式只能访问性能事件计数器(和用户模式性能事件计数器寄存器不同)和性能事件选择器。
C920寄存器

3.汇编代码分析

3.1.C语言代码

下面使用内联汇编,执行ecall特权指令,陷入内核中调用系统调用write和exit,执行打印消息和退出进程的功能。
对于系统调用,RISC-V Linux ABI 规定:

  • 系统调用号: 放在a7寄存器中。
  • 系统调用的参数: 依次放在a0、a1、a2、a3、a4、a5寄存器中。
  • 系统调用的返回值: 放在a0寄存器中。
#include <stdio.h>
#include <unistd.h>
#include <string.h>

#define SYS_write 64
#define SYS_exit 93

// 使用内联汇编实现 write 系统调用
void riscv_write(int fd, const void *buf, size_t count) {
    __asm__ volatile (
        // li为伪指令,编译器会展开为
        "li a7, %[syscall_num]\n"  // 将系统调用号加载到 a7
        "mv a0, %[fd]\n"           // 将文件描述符加载到 a0
        "mv a1, %[buf]\n"          // 将缓冲区地址加载到 a1
        "mv a2, %[count]\n"        // 将缓冲区长度加载到 a2
        "ecall\n"                  // 触发系统调用
        :
        : [syscall_num] "i" (SYS_write), [fd] "r" (fd), \
          [buf] "r" (buf), [count] "r" (count)
        : "a7", "a0", "a1", "a2"
    );
}

// 使用内联汇编实现 exit 系统调用
void riscv_exit(int status) {
    __asm__ volatile (
        "li a7, %[syscall_num]\n"   // 将系统调用号加载到 a7
        "mv a0, %[status]\n"        // 将退出状态码加载到 a0
        "ecall\n"                   // 触发系统调用
        :
        : [syscall_num] "i" (SYS_exit), [status] "r" (status)
        : "a7", "a0"
    );
}

int main(int argc, char *argv[])
{
    char msg[20];
    int a = 1, b = 2;

    snprintf(msg, sizeof(msg), "a + b = %d\n", a + b);
    // 调用 riscv_write 函数,向标准输出写入消息
    riscv_write(1, msg, strlen(msg));
    // 调用 riscv_exit 函数,退出程序
    riscv_exit(66);
    return 0;  // 这行代码不会被执行,因为 riscv_exit 会终止程序
}

使用下面的命令编译可执行程序。

riscv64-unknown-linux-gnu-gcc -o riscv riscv.c

上述程序的执行结果如下,打印了a和b的和,且程序的返回值为66,说明是调用了riscv_exit退出,而不是return 0。

# ./riscv 
a + b = 3
# echo $?
66

3.2.汇编代码

使用下面的命令,将上述的C语言代码反汇编为RISC-V汇编代码。-j .text选项只反汇编代码段,-M no-aliases禁止别名,即禁止显示伪指令。这样反汇编出来的代码是真正的RISC-V指令。

riscv64-unknown-linux-gnu-objdump -D -j .text -M no-aliases riscv  > riscv-Dj.S

main、riscv_write、riscv_exit三个函数的反汇编的代码如下。前缀c.是RISC-V的压缩指令集(C扩展),指令长度为16位,属于实际的机器指令,不是伪指令。

# a0 a1 a2保存了函数三个参数
0000000000010546 <riscv_write>:
   10546:   7179        c.addi16sp  sp,-48    # sp = sp - 48(非零符号扩展的6位立即数)
   10548:   f406        c.sdsp   ra,40(sp)    # 返回地址压栈
   1054a:   f022        c.sdsp   s0,32(sp)    # 上个函数的栈帧指针压栈
   1054c:   1800        c.addi4spn  s0,sp,48  # 记录当前函数的栈帧指针
   1054e:   87aa        c.mv  a5,a0
   10550:   feb43023    sd a1,-32(s0)         # 参数a1压栈
   10554:   fcc43c23    sd a2,-40(s0)         # 参数a2压栈
   10558:   fef42623    sw a5,-20(s0)         # 参数a0压栈
   1055c:   fec42783    lw a5,-20(s0)         # 获取a0参数(lw:有符号扩展字加载指令)
   10560:   86be        c.mv  a3,a5           # 保存a0参数到a3
   10562:   fe043783    ld a5,-32(s0)         # 保存a1参数到a5
   10566:   fd843703    ld a4,-40(s0)         # 保存a2参数到a4
   1056a:   04000893    addi  a7,zero,64      # a7保存write的系统调用编号,li伪指令被翻译为addi 
   1056e:   8536        c.mv  a0,a3           # 保存系统调用write的函数的参数
   10570:   85be        c.mv  a1,a5
   10572:   863a        c.mv  a2,a4
   10574:   00000073    ecall                 # 执行特权指令,陷入内核
   10578:   0001        c.addi   zero,0
   1057a:   70a2        c.ldsp   ra,40(sp)    # 恢复返回地址
   1057c:   7402        c.ldsp   s0,32(sp)    # 恢复上个函数的栈帧指针
   1057e:   6145        c.addi16sp  sp,48     # 释放当前函数栈占用的空间
   10580:   8082        c.jr  ra              # 跳转到ra保存的地址处

0000000000010582 <riscv_exit>:
   10582:   1101        c.addi   sp,-32
   10584:   ec06        c.sdsp   ra,24(sp)
   10586:   e822        c.sdsp   s0,16(sp)
   10588:   1000        c.addi4spn  s0,sp,32
   1058a:   87aa        c.mv  a5,a0
   1058c:   fef42623    sw a5,-20(s0)
   10590:   fec42783    lw a5,-20(s0)
   10594:   05d00893    addi  a7,zero,93
   10598:   853e        c.mv  a0,a5
   1059a:   00000073    ecall
   1059e:   0001        c.addi   zero,0
   105a0:   60e2        c.ldsp   ra,24(sp)
   105a2:   6442        c.ldsp   s0,16(sp)
   105a4:   6105        c.addi16sp  sp,32
   105a6:   8082        c.jr  ra

00000000000105a8 <main>:
   105a8:   7139        c.addi16sp  sp,-64
   105aa:   fc06        c.sdsp   ra,56(sp)
   105ac:   f822        c.sdsp   s0,48(sp)
   105ae:   0080        c.addi4spn  s0,sp,64
   105b0:   87aa        c.mv  a5,a0
   105b2:   fcb43023    sd a1,-64(s0)
   105b6:   fcf42623    sw a5,-52(s0)
   105ba:   4785        c.li  a5,1
   105bc:   fef42623    sw a5,-20(s0)
   105c0:   4789        c.li  a5,2
   105c2:   fef42423    sw a5,-24(s0)
   105c6:   fec42783    lw a5,-20(s0)
   105ca:   873e        c.mv  a4,a5
   105cc:   fe842783    lw a5,-24(s0)
   105d0:   9fb9        c.addw   a5,a4
   105d2:   2781        c.addiw  a5,0
   105d4:   fd040713    addi  a4,s0,-48
   105d8:   86be        c.mv  a3,a5
   105da:   67c1        c.lui a5,0x10
   105dc:   67078613    addi  a2,a5,1648 # 10670 <__libc_csu_fini+0x2>
   105e0:   45d1        c.li  a1,20
   105e2:   853a        c.mv  a0,a4
   105e4:   eadff0ef    jal   ra,10490 <snprintf@plt>
   105e8:   fd040793    addi  a5,s0,-48
   105ec:   853e        c.mv  a0,a5
   105ee:   e93ff0ef    jal   ra,10480 <strlen@plt>
   105f2:   872a        c.mv  a4,a0
   105f4:   fd040793    addi  a5,s0,-48
   105f8:   863a        c.mv  a2,a4
   105fa:   85be        c.mv  a1,a5
   105fc:   4505        c.li  a0,1
   105fe:   f49ff0ef    jal   ra,10546 <riscv_write>
   10602:   04200513    addi  a0,zero,66
   10606:   f7dff0ef    jal   ra,10582 <riscv_exit>
   1060a:   4781        c.li  a5,0
   1060c:   853e        c.mv  a0,a5
   1060e:   70e2        c.ldsp   ra,56(sp)
   10610:   7442        c.ldsp   s0,48(sp)
   10612:   6121        c.addi16sp  sp,64
   10614:   8082        c.jr  ra

http://www.kler.cn/a/581843.html

相关文章:

  • MATLAB 控制系统设计与仿真 - 22
  • 从零开始用AI开发游戏(一)
  • 关于在electron(Nodejs)中使用 Napi 的简单记录
  • 【GPT入门】第6课 openai接口介绍与参数说明
  • 远程手机遥控开关原理及应用
  • git commit messege 模板设置 (规范化管理git)
  • 机器学习基础(4)
  • 清华同方国产电脑能改windows吗_清华同方国产系统改win7教程
  • 《PaddleOCR》—— OCR
  • Agentic RAG 详解 - 从头开始​​构建你自己的智能体系统
  • 【web】网页崩溃
  • Cannot resolve symbol ‘view‘ Androidstudio报错解决办法
  • json中文编码问题
  • python之左移右移位运算
  • 政安晨【零基础玩转各类开源AI项目】Wan 2.1 本地部署,基于ComfyUI运行,最强文生视频 图生视频,一键生成高质量影片
  • Python+DeepSeek:开启AI编程新次元——从自动化到智能创造的实战指南
  • 【LLM学习】1-NLP回顾+Pytorch复习
  • jmeter-md5加密
  • C++和标准库速成(一)——HelloWorld和名称空间
  • Dify Web 前端独立部署指南(与后端分离,独立部署)