首页 > 其他分享 >xv6 traps

xv6 traps

时间:2023-11-22 10:48:33浏览次数:31  
标签:调用 traps 用户 xv6 保存 内核 陷阱 寄存器

traps

引入

三种类型的事件会导致CPU暂时搁置普通指令的执行,并强制将控制转移给处理事件的特殊代码。

  • 系统调用。用户程序执行调用指令要求内核为它做一些事情
  • 异常。指令(用户或内核)做了一些非法的事情,例如除以零或使用无效的虚拟地址
  • 设备中断。当设备发出需要注意的信号时,例如当磁盘硬件完成读或写请求时。

陷阱是这些情况的统称。在陷阱发生时正在执行的代码稍后将需要恢复,并且不需要知道发生了什么特殊情况。
通常的顺序是陷阱强制将控制转移到内核;内核保存寄存器和其他状态,以便可以恢复执行;内核执行适当的处理程序代码(例如,系统调用实现或设备驱动程序);内核恢复保存的状态并从陷阱中返回;
xv6陷阱处理分四个阶段进行:RISC-V CPU采取的硬件操作,为内核C代码做准备的一些汇编指令,决定如何处理陷阱的C函数,以及系统调用或设备驱动程序服务例程。虽然三种陷阱类型之间的通用性表明内核可以用单个代码路径处理所有的陷阱,但事实证明,为三种不同的情况使用单独的代码是很方便的:来自用户空间的陷阱、来自内核空间的陷阱和计时器中断。

xv6 traps机制

每个RISC-V CPU都有一组控制寄存器,来告诉CPU如何处理陷阱。

  • stvec: 保存陷阱处理程序的地址
  • sepc: 保存程序计数器即pc的地址
  • scause: 保存陷阱原因,用一个数字描述
  • sstatus: 保存CPU状态,包括中断使能位
  • sscratch: 避免在保存寄存器前被重写,保存a0
  • sstatus: 特权状态寄存器,包括使能中断位

RISC-V硬件对所有类型的陷阱(计时器中断除外)执行以下操作:

  • 如果是设备中断,且SIE被清除,则什么也不做
  • 清除SIE位来禁用中断
  • 复制pc到sepc
  • 将当前模式(用户或内核)记录到sstatus的SPP位
  • 设置scase的值来指示陷阱类型
  • 设置模式为内核模式
  • 将stvec的值复制到pc
  • 从pc处开始执行

CPU不会切换到内核页表,不会切换到内核中的堆栈,也不会保存除pc之外的任何寄存器。这些任务交给内核软件来做。

用户traps

xv6对内核陷阱和用户陷阱的处理方式不同。
来自用户空间的陷阱的高级路径是

  • uservec (kernel/trampoline.S:21)
  • usertrap (kernel/trap.c:37)

返回时

  • usertrapret (kernel/trap.c:90)
  • userret (kernel/trampoline.S:101)

xv6陷阱处理设计上的一个主要约束是RISC-V硬件在强制执行陷阱时不会切换页表。这意味着stvec中的陷阱处理程序地址必须在用户页表中有一个有效的映射,以便陷阱处理代码开始执行。此外,xv6的陷阱处理代码需要切换到内核页表;为了能够在切换后继续执行,内核页表还必须有一个映射到由stvec指向的处理程序的映射。

当uservec启动时,所有32个寄存器都包含被中断的用户代码所拥有的值。这32个值需要保存在内存的trapframe(proc.trapframe为物理地址),以便当trap返回到用户空间时可以恢复它们。trapframe包含当前进程的内核栈地址、当前CPU的hartid、usertrap函数地址和内核页表地址,加载这些项到寄存器之后启动usertrap函数。

usertrap的工作是确定产生trap的原因,处理它,然后返回(kernel/trap.c:37)。它首先更改stvec,以便内核中的陷阱将由kernelvec而不是uservec处理。它保存sepc寄存器(保存的用户程序计数器),因为usertrap可能调用yield切换到另一个进程的内核线程,而该进程可能返回到用户空间,在这个过程中它将修改sepc。如果陷阱是一个系统调用,usertrap调用sycall来处理它;如果设备中断,devintr;否则,它就是一个异常,内核将终止故障进程。系统调用路径为保存的用户程序计数器增加了4,因为在系统调用的情况下,RISC-V使程序指针指向调用指令,但用户代码需要在后续指令上继续执行。在退出时,usertrap检查进程是否已被终止或应该放弃CPU(如果此trap是计时器中断)。

