Lab4:RV64 时钟中断处理
代码部分¶
1 准备工程¶
1.1 同步代码¶
- 从 repo 和上一次完成的 lab3 同步代码。按照实验指导修改 print 函数,test.c 等。目录结构如下:
.
|-- Makefile
|-- arch
| `-- riscv
| |-- Makefile
| |-- include
| | |-- clock.h
| | |-- defs.h
| | `-- sbi.h
| `-- kernel
| |-- Makefile
| |-- clock.c
| |-- entry.S
| |-- head.S
| |-- sbi.c
| |-- trap.c
| `-- vmlinux.lds
|-- include
| |-- printk.h
| |-- stddef.h
| `-- types.h
|-- init
| |-- Makefile
| |-- main.c
| `-- test.c
`-- lib
|-- Makefile
`-- printk.c
1.2 修改 vmlinux.lds 和 head.S¶
- 在 vmlinux.lds 的 text 段中添加了一个 .text.init
- 在 vmlinux 添加了一个自定义的 stack 段
vmlinux.lds 修改部分:
.text : ALIGN(0x1000){
_stext = .;
*(.text.init) /* <- 加入了 .text.init */
*(.text.entry) /* <- 之后我们实现 中断处理逻辑 会放置在 .text.entry */
*(.text .text.*)
_etext = .;
}
...
_end = .; /* 记录程序的结束地址为当前地址 */
.stack : ALIGN(0x1000) /* .stack 段,4KB 对齐 */
{
_stack_bottom = .; /* 记录栈底的地址为当前地址 */
*(.stack.entry) /* 加载 .stack.entry 段的内容 */
_stack_top = .; /* 记录栈顶的地址为当前地址 */
}
2 开启异常处理¶
- 设置
stvec
, 将_traps
(_trap
在 4.3 中实现)所表示的地址写入stvec
,这里我们采用Direct 模式
, 而_traps
则是中断处理入口函数的基地址。 - 开启时钟中断,将
sie[STIE]
置 1。即将 SIE 寄存器的第五位置 1。
![[Pasted image 20231125201904.png]]
- 设置第一次时钟中断,参考
clock_set_next_event()
(clock_set_next_event()
在 4.5 中介绍)中的逻辑用汇编实现。 - 开启 S 态下的中断响应, 将
sstatus[SIE]
置 1。即将 sstatus 的第 1 位置 1。
![[Pasted image 20231125202111.png]]
.extern start_kernel
.section .text.init
.globl _start
_start:
# MY CODE HERE
la sp, _stack_top
# ------------------ # set stvec = _traps
la a0, _traps
csrw stvec, a0
# ------------------ # set sie[STIE] = 1
li a0, 0b100000
csrs sie, a0
# ------------------ # set first time interrupt
rdtime a0
li t0, 10000000
add a0, a0, t0
call sbi_set_timer
# ------------------ # set sstatus[SIE] = 1
csrs sstatus, 0b10
# ------------------ # Initialize Stack
li a0, 2022
j start_kernel
# ------------------
.section .stack.entry
.globl _stack_top, _stack_bottom
_stack_bottom:
// .skip 0x1000
.space 0x1000 // initialize to 0
_stack_top:
3 实现上下文切换¶
修改 entry.S 文件。四个部分:
- 设置 sp 指针,将 32 个寄存器保存在栈上
- 将 scause 和 sepc 参数传递到 a0,a1,调用 trap_handler 函数
- 恢复 32 个寄存器中的值,恢复 sp 指针
- sret 返回
.section .text.entry
.align 2
.globl _traps
_traps:
addi sp, sp, -0xf8
sd x0, 0x0(sp) # zero
...
sd x31, 0xf8(sp)
csrr a0, scause # record the cause of the trap
csrr a1, sepc # record the address of the instruction that caused the trap
call trap_handler
ld x0, 0x0(sp)
...
ld x31, 0xf8(sp)
addi sp, sp, 0x8
sret
4 实现异常处理函数¶
![[sys2lab4scause.png]] ![[sys2lab4supervisortimeinterrupt.png]]
5 实现时钟中断相关函数¶
在 lab3 中,实现了三个函数,所以在 sbi_ecall 时调用 sbi_set_timer 函数即可
![[Pasted image 20231121125215.png]]
unsigned long get_cycles() {
unsigned long time;
__asm__ volatile (
"rdtime %[time]\n" // csrr t0, mtime
: [time] "=r" (time)
:
:
);
return time;
}
void clock_set_next_event() {
unsigned long next_time = get_cycles() + TIMECLOCK;
sbi_set_timer(next_time);
}
编译及测试¶
运行 make run 得到预期结果:
![[Pasted image 20231121142403.png]]
使用 Spike 记录¶
1. 安装 Spike¶
- 运行 make install
- 根据报错,修改 Makefile,将
CROSS_COMPILE=riscv64-linux-unknown-gnu-
修改为CROSS_COMPILE=riscv64-linux-gnu-
- 手动
git clone
仓库 openocd 及其子仓库 jmltcl。 - 查看 README 编译。在openocd 中
./bootstrap
产生 configure,./configure
生成 Makefile,然后 make 运行。jmltcl 中同样。 - 再次 make install,得到输出 install finish
用 lab3 生成的 Image 做测试,成功运行。
![[Pasted image 20231121142859.png]]
2. 使用 Spike Debug¶
输出 Kernel is running! Time: 1s
后无输出
![[Pasted image 20231121134657.png]]
定位在 trap_handler 时出错,导致没有输出 "[S] Supervisor Mode Timer Interrupt\n" 以及设置下一个时钟事件:
![[Pasted image 20231121134842.png]]
使用 Spike 调试,在 trap handler 处打断点:
![[Pasted image 20231121140435.png]]
观察到,scause 传参正确,Interrpt 位为 1,opcode 位为 5,但是没有接下去输出字符串信息,推测是判断条件出错。查看 trap_handler 部分的内容,发现没有进行任何比较操作直接就返回了,仔细查看判断条件发现是由于运算符优先级出错导致判断条件永为假,被编译器优化成直接返回了。。
![[Pasted image 20231121141716.png]]
修改判断条件后,成功得到结果:
![[sys2lab4spikefinish.png]]
思考题¶
1. 通过查看 RISC-V Privileged Spec
中的 medeleg
和 mideleg
解释上面 MIDELEG
值的含义,如果实验中mideleg没有设定为正确的值结果会怎么样呢?¶
![[Pasted image 20231121143707.png]]
它们表示是否将对应位上的 trap 交给 S 模式来处理,如果对应位为 1 则代理给 S,否则由机器模式处理。位位置的索引与 mcause 寄存器返回的值相等。
- medeleg: Machine exception delegation register。设置值为 0xb109,第 8 位为 1,对应中断 User external interrupt。控制将特定类型的异常委派给更低特权模式的处理程序。每个位对应于一种异常类型,并与 scause 寄存器中的定义相对应。如果某一位被设置为 1,表示对应的异常类型将被委派给较低特权模式的异常处理程序处理。
- mideleg: Machine interrupt delegation register。用于控制将特定类型的中断委派给更低特权模式的处理程序。每个位对应于一种中断类型,并与 mcause 寄存器中的定义相对应。如果某一位被设置为 1,表示对应的中断类型将被委派给较低特权模式的中断处理程序处理。STIP interrupt delegation control is located in bit 5。设置值为 0x222,第 5 位为 1,对应中断 Supervisor timer interrupt。
![[Pasted image 20231121153848.png]]
如果没有设置为正确的值,则无法正确处理中断和异常。使用 gdb 在运行中将这两个寄存器的值修改成 0,则无法正确处理:
![[Pasted image 20231121144434.png]]
2. 机器启动后 time、cycle 寄存器分别是从 0 开始计时的吗?¶
从 spike 中查看,time 和 cycle 都不是从 0 开始计时的。
![[Pasted image 20231121155954.png]]
在 QEMU 中查看,报错 Could not fetch register "time"; remote failure reply 'E14' 提交了 issue,得到由于版本问题不支持查看。
![[Pasted image 20231121160336.png]]
查手册,得知 time CSR 寄存器表示一个实时计数器,它从过去的任意起始时间开始计算经过的实时时间。它提供了执行环境中经过的真实时间。所以,time 寄存器的起始值不应该是 0。
![[Pasted image 20231126164412.png]]
![[Pasted image 20231126165836.png]]
3. 谈谈如何在一台不支持乘除法指令扩展的处理器上执行乘除法指令¶
在不支持乘除法指令扩展的处理器上,如果遇到乘除法指令,会触发 Illegal Instruction 异常(对应值为 2),进而进入 trap handler。在这种情况下,非法指令的编码会被存储到 mtval 寄存器(或 stval 寄存器)中。可以在 trap handler 中,读取 mtval(或 stval)中的非法指令编码,并根据非法指令编码进行乘除法运算,获得正确的结果。
![[Pasted image 20231121162948.png]]