一、xv6中断异常

在操作系统运行用户进程时处于用户态,当发生异常、中断或陷入时,需要从用户态进入内核态调用处理机制。陷入内核之前需要保存上下文,而在处理完成,回到用户态时需要恢复上下文。代码包括trap.c, proc.h, syscall.c, sysproc.c, plic.c, trampoline.S等。

1.1代码阅读与分析

1.1.1trampoline.S与kernelvec.S

riscv为处理trap提供了scause(记录发生trap原因)、stvec(记录trap wrapper地址,在trap发生时读入pc)、sepc(保存更新pc前的pc值)、sscratch(保存进程的trapframe地址,保存相关寄存器在其上)等寄存器。

trampoline汇编文件负责处理在用户态发生的中断,当用户态中断时,trap.c将其中uservec的地址放入stvec中,处理程序从这里开始。uservec首先找到trapframe地址并把当前的寄存器保存在trapframe中,包括用户寄存器、栈指针、返回地址等,最后跳转到usertrap()函数中判断trap类型并作出处理。

stvec寄存器另一个可能的值是kernelvec.S中的kernelvec,它将现场保存在内核栈中然后调用trap.c中的C trap handler kerneltrap。

在usertrap和kerneltrap之外,riscv还需要处理由计时器引发的timertrap。xv6为timertrap提供了许多专用寄存器,并产生一个软中断,让kernelvec来处理timertrap。

只要发生trap,必定会进入内核态,因此不会有多重的usertrap,正因如此,进程也只有一个trapframe。但在内核中,可能会发生多重中断,为了在内核代码出错时进行trap,处理多重中断需要有能存放多个trapframe的空间。

1.1.2trap.c

在trap.c中,包含初始化函数,usertrap及ret,kerneltrap和devicetrap几部分。

初始化函数在main.c中被调用,第一个CPU上初始化tick的锁,全部CPU上都把stevc里面的中断处理入口设置为了kernelvec。

在usertrap中,首先判断是否处于用户模式,然后把kernelvec填入stvec以切换到内核模式进行中断并保存现场。在通过r_scause()函数读取中断号后判断是什么类型的中断。如果是系统调用,就通过syscall()开始执行系统调用的流程,然后调用devintr判断是软件中断还是外部中断,并执行对应的处理程序,如果都不符合就报错打印信息杀死进程。最后调用usertrapret。

在usertrapret中,先关中断,然后设置stevc为TRAMPOLINE + (uservec - trampoline),正是uservec的值(TRAMPOLINE)。然后是一些恢复现场的工作。这个函数是为了将来的来自用户控件的陷阱做准备。最后在用户和内核页表中都映射的trampoline页面上调用userret,让汇编代码切换页表。

在kerneltrap中,首先是判断是否处于正确的状态,如监管态、关中断。如上一节中所说,kerneltrap需要做好多重中断的准备。

最后是外部设备引起的中断如时钟,导致进程切换也是一种设备中断。在devintr中,通过plic_claim()函数获取中断的类型,可能会有物理内存泄露、磁盘错误或者超时。如果是时钟引发的中断,就引用上面的clockintr,把tick加加。

1.1.3proc.h、proc

proc.h定义了栈帧结构、进程的结构和上下文。proc中有一些内核函数。

1.1.4syscall、sysproc

在syscall.h中,定义了不同的系统调用号,在syscall.c中,有一些帮助取值的函数,然后是对系统调用的声明的定义函数数组。最后有syscall()函数来判断需要做什么系统调用并填装对应的处理函数地址。这正是trap.c当中做的一部分。

函数artint、artaddr、argfd从陷阱框架中检索第n个系统调用参数并分别用对应的形式保存在其参数*ip处。他们都调用argraw来检索相应的保存的用户寄存器。

fetchstr是内核传输数据的一个例子。文件系统调用如exec使用fetchstr从用户空间检索字符串名称,实际上是对copyinstr的包装。

在sysproc.c中有一些对系统调用的包装,在proc.c中有其实质的内容。

1.2 机制

