跳转至

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 开启异常处理

  1. 设置 stvec, 将 _traps_trap 在 4.3 中实现)所表示的地址写入 stvec,这里我们采用 Direct 模式, 而 _traps 则是中断处理入口函数的基地址。
  2. 开启时钟中断,将 sie[STIE] 置 1。即将 SIE 寄存器的第五位置 1。

![[Pasted image 20231125201904.png]]

  1. 设置第一次时钟中断,参考 clock_set_next_event()clock_set_next_event() 在 4.5 中介绍)中的逻辑用汇编实现。
  2. 开启 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

  1. 运行 make install
  2. 根据报错,修改 Makefile,将 CROSS_COMPILE=riscv64-linux-unknown-gnu- 修改为 CROSS_COMPILE=riscv64-linux-gnu-
  3. 手动 git clone 仓库 openocd 及其子仓库 jmltcl。
  4. 查看 README 编译。在openocd 中./bootstrap 产生 configure,./configure 生成 Makefile,然后 make 运行。jmltcl 中同样。
  5. 再次 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]]