引言:
时钟中断是操作系统中一个十分重要的概念,一个好的时钟中断,将能使一个CPU发挥两个CPU的功效,大大方便和简化程序的编制,提高系统的效率与可操作性。本实验将完成RV64架构下时钟中断的设置及相关的一些处理。
1 实验目的
2 实验步骤
首先将lab3文件夹中的sbi.h等文件移入lab4工程
2-1 基于lab3的代码更改
按照实验指导,对head.s,vmlinux.lds,test.c等文件进行更改。(在此不表)
2-2 异常处理的开启
这里需要我们去填充head.s
通过学习RV64的特权寄存器手册,明确以下几点内容:
- 对特权寄存器的操作需要使用特权指令,本实验中主要用到以下几条指令:
1.
csrr
,读取一个 CSR 的值到通用寄存器。如:csrr t0, mstatus
,读取mstatus
的值到t0
中。2.
csrw
,把一个通用寄存器中的值写入 CSR 中。如:csrw mstatus, t0
,将t0
的值写入mstatus
。3.
csrs
,把 CSR 中指定的 bit 置 1。如:csrsi mstatus, (1 << 2)
,将mstatus
的右起第 3 位置 1。4.
csrc
,把 CSR 中指定的 bit 置 0。如:csrci mstatus, (1 << 2)
,将mstatus
的右起第 3 位置 0。5.
csrrw
,读取一个 CSR 的值到通用寄存器,然后把另一个值写入该 CSR。如:csrrw t0, mstatus, t0
,将mstatus
的值与t0
的值交换。6.
csrrs
,读取一个 CSR 的值到通用寄存器,然后把该 CSR 中指定的 bit 置 1。7.
csrrc
,读取一个 CSR 的值到通用寄存器,然后把该 CSR 中指定的 bit 置 0。
sie
寄存器的第5位为1标志着开启时钟中断- 在qemu下1s等于10000000个时钟周期
- 可以使用
rdtime
获取当前系统的时间(时钟周期数)- sstatus的第1位置1表示开始S态下的响应中断
head.s代码如下:
1 | .extern start_kernel |
需要注意的是我们必须在call函数之前设置sp的值(栈地址)
2-3 实现上下文切换
- 对于保存CPU的寄存器到栈上和恢复上下文,riscv-v没有push和pop,只能通过sd和ld一个个存,一个个取。
代码如下:
1 | sd sp, -8(sp) |
- call trap_handler
1 | csrr a0, scause |
- 从trap中返回
1 | sret |
需要使用S态ret
2-4 实现异常处理函数
要求如下:
1 | // trap.c |
代码如下:
1 |
|
需要注意,为了可以调用clock.c里面的函数,我们需要在外部创建一个clock.c的头文件clock.h
- scause是64位寄存器,通过与操作提取最高位
- 如果是s态中断,scause的低位应为
000101
2-5 实现时钟中断相关函数
要求
1 | unsigned long get_cycles() { |
代码如下:
1 |
|
3 编译及测试
运行结果符合预期
4 思考题
4-1 通过查看 RISC-V Privileged Spec
中的 medeleg
和 mideleg
解释上面 MIDELEG
值的含义,如果实验中mideleg没有设定为正确的值结果会怎么样呢?
在RISC-V特权架构规范中,medeleg
(Machine Exception Delegation Register)和mideleg
(Machine Interrupt Delegation Register)是用于配置异常和中断的委托寄存器。它们允许将特定的异常和中断委托给低特权级别的处理模式(例如,委托给用户态)来处理。
mideleg
寄存器用于配置需要委托给低特权级别处理的中断。每个中断对应于mideleg
寄存器的一个位,如果相应的位被设置为1,则表示将该中断委托给低特权级别的处理模式。
- 对于
MIDELEG
寄存器的值0x0000000000000222,可以将其转换为二进制形式:
0000 0000 0000 0000 0000 0000 0010 0010
每个位对应一个中断,从右到左进行编号。对于位为1的中断,表示将该中断委托给低特权级别的处理模式。
根据二进制表示,可以看出以下中断被委托给低特权级别的处理模式:
位2(从右到左,从0开始计数):软件中断(软中断)
位9:时钟中断
这意味着软件中断和时钟中断将被委托给低特权级别的处理模式来处理。
- 对于
MEDELEG
寄存器的值0x000000000000b109,可以将其转换为二进制形式:
0000 0000 0000 0000 1011 0001 0000 1001
每个位对应一个异常,从右到左进行编号。同样,对于位为1的异常,表示将该异常委托给低特权级别的处理模式。
根据二进制表示,可以看出以下异常被委托给低特权级别的处理模式:
位0(从右到左,从0开始计数):指令地址对齐异常
位3:访问权限异常
位4:非法指令异常
位5:断点异常
位6:加载地址对齐异常
位7:存储地址对齐异常
位9:时钟中断
位11:用户态异常
位12:系统调用异常
这意味着这些异常将被委托给低特权级别的处理模式来处理。
如果在实验中未正确设置mideleg
寄存器的值,将会导致中断在低特权级别的处理模式中无法被正确处理。可能会导致以下结果:
- 中断被忽略:如果没有正确委托中断给低特权级别的处理模式,那么当中断发生时,处理器可能不会响应中断请求,从而导致中断被忽略。
- 中断在高特权级别处理:如果中断没有正确委托给低特权级别的处理模式,处理器可能会在高特权级别(如机器态)下处理中断,而不是在低特权级别(如用户态)下处理。这可能会破坏系统的安全性和隔离性。
- 用户程序无法捕获中断:在某些情况下,用户程序可能需要捕获和处理特定的中断。如果中断未正确委托给用户态,用户程序将无法捕获中断并执行相应的处理操作。
4-2 机器启动后 time、cycle 寄存器分别是从 0 开始计时的吗,从 0 计时是否是必要的呢?(有关 mcycle
寄存器的内容可以参考手册)
make debug后用gdb链接,直接输出time和cycle寄存器中的值。(不要c
)
可以看到,time寄存器位0,cycle寄 器不为0 。
- 对于time寄存器来说,他需要在最开始的时候归零来记录启动时间,所以从0计时是必要的
- 对于cycle寄存器来说,从0开始计时没有必要。
4-3 阅读 The RISC-V Instruction Set Manual Volume I: Unprivileged ISA (V20191213) 中第 1.2 章节 RISC-V Software Execution Environments and Harts,谈谈如何在一台不支持乘除法指令扩展的处理器上执行乘除法指令。
(找到对应位置)在1.2中描述了中断的处理过程,在1.6中详细介绍了trap的具体分类。
在指令手册中介绍说,如果我们使用了一个没有被定义编码的指令,那么会导致fatal trap。
也就是说:如果我们使用了mul/div指令在不支持乘除法指令的处理器上,会在此处中断,进入到trap_handler中。此时非法指令的编码会被存储到 mtval。我们只需要在trap_handler中通过其他处理器支持的非特权指令模拟乘除法进行运算即可。
乘法类似于:
1 | # 参数:a - 乘数, b - 被乘数 |
除法类似于:
1 | # 除法函数 |