目录

  • 说明
  • 陷入机制概述
  • Traps from user space
    • 调用逻辑
    • ecall
    • uservec
    • usertrap
    • usertrapret
    • userret
    • sret
  • Traps from kernel mode
    • kernelvec
    • kerneltrap
  • 感言
  • 参考资料

说明

  • 阅读的代码是 xv6-riscv 版本的

陷入机制概述

每个RISC-V CPU都有一组控制寄存器,内核通过向这些寄存器写入内容来告诉CPU如何处理陷阱,内核可以读取这些寄存器来明确已经发生的陷阱。RISC-V文档包含了完整的内容。riscv.h(kernel/riscv.h:1)包含在xv6中使用到的内容的定义。以下是最重要的一些寄存器概述:

  • stvec:内核在这里写入其陷阱处理程序的地址;RISC-V跳转到这里处理陷阱。
  • sepc:当发生陷阱时,RISC-V会在这里保存程序计数器pc(因为pc会被stvec覆盖)
    sret(从陷阱返回)指令会将sepc复制到pc。内核可以写入sepc来控制sret的去向。
  • scause: RISC-V在这里放置一个描述陷阱原因的数字。
  • sscratch:内核在这里放置了一个值,这个值在陷阱处理程序一开始就会派上用场。
  • sstatus:其中的SIE位控制设备中断是否启用。如果内核清空SIE,RISC-V将推迟设备中断,直到内核重新设置SIE。SPP位指示陷阱是来自用户模式还是管理模式,并控制sret返回的模式。

上述寄存器都用于在管理模式下处理陷阱,在用户模式下不能读取或写入。在机器模式下处理陷阱有一组等效的控制寄存器,xv6仅在计时器中断的特殊情况下使用它们。

Traps from user space

在用户空间中,使用系统调用会触发trap机制
例如:write()函数

.global write
write:li a7, SYS_writeecallret

调用逻辑

ecall

这是一个汇编指令,他会做下面操作

1.清除SIE以禁用中断。
2.将pc复制到sepc。
3.将当前模式(用户或管理)保存在状态的SPP位中。
4.设置scause以反映产生陷阱的原因。
5.将模式设置为管理模式。
6.将stvec复制到pc。
7.在新的pc上开始执行。

注意:stvec指向的地址是 uservec,将stvec复制到pc后,下面会到从uservec开始执行

uservec

# trampoline.S
uservec:# 交换 a0 和 sscratch 寄存器的值# so that a0 is TRAPFRAMEcsrrw a0, sscratch, a0# 将寄存器保存到当前进程的 trapframe 中sd ra, 40(a0)# ... 保存寄存器sd t6, 280(a0)# 同时也保存 a0csrr t0, sscratchsd t0, 112(a0)# 从 user mode 的 traptable 中恢复一些内核的信息ld sp, 8(a0)ld tp, 32(a0)ld t0, 16(a0)ld t1, 0(a0)csrw satp, t1sfence.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 returnjr t0
  • 在进行系统调用之前,p->trapframe 的起始地址会被保存在 sscratch 寄存器中
  • usertrap 首先将所有的寄存器保存在 p->trapframe
  • 将原本保存在p->trapframe中的内核信息加载到寄存器当中
    • sp
    • tp
    • 内核页表地址
    • usertrap地址
  • 将页表切换到内核页表
  • 跳转到usertrap

usertrap

void usertrap(void) {int which_dev = 0;if((r_sstatus() & SSTATUS_SPP) != 0)panic("usertrap: not from user mode");// 设置 stvec 为 kernelvecw_stvec((uint64)kernelvec);struct proc *p = myproc();// 保存PC, 否则可能会有其他的 usertrap 修改它p->trapframe->epc = r_sepc();if(r_scause() == 8){// system callif(p->killed)exit(-1);// 系统调用返回下一条命令p->trapframe->epc += 4;// 做完寄存器的操作之后打开设备中断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();usertrapret();
}
  • 判断是否在管理模式下
  • 设置 stvec 为 kernelvec
  • 保存PC, 否则可能会有其他的 usertrap 修改它
  • 判断trap类型
    • 系统调用

      • p->trapframe->epc += 4(系统调用返回下一条命令)
      • 打开设备中断
      • syscall()进行系统调用
    • 设备中断
      • yield
    • 异常
  • 根据trap类型执行相应操作
  • 调用usertrapret函数

usertrapret

该部分代码就是做一些返回用户模式的准备

