实验目的

  1. 了解CPU的中断机制
  2. 了解RISC-v架构是如何支持CPU中断的
  3. 掌握与软件相关的中断处理
  4. 掌握时钟中断管理

实验内容

  1. 跟着实验指导书理解lab1框架代码。
  2. 阅读RISC-V手册有关中断部分。
  3. 完成练习。
  4. 撰写并提交实验报告。

中断相关

寄存器

操作系统一般运行在RISC-V特权模式下的S模式,这个模式具有的CSR有

名称 功能
sepc 指向发生异常的指令
stvec 保存发生异常时跳转到的地址
scause 指向发生异常的种类
sscratch 暂时存放一个字大小的数据
stval 保存了陷入的附加信息
sstatus 保存全局中断使能

特权指令

ecall:通过引发环境调用异常来请求执行环境
ebreak:通过抛出断点异常的方式来请求执行环境
sret:管理员模式例外返回,从管理员模式的例外处理程序中返回
mret:机器模式异常返回,从机器模式异常处理程序返回

上下文处理

中断处理要求执行完中断后寄存器能够恢复为执行中断前的现场。
因此上下文处理就是:

  • 将CPU的寄存器(上下文)保存到内存(栈上)。
  • 将内存(栈上)恢复到CPU的寄存器(上下文)。

RISC-V用到的寄存器有32个通用寄存器和4个控制状态寄存器,
用结构题将这些寄存器加以组织。

struct pushregs {uintptr_t zero;  // Hard-wired zerouintptr_t ra;    // Return addressuintptr_t sp;    // Stack pointeruintptr_t gp;    // Global pointeruintptr_t tp;    // Thread pointeruintptr_t t0;    // Temporaryuintptr_t t1;    // Temporaryuintptr_t t2;    // Temporaryuintptr_t s0;    // Saved register/frame pointeruintptr_t s1;    // Saved registeruintptr_t a0;    // Function argument/return valueuintptr_t a1;    // Function argument/return valueuintptr_t a2;    // Function argumentuintptr_t a3;    // Function argumentuintptr_t a4;    // Function argumentuintptr_t a5;    // Function argumentuintptr_t a6;    // Function argumentuintptr_t a7;    // Function argumentuintptr_t s2;    // Saved registeruintptr_t s3;    // Saved registeruintptr_t s4;    // Saved registeruintptr_t s5;    // Saved registeruintptr_t s6;    // Saved registeruintptr_t s7;    // Saved registeruintptr_t s8;    // Saved registeruintptr_t s9;    // Saved registeruintptr_t s10;   // Saved registeruintptr_t s11;   // Saved registeruintptr_t t3;    // Temporaryuintptr_t t4;    // Temporaryuintptr_t t5;    // Temporaryuintptr_t t6;    // Temporary};struct trapframe {struct pushregs gpr;uintptr_t status;uintptr_t epc;uintptr_t badvaddr;uintptr_t cause;
};

然后将上下文(也就是一个trapframe)保存在内存中。

