OS Lab1 实验报告
实现功能
本次实验中我主要新增了一个系统调用 sys_task_info
,用于查询当前正在执行的任务信息。
具体而言,我在进程结构 proc
中新增了两个字段 syscall_times
和 start_time
,分别用于记录系统调用次数和进程首次分配的起始时钟周期。每次进入 syscall
异常处理函数会根据 id
对系统调用计数,在 sys_task_info
中通过当前 get_cycle()
与进程的 start_time
计算出任务运行时间。
问答题
第 1 题
通过 make user CHAPTER=2_bad
和 make 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 | case IllegalInstruction: |
表示当前执行了非法指令,即尝试执行 S 态指令或访问 S 态寄存器。
第 2 题
a0
是TRAPFRAME
地址,a1
是用户页表地址。sfence
指令用于清空 TLB 缓存,在从内核页表切换回用户页表后也需要刷新相应缓存。但由于当前虚存机制尚未启用,当前章节中删掉该指令不会出错。- 因为当前
a0
中存储的TRAPFRAME
地址还需要使用,不能立刻更新回TRAPFRAME
中保存的原来的a0
值,否则后续的寄存器都无法从TRAPFRAME
中恢复。在当前a0
指向TRAPFRAME
基址的情况下,112(a0)
对应的地址存储的是待恢复的a0
值,现在这个a0
值已经暂存到sscratch
寄存器中了。 sret
后从 S 态切换回 U 态。在userret
前的usertrapret
函数中已经设置了sepc
和sstatus
寄存器,sepc
中存有处理完毕中断异常之后需要返回的用户程序的 PC 值,sstatus
将spp
清零,表示用于返回 U 态;这样,在sret
后,程序将从sepc
中恢复用户程序 PC 值,并从 S 态切换回 U 态继续执行。- 这条
csrrw
读写指令将sscratch
中的值写入a0
,将a0
中的值写入sscratch
,相当于实现a0
和sscratch
之间的交换。交换后,a0
中是用户程序的TRAPFRAME
地址,sscratch
中是用户程序的a0
值。 - 从
TRAPFRAME
的第 6 项,即ra
寄存器开始保存;因为前几项主要与内核相关,均在第一次执行usertrapret
进入 U 态之前设置(除了epc
项表示用户程序当前 PC,在sepc
寄存器,也需要存到TRAPFRAME
):这里反而是要从1
2
3
4
5
6struct 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
7ld sp, 8(a0)
ld tp, 32(a0)
ld t1, 0(a0)
# csrw satp, t1
# sfence.vma zero, zero
ld t0, 16(a0)
jr t0a0
的值现在在sscratch
中暂存,在存完所有其他寄存器的值后,最后会将sscratch
中的值写入TRAPFRAME
中相应位置,即:1
2csrr t0, sscratch
sd t0, 112(a0) - 进入 S 态是在
ecall
指令发生,执行ecall
时,会从 U 态进入 S 态,并跳转到stvec
保存的地址,该寄存器在执行trap_init
函数时被设置为uservec
:1
2
3
4void trap_init(void)
{
w_stvec((uint64)uservec & ~0x3);
} - 此时
t0
中的值是TRAPFRAME
中kernel_trap
项,正如第 7 问中说明的,这一项是在第一次执行usertrapret
进入 U 态之前设置为usertrap
函数的地址,执行完uservec
后将跳转到usertrap
进行异常处理。