void usertrapret(void)
{struct proc *p = myproc();// we're about to switch the destination of traps from// kerneltrap() to usertrap(), so turn off interrupts until// we're back in user space, where usertrap() is correct.intr_off();// send syscalls, interrupts, and exceptions to trampoline.Sw_stvec(TRAMPOLINE + (uservec - trampoline));// set up trapframe values that uservec will need when// the process next re-enters the kernel.p->trapframe->kernel_satp = r_satp();         // kernel page tablep->trapframe->kernel_sp = p->kstack + PGSIZE; // process's kernel stackp->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.// set S Previous Privilege mode to User.unsigned long x = r_sstatus();x &= ~SSTATUS_SPP; // clear SPP to 0 for user modex |= SSTATUS_SPIE; // enable interrupts in user modew_sstatus(x);// set S Exception Program Counter to the saved user pc.w_sepc(p->trapframe->epc);// tell trampoline.S the user page table to switch to.uint64 satp = MAKE_SATP(p->pagetable);// jump to trampoline.S at the top of memory, which// switches to the user page table, restores user registers,// and switches to user mode with sret.uint64 fn = TRAMPOLINE + (userret - trampoline);((void (*)(uint64, uint64))fn)(TRAPFRAME, satp);
}
  • 关闭中断
  • 将内核信息保存到p->trapfarm当中
  • 设置sstatus寄存器 (用户模式)
  • 将p->trapframe->epc(用户模式下要执行的下一条命令)放到sepc寄存器当中
  • 将用户页表保存在stap(此处是定义的变量,并非是寄存器)
  • 跳转到rampoline,执行userret

userret

.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, a1sfence.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 TRAPFRAMEld ra, 40(a0)# ...恢复寄存器ld t6, 280(a0)# restore user a0, and save TRAPFRAME in sscratchcsrrw a0, sscratch, a0# return to user mode and user pc.# usertrapret() set up sstatus and sepc.sret
  • 切换到用户页表
  • 将之前保存的寄存器恢复
  • 将TRAPFRAME保存回ssractch当中
  • sret

sret

  • 程序切换到用户模式
  • 将sepc存到pc当中
  • 开启中断
  • 跳到pc开始执行

Traps from kernel mode

  • 在内核模式下,trap 只有两类:exceptions 、device interrupt
  • 当一个 trap 发生的时候,首先硬件开始工作,配置寄存器
  • 此时 stvec 指向了 kernelvec 的起始地址

kernelvec

因为是发生在内核状态下的,所以相较于系统调用,就简单很多

# kernel/kernelvec.S
.globl kerneltrap
.globl kernelvec
.align 4
kernelvec:# 在栈上开辟一块空间用于保存寄存器addi sp, sp, -256sd ra, 0(sp)# ... 保存所有寄存器sd t6, 240(sp)# 调用 C 处理程序call kerneltrap# 恢复寄存器到之前的状态ld ra, 0(sp)# ... 恢复所有的寄存器(除了 tp)ld t6, 240(sp)# 恢复栈指针addi sp, sp, 256# 返回到之前的运行状态sret
  • 在栈上开辟一段空间,用来保存寄存器
  • 保存寄存器
  • 调用 C 处理程序kerneltrap
  • 恢复寄存器到之前的状态
  • 恢复栈指针
  • 返回到之前的运行状态

kerneltrap

kerneltrap的处理和usertrap还是很了类似的

// interrupts and exceptions from kernel code go here via kernelvec,
// on whatever the current kernel stack is.
void kerneltrap()
{int which_dev = 0;uint64 sepc = r_sepc();uint64 sstatus = r_sstatus();uint64 scause = r_scause();if ((sstatus & SSTATUS_SPP) == 0)panic("kerneltrap: not from supervisor mode");if (intr_get() != 0)panic("kerneltrap: interrupts enabled");if ((which_dev = devintr()) == 0){printf("scause %p\n", scause);printf("sepc=%p stval=%p\n", r_sepc(), r_stval());panic("kerneltrap");}// give up the CPU if this is a timer interrupt.if (which_dev == 2 && myproc() != 0 && myproc()->state == RUNNING)yield();// the yield() may have caused some traps to occur,// so restore trap registers for use by kernelvec.S's sepc instruction.w_sepc(sepc);w_sstatus(sstatus);
}
  • 保存sepc,sstatus,scause寄存器(因为 yield()可能会引起其他陷阱,修改寄存器)
  • 判断是否处于管理模式
  • 判断trap类型,并执行相应操作
  • 恢复sepc,sstatus,scause寄存器

感言

这部分还是卡了很久,因为出现了很多汇编,需要了解xv6的寄存器,以及RSICV指令集,还有栈桢相关的知识,后续继续一步一步完成,继续努力

参考资料

  • http://xv6.dgs.zone/tranlate_books/book-riscv-rev1/c1/s0.html
  • xv6-riscv源码

