首页 > 其他分享 >yield test

yield test

时间:2024-11-13 09:08:26浏览次数:1  
标签:__ ecall csrs yield Context test 异常

yield test

yield test调用yield()开始, 到从yield()返回的期间, 这一趟旅程具体经历了什么?

准备工作

在调用自陷操作前,CTE已经做好了初始化CTE环境,设置好CTE的异常处理程序__am_asm_trap地址,同时注册特定的事件处理函数simple_trap.

CTE(simple_trap)
static Context* (*user_handler)(Event, Context*) = NULL;

bool cte_init(Context*(*handler)(Event, Context*)) {
  // initialize exception entry
  asm volatile("csrw mtvec, %0" : : "r"(__am_asm_trap));

  // register event handler
  user_handler = handler;

  return true;
}

应用程序调用yield()

hello_intr调用函数yield()

void hello_intr() {
  printf("Hello, AM World @ " __ISA__ "\n");
  printf("  t = timer, d = device, y = yield\n");
  io_read(AM_INPUT_CONFIG);
  iset(1);
  while (1) {
    for (volatile int i = 0; i < 1000000; i++) ;
    yield();
  }
}

yield的实现

void yield() {
  asm volatile("li a7, -1; ecall");
}
  • 将立即数0xFFFFFFFF赋值给寄存器a7
  • 调用自陷指令ecall

使用a7寄存器的原因:程序的许多事件都会使用自陷指令ecall,所以CTE使用寄存器a7的值来区分不同的事件a7 的用途是由 CTE(Context Extension)和操作系统(AM)约定的,因此在执行 ecall 之前,用户程序会将系统调用号放入 a7,供操作系统在处理时读取。

硬件

译码自陷指令 ecall 时,译码器的操作为:

  • 将异常号和当前PC传递给异常响应硬件处理
  • 跳转到异常入口地址

在 RISC-V 架构中,ecall 指令的异常号是由硬件定义的。而此异常号会被控制状态寄存器(CSR)mcause寄存器保存。这里我们需要在riscv32的NEMU中加入CSR寄存器的相关支持(在nemu/src/isa/riscv32/include/isa-def.h中定义)

// 用于控制和监控 CPU 状态的特殊寄存器
typedef struct control_and_status_registers {
	word_t mtvec;  // 异常入口地址
	word_t mepc;   // 触发异常的PC
	word_t mstatus;// 处理器的状态
	word_t mcause; // 触发异常的原因
}CSRs;

// 常用 CSR 地址
typedef enum {
  // Machine Trap Setup
	CSR_MSTATUS = 0x300,  // mstatus
	CSR_MTVEC   = 0x305,  // mtvec

  // Machine Trap Handling
	CSR_MEPC    = 0x341,	// mepc
	CSR_MCAUSE  = 0x342,  // mcause
}csr_id;

typedef struct {
  word_t gpr[MUXDEF(CONFIG_RVE, 16, 32)];
  vaddr_t pc;
	CSRs csrs;
} MUXDEF(CONFIG_RV64, riscv64_CPU_state, riscv32_CPU_state);

对于其CSR的读写功能,则放到寄存器功能模块中实现(在nemu/src/isa/riscv32/local-include/reg.h中定义)

word_t get_csr_val_by_id(int csr_id);
void set_csr_val_by_id(int csr_id, word_t val);

#define read_csrs(idx) (get_csr_val_by_id(idx)) 
#define write_csrs(idx, val) (set_csr_val_by_id(idx, val))

将 RISC-V 机器中的手册中mcause中保存的异常号定义为枚举类(在nemu/src/isa/riscv32/include/isa-def.h中定义):

typedef enum {
  //mcause 的最高位在发生中断时置 1,发生同步异常时置 0
    INSTRUCTION_ADDRESS_MISALIGNED = 0,  // 指令地址未对齐
    INSTRUCTION_ACCESS_FAULT       = 1,  // 指令访问故障
    ILLEGAL_INSTRUCTION            = 2,  // 非法指令
    BREAKPOINT                     = 3,  // 断点
    LOAD_ADDRESS_MISALIGNED        = 4,  // 加载地址未对齐
    LOAD_ACCESS_FAULT              = 5,  // 加载访问故障
    STORE_ADDRESS_MISALIGNED       = 6,  // 存储地址未对齐
    STORE_ACCESS_FAULT             = 7,  // 存储访问故障
    ENVIRONMENT_CALL_FROM_U_MODE   = 8,  // 用户模式的环境调用
    ENVIRONMENT_CALL_FROM_S_MODE   = 9,  // 管理模式的环境调用(若存在)
    ENVIRONMENT_CALL_FROM_M_MODE   = 11, // 机器模式的环境调用
    INSTRUCTION_PAGE_FAULT         = 12, // 指令页面故障
    LOAD_PAGE_FAULT                = 13, // 加载页面故障
    STORE_PAGE_FAULT               = 15, // 存储页面故障
    // 中断的最高位为1,这里使用更大的数值表示
    INTERRUPT_MACHINE_TIMER        = 0x80000007, // 机器定时器中断
    INTERRUPT_MACHINE_EXTERNAL     = 0x8000000b  // 机器外部中断
} RiscV_ExceptionCode;   