.macro SAVE_ALLcsrw sscratch, spaddi sp, sp, -36 * REGBYTES# save x registersSTORE x0, 0*REGBYTES(sp)STORE x1, 1*REGBYTES(sp)STORE x3, 3*REGBYTES(sp)STORE x4, 4*REGBYTES(sp)STORE x5, 5*REGBYTES(sp)STORE x6, 6*REGBYTES(sp)STORE x7, 7*REGBYTES(sp)STORE x8, 8*REGBYTES(sp)STORE x9, 9*REGBYTES(sp)STORE x10, 10*REGBYTES(sp)STORE x11, 11*REGBYTES(sp)STORE x12, 12*REGBYTES(sp)STORE x13, 13*REGBYTES(sp)STORE x14, 14*REGBYTES(sp)STORE x15, 15*REGBYTES(sp)STORE x16, 16*REGBYTES(sp)STORE x17, 17*REGBYTES(sp)STORE x18, 18*REGBYTES(sp)STORE x19, 19*REGBYTES(sp)STORE x20, 20*REGBYTES(sp)STORE x21, 21*REGBYTES(sp)STORE x22, 22*REGBYTES(sp)STORE x23, 23*REGBYTES(sp)STORE x24, 24*REGBYTES(sp)STORE x25, 25*REGBYTES(sp)STORE x26, 26*REGBYTES(sp)STORE x27, 27*REGBYTES(sp)STORE x28, 28*REGBYTES(sp)STORE x29, 29*REGBYTES(sp)STORE x30, 30*REGBYTES(sp)STORE x31, 31*REGBYTES(sp)# get sr, epc, badvaddr, cause# Set sscratch register to 0, so that if a recursive exception# occurs, the exception vector knows it came from the kernelcsrrw s0, sscratch, x0csrr s1, sstatuscsrr s2, sepccsrr s3, sbadaddrcsrr s4, scauseSTORE s0, 2*REGBYTES(sp)STORE s1, 32*REGBYTES(sp)STORE s2, 33*REGBYTES(sp)STORE s3, 34*REGBYTES(sp)STORE s4, 35*REGBYTES(sp).endm

在上述汇编中,首先将sp寄存器的值保存在sscratch寄存器中,然后使sp寄存器向低地址生长了36个寄存器长度,用于分别保存32个通用寄存器和4个CSR。

.macro RESTORE_ALLLOAD s1, 32*REGBYTES(sp)LOAD s2, 33*REGBYTES(sp)csrw sstatus, s1csrw sepc, s2# restore x registersLOAD x1, 1*REGBYTES(sp)LOAD x3, 3*REGBYTES(sp)LOAD x4, 4*REGBYTES(sp)LOAD x5, 5*REGBYTES(sp)LOAD x6, 6*REGBYTES(sp)LOAD x7, 7*REGBYTES(sp)LOAD x8, 8*REGBYTES(sp)LOAD x9, 9*REGBYTES(sp)LOAD x10, 10*REGBYTES(sp)LOAD x11, 11*REGBYTES(sp)LOAD x12, 12*REGBYTES(sp)LOAD x13, 13*REGBYTES(sp)LOAD x14, 14*REGBYTES(sp)LOAD x15, 15*REGBYTES(sp)LOAD x16, 16*REGBYTES(sp)LOAD x17, 17*REGBYTES(sp)LOAD x18, 18*REGBYTES(sp)LOAD x19, 19*REGBYTES(sp)LOAD x20, 20*REGBYTES(sp)LOAD x21, 21*REGBYTES(sp)LOAD x22, 22*REGBYTES(sp)LOAD x23, 23*REGBYTES(sp)LOAD x24, 24*REGBYTES(sp)LOAD x25, 25*REGBYTES(sp)LOAD x26, 26*REGBYTES(sp)LOAD x27, 27*REGBYTES(sp)LOAD x28, 28*REGBYTES(sp)LOAD x29, 29*REGBYTES(sp)LOAD x30, 30*REGBYTES(sp)LOAD x31, 31*REGBYTES(sp)# restore sp lastLOAD x2, 2*REGBYTES(sp)#addi sp, sp, 36 * REGBYTES.endm

恢复上下文,只需要将CSR中的sstatus寄存器和sepc寄存器恢复,其余CSR不用恢复。
中断入口:

.globl __alltraps.align(2)__alltraps:SAVE_ALL    #保存上下文move  a0, sp  #传递参数jal trap       #中断处理程序# sp should be the same as before "jal trap".globl __trapret__trapret:RESTORE_ALL# return from supervisor callsret #从s模式下返回u模式

中断处理程序

初始化