usertrapret 和 userret 是对应过程的逆过程。

系统调用

initcode.S将exec的参数放在寄存器a0和a1中,并将系统调用号放在a7中。系统调用号匹配syscalls数组中的条目,一个函数指针表(kernel/sycall.c:107)。调用指令进入内核并导致uservec、usertrap和sycall执行。

sycall从trapframe中保存的a7中检索系统调用号,并使用它来索引系统调用。对于第一个系统调用,a7包含SYS_exec,导致对系统调用实现函数SYS_exec的调用。

当sys_exec返回时,syscall记录其返回值p->trapframe->a0。这将导致对exec()的原始用户空间调用返回该值,因为RISC-V上的C调用约定将返回值放在a0中。系统调用通常返回负数表示错误,返回零或正数表示成功。如果系统调用号无效,则输出错误并返回−1。

内核中的系统调用实现需要查找用户代码传递的参数。参数最初位于RISC-V C调用约定放置它们的地方:寄存器中,而内核陷阱代码将用户寄存器保存到当前进程的陷阱帧中,内核代码可以在trapframe中找到它们。内核函数argint、argaddr和argfd从trap帧中以整数、指针或文件描述符的形式检索第n个系统调用参数。它们都调用argraw来检索适当保存的用户寄存器。

一些系统调用传递指针作为参数,内核必须使用这些指针来读写用户内存。例如,exec系统调用向内核传递一个指针数组,该数组指向用户空间中的字符串参数。这些指标提出了两个挑战。首先,用户程序可能是错误的或恶意的,并且可能向内核传递一个无效的指针或一个旨在欺骗内核访问内核内存而不是用户内存的指针。其次,xv6内核页表映射与用户页表映射不同,因此内核不能使用普通指令从用户提供的地址加载或存储。

内核实现了在用户提供的地址之间安全地传输数据的函数,详见vm.c。

内核traps

当内核在CPU上执行时,内核将stvec指向位于kernelvec (kernel/kernelvec. s:12)的汇编代码。由于xv6已经在内核中,因此kernelvec可以依赖于设置到内核页表的satp,以及指向有效内核堆栈的堆栈指针。Kernelvec将所有32个寄存器压入栈,稍后将从中恢复它们,以便中断的内核代码可以不受干扰地恢复。

Kernelvec将寄存器保存在被中断的内核线程的栈上,这是有意义的,因为寄存器值属于该线程。如果陷阱导致切换到另一个线程,陷阱实际上会从新线程的栈返回,将被中断线程保存的寄存器安全地留在栈上。

保存寄存器后,Kernelvec跳转到kerneltrap。Kerneltrap是为两种类型的trap准备的:设备中断和异常。它调用devintr (kernel/trap.c:178)来检查和处理前者。如果陷阱不是设备中断,它一定是一个异常,如果它发生在xv6内核中,那总是一个致命错误;内核调用panic并停止执行。

如果由于计时器中断而调用kerneltrap,并且进程的内核线程正在运行(与调度程序线程相反),则调用kerneltrap以给其他线程一个运行的机会。在某个时刻,其中一个线程会退出,然后让我们的线程和它的内核再次恢复。

x6对异常的响应:如果在用户空间中发生异常,内核将终止故障进程。如果内核中发生异常,内核就会出现恐慌。

Taps Lab

主要讲一下alarm的流程

用户通过sigalarm设置internal和handler函数,使得每个进程在使用cpu达到一定internal周期后,会调用handler函数。
而handler函数调用中,先完成一些任务,如计数后,调用sigreturn函数。

时钟中断时,保存用户寄存器,转入usertrap,若果进程使用cpu时间到达internal,需要进入handler函数。采取的策略是,使用进程新的alarm_trap_frame保存用户寄存器,然后将epc设置为handler函数的地址。这样,中断返回后不会直接进入原用户空间而是进入handler函数。handler函数完成后,调用sigreturn函数,使用记录的alarm_trap_frame替换trapframe,然后返回到原用户空间。

