Trap(陷⼊中断)源码解析
⽤户空间和内核空间之间的切换通常称为trap
trap的三种形式
1. 系统调⽤引发
2. 异常发⽣
3. 设备中断 (时间中断、IO中断、⽹络中断等)
supervi mode的权限
⽤户态和内核态之间的到底有什么区别?其实区别很⼩:
其中的⼀件事情是,你现在可以读写控制寄存器了。⽐如说,当你在supervisor mode时,你可以:读写SATP寄存器,也就是page table的指针;STVEC,也就是处理trap的内核指令地址;SEPC,保存当发⽣trap时的程序计数器;SSCRATCH等等。在supervisor mode你可以读写这些寄存器,⽽⽤户代码不能做这样的操作。
另⼀件事情supervisor mode可以做的是,它可以使⽤PTE_U标志位为0的PTE(页表项详见)。当PTE_U标志位为1的时候,表明⽤户代码可以使⽤这个页表;如果这个标志位为0,则只有supervisor mode可以使⽤这个页表。
这也是supervi mode唯⼆可以做的事情了。
ecall指令
系统使⽤ecall指令来执⾏系统调⽤
第⼀,ecall将代码从ur mode改到supervisor mode
第⼆,ecall将程序计数器的值保存在了SEPC寄存器。
第三,ecall会跳转到STVEC寄存器指向的指令即trampoline.S
trap的全过程
以为例。
⾸先,⽤户程序中调⽤exec函数,编译器解析exec函数时,并不会调⽤其源码,⽽是将exec系统调⽤
的编号写⼊A7寄存器中,执⾏ecall指令,ecall指令将跳转到trampoline.S。
系统从⽤户态进⼊了内核态,但⽬前页表依旧是ur page table。trampoline.S主要是使⽤(附录部分)保存了系统的状态以及⽤户寄存器,⽤于恢复⽤户进程,通过这个操作也实现⽤户进程对于进程切换的⽆感知。在将需要存储的状态存⼊ur page table后,系统将切换内核页表,执⾏相应的内核代码,即urtrap函数。
urtrap根据trap的不同情况(scau寄存器中存储着中断原因),调⽤相应的处理函数。
代码解析
substantial翻译
下⾯的代码是操作系统的源代码,⾥⾯包含了很多的细节,你需要⾥了解的是整个流程、体系,不要过多的关注细节
trap的保存流程
1. ⾸先,我们需要保存32个⽤户寄存器。因为很显然我们需要恢复⽤户应⽤程序的执⾏,尤其是当⽤户程序随机的被设备中断所打断
时。(存储⽤户寄存器)
2. 程序计数器也需要在某个地⽅保存,它⼏乎跟⼀个⽤户寄存器的地位是⼀样的,我们需要能够在⽤户程序运⾏中断的位置继续执⾏⽤
户程序。(存储⼀些系统寄存器如PC)
3. 我们需要将mode改成supervisor mode,因为我们想要使⽤内核中的各种各样的特权指令。(修改运⾏模式)
4. SATP(Supervisor Address Translation and Protection,存储了根页表的地址)寄存器现在正指向ur page table,⽽ur page table只
包含了⽤户程序所需要的内存映射和⼀两个其他的映射,它并没有包含整个内核数据的内存映射。所以在运⾏内核代码之前,我们需要将SATP指向kernel page table。(修改SATP,获得内核的页表)
5. 我们需要将堆栈寄存器指向位于内核的⼀个地址,因为我们需要⼀个堆栈来调⽤内核的C函数。(转换到内核堆栈)
6. ⼀旦我们设置好了,并且所有的硬件状态都适合在内核中使⽤,我们需要跳⼊内核的C代码urtrap函数。
trampoline.S
#
# code to switch between ur and kernel space.
#
# this code is mapped at the same virtual address
# (TRAMPOLINE) in ur and kernel space so that
# it continues to work when it switches page tables.
#
# kernel.ld caus this to be aligned
# to a page boundary.
#
.ction trampc
dateofissue.
globl trampoline
trampoline:
.align 4
.globl urvec
//trap状态保存
urvec:
# trap.c ts stvec to point here, so
# traps from ur space start here,
# in supervisor mode, but with a
warspite
# ur page table.
#
# sscratch points to where the process's p->trapframe is
# mapped into ur space, at TRAPFRAME.
#
# swap a0 and sscratch
# so that a0 is TRAPFRAME
csrrw a0, sscratch, a0
# save the ur registers in TRAPFRAME
sd ra, 40(a0)
sd sp, 48(a0)
sd gp, 56(a0)
sd tp, 64(a0)
sd t0, 72(a0)
sd t1, 80(a0)
sd t2, 88(a0)
sd s0, 96(a0)
sd s1, 104(a0)
sd a1, 120(a0)
sd a2, 128(a0)
sd a3, 136(a0)
sd a4, 144(a0)
sd a5, 152(a0)
sd a6, 160(a0)
sd a7, 168(a0)
sd s2, 176(a0)
sd s3, 184(a0)
sd s4, 192(a0)
sd s5, 200(a0)
sd s6, 208(a0)
sd s7, 216(a0)
sd s8, 224(a0)
sd s9, 232(a0)
sd s10, 240(a0)
sd s11, 248(a0)
sd t3, 256(a0)
sd t4, 264(a0)
sd t5, 272(a0)
sd t6, 280(a0)
# save the ur a0 in p->trapframe->a0
csrr t0, sscratch
sd t0, 112(a0)
# restore kernel stack pointer from p->trapframe->kernel_sp
ld sp, 8(a0)
# make tp hold the current hartid, from p->trapframe->kernel_hartid ld tp, 32(a0)
# load the address of urtrap(), p->trapframe->kernel_trap
ld t0, 16(a0)
# restore kernel page table from p->trapframe->kernel_satp
ld t1, 0(a0)
csrw satp, t1
sfence.vma zero, zero
# a0 is no longer valid, since the kernel page
# table does not specially map p->tf.
# jump to urtrap(), which does not return
jr t0
//trap状态恢复
.globl urret
urret:
# urret(TRAPFRAME, pagetable)
# switch from kernel to ur.
# urtrapret() calls here.
# a0: TRAPFRAME, in ur page table.
# a1: ur page table, for satp.
# switch to the ur page table.
csrw satp, a1
sfence.vma zero, zero
choice
# put the saved ur a0 in sscratch, so we
# can swap it with our a0 (TRAPFRAME) in the last step.
ld t0, 112(a0)
csrw sscratch, t0
# restore all but a0 from TRAPFRAME
ld ra, 40(a0)
ld sp, 48(a0)
ld gp, 56(a0)
ld tp, 64(a0)
ld t0, 72(a0)
ld t1, 80(a0)
ld t2, 88(a0)
ld s0, 96(a0)mmb
胶囊公寓ld s1, 104(a0)
ld a1, 120(a0)
ld a2, 128(a0)
ld a3, 136(a0)
ld a4, 144(a0)
ld a5, 152(a0)
ld a6, 160(a0)
ld a7, 168(a0)
ld s2, 176(a0)
ld s3, 184(a0)
ld s4, 192(a0)
ld s5, 200(a0)
ld s6, 208(a0)
ld s7, 216(a0)
ld s8, 224(a0)
ld s9, 232(a0)
ld s10, 240(a0)
ld s11, 248(a0)
ld t3, 256(a0)
ld t4, 264(a0)
ld t5, 272(a0)
ld t6, 280(a0)
# restore ur a0, and save TRAPFRAME in sscratch
csrrw a0, sscratch, a0
# return to ur mode and ur pc.
dustpan# urtrapret() t up sstatus and pc.
sret
⽤户trap处理程序
void
urtrap(void)
{
int which_dev = 0;
//判断系统mode状态
if((r_sstatus() & SSTATUS_SPP) != 0)
panic("urtrap: not from ur mode");
// nd interrupts and exceptions to kerneltrap(),
// since we're now in the kernel.
// 将中断处理程序设置为kernelvec
// 即将kernelvec函数地址写⼊到stvec寄存器
// 和ur model下的中断对应,kernelvec⽤来处理内核态下的中断 // 两个流程很相似,kernelvec更简单⼀些,就不做解释了
w_stvec((uint64)kernelvec);
struct proc *p = myproc();
//保存⽤户的PC值
// save ur program counter.
p->trapframe->epc = r_pc();
//根据scau寄存器的值判断异常原因
if(r_scau() == 8){
// system call
if(p->killed)
exit(-1);
// pc points to the ecall instruction,
// but we want to return to the next instruction.
// 设置返回位置
p->trapframe->epc += 4;
// an interrupt will change sstatus &c registers,
/
/ so don't enable until done with tho registers.
// 寄存器保存完成,开中断
intr_on();
// 执⾏系统调⽤
syscall();
} el if((which_dev = devintr()) != 0){
// 处理设备中断
// ok
} el {
printf("urtrap(): unexpected scau %p pid=%d\n", r_scau(), p->pid); printf(" pc=%p stval=%p\n", r_pc(), r_stval());
p->killed = 1;
}
if(p->killed)
exit(-1);
// give up the CPU if this is a timer interrupt.
if(which_dev == 2)
yield();
//trap处理完毕,恢复⽤户进程状态
urtrapret();
}
附:
系统调⽤函数 (根据A7寄存器存储的内容,调⽤不同的函数指针) extern uint64 sys_chdir(void);
extern uint64 sys_clo(void);
choo过去分词
extern uint64 sys_dup(void);
extern uint64 sys_exec(void);
extern uint64 sys_exit(void);
extern uint64 sys_fork(void);
extern uint64 sys_fstat(void);
extern uint64 sys_getpid(void);
extern uint64 sys_kill(void);
extern uint64 sys_link(void);
extern uint64 sys_mkdir(void);
extern uint64 sys_mknod(void);
extern uint64 sys_open(void);
extern uint64 sys_pipe(void);
extern uint64 sys_read(void);
extern uint64 sys_sbrk(void);
extern uint64 sys_sleep(void);
extern uint64 sys_unlink(void);
extern uint64 sys_wait(void);
extern uint64 sys_write(void);
extern uint64 sys_uptime(void);
static uint64 (*syscalls[])(void) = {
[SYS_fork] sys_fork,
[SYS_exit] sys_exit,
[SYS_wait] sys_wait,
[SYS_pipe] sys_pipe,
[SYS_read] sys_read,
[SYS_kill] sys_kill,
高考必备物品[SYS_exec] sys_exec,
[SYS_fstat] sys_fstat,
[SYS_chdir] sys_chdir,
[SYS_dup] sys_dup,
[SYS_getpid] sys_getpid,
[SYS_sbrk] sys_sbrk,
[SYS_sleep] sys_sleep,
[SYS_uptime] sys_uptime,
[SYS_open] sys_open,
[SYS_write] sys_write,
[SYS_mknod] sys_mknod,
[SYS_unlink] sys_unlink,
[SYS_link] sys_link,
[SYS_mkdir] sys_mkdir,jap
[SYS_clo] sys_clo,
};
void
syscall(void)
{
int num;
struct proc *p = myproc();
num = p->trapframe->a7;
if(num > 0 && num < NELEM(syscalls) && syscalls[num]) { p->trapframe->a0 = syscalls[num]();
} el {
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}