uCore OS(on RISC-V64)——LAB1:中断机制
实验目的
- 了解CPU的中断机制
- 了解RISC-v架构是如何支持CPU中断的
- 掌握与软件相关的中断处理
- 掌握时钟中断管理
实验内容
- 跟着实验指导书理解lab1框架代码。
- 阅读RISC-V手册有关中断部分。
- 完成练习。
- 撰写并提交实验报告。
中断相关
寄存器
操作系统一般运行在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_handler
和exception_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
只恢复了其中的status
和epc
寄存器。这主要是因为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:中断机制相关推荐
- 操作系统实验—ucore Lab1
一.内容 通过 Lab1 中的 bootloader 可以从实模式切换的保护模式,然后再读取磁盘并加载 ELF 文件以加载 OS 操作系统,操作系统能够读入字符并显示到屏幕上,具体内容如下: 练习 1 ...
- 操作系统 ucore lab1
操作系统 ucore lab1 实验目的 操作系统是一个软件,也需要通过某种机制加载并运行它.在这里我们将通过另外一个更加简单的软件-bootloader来完成这些工作.为此,我们需要完成一个能够切换 ...
- Ucore学习笔记-Lab1基础知识
系统软件启动过程 前言:计算机通过bootloader(引导装置)来完成操作系统程序的加载和运行.下面的实验提供了一个小的bootloader和Ucore OS,注意bootloader执行代码需要小 ...
- 《ucore lab1 练习5》实验报告
[练习5]实现函数调用堆栈跟踪函数 我们需要在lab1中完成kdebug.c中函数print_stackframe的实现,可以通过函数print_stackframe来跟踪函数调用堆栈中记录的返回地址 ...
- 《ucore lab1 exercise5》实验报告
资源 ucore在线实验指导书 我的ucore实验代码 题目:实现函数调用堆栈跟踪函数 我们需要在lab1中完成kdebug.c中函数print_stackframe的实现,可以通过函数print_s ...
- ucore实验报告lab1
练习1 1.生成操作系统镜像文件ucore.img 生成ucore.imge的代码如下: $(UCOREIMG): $(kernel) $(bootblock)$(V)dd if=/dev/zero ...
- 《Tsinghua os mooc》第1~4讲 启动、中断、异常和系统调用
资源 OS2018Spring课程资料首页 uCore OS在线实验指导书 ucore实验基准源代码 MOOC OS习题集 OS课堂练习 Piazza问答平台 暂时无法注册 疑问 为什么用户态和内核态 ...
- 操作系统-ucore-lab1 Bootloader启动操作系统 A20 GDT全局描述符 使能和进入保护模式 ELF格式os 8259A中断控制器 8253定时器 函数调用堆栈跟踪函数
操作系统-ucore-lab1 本文详细地址 实验一:系统软件启动过程 参考 重要文件 调用顺序 1. boot/bootasm.S | bootasm.asm(修改了名字,以便于彩色显示)a. 开启 ...
- 清华大学ucore实验lab
清华大学实验lab1 实验目的: 操作系统是一个软件,也需要通过某种机制加载并运行它.在这里我们将通过另外一个更加简单的软件-bootloader来完成这些工作.为此,我们需要完成一个能够切换到x86 ...
最新文章
- java节假日api--关于节假日想到的
- python在审计中的应用-【干货】Python自动化审计及实现
- Ubuntu常用快捷键
- JavaWeb_检查用户是否登录的过滤器
- fatal error LNK1112: module machine type 'X86' conflicts with target machine type 'x64'
- JMM如何解决顺序一致性问题-JMM层面的内存屏障
- 使用Angular可重用Component思路实现一个自带图标(icon)的input控件
- python做自动化如何定位动态元素_python-web自动化-元素定位
- Maven 打成 Webjar的方法
- 结合MSDN理解windows service 服务安装的三个类。
- linux组合键 发送指定信号_linux trap脚本信号捕获命令的使用
- 快用苹果助手安装失败_最新建行信用卡调额失败后的抓包详细教程
- linux cat命令详解
- js表单提交的三种方式
- 个人观点:苹果对iPad商标事件的解决办法
- Redis数据结构之——sds
- 房屋征收拆迁信息化管理平台
- 看到它,让我想起了帅出天际的精灵王子……
- python跳出循环的方法_Python 跳出嵌套循环的5种方法
- 计算机弹奏致爱数字,华为电脑——我的至爱
热门文章
- HEVC编码块CU递归划分
- 关于“Guice ”
- mysql string agg_postgresql合并string_agg函数的实例
- 年入800万!韩国第一虚拟网红的崛起
- 扁平化设计颜色之翡翠绿
- 合肥一中2021高考成绩查询,2021年合肥重点高中名单及排名,合肥高中高考成绩排名榜...
- 测试2k显示器的软件,高性价比的27吋2K显示器 AOC Q27P1U评测
- layui数据表格显示序号
- 无向图、深度优先搜索(无向图)、广度优先搜索(无向图)、无向图路径查找(基于深度优先搜索)
- AUC的是如何计算的