首页 > 其他分享 >异常处理记录

异常处理记录

时间:2024-06-14 22:10:11浏览次数:15  
标签:mepc 记录 处理 保存 指令 寄存器 异常 CSR

异常处理过程:

当我们遇到异常时,我们首先需要把当前程序P的状态保存起来,而后跳到异常处理程序进行诊断。

  • 这里我们从指令集状态机S = {<R,M>}的视角来讨论咯 R为寄存器,M为内存。

异常处理程序和P事两个不同的程序,它们使用不同的M,所以:只要异常处理程序不随意修改P的M,则不必进行实质性的保存操作。
但是R只有一份,即P和异常处理程序共用寄存器,所以我们需要把P的寄存器状态保存起来。
但是R保存到哪里呢??
有几种方法:
1.保存到R:也就是增加新的一组寄存器,把P的寄存器状态复制到新寄存器中。
2.保存到M:也就是存到内存中,但是需要找到一处空闲的内存呢
3.保存到栈:栈有空间的话,这个可以有。

谁来保存?

  • 硬件保存:在CPU状态机的控制下保存
  • 软件保存: 通过指令控制CPU进行保存

所以保存R的设计,共有四种方法

R M
硬件保存 硬件保存到R_save 硬件保存到M
软件保存 软件保存到R_save 软件保存到M

当发生异常时的流程如下:P发生异常->硬件保存->跳转到异常处理程序->软件保存
这里要注意的是,当异常处理程序诊断时需要读取R_save。所以CPU还要添加相应的读取等指令。

RISCV硬件将PC保存到mepc这个特殊的寄存器中,也叫控制状态寄存器(CSR,control and status register)

CSR

用于控制和反映处理器状态的特殊寄存器(eg:mepc)

  • 硬件发生某些事会自动更新CSR,或者从CSR中读出值直接用
  • 软件也可以通过CSR指令来访问CSR
  • 所以每个CSR都有一个软件可见的编号(CSR地址空间)
    下面的图片中最上就是一条CSR指令,其中0x341代表mepc寄存器的地址空间(也是编号:-)
    image

最简单异常处理还需要的CSR——mtvec

mtvec:异常处理程序的入口地址,即当发生异常时CPU自动跳转到这个地址
一个简单的异常处理程序

#include <klib.h>
void handler() {
  uintptr_t mepc;  // 定义一个变量来存储异常发生时的程序计数器 (Program Counter)
  // 使用内联汇编读取 mepc 寄存器的值
  asm volatile ("csrr %0, mepc" : "=r"(mepc)); 
  printf("exception at mepc = %p\n", mepc); // 打印异常发生的地址
  while (1); // 无限循环,防止返回
}
int main() {
  // 设置 mtvec 寄存器,指向异常处理程序 handler
  asm volatile ("csrw mtvec, %0" : :"r"(handler));
  // 触发非法指令异常
  asm volatile (".word 0"); // 这行代码试图执行一个无效的指令
  printf("I am alive!\n");
  while (1); 
}

类似的还有mcause寄存器,即发生异常时,CPU将异常号写入这个CSR
具体的异常号如下

# RTFM了解异常号的含义
 0 - Instruction address misaligned
 1 - Instruction access fault
 2 - Illegal Instruction
 3 - Breakpoint
 4 - Load address misaligned
 5 - Load access fault
 6 - Store/AMO address misaligned
 7 - Store/AMO access fault
 8 - Environment call from U-mode
 9 - Environment call from S-mode
11 - Environment call from M-mode
12 - Instruction page fault
13 - Load page fault
15 - Store/AMO page fault

从异常状态返回

若诊断问题不大,P可以继续执行,则需要从异常处理程序返回P。那么返回需要先回复之前为P保存的状态(恢复寄存器就行)

  • RISC架构通过load指令将M中保存的内容恢复到R,然后返回P
  • 但是异常处理程序和P事两个不同的程序,不可以通过ret/jal返回
  • jalr指令需要先把返回地址写入一个寄存器,但是那样会改变P的状态,如果返回后P需要用这个寄存器,就会报错。
    所以综上,我们需要添加一条特殊的返回指令mret,即跳转到mepc中存放的地址
uintptr_t mepc, mcause;
// 读取 mepc 寄存器的值(异常发生时的程序计数器值)
asm volatile ("csrr %0, mepc" : "=r"(mepc));
// 读取 mcause 寄存器的值(异常原因)
asm volatile ("csrr %0, mcause" : "=r"(mcause));
// 打印异常原因和异常发生时的程序计数器值
printf("exception mcause = %p at mepc = %p\n", mcause, mepc);
// 仅限于演示,没有恢复其他寄存器
// 检查异常原因是否为2(通常表示非法指令异常)
if (mcause == 2) {
  // 更新 mepc 寄存器的值为 mepc + 4(跳过导致异常的指令)
  asm volatile ("csrw mepc, %0; mret" : : "r"(mepc + 4));
}
while (1);

硬件实现异常处理

在单周期实现异常