在发生trap时,一般的处理流程是

  1. 控制转移到kernel
  2. kernel保存当前进程状态(在trampoline.S中,保存寄存器到trapframe,设置内核栈指针,恢复tp寄存器保存当前CPU的ID,从trapframe中取出内核页表地址,设置为satp的值,刷新tlb,从tf中取出usertrap地址并跳转)
  3. usertrap执行handler代码
  4. 执行usertrapret
  5. 回到用户态,继续执行程序

riscv还有硬件用于处理trap,这在代码中并没有体现。

  1. 如果陷阱是设备中断,并且状态SIE位被清空,则不执行以下任何操作。
  2. 清除SIE以禁用中断。
  3. 将pc复制到sepc。
  4. 将当前模式(用户或管理)保存在状态的SPP位中。
  5. 设置scause反映产生陷阱的原因。
  6. 设置管理模式,赋值stvec到pc,执行新pc

1.2.1 从用户空间陷入

用户程序发出系统调用或执行非法操作或设备中断时都会产生trap。trap的路径为uservec(kernel/trampoline.S:16), usertrap(kernel/trap.c:37); 在返回时,先是usertrapret(kernel/trap.c:90), 然后是userret(kernel/trampoline.S:16)。

在uservec启动时,32个寄存器都被先前触发中断的代码使用,riscv提供了sscratch寄存器交换a0,使得uservec有一个寄存器可以使用。交换后a0持有指向trapframe的指针(原sscratch保存),然后将所有寄存器包括现在的sscratch保存进trapframe。uservec取得trapframe中包含的信息并将satp切换到内核页表,调用usertrap。

usertrap确定陷阱的原因。在1.1.2中已经说明了它的行为。

返回用户空间第一步是调用usertrapret,见1.1.2。

usertrapret调用userret将指针传到a0中的用户页表和a1中的trapframe。userret将satp切换为用户页表,并复制trapframe保存的用户a0到sscratch,为以后的交换做准备。最终使用sret返回用户空间。

1.2.2从内核空间陷入

当CPU上执行内核时,stvec被设定为指向kernelvec(kernel/kernelvec.S:10)的汇编代码,kernelvec将寄存器保存在被中断的内核线程的栈上,因为寄存器值属于该线程。如果trap导致切换进程,那么保存当前寄存器的值是很重要的,因为返回时会直接去往新进程的栈。

kernelvec保存寄存器后跳转到kerneltrap(kernel/trap.c:134),检查异常类型,通过devintr来处理设备中断,如果不是设备中断,那就是一个异常,需要调用panic杀死进程。

如果是时钟中断导致的异常,且一个进程的内核线程正在运行,那么就会通过yield切换其他线程来执行。在某个时刻,中断之前的线程会再次恢复。

kerneltrap完成后,它需要返回到任何被陷阱中断的代码。kerneltrap恢复在启动时保存的sepc和sstatus等寄存器,返回到kernelvec(kernel/kernelvec.S:48),kernelvec恢复保存的寄存器,执行sret,将sepc复制到pc,恢复执行中断的内核代码。

1.2.3系统调用

用户代码将exec需要的参数放在a0和a1寄存器中,将系统调用号放在a7中,通过系统调用号在syscalls函数指针数组中寻址一个条目。ecall指令陷入到内核中,执行uservec、usertrap、syscall,正如上文所说。

syscall()从trapframe保存的a7检索系统调用号,并所引导syscall中。在第一次系统调用,执行的是SYS_exec,导致调用sys_exec函数。

当系统调用函数返回时,syscall把返回值记录在trapframe->a0寄存器,使得原本用户空间调用的exec()返回该值,通常负数代表错误,0或正数表示成功。

1.3小结

Trap的引入,让PC改变了简单状态机式的工作,trap强行修改了PC的值,并为修改前后能正常工作做了许多准备。操作系统的功能几乎都离不开trap,如进程线程管理、内存管理、设备和文件的管理等。

