用户空间和内核空间之间的切换通常称为trap

trap的三种形式
  1. 系统调用引发
  2. 异常发生
  3. 设备中断 (时间中断、IO中断、网络中断等)
supervise 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可以使用这个页表。

这也是supervise mode唯二可以做的事情了。

ecall指令

系统使用ecall指令来执行系统调用

第一,ecall将代码从user mode改到supervisor mode

第二,ecall将程序计数器的值保存在了SEPC寄存器。

第三,ecall会跳转到STVEC寄存器指向的指令 即trampoline.S

trap的全过程

exec系统调用为例。

首先,用户程序中调用exec函数,编译器解析exec函数时,并不会调用其源码,而是将exec系统调用的编号写入A7寄存器中,执行ecall指令,ecall指令将跳转到trampoline.S。

系统从用户态进入了内核态,但目前页表依旧是user page table。trampoline.S主要是使用trapFrame(附录部分)保存了系统的状态以及用户寄存器,用于恢复用户进程,通过这个操作也实现用户进程对于进程切换的无感知。在将需要存储的状态存入user page table后,系统将切换内核页表,执行相应的内核代码,即usertrap函数。

usertrap根据trap的不同情况(scause寄存器中存储着中断原因),调用相应的处理函数。

代码解析

下面的代码是操作系统的源代码,里面包含了很多的细节,你需要里了解的是整个流程、体系,不要过多的关注细节

trap的保存流程
  1. 首先,我们需要保存32个用户寄存器。因为很显然我们需要恢复用户应用程序的执行,尤其是当用户程序随机的被设备中断所打断时。(存储用户寄存器)
  2. 程序计数器也需要在某个地方保存,它几乎跟一个用户寄存器的地位是一样的,我们需要能够在用户程序运行中断的位置继续执行用户程序。(存储一些系统寄存器 如PC)
  3. 我们需要将mode改成supervisor mode,因为我们想要使用内核中的各种各样的特权指令。(修改运行模式)
  4. SATP(Supervisor Address Translation and Protection,存储了根页表的地址)寄存器现在正指向user page table,而user page table只包含了用户程序所需要的内存映射和一两个其他的映射,它并没有包含整个内核数据的内存映射。所以在运行内核代码之前,我们需要将SATP指向kernel page table。(修改SATP,获得内核的页表)
  5. 我们需要将堆栈寄存器指向位于内核的一个地址,因为我们需要一个堆栈来调用内核的C函数。(转换到内核堆栈)
  6. 一旦我们设置好了,并且所有的硬件状态都适合在内核中使用, 我们需要跳入内核的C代码usertrap函数。
trampoline.S
	#
# code to switch between user and kernel space.
#
# this code is mapped at the same virtual address
# (TRAMPOLINE) in user and kernel space so that
# it continues to work when it switches page tables.
#
# kernel.ld causes this to be aligned
# to a page boundary.
#
.section trampsec
.globl trampoline
trampoline:
.align 4
.globl uservec
//trap状态保存
uservec:
#
# trap.c sets stvec to point here, so
# traps from user space start here,
# in supervisor mode, but with a
# user page table.
#
# sscratch points to where the process's p->trapframe is
# mapped into user space, at TRAPFRAME.
# # swap a0 and sscratch
# so that a0 is TRAPFRAME
csrrw a0, sscratch, a0 # save the user 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 user 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 usertrap(), 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 usertrap(), which does not return
jr t0
//trap状态恢复
.globl userret
userret:
# userret(TRAPFRAME, pagetable)
# switch from kernel to user.
# usertrapret() calls here.
# a0: TRAPFRAME, in user page table.
# a1: user page table, for satp. # switch to the user page table.
csrw satp, a1
sfence.vma zero, zero # put the saved user 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)
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 user a0, and save TRAPFRAME in sscratch
csrrw a0, sscratch, a0 # return to user mode and user pc.
# usertrapret() set up sstatus and sepc.
sret
用户trap处理程序
void
usertrap(void)
{
int which_dev = 0; //判断系统mode状态
if((r_sstatus() & SSTATUS_SPP) != 0)
panic("usertrap: not from user mode"); // send interrupts and exceptions to kerneltrap(),
// since we're now in the kernel.
// 将中断处理程序设置为kernelvec
// 即将kernelvec函数地址写入到stvec寄存器
// 和user model下的中断对应,kernelvec用来处理内核态下的中断
// 两个流程很相似,kernelvec更简单一些,就不做解释了
w_stvec((uint64)kernelvec); struct proc *p = myproc(); //保存用户的PC值
// save user program counter.
p->trapframe->epc = r_sepc(); //根据scause寄存器的值判断异常原因
if(r_scause() == 8){
// system call if(p->killed)
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 sstatus &c registers,
// so don't enable until 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());
p->killed = 1;
} if(p->killed)
exit(-1); // give up the CPU if this is a timer interrupt.
if(which_dev == 2)
yield(); //trap处理完毕,恢复用户进程状态
usertrapret();
}