要注意的是,TRAPFRAME就是用户用来保存寄存器的虚拟地址,在trampoline.S中,使用li a0, TRAPFRAME就将p->trapframe的虚拟地址放入a0中,然后恢复寄存器。

标签:调用,traps,用户,xv6,保存,内核,陷阱,寄存器
From: https://www.cnblogs.com/wangerblog/p/17848410.html

相关文章

  • xv6book阅读 chapter1
    xv6book主要研究了xv6如何实现它的类Unix接口,但是其思想和概念不仅仅适用于Unix。任何操作系统都必须将进程多路复用到底层硬件上,相互隔离进程,并提供受控制的进程间通信机制。1了解xv6xv6是一个模仿unix内部设计的操作系统,其提供了unix中对应的部分系统调用。理解xv6对于我们理......
  • mitos - xv6 for riscv
    参考:code:https://github.com/mit-pdos/xv6-riscvbook:https://pdos.csail.mit.edu/6.828/2021/xv6/book-riscv-rev2.pdfnote:https://mit-public-courses-cn-translatio.gitbook.io/mit6-s081/......
  • xv6 traps
    trap:在xv6操作系统中,"trap"是指程序从用户态切换到内核态的一种机制。这种切换通常会在几种情况下发生,例如:系统调用、出现页错误(pagefault)或者外部设备触发了中断。Trap机制是通过一些特定的硬件指令和硬件状态来实现的,例如修改程序计数器(PC)的值,以便将程序的控制权转移到内核中......
  • xv6 进程切换中的锁:MIT6.s081/6.828 lectrue12:Coordination 以及 Lab6 Thread 心得
    引言这节课和上一节xv6进程切换是一个完整的的进程切换专题,上一节主要讨论进程切换过程中的细节,而这一节主要讨论进程切换过程中锁的使用,所以本节的两大关键词就是"Coordination"(协调)和"lostwakeup"Coordination就是有关出让CPU,直到等待的事件发生再恢复执行。人们发明了很......
  • Xv6 Lab10: file system
    Largefiles这个作业需要我们将xv6的最大文件大小从12+256Bytes修改为11+256+256*256Bytes。为了达成这个目标,我们需要使用二级索引块,对inode的addrs字段,首先将NDIRECT从$12$修改为$11$,即前$11$个block是directblock,addrs[NDIRECT]对应的块是一......
  • Mit6.s081 Lec15: xv6 的 logging system
    Logginglayerfilesystem设计的一大重要问题就是crashrecovery。这是因为文件系统操作往往涉及向磁盘多次写入,而几次写入之后的crash可能导致磁盘上的文件系统处于一个不一致的状态。Forexample,supposeacrashoccursduringfiletruncation(settingthelengthof......
  • Xv6 Lab9: Locks
    Memoryallocator这一题很简单,主要任务,就是为每个cpu维护一个空闲物理内存的链表freelist,xv6默认使用的结构体kmem,其中包含一个freelist供所有的cpu使用。我们要做的,就是把freelist修改成freelist的数组,即structrun*freelist[NCPU],其中NCPU是定义于kernel/par......
  • Xv6 Lab7: Multithreading
    Uthread:switchingbetweenthreads这个题还是对的起它moderate的难度了,如果认真看了book-riscv-rev2.pdf的Scheduling章节,以及看了这个课程翻译,那么这题可以很快做出来,个人觉得pdf讲得更加清楚一些。这个题甚至帮你把需要添加代码的地方都标注出来了,参照题目说明,主要......
  • Xv6 Lab6: Copy-on-Write Fork for xv6
    思路经过lab5:lazypageallocation之后,对xv6的pagefault的处理,算是有所了解了。今天这个COW实验,在2020年的课程视频中有对思路的讲解,可以先看看课程翻译,厘清一下思路。整体思路其实也不难,默认情况下,fokr会调用uvmcopy,将父进程的PP(物理页)复制一份,将这个PP的......
  • Xv6 Lab2
    系统调用Lab1主要是基于提供的系统调用接口来编写一些小工具程序,而Lab2则是要我们自己实现系统调用,并提供系统调用的接口。以本次Lab要我们实现的trace调用为例,说明一下系统调用的流程:在user/trace.c的第$15$行,调用了属于systemcall的trace函数,当前执行makeq......