XV6 RISCV源码阅读报告之中断相关推荐

  1. XV6 RISCV 源码阅读报告之 进程调度

    xv6阅读之进程调度 四.进程调度 在计算机资源不足以同时运行所有进程时,就需要操作系统考虑如何分配有限的资源,如CPU时间和内存.xv6通过切换每个CPU上的进程实现多路复用.当进程等待设备或管道I ...

  2. XV6 RISCV源码阅读报告之 锁

    二.xv6的锁 为了在多处理器上防止多个CPU操作同一片地址空间互相干扰引起的错误,以及即使是在单个处理器上防止中断处理程序与非中断代码之间互相干扰,xv6使用锁来实现互斥. 2.1代码阅读与分析 x ...

  3. XV6 RISC-V 源码阅读报告之进程模型

    三.xv6进程模型 进程是对运行程序的抽象,通过CPU调度和虚拟地址等机制,为每个程序提供了独占处理器和内存空间的错觉. 3.1代码阅读 代码主要在proc.h和proc.c 3.1.1proc.h ...

  4. XV6 RISCV源码阅读之 虚拟内存

    五.内存管理 通过进程线程模型,xv6为每个程序提供了独占处理器的错觉,而通过虚拟内存,xv6为程序提供了统一的0-MAXVA地址的虚拟内存,并通过页表进行内存的管理.RISCV页表通过将每个虚拟地址 ...

  5. XV6 RISCV 源码阅读之文件系统

    六.文件系统 xv6的文件系统分为七层,由低到高如下所示. 磁盘层读取和写入virtio硬盘上的块. BufferCache层缓存磁盘块,同步对他们的访问. 日志层允许更高层在一次事务中将更新包装到多 ...

  6. NS3-LENA源码阅读报告(2)

    2 LENA源码组成结构 LENA作为NS3的程序模块,采用标准的NS3模块结构内容来对代码进行组织.这种标准结构内容可以通过NS3提供的create-module.py来进行创建,也可以依照惯例进行 ...

  7. xv6操作系统源码阅读之init进程

    首先看main函数,里面调用了一个userinit函数,这就是启动第一个init进程. // Bootstrap processor starts running C code here. // Al ...

  8. NS3-LENA源码阅读报告(1)

    动态系统仿真通过连续的时间步仿真用户在网络中的移动性和其它特性,构成时间驱动的状态机.仿真过程将实际的网络环境.通信协议和用户行为等抽象为数学模型,然后借助于编程语言和软件框架进行实现.NS3是基于离 ...

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

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

最新文章

  1. 谷歌把安全融入主机芯片
  2. c++ uint32转为int_【转】用python将GBK编码文件转为UTF-8编码文件
  3. 推荐 7 个牛哄哄 Spring Cloud 实战项目
  4. java dataconvert_Java DateConverter类代码示例
  5. saiku、mondrian前奏之——立方体、维度、Schema的基本概念
  6. C#中对泛型List进行分组输出元素
  7. Redhat 5.4 Oracle 10g RAC 删除节点
  8. Sportisimo EDI项目需求及包装标准
  9. 【Springboot项目】电信知识库系统
  10. python输入两个数求和笔试题_Python练习题1.1从键盘输入两个数,求它们的和并输出...
  11. 中国版Second Life前途难料
  12. linux 1.0 如何运行,观点|Linux 1.0 之旅:回顾这一切的开始
  13. 今天心情好,给各位免费呈上200兆SVN代码服务器一枚,不谢!
  14. “大数据”加盟“网格化”管理
  15. 推荐好用的开源 Docker 工具
  16. SOLIDWORKS中如何使用配置创建系列零件
  17. ci写微博php,php(CI框架)+ajax实现类似微博的东东
  18. 海投的简历暴露了你的什么问题?
  19. cmd如何实现快速粘贴复制
  20. python编程ppt_python编程操作office三剑客之PPT篇

热门文章

  1. JPA实体继承实体的映射策略
  2. python发票二维码条码识别_Python zxing 库解析(条形码二维码识别)
  3. Docker下安装MCR windows镜像安装Matlab 静默安装MCR silent install 无交互安装 无Gui安装 控制台安装
  4. C语言大数运算-乘除法篇
  5. sql注入在线检测(sqlmapapi)
  6. 卓越员工对“怠惰”说不
  7. 计算机组成原理——乘法运算(一位乘)
  8. HDU - 最大报销额(01背包|贪心)
  9. 木子-前端-方法标签属性小记(普通jsp/html篇)2018
  10. node-inspector调试node程序