附:

系统调用函数 (根据A7寄存器存储的内容,调用不同的函数指针)

extern uint64 sys_chdir(void);
extern uint64 sys_close(void);
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,
[SYS_close] sys_close,
}; 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]();
} else {
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}

Trap (陷入/中断) 源码解析的更多相关文章

  1. Java并发之ThreadPoolExecutor源码解析(二)

    ThreadPoolExecutor ThreadPoolExecutor是ExecutorService的一种实现,可以用若干已经池化的线程执行被提交的任务.使用线程池可以帮助我们限定和整合程序资源 ...

  2. Java并发之ReentrantLock源码解析(二)

    在了解如何加锁时候,我们再来了解如何解锁.可重入互斥锁ReentrantLock的解锁方法unlock()并不区分是公平锁还是非公平锁,Sync类并没有实现release(int arg)方法,这里会 ...

  3. Java并发之ReentrantLock源码解析(四)

    Condition 在上一章中,我们大概了解了Condition的使用,下面我们来看看Condition再juc的实现.juc下Condition本质上是一个接口,它只定义了这个接口的使用方式,具体的 ...

  4. Java并发之Semaphore源码解析(一)

    Semaphore 前情提要:在学习本章前,需要先了解笔者先前讲解过的ReentrantLock源码解析,ReentrantLock源码解析里介绍的方法有很多是本章的铺垫.下面,我们进入本章正题Sem ...

  5. Java并发之Semaphore源码解析(二)

    在上一章,我们学习了信号量(Semaphore)是如何请求许可证的,下面我们来看看要如何归还许可证. 可以看到当我们要归还许可证时,不论是调用release()或是release(int permit ...

  6. 源码解析Synchronous Queue 这种特立独行的队列

    摘要:Synchronous Queue 是一种特立独行的队列,其本身是没有容量的,比如调用者放一个数据到队列中,调用者是不能够立马返回的,调用者必须等待别人把我放进去的数据消费掉了,才能够返回. 本 ...

  7. jQuery2.x源码解析(设计篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 这一篇笔者主要以设计的角度探索jQuery的源代 ...

  8. Python2 基本数据结构源码解析

    Python2 基本数据结构源码解析 Contents 0x00. Preface 0x01. PyObject 0x01. PyIntObject 0x02. PyFloatObject 0x04. ...

  9. Heritrix 3.1.0 源码解析(三十七)

    今天有兴趣重新看了一下heritrix3.1.0系统里面的线程池源码,heritrix系统没有采用java的cocurrency包里面的并发框架,而是采用了线程组ThreadGroup类来实现线程池的 ...

随机推荐

  1. windows 中cmd一些特殊命令

    chcp 65001  就是换成UTF-8代码页 chcp 936 可以换回默认的GBK chcp 437 是美国英语 shutdown -s -t 60   60秒后关机 shutdown /a  ...

  2. Kafka 3.0新特性

    1.概述 Kafka是一个分布表示实时数据流平台,可独立部署在单台服务器上,也可部署在多台服务器上构成集群.它提供了发布与订阅的功能,用户可以发送数据到Kafka集群中,也可以从Kafka集群中读取数 ...

  3. javascript 无限分类

    * 根据php无限分类实现js版本的 /** * 根节点 parentid=0, 每个节点都有id, parentid字段 * @param items * @returns {*} */ funct ...

  4. PHP 流行的框架

    Aura Laravel Symphony Yii Zend php components Packagist 最好的组件: Awesome PHP https://www.yiiframework. ...

  5. Selenium多浏览器处理 (Chrome/Firefox/IE)

    测试用例文件:test_selenium/test_hogwarts.py 使用pytest框架 定义一个变量,通过外部传入变量,确定使用哪个浏览器 browser = os.getenv(" ...

  6. Python简单算法的实现

    #coding=utf-8 #!/usr/bin/python def GCD(a,b): """ :求两个数的最大公约数 :param a: :param b: :re ...

  7. equals之List

    School类 package com.collection.equals; /* * 定义一个学校类 相当于一个学校模板 * 状态: 1.学校id 2.学校名称 * */ public class ...

  8. Git学习笔记03-原理

    在Git中,算上远程Git仓库有四个工作区域 Git本地有三个区域(工作区域.暂存区,资源区,远程Git仓库) 工作区域:就是你本机写好的代码,你可以看到的 暂存区:你写好的代码上传后被git管理的内 ...

  9. 关于C、Java、Python程序运行耗时及内存用量

    最近没有刷题,而是在PTA找几个题目寻找有关程序输入流问题以及各种语言在运行时对计算机消耗内存的问题, 以免很多同学解题的时候发现自己做的对但是出现运行超时的问题:针对运行内存,肯定用C/C++的同学 ...

  10. ByteCTF2021 double sqli

    double sqli easy sqli http://39.105.175.150:30001/?id=1 http://39.105.116.246:30001/?id=1 http://39. ...