实现异常处理我们得先

  • 实现CSR

  • 添加CSR的读写指令,而后在指令异常时注意通用寄存器GPR和CSR之间的数据交换。

  • 实现异常的触发,在译码时要检查非法指令,识别ecall指令等,识别到异常事件后,我们还需要通过电路更新mmepc,,mcause等CSR,而后跳转到mevec中存放的地址

  • 实现mret指令,即跳转到mepc中存放的地址

异常处理的状态机模型

在异常处理状态下,状态机模型又需要改变了,状态机模型需要加一个扩展(CSR),也就是:
R = {PC, GPR,CSR},M无需扩展,注意在这里指令的执行并不是always成功了,定义一个函数
f_ex : S->{0,1},给定任意状态S:

  • f_ex(S) = 0,则按照当前指令的语义进行状态转移
  • f_ex\(S)= 1,则执行一条特殊指令raise_intr异常号,并更新状态如下
CSR[mepc] <- PC
CSR[mcause] <- 异常号
PC <- CSR[mtvec]

说白了,模型状态机一旦异常(f_ex(S)=1),就是把各种异常号,异常的pc值存到CSR寄存器中
image

标签:mepc,记录,处理,保存,指令,寄存器,异常,CSR
From: https://www.cnblogs.com/ink-bai/p/18248711

相关文章

  • 程序环境和预处理
    1.程序的翻译环境和运行环境在ANSIC是美国国家标准协会(ANSI)及国际标准化组织(ISO)推出的关于C语言的标准,在ANSIC的任何一种实现中,存在两个不同的环境。第一种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。翻译环境主要执行两大任务,编译和汇编,其中编译又分为三......
  • 【学习笔记】透视HTTP协议(六):发起请求后,处理流程是怎么样子的?
      本文是一篇学习笔记,学习的课程是极客时间的《透视HTTP协议》。透视HTTP协议_HTTP_HTTPS-极客时间(geekbang.org)本文主要描述发起请求后,具体的处理过程。目录一、简述浏览器HTTP请求过程二、详述浏览器HTTP请求过程一、简述浏览器HTTP请求过程示例:在Chrome浏览......
  • 【Azure Developer】记录一段验证AAD JWT Token时需要设置代理获取openid-configurati
    问题描述如果在使用.NET代码对AADJWTToken进行验证时候,如果遇见无法访问 Unabletoobtainconfigurationfrom:'https://login.partner.microsoftonline.cn/<commonoryourtenantid>/v2.0/.well-known/openid-configuration‘,可以配置 HttpClientHandler.Proxy代理。......
  • Superset二次开发之基于GitLab OpenAPI 查询项目的提交记录中修改的文件
    背景:Superset二次开发,在处理版本升级的过程中,需要手动迁移代码,如何在Superset项目众多的文件中,记录修改过的文件,迁移代码时只需重点关注这些文件修改的内容即可,但是针对项目中多次的commit信息,每个commit又涉及不同的文件,如何快速梳理出这些二开工作中修改的文件,是我们......
  • 处理机调度
    目录第1关:先来先服务调度算法任务描述相关知识先来先服务调度算法FCFS周转时间和带权周转时间编程要求分析:答案: 第2关:短作业优先调度算法 任务描述相关知识短作业优先调度算法SJF周转时间和带权周转时间编程要求分析: 答案: 第3关:高响应比优先调度算法任......
  • 记录--N 个值得一看的前端代码片段
    ......
  • python watchdog检测到文件产生,动态创建进程,不指定进程数去处理,处理过程中需要写文件,
    如果希望在检测到文件时动态创建进程而不预先指定进程数,并确保写文件时不发生冲突,可以使用队列和锁的机制。以下是一个改进的方案:pythonfrommultiprocessingimportProcess,Queue,Lockfromwatchdog.observersimportObserverfromwatchdog.eventsimportFileSystemE......
  • K8S故障处理:临时设置节点为不可调度(cordon与drain区别)
    在Kubernetes中,节点驱逐是一种管理和维护集群的重要操作,允许节点在维护、升级或者发生故障时从集群中移除,等到节点修复后,再重新承担pod调度功能。1.K8s节点驱逐节点驱逐是指将节点上运行的Pod迁移到其他可用节点上,并暂时从集群中移除目标节点。这个操作通常在节点维护、系统升级......
  • 18.9k star!一个高性能的嵌入式分析型数据库,主要用于数据分析和数据处理任务。
    大家好,今天给大家分享的是一个开源的面向列的关系数据库管理系统(RDBMS)。DuckDB是一个嵌入式的分析型数据库,它提供了高性能的数据分析和数据处理能力。DuckDB的设计目标是为数据科学家、分析师和数据工程师提供一个快速、灵活且易于使用的数据分析工具。它支持SQL查询语言,并提......
  • MySQL 游标遍历每一行数据做处理。
     delimiter$$--分隔标记CREATEPROCEDUREprocess_test()begin--声明变量declareSuoshuQY_pvarchar(255);declaredoneint;declarecurcursorforSELECTSuoshuQYasSuoshuQY_pFROMdiy_cabinet_listWHEREIsDeleted=0;declareco......