OS Lab1 Report

OS Lab1 实验报告

实现功能

本次实验中我主要新增了一个系统调用 sys_task_info,用于查询当前正在执行的任务信息。

具体而言,我在进程结构 proc 中新增了两个字段 syscall_timesstart_time,分别用于记录系统调用次数和进程首次分配的起始时钟周期。每次进入 syscall 异常处理函数会根据 id 对系统调用计数,在 sys_task_info 中通过当前 get_cycle() 与进程的 start_time 计算出任务运行时间。

问答题

第 1 题

通过 make user CHAPTER=2_badmake run 编译并测试 U 态下访问错误地址、执行 S 态特权指令、访问 S 态寄存器等出错行为。使用的 SBI 版本如下:

1
[rustsbi] RustSBI version 0.3.0-alpha.2, adapting to RISC-V SBI v1.0.0

第一个测例 __ch2_bad_address.c 尝试写入无效内存,报错如下:

1
[ERROR 1]unknown trap: 0x0000000000000007, stval = 0x0000000000000000

查询资料后知道,异常码 0x0000000000000007 正表示 Store/AMO access fault,即尝试写入无效地址:

此时 stval 寄存器中存储的是导致异常的虚拟地址, 0x0000000000000000 正是当前访问的无效地址。

第二个测例 __ch2_bad_instruction.c 尝试执行 S 态指令 rset,第三个测例 __ch2_bad_register.c 尝试访问 S 态寄存器 sstatus,报错均如下:

1
IllegalInstruction in application, core dumped.

触发位置为 usertrap 异常处理函数中的下列位置:

1
2
3
4
case IllegalInstruction:
printf("IllegalInstruction in application, core dumped.\n");
exit(-3);
break;

表示当前执行了非法指令,即尝试执行 S 态指令或访问 S 态寄存器。

第 2 题

  1. a0TRAPFRAME 地址, a1 是用户页表地址。
  2. sfence 指令用于清空 TLB 缓存,在从内核页表切换回用户页表后也需要刷新相应缓存。但由于当前虚存机制尚未启用,当前章节中删掉该指令不会出错。
  3. 因为当前 a0 中存储的 TRAPFRAME 地址还需要使用,不能立刻更新回 TRAPFRAME 中保存的原来的 a0 值,否则后续的寄存器都无法从 TRAPFRAME 中恢复。在当前 a0 指向 TRAPFRAME 基址的情况下, 112(a0) 对应的地址存储的是待恢复的 a0 值,现在这个 a0 值已经暂存到 sscratch 寄存器中了。
  4. sret 后从 S 态切换回 U 态。在 userret 前的 usertrapret 函数中已经设置了 sepcsstatus 寄存器,sepc 中存有处理完毕中断异常之后需要返回的用户程序的 PC 值,sstatusspp 清零,表示用于返回 U 态;这样,在 sret 后,程序将从 sepc 中恢复用户程序 PC 值,并从 S 态切换回 U 态继续执行。
  5. 这条 csrrw 读写指令将 sscratch 中的值写入 a0,将 a0 中的值写入 sscratch,相当于实现 a0sscratch 之间的交换。交换后,a0 中是用户程序的 TRAPFRAME 地址, sscratch 中是用户程序的 a0 值。
  6. TRAPFRAME 的第 6 项,即 ra 寄存器开始保存;因为前几项主要与内核相关,均在第一次执行 usertrapret 进入 U 态之前设置(除了 epc 项表示用户程序当前 PC,在 sepc 寄存器,也需要存到 TRAPFRAME):
    1
    2
    3
    4
    5
    6
       struct trapframe *trapframe = curr_proc()->trapframe;
    trapframe->kernel_satp = r_satp(); // kernel page table
    trapframe->kernel_sp =
    curr_proc()->kstack + PGSIZE; // process's kernel stack
    trapframe->kernel_trap = (uint64)usertrap;
    trapframe->kernel_hartid = r_tp(); // hartid for cpuid()
    这里反而是要从 TRAPFRAME 中恢复出这些值从而将处理权转移到内核,即 uservec 中最后执行的一段指令,恢复内核栈并跳转到 usertrap 处理函数:
    1
    2
    3
    4
    5
    6
    7
    ld sp, 8(a0)
    ld tp, 32(a0)
    ld t1, 0(a0)
    # csrw satp, t1
    # sfence.vma zero, zero
    ld t0, 16(a0)
    jr t0
    而前面主要是保存用户程序执行时的寄存器上下文;不是,用户程序 a0 的值现在在 sscratch 中暂存,在存完所有其他寄存器的值后,最后会将 sscratch 中的值写入 TRAPFRAME 中相应位置,即:
    1
    2
    csrr t0, sscratch
    sd t0, 112(a0)
  7. 进入 S 态是在 ecall 指令发生,执行 ecall 时,会从 U 态进入 S 态,并跳转到 stvec 保存的地址,该寄存器在执行 trap_init 函数时被设置为 uservec
    1
    2
    3
    4
    void trap_init(void)
    {
    w_stvec((uint64)uservec & ~0x3);
    }
  8. 此时 t0 中的值是 TRAPFRAMEkernel_trap 项,正如第 7 问中说明的,这一项是在第一次执行 usertrapret 进入 U 态之前设置为 usertrap 函数的地址,执行完 uservec 后将跳转到 usertrap 进行异常处理。