xv6源码阅读——中断与异常相关推荐

  1. xv6源码阅读——文件系统

    说明 阅读的代码是 xv6-riscv 版本的 七层结构 xv6文件系统实现分为七层 文件描述符(File descriptor) 路径名(Pathname) 目录(Directory) 索引结点(I ...

  2. java io中断_JDK源码阅读:InterruptibleChannel 与可中断 IO

    来源:木杉的博客 , imushan.com/2018/08/01/java/language/JDK源码阅读-InterruptibleChannel与可中断IO/ Java传统IO是不支持中断的, ...

  3. Java8 ArrayBlockingQueue 源码阅读

    一.什么是 ArrayBlockingQueue ArrayBlockingQueue 是 GUC(java.util.concurrent) 包下的一个线程安全的阻塞队列,底层使用数组实现. 除了线 ...

  4. DM 源码阅读系列文章(四)dump/load 全量同步的实现

    作者:杨非 本文为 DM 源码阅读系列文章的第四篇,上篇文章 介绍了数据同步处理单元实现的功能,数据同步流程的运行逻辑以及数据同步处理单元的 interface 设计.本篇文章在此基础上展开,详细介绍 ...

  5. r8169驱动源码阅读记录

    r8169驱动源码阅读记录 初始化 发包 收包 源码地址:linux-4.19.90\drivers\net\ethernet\realtek\r8169.c 源码阅读环境:Windows 搭建 op ...

  6. 源码阅读(34):Java中线程安全的Queue、Deque结构——ArrayBlockingQueue(4)

    (接上文<源码阅读(33):Java中线程安全的Queue.Deque结构--ArrayBlockingQueue(3)>) 2.3.3.3.forEachRemaining() 方法 f ...

  7. 【源码阅读计划】浅析 Java 线程池工作原理及核心源码

    [源码阅读计划]浅析 Java 线程池工作原理及核心源码 为什么要用线程池? 线程池的设计 线程池如何维护自身状态? 线程池如何管理任务? execute函数执行过程(分配) getTask 函数(获 ...

  8. redis源码阅读-持久化之RDB

    持久化介绍: redis的持久化有两种方式: rdb :可以在指定的时间间隔内生成数据集的时间点快照(point-in-time snapshot) aof : 记录redis执行的所有写操作命令 根 ...

  9. 6.S081——陷阱部分(一文读懂xv6系统调用)——xv6源码完全解析系列(5)

    0.briefly speaking 这篇博客将要开始尝试阅读和研究与Xv6陷阱机制相关的代码,主要有以下文件,最重要的是结合Xv6 book将Xv6处理陷阱的相关逻辑和流程弄透.在Xv6的语境中所谓 ...

最新文章

  1. SpringCloud Alibaba微服务实战(一) - 基础环境搭建
  2. Spring Quartz
  3. 抓取SAP报表ALV GRID上的数据
  4. java中的工厂模式_深入理解Java的三种工厂模式
  5. 2021-07-05 操作系统实操相关知识点笔记--中断、异常的响应和处理
  6. macos模拟器_苹果芯补完计划,iOS终将回归mac OS?
  7. 敏捷开发中“可运行软件”的评审标准(兼谈敏捷开发中的迭代中期质量控制)...
  8. 从字节码层面,解析 Java 布尔型的实现原理
  9. 实施ERP系统的一般方法和步骤
  10. CAN与CAN FD通信之间存在的问题
  11. linux查看网卡物理编号_Linux下查看网卡信息
  12. 离散时间傅里叶变换(DTFT)与离散傅里叶级数(DFS)
  13. C语言执行时进行窗口隐藏
  14. 巧用Q盘搭建SVN服务器
  15. PPT总是处于“只读模式”可以这样解决
  16. Netcdf4.4的安装过程(附netcdf4.1.3的安装过程)
  17. 基于asp.net房屋按揭贷款管理系统
  18. 大型软件外包项目的开发流程
  19. 2022年高压电工操作证考试题库及模拟考试
  20. 色值的计算、转换、获取

热门文章

  1. 解救西西弗斯- 模型驱动架构(MDA,Model Driven Architecture)浅述
  2. 轻量级模型设计/部署
  3. 浅析简历——中华英才网
  4. ROS学习——Ubuntu 安装软件报错问题(关于进程锁)
  5. vue请求后台数据的几种方式
  6. php1064,PHP创建表错误1064
  7. Tableau 表计算函数
  8. 中国大陆收货地址智能解析
  9. Linux中/proc目录下文件详解 /proc/devices文件 /proc/modules文件
  10. 895计算机专业基础,北京工业大学2020年考研895计算机学科专业基础考试大纲