6.s081 xv6-Trap实现
xv6-源码解析-Trap
为了实现安全和隔离,操作系统分为了用户态和内核态
trap 主要描述了发生中断时,用户态到内核态的切换,以及执行对应的中断处理程序之后,返回到用户态之间的一系列操作。
几个关键的寄存器
PC 指向当前指令的地址(用户态和内核态不同)
SATP 指向pageTable(内核或用户进程)
STVEC 指向内核处理指令的起始地址,trampoline的uservec函数的起始位置
SEPC 进入内核后保存用户态的PC值
SSRATCH 用来和另外一个寄存器值做交换
Trap过程

ecall
ecall 是一条指令,做以下事情,是进入内核的入口
-
user模式切换到supervisor模式(修改寄存器)
- 把当前PC保存在SEPC,PC切换成trampoline的代码开始地址(保存在STEVC中的),从内核回来时,使用SEPC恢复用户态的代码位置
- 跳转进PC(trampoline)的代码
trampoline
目的是为了用户态切换内核态,内核切换用户态之间,保存两者其一的上下文信息(pc,寄存器,页表等)
每一个用户进程都有(page table里有映射),与内核 page table的trampoline的映射完全一样,用户与内核页表切换是在trampoline完成的,所以切换前后不会发生异常。PTE_U=0,用户代码不能写,保证了trap过程的安全
uservec
trampoline需要调用uservec函数做以下事情
-
获取trapframe,trapframe地址保存在SSRATCH 中的,会和a0做一个交换
-
使用a0的偏移,一个一个保存用户态的31个寄存器的值到trapframe页上(每个进程都有一个trapframe),保存用户经常的a0变量
-
从trapframe恢复内核栈到sp,恢复usertrap函数的指针到t0,恢复内核页表到t1
-
跳转到t0(也就是执行内核的usertrap函数)
uservec:
# 1.获取trapframe地址
csrw sscratch, a0
# each process has a separate p->trapframe memory area,
# but it's mapped to the same virtual address
# (TRAPFRAME) in every process's user page table.
li a0, TRAPFRAME
# 2. save the user registers in TRAPFRAME
sd ra, 40(a0)
sd sp, 48(a0)
# .... 省略一些保存的寄存
sd t6, 280(a0)
#3. 恢复内核寄存器
# initialize kernel stack pointer, from p->trapframe->kernel_sp
ld sp, 8(a0)
# 内核cpuid, from p->trapframe->kernel_hartid
ld tp, 32(a0)
# 内核 usertrap() 地址, from p->trapframe->kernel_trap
ld t0, 16(a0)
# 恢复内核页表, from p->trapframe->kernel_satp.
ld t1, 0(a0)
csrw satp, t1
# 4.跳转 usertrap()
jr t0
usertrap
根据cause执行不同到trap处理,然后调用usertrapret()返回
void
usertrap(void)
{
// send interrupts and exceptions to kerneltrap(),
// since we're now in the kernel.
w_stvec((uint64)kernelvec);
struct proc *p = myproc();
// 保存第32个寄存器到trapframe
p->trapframe->epc = r_sepc();
if(r_scause() == 8){
// system call
if(killed(p))
exit(-1);
// sepc points to the ecall instruction,
// but we want to return to the next instruction.
p->trapframe->epc += 4;
// an interrupt will change sepc, scause, and sstatus,
// so enable only now that we're done with those registers.
intr_on();
syscall();
} else if((which_dev = devintr()) != 0){
// ok
} else {
printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid);
printf(" sepc=%p stval=%p\n", r_sepc(), r_stval());
setkilled(p);
}
if(killed(p))
exit(-1);
// give up the CPU if this is a timer interrupt.
if(which_dev == 2)
yield();
//返回
usertrapret();
}
usertrapret
- 重新设置stvec到 tramoline的uservec指令处
- 保存内核的sp栈指针,usertrap函数地址,satp页表寄存器到trampoline
- 为了返回用户空间,设置sstatus寄存器,控制sret行为
- 从trampoline的sepc 恢复用户的 pc,恢复satp为用户页表
- 跳转到userret
void
usertrapret(void)
{
struct proc *p = myproc();
//关中断
//防止此时又重新进入了中断处理程序,导致内核出错
intr_off();
// 1. stvec 保存 trampoline的uservec函数地址
uint64 trampoline_uservec = TRAMPOLINE + (uservec - trampoline);
w_stvec(trampoline_uservec);
//2. 保存内核寄存器到trapframe,用来下一次用户到内核的切换,恢复内核
p->trapframe->kernel_satp = r_satp(); // kernel page table
p->trapframe->kernel_sp = p->kstack + PGSIZE; // process's kernel stack
p->trapframe->kernel_trap = (uint64)usertrap;
p->trapframe->kernel_hartid = r_tp(); // hartid for cpuid()
// set up the registers that trampoline.S's sret will use
// to get to user space.
//3. SPP 控制 sret的行为,保证正常回到用户态
unsigned long x = r_sstatus();
x &= ~SSTATUS_SPP; // clear SPP to 0 for user mode
x |= SSTATUS_SPIE; // enable interrupts in user mode
w_sstatus(x);
//4. sepc 设置为用户态之前的pc值
w_sepc(p->trapframe->epc);
// 恢复用户页表指针
uint64 satp = MAKE_SATP(p->pagetable);
//回到trampoline的userret
// jump to userret in trampoline的.S at the top of memory, which
// switches to the user page table, restores user registers,
// and switches to user mode with sret.
//5.计算出userret函数地址的,再调用,相当于userret(satp);
uint64 trampoline_userret = TRAMPOLINE + (userret - trampoline);
((void (*)(uint64))trampoline_userret)(satp);
}
userret
与trampoline的uservec函数做的事情相反
- 恢复用户态页表设置satp
- 恢复用户态寄存器
- 跳转回用户态之前的pc所指的程序
userret:
# userret(pagetable)
# called by usertrapret() in trap.c to
# switch from kernel to user.
# a0: user page table, for satp.
#1. switch to the user page table.
csrw satp, a0
li a0, TRAPFRAME
# 2. restore all but a0 from TRAPFRAME
ld ra, 40(a0)
ld sp, 48(a0)
ld gp, 56(a0)
#......从trapframe恢复用户态的寄存器
ld t5, 272(a0)
ld t6, 280(a0)
# restore user a0
ld a0, 112(a0)
#3. return to user mode and user pc.
# usertrapret() set up sstatus and sepc.
sret