因为PA不涉及特权级的切换, 这里不需要关心和特权级切换相关的内容,所以自陷指令ecall的异常号设置为ENVIRONMENT_CALL_FROM_U_MODE(8)即可。

随后译码器调用硬件的异常处理模块,并将自陷指令的异常码和当前PC作为参数,进行异常响应。这里模拟硬件的异常响应机制的函数为isa_raise_intr()

/** 模拟硬件异常响应机制*/
word_t isa_raise_intr(word_t NO, vaddr_t epc) {

	// 将当前PC值保存到mepc寄存器
	cpu.csrs.mepc = epc;
  
  // 在mcause寄存器中设置异常号
	cpu.csrs.mcause = NO;

	// 从mtvec寄存器中取出异常入口地址
	return cpu.csrs.mtvec;
}

这样译码ecall的操作为

#define ECALL(dnpc) { dnpc = isa_raise_intr(ENVIRONMENT_CALL_FROM_U_MODE, s->pc);}

INSTPAT("0000000 00000 00000 000 00000 11100 11", ecall  , N, ECALL(s->dnpc));

译码结束后,此时PC指向异常处理程序__am_asm_trap的入口地址

操作系统

成功跳转到异常入口地址之后, 我们就要在软件上开始真正的异常处理过程了.异常处理过程包括:

  1. 保存上下文(程序的状态)
  2. 事件分发(按照异常号分发事件和按照事件分发具体事件处理)
  3. 恢复上下文

异常处理的时候,首先会将程序异常状态的上下文保存起来。CTE定义通用的上下文包括:

  • 通用寄存器
  • 触发异常时的PC和处理器状态
  • 异常号
  • 地址空间

看代码的操作可以得知,CTE从栈顶sp位置从近到远依次保存了:

  • 通用寄存器:32个通用寄存器(不包含0号和2号寄存器)
  • mcause
  • mstatus
  • mepc

地址空间去哪了?栈申请了36个空间保存上下文成员,此时栈底sp+35还存在一个空闲位置,应该就是存放地址空间的位置了。

中途用到的CSR读写指令为

  INSTPAT("??????? ????? ????? 001 ????? 11100 11", csrrw  , I, R(rd) = read_csrs(imm); write_csrs(imm, src1));
  INSTPAT("??????? ????? ????? 010 ????? 11100 11", csrrs  , I, R(rd) = read_csrs(imm); word_t val = read_csrs(imm) | src1; write_csrs(imm,val));

分析异常事件处理函数__am_irq_handle(),其入参为一个指向上下文结构体Context的指针。而栈sp就是保存着结构体Context的指针,所以上下文保存完毕后,将sp作为参数,调用函数__am_irq_handle()

在CTE中,按照栈保存上下文的顺序,可以得出结构体Context的顺序:

struct Context {
	uintptr_t gpr[NR_REGS], mcause, mstatus, mepc;
	void *pdir;
};

保存上下文结束后,调用的异常处理函数__am_irq_handle()进行异常号的分发。此操作会将执行流切换的原因打包成事件, 然后调用在cte_init()中注册的事件处理回调函数(simple_trap()), 将事件交给yield test来处理.

这时候需要__am_irq_handle()通过异常号识别出自陷异常,并打包成编号为EVENT_YIELD的自陷事件。要想在AM上识别RISCV32硬件的异常号,还需要再从AM上定义一份mcause的异常号(在abstract-machine/am/src/riscv/riscv.h中定义)。实现后的__am_irq_handle()

Context* __am_irq_handle(Context *c) {
  if (user_handler) {
    Event ev = {0};
    switch (c->mcause)
    {
      case ENVIRONMENT_CALL_FROM_U_MODE:  ev.event = EVENT_YIELD; break;
      default:  ev.event = EVENT_YIELD; break;
    }
    c = user_handler(ev, c);	// simple_trap
    assert(c != NULL);
  }

  return c;
}

其中调用注册的simple_trap()函数进行事件的处理:

Context *simple_trap(Event ev, Context *ctx) {
  switch(ev.event) {
    case EVENT_IRQ_TIMER:
      putch('t'); break;
    case EVENT_IRQ_IODEV:
      putch('d'); break;
    case EVENT_YIELD:
      putch('y'); break;
    default:
      panic("Unhandled event"); break;
  }
  return ctx;
}

根据事件EVENT_YIELD输出了一个字符y

这样异常处理函数__am_irq_handle()处理完应用程序的异常号后,返回到异常处理程序__am_asm_trap。随后进行恢复上下文。此时由于CSR寄存器mepc保存的是调用ecall时候的地址,需要在恢复过程中,将epc设置为执行ecall的下一条指令。这样异常处理程序最后调用mret指令取出下一条指令的地址就是正确的。

