3.系统调用跳转流程
系统调用跳转流程
以write()系统调用为例
1. 用户态
- 
调用 write()
- 
跳转到 usys.s\write#include "kernel/syscall.h" write: li a7, SYS_write ecall retSYS_write的定义在kernel/syscall.h中#define SYS_write 16将 SYS_write的索引16放到a7寄存器中执行 ecall,跳转到STVEC指向的地址!!! STVEC涉及到中断相关知识,后续补齐!!!??? STVEC中的地址在哪里设置的此处 STVEC指向trampoline.S\uservec()
Q1.ECALL功能?
- 从user mode切换到supervisor mode
- 将pc的值保存到SEPC寄存器
- 跳转到STVEC指向的地址
ecall的功能十分简单,主要是为了提升软件设计的灵活性,但此时ecall做的事远远不够,对于xv6,我们还需要完成
- 保存31个用户寄存器的内容,之后用来恢复代码运行状态
- ecall不切换- page table,因此此时还在使用- user page table,需要切换到- kernel page table。
- 需要创建或者找到一个kernel stack,并将Stack Pointer寄存器的内容指向这个kernel stack。代码需要使用栈执行程序。
- 需要跳转到内核代码的某些合理的位置。
使用ECALL指令时,将系统调用类型存在a7寄存器,参数存在a0-a5寄存器
2 trampoline.s\uservec()
保存32个通用寄存器
uservec:
	# 需要保存所有寄存器的值,之后需要恢复执行
	# 此时所有通用寄存器都不可修改
  
  # 将`a0`存储到`sscratch`,这样`a0`就可以修改了
  csrw sscratch, a0
  
  # 将`TRAPFRAME`放到`a0`中,给后续存储提供基地址
  li a0, TRAPFRAME
  
  # 保存其他通用寄存器
  sd ra, 40(a0)
  sd sp, 48(a0)
  sd gp, 56(a0)
  sd tp, 64(a0)
  ....
	# 将之前保存在`sscratch`中的`a0`取出并保存
  csrr t0, sscratch
  sd t0, 112(a0)
  # 加载`TRAPFRAME`中的`kernel_sp`到`stack pointer`寄存器中
  ld sp, 8(a0)
  # 加载`kernel_hartid`(当前CPU编号)到`tp`寄存器
  ld tp, 32(a0)
  # 加载`usertrap()`地址到`t0`寄存器
  ld t0, 16(a0)
  # 加载`kernel page table`到`t1`寄存器中
  ld t1, 0(a0)
  # 等待之前内存访问完成,清空tle
  sfence.vma zero, zero
  # 加载`t1`到`satp`,页表转换为内核页表
  csrw satp, t1
  
  sfence.vma zero, zero
	# jump to usertrap(), which does not return
	jr t0
??? t0在哪里设置的
3 trap.c\usertrap()
确定trap类型并进行处理
void usertrap(void) {
  int which_dev = 0;
	
  // 获取`sstatus`寄存器的值,`SPP`位指示`trap`是来自
  // 用户模式还是管理模式
  if((r_sstatus() & SSTATUS_SPP) != 0)
    panic("usertrap: not from user mode");
  // 令`stvec`指向`kernelvec`而不是之前的`uservec`
  // 在kernel中,trap处理不必使用user模式的逻辑
  w_stvec((uint64)kernelvec);
	
  // myproc()根据之前存储的hart_id值,查询进程数组
  // 获取当前进程信息
  struct proc *p = myproc();
  
  // 将`ecall`指令存储在`sepc`中的`pc`值保存到`trapframe`中
  p->trapframe->epc = r_sepc();
  
  //获取`scause`的值,判断trap类型
  if(r_scause() == 8){
    // system call
    // 判断当前进程是否被杀掉
    if(killed(p))
      exit(-1);
    // pc存储的是系统调用的地址,返回时需指向下一条指令
    p->trapframe->epc += 4;
    // 修改`sstatus`的SIE位,打开中断,trap执行过程中可响应中断
    intr_on();
		// 根据之前存储在`p->trapframe->a7`中的值找到系统调用并执行
    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();
}
Q1 为什么需要存储sepc中的pc值?
当前程序运行时,CPU可能切换到别的进程,而别的进程如果也进行系统调用,sepc寄存器就会被修改,这个操作可以放到之前的trampoline.s\uservec()中,逻辑更统一。
?scause是何时被设置的
ecall指令用于向运行时环境发出请求,如系统调用,因此应该是ecall设置的
4.1.4 trap.c\usertrapret()
void usertrapret(void)
{
  struct proc *p = myproc();
  // 关闭中断,因为接下来会修改`stvec`指向uservec,此时如果发生中断,程序会跳转到用户trap处理代码,容易出错
  intr_off();
  // 设置STVEC指向uservec
  uint64 trampoline_uservec = TRAMPOLINE + (uservec - trampoline);
  w_stvec(trampoline_uservec);
  // 将一些内核信息存储到trapframe中方便下次使用
  p->trapframe->kernel_satp = r_satp();
  p->trapframe->kernel_sp = p->kstack + PGSIZE;
  p->trapframe->kernel_trap = (uint64)usertrap;
  p->trapframe->kernel_hartid = r_tp();
  unsigned long x = r_sstatus();
  
  //SPP位和SPIE位都会影响sret指令的行为
  
  // SPP为0表示下次执行sret的时候,返回user mode
  // 而不是supervisor mode
  x &= ~SSTATUS_SPP;
  
  //SPIE位控制在执行完sret之后,是否打开中断。因为我们希望打开中断
  //所以这里将SPIE bit位设置为1
  x |= SSTATUS_SPIE; // enable interrupts in user mode
  w_sstatus(x);
  // 将之前存储的sepc值写回
  w_sepc(p->trapframe->epc);
  // 根据user page table地址生成相应的SATP值
  uint64 satp = MAKE_SATP(p->pagetable);
  // 我们会在汇编代码trampoline.S\userret()中完成page table
  // 的切换,切换只能在trampoline中完成,因为只有trampoline中
  // 的代码是同时在用户和内核空间中映射的
  
  // 求出trampoline.S\userret()的地址
  uint64 trampoline_userret = TRAMPOLINE + (userret - trampoline);
  // satp会作为第二个参数传给userret()
  ((void (*)(uint64))trampoline_userret)(satp);
}
4.1.5 trampoline.S\userret()
userret:
	# a0: user page table, for satp.
	
  # switch to the user page table.
  sfence.vma zero, zero
  csrw satp, a0
  sfence.vma zero, zero
	# a0指向TRAPFRAME作为基地址
  li a0, TRAPFRAME
  # restore all but a0 from TRAPFRAME
  ld ra, 40(a0)
  ld sp, 48(a0)
  ld gp, 56(a0)
  ld tp, 64(a0)
  ...
	# restore user a0
	ld a0, 112(a0)
	# sret功能:
	# 1.程序切换回user mode
	# 2.SEPC寄存器的数值会被拷贝到PC寄存器
	# 3.重新打开中断
	sret
系统调用执行结束
 
                    
                
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号