traps源码阅读


这部分的文件主要是trampoline.Skernelvec.Strap.c


trampoline.S

uservec

这是一个汇编代码文件,在xv6中负责处理用户态和内核态的切换。

.section trampsec  // 声明代码位于trampsec段
.globl trampoline  // 声明trampoline代码起始地址
.globl usertrap    // 声明trap处理函数
trampoline:        // trampoline代码起始标签
.align 4           // 对齐

首先是一些声明。

.globl uservec     // 声明用户态trap入口

然后是uservec代码,它在用户态到内核态切换时保存寄存器状态并设置内核环境。trap.c会设置STVEC寄存器指向这里,因此用户空间中的陷阱触发后从这里开始,使用用户页表,但是在supervisor模式下。

csrw sscratch, a0  // 将用户a0寄存器值暂存到sscratch寄存器
li a0, TRAPFRAME   // 加载TRAPFRAME虚拟地址到a0

// 下面这些里面只少了112,因为它是专门用来存原始a0值
sd ..., ...(a0)    // 保存用户寄存器到trapframe

csrr t0, sscratch  // 恢复用户原始a0值
sd t0, 112(a0)     // 存入p->trapframe->a0

ld sp, 8(a0)       // 加载内核栈指针,从p->trapframe->kernel_sp
ld tp, 32(a0)      // 设置当前hartid,从p->trapframe->kernel_hartid
ld t0, 16(a0)      // 加载usertrap函数地址,从trapframe->kernel_trap
ld t1, 0(a0)       // 获取内核页表地址,从trapframe->kernel_satp

a0前面的数字是trapframe结构体中各字段的内存偏移量,trapframe的定义在proc.h中。

然后sfence.vma zero, zero是内存屏障指令,会保证之前的内存操作完成。

这时sfence.vma zero zero会刷新TLB,清除旧页表缓存。

然后jr t0跳转到usertrap函数。

userret

trap.c中的usertrapret调用,负责从内核态回到用户态。

先切换回用户页表。

这里的a0是内核a0而非前面的用户a0,用户a0在先前被修改,但这里的内核a0是内核准备的参数值。

然后加载TRAPFRAME虚拟地址,保存寄存器等。

最后会把重新存回用户a0寄存器的值,并返回用户空间。


kernelvec.S

kernelvec是内核陷阱的入口,处理来自内核代码中的中断或异常。

它先分配256字节栈空间来保存寄存器的值。然后通过call kerneltrap调用C处理函数,处理完成后再恢复寄存器状态。


trap.c

trapinithart

trapinithart是内核陷阱处理的核心初始化函数。

它通过w_stvec设置陷阱向量基址寄存器,将内核陷阱处理程序入口地址设置为kernelvec

kerneltrap

这是处理内核空间陷阱的核心函数。

首先保存关键寄存器的值,然后验证是否来自supervisor mode并检查中断是否已经禁用,再尝试用devintr处理中断。若是定时器中断且进程存在,则调用yield触发进程调度。最后恢复寄存器的值。

usertrap

这是处理用户空间陷阱的核心函数。

首先检查确认来自用户模式,然后设置入口地址,后续若触发中断将交由内核陷阱处理程序来处理。

然后保存用户程序计数器:

再根据scause寄存器的值来确定不同的处理方法:

当定时器中断则触发yield让出CPU:

最后调用usertrapret返回用户空间。

usertrapret

这个函数用于返回用户空间。

首先intr_off关闭中断,然后重新设置用户陷阱处理入口:

并重新配置trapframesstatus寄存器:

返回地址使用之前保存的:

然后切换页表,重新跳转到用户空间。

其他

devintrclockintr涉及中断,后面再补充。