INSTPAT("0011000 00010 00000 000 00000 11100 11", mret   , N, s->dnpc = read_csrs(CSR_MEPC));

异常处理程序执行完毕后,PC指向ecall的下一条指令。此时调用yield()触发自陷操作的函数hello_intr,会进入一个循环,循环结束后,又会再来调用yield()触发自陷操作。

  while (1) {
    for (volatile int i = 0; i < 1000000; i++) ;
    yield();
  }

至此完结

标签:__,ecall,csrs,yield,Context,test,异常
From: https://www.cnblogs.com/shangshankandashu/p/18543073

相关文章

  • 2024.11.12总结报告(一本“英语八年级上册”TEST4 A完形填空 难度:2)
    今日份错误:基本介绍:本题为完形填空选择题,一共10题,错误2题基本考点:本题考查重点为翻译和理解,难点为语法和词汇错误题目:(7)(10)分析:(7)本小题的错误原因为语法,理解中出现错误,具体为动词的过去式与过去分词并未熟练掌握,上下文的联系不够紧密,对文章的理解能力出现问题,思路出现错误不......
  • AtCoder Beginner Contest 378 题解
    AtCoderBeginnerContest378题解比赛链接A-Pairing贪心#include<bits/stdc++.h>usingnamespacestd;usingi64=longlong;voidShowball(){vector<int>a(5);for(inti=0;i<4;i++){intx;cin>>x;a[x]++;......
  • 使用YOLOv8训练无人机检测数据集10158张 txt格式小目标检测 txt标注 标签名UAV 图片与
    准备工作安装依赖首先,确保你的开发环境中安装了必要的软件和库。YOLOv8是基于PyTorch框架的,因此你需要安装Python以及PyTorch。安装Python(推荐3.7或更高版本)安装PyTorch:你可以从PyTorch官方网站获取安装命令,根据你的系统配置选择合适的安装方式。克隆YOLOv8的官方仓库......
  • 如何使用YOLOv8进行训练变电站电力设备缺陷数据集 共6004张图像 train test val比例为
    表计读数异常、表计外壳破损、异物鸟巢、空中漂浮物、表盘模糊、表盘破损、绝缘子破裂、地面油污、硅胶桶变色变电站电力设备缺陷数据集共6004张图像traintestval比例为7:2:1有txt和yaml两种格式数据集共7种标签,包括表计读数异常、表计外壳破损、异物鸟巢、空中漂浮......
  • AtCoder Beginner Contest 356 - VP记录
    A-SubsegmentReverse点击查看代码#include<cstdio>#include<numeric>#include<algorithm>usingnamespacestd;constintN=105;intn,a[N],l,r;intmain(){ scanf("%d%d%d",&n,&l,&r); iota(a+1,a+n+1,1); reverse(a+l,......
  • 地下水数值模拟软件Visual MODFLOW Flex安装,PEST操作方法,Aquifer Test抽水试验设计,地
    主要围绕的目前应用较为广泛的VisualModflowFlex6.1软件版本开展,结合具体应用场景,实例讲解软件的全流程应用过程,包括数据处理分析、数值模型构建以及模拟结果的输出等。本教程有助于提升技术人员地下水模拟软件的操作能力,解决地下水数值模拟技术实施过程中遇到的困难。同时,......
  • The 2024 ICPC Asia East Continent Online Contest (I) G
    Link:TheMedianoftheMedianoftheMedian考虑二分答案,对中位数进行二分,每次去判断是否比中位数大即可。我们钦定了一个中位数\(x\),对于\(\{a\}\)数组,若\(a_i\gex\),则令\(a_i=1\),否则\(a_i=0\),这样有一个好处,我们只关心\(1\)和\(0\)的数量,就可以知道中位数......
  • AtCoder Beginner Contest 379
    这次又是倒在了t5,没救了。ABC379A-Cyclic难度:红#include<bits/stdc++.h>usingnamespacestd;intmain(){chara,b,c;cin>>a>>b>>c;cout<<b<<c<<a<<''<<c<<a<<b;return0;}B-......
  • 如何使用gtest编写C++单元测试代码
    目录一.为什么要编写单元测试代码二.gtest是什么三.下载四.使用方法4.1场景一4.2场景二4.3场景三五.其他一.为什么要编写单元测试代码相信很多人都不喜欢编写单元测试代码,但是单元测试对我们来说真的很重要,单元测试可以暴露出我们自己的代码的内部问题,从而保证我......
  • "stackblitz": { "startCommand": "yarn run test:unit" } 这个命令的作用是
    在package.json文件中,stackblitz字段用于配置StackBlitz环境中的特定设置。StackBlitz是一个基于云的开发环境,允许用户在线编写、运行和调试代码。startCommand字段指定了在StackBlitz环境中启动项目时应该执行的命令。startCommand字段的作用"stackblitz":{"star......