// kern/init/init.c
#include <trap.h>
int kern_init(void) {extern char edata[], end[];memset(edata, 0, end - edata);cons_init();  // init the consoleconst char *message = "(THU.CST) os is loading ...\n";cprintf("%s\n\n", message);print_kerninfo();// grade_backtrace();//trap.h的函数,初始化中断idt_init();  // init interrupt descriptor table//clock.h的函数,初始化时钟中断clock_init();  //intr.h的函数,使能中断intr_enable();  // LAB1: CAHLLENGE 1 If you try to do it, uncomment lab1_switch_test()// user/kernel mode switch test// lab1_switch_test();/* do nothing */while (1);
}
// kern/trap/trap.c
void idt_init(void) {extern void __alltraps(void);//约定:若中断前处于S态,sscratch为0//若中断前处于U态,sscratch存储内核栈地址//那么之后就可以通过sscratch的数值判断是内核态产生的中断还是用户态产生的中断//我们现在是内核态所以给sscratch置零write_csr(sscratch, 0);//我们保证__alltraps的地址是四字节对齐的,将__alltraps这个符号的地址直接写到stvec寄存器write_csr(stvec, &__alltraps);
}
//kern/driver/intr.c
#include <intr.h>
#include <riscv.h>
/* intr_enable - enable irq interrupt, 设置sstatus的Supervisor中断使能位 */
void intr_enable(void) { set_csr(sstatus, SSTATUS_SIE); }
/* intr_disable - disable irq interrupt */
void intr_disable(void) { clear_csr(sstatus, SSTATUS_SIE); }

在原来的init.c的基础上加入了
idt_init:初始化中断向量表
clock_init:初始化时钟中断
intr_enable:使能中断

处理

// kern/trap/trap.c
/* trap_dispatch - dispatch based on what type of trap occurred */
static inline void trap_dispatch(struct trapframe *tf) {//scause的最高位是1,说明trap是由中断引起的if ((intptr_t)tf->cause < 0) {// interruptsinterrupt_handler(tf);} else {// exceptionsexception_handler(tf);}
}/* ** trap - handles or dispatches an exception/interrupt. if and when trap()* returns,* the code in kern/trap/trapentry.S restores the old CPU state saved in the* trapframe and then uses the iret instruction to return from the exception.* */
void trap(struct trapframe *tf) { trap_dispatch(tf); }

根据RISC-V的scause寄存器的格式,如果最高位是1是中断;如果最高位是0是异常,根据分类的结果交给函数interrupt_handlerexception_handler分别处理。

时钟中断

//libs/sbi.c//当time寄存器(rdtime的返回值)为stime_value的时候触发一个时钟中断
void sbi_set_timer(unsigned long long stime_value) {sbi_call(SBI_SET_TIMER, stime_value, 0, 0);
}// kern/driver/clock.c
#include <clock.h>
#include <defs.h>
#include <sbi.h>
#include <stdio.h>
#include <riscv.h>//volatile告诉编译器这个变量可能在其他地方被瞎改一通,所以编译器不要对这个变量瞎优化
volatile size_t ticks;//对64位和32位架构,读取time的方法是不同的
//32位架构下,需要把64位的time寄存器读到两个32位整数里,然后拼起来形成一个64位整数
//64位架构简单的一句rdtime就可以了
//__riscv_xlen是gcc定义的一个宏,可以用来区分是32位还是64位。
static inline uint64_t get_time(void) {//返回当前时间
#if __riscv_xlen == 64uint64_t n;__asm__ __volatile__("rdtime %0" : "=r"(n));return n;
#elseuint32_t lo, hi, tmp;__asm__ __volatile__("1:\n""rdtimeh %0\n""rdtime %1\n""rdtimeh %2\n""bne %0, %2, 1b": "=&r"(hi), "=&r"(lo), "=&r"(tmp));return ((uint64_t)hi << 32) | lo;
#endif
}// Hardcode timebase
static uint64_t timebase = 100000;void clock_init(void) {// sie这个CSR可以单独使能/禁用某个来源的中断。默认时钟中断是关闭的// 所以我们要在初始化的时候,使能时钟中断set_csr(sie, MIP_STIP); // enable timer interrupt in sie//设置第一个时钟中断事件clock_set_next_event();// 初始化一个计数器ticks = 0;cprintf("++ setup timer interrupts\n");
}
//设置时钟中断:timer的数值变为当前时间 + timebase 后,触发一次时钟中断
//对于QEMU, timer增加1,过去了10^-7 s, 也就是100ns
void clock_set_next_event(void) { sbi_set_timer(get_time() + timebase); }

在clock.c中封装着一个gettime函数对于64位系统可以直接读取,对于32位系统需要分成两个32位整数读取time寄存器的值然后拼接。

然后在clock_init函数中需要首先将sie寄存器中的时钟使能信号打开,然后设置一个时钟中断信息,并设定timebase = 100000,对于QEMU,模拟出来CPU的主频是10MHz,每个时钟周期也就是100ns,达到timebase共需要10ms,即10ms触发一次时钟中断。

// kern/trap/trap.c
#include<clock.h>#define TICK_NUM 100
static void print_ticks() {cprintf("%d ticks\n", TICK_NUM);
#ifdef DEBUG_GRADEcprintf("End of Test.\n");panic("EOT: kernel seems ok.");
#endif
}void interrupt_handler(struct trapframe *tf) {intptr_t cause = (tf->cause << 1) >> 1;switch (cause) {/* blabla 其他case*/case IRQ_S_TIMER:clock_set_next_event();//发生这次时钟中断的时候,我们要设置下一次时钟中断if (++ticks % TICK_NUM == 0) {print_ticks();}break;/* blabla 其他case*/
}

每100次时钟中断打印一次信息,也就是每1s打印一次100 ticks。

执行流

内核的执行流为:
加电 -> OpenSBI启动 -> 跳转到 0x80200000 (kern/init/entry.S)->进入kern_init()函数(kern/init/init.c) ->调用cprintf()输出一行信息->调用print_kerninfo()打印内核信息->调用idt_init(),初始化sscratch和stvec寄存器->调用clock_init()初始化时钟中断->初始化使能中断->结束

时钟中断的执行流为:
调用clock_init()函数中->调用set_csr()函数将sie中的时钟中断使能打开->调用sbi_set_timer()函数,在time达到timebase时发生中断,进入中断入口->先保存现场,然后通过tail指令进入trap.c执行trap_dispatch()函数->恢复现场->结束。

练习

练习1:描述处理中断异常的流程

以时钟中断为例,调用clock_init()函数中->调用set_csr()函数将sie中的时钟中断使能打开->调用sbi_set_timer()函数,在time达到timebase时发生中断,进入中断入口->先保存现场,然后通过tail指令进入trap.c执行trap_dispatch()函数->恢复现场->结束。

练习2:对于任何中断,都需要保存所有寄存器吗?为什么?

不需要,在恢复上下文的代码中,我们可以看到在恢复现场的时候,对于控制状态寄存器的四个寄存器status,epc,badaddr,cause只恢复了其中的statusepc寄存器。这主要是因为badaddr寄存器和cause寄存器中保存的分别是出错的地址以及出错的原因,当我们处理完这个中断的时候,也就不需要这两个寄存器中保存的值,所以可以不用恢复这两个寄存器。

练习3:触发、捕获、处理异常

在trap.c中的根据cause寄存器进行例外的分类时,在illegal_intruction中输入

cprintf("illegal insttruction at 0x%016llx\n",tf->epc);
tf->epc += 2;


表明例外的类型和发生例外的地址
在init.c中使用内联汇编使用mret函数即会触发这个例外

在终端运行,查看结果:

uCore OS(on RISC-V64)——LAB1:中断机制相关推荐

  1. 操作系统实验—ucore Lab1

    一.内容 通过 Lab1 中的 bootloader 可以从实模式切换的保护模式,然后再读取磁盘并加载 ELF 文件以加载 OS 操作系统,操作系统能够读入字符并显示到屏幕上,具体内容如下: 练习 1 ...

  2. 操作系统 ucore lab1

    操作系统 ucore lab1 实验目的 操作系统是一个软件,也需要通过某种机制加载并运行它.在这里我们将通过另外一个更加简单的软件-bootloader来完成这些工作.为此,我们需要完成一个能够切换 ...

  3. Ucore学习笔记-Lab1基础知识

    系统软件启动过程 前言:计算机通过bootloader(引导装置)来完成操作系统程序的加载和运行.下面的实验提供了一个小的bootloader和Ucore OS,注意bootloader执行代码需要小 ...

  4. 《ucore lab1 练习5》实验报告

    [练习5]实现函数调用堆栈跟踪函数 我们需要在lab1中完成kdebug.c中函数print_stackframe的实现,可以通过函数print_stackframe来跟踪函数调用堆栈中记录的返回地址 ...

  5. 《ucore lab1 exercise5》实验报告

    资源 ucore在线实验指导书 我的ucore实验代码 题目:实现函数调用堆栈跟踪函数 我们需要在lab1中完成kdebug.c中函数print_stackframe的实现,可以通过函数print_s ...

  6. ucore实验报告lab1

    练习1 1.生成操作系统镜像文件ucore.img 生成ucore.imge的代码如下: $(UCOREIMG): $(kernel) $(bootblock)$(V)dd if=/dev/zero ...

  7. 《Tsinghua os mooc》第1~4讲 启动、中断、异常和系统调用

    资源 OS2018Spring课程资料首页 uCore OS在线实验指导书 ucore实验基准源代码 MOOC OS习题集 OS课堂练习 Piazza问答平台 暂时无法注册 疑问 为什么用户态和内核态 ...

  8. 操作系统-ucore-lab1 Bootloader启动操作系统 A20 GDT全局描述符 使能和进入保护模式 ELF格式os 8259A中断控制器 8253定时器 函数调用堆栈跟踪函数

    操作系统-ucore-lab1 本文详细地址 实验一:系统软件启动过程 参考 重要文件 调用顺序 1. boot/bootasm.S | bootasm.asm(修改了名字,以便于彩色显示)a. 开启 ...

  9. 清华大学ucore实验lab

    清华大学实验lab1 实验目的: 操作系统是一个软件,也需要通过某种机制加载并运行它.在这里我们将通过另外一个更加简单的软件-bootloader来完成这些工作.为此,我们需要完成一个能够切换到x86 ...

最新文章

  1. java节假日api--关于节假日想到的
  2. python在审计中的应用-【干货】Python自动化审计及实现
  3. Ubuntu常用快捷键
  4. JavaWeb_检查用户是否登录的过滤器
  5. fatal error LNK1112: module machine type 'X86' conflicts with target machine type 'x64'
  6. JMM如何解决顺序一致性问题-JMM层面的内存屏障
  7. 使用Angular可重用Component思路实现一个自带图标(icon)的input控件
  8. python做自动化如何定位动态元素_python-web自动化-元素定位
  9. Maven 打成 Webjar的方法
  10. 结合MSDN理解windows service 服务安装的三个类。
  11. linux组合键 发送指定信号_linux trap脚本信号捕获命令的使用
  12. 快用苹果助手安装失败_最新建行信用卡调额失败后的抓包详细教程
  13. linux cat命令详解
  14. js表单提交的三种方式
  15. 个人观点:苹果对iPad商标事件的解决办法
  16. Redis数据结构之——sds
  17. 房屋征收拆迁信息化管理平台
  18. 看到它,让我想起了帅出天际的精灵王子……
  19. python跳出循环的方法_Python 跳出嵌套循环的5种方法
  20. 计算机弹奏致爱数字,华为电脑——我的至爱

热门文章

  1. HEVC编码块CU递归划分
  2. 关于“Guice ”
  3. mysql string agg_postgresql合并string_agg函数的实例
  4. 年入800万!韩国第一虚拟网红的崛起
  5. 扁平化设计颜色之翡翠绿
  6. 合肥一中2021高考成绩查询,2021年合肥重点高中名单及排名,合肥高中高考成绩排名榜...
  7. 测试2k显示器的软件,高性价比的27吋2K显示器 AOC Q27P1U评测
  8. layui数据表格显示序号
  9. 无向图、深度优先搜索(无向图)、广度优先搜索(无向图)、无向图路径查找(基于深度优先搜索)
  10. AUC的是如何计算的