2.CH-4文档学习笔记

news/2024/5/18 12:49:00

一、trap简介

三类 trap

  1. 系统调用:用户程序执行ecall指令要求内核为其提供服务
  2. 异常:(用户或内核)指令做了一些非法的事情,例如除以零或使用无效的虚拟地址;
  3. 设备中断,一个设备,例如当磁盘硬件完成读或写请求时,向系统表明需要处理。

trap执行流程

1. 强制将控制权转移到内核 
2. 内核保存寄存器和其他状态,以便之后恢复执行。
3. 内核根据trap类型进行对应的处理(例如,系统调用接口或设备驱动程序)
4. 内核恢复状态,并从trap返回到用户态
5. 机器从之前停止的地方恢复执行

xv6 trap处理流程

RISC-V CPU进行硬件操作
为内核C代码执行而准备的汇编程序集“向量”(vector)
决定如何处理trap的C trap处理程序
系统调用或设备驱动程序服务例程
  • 三种trap类型,用同一种处理路径进行处理
  • 但对于三种不同的情况:来自用户空间的trap、来自内核空间的trap和定时器中断,则分别使用不同的程序进行处理
  • 处理trap的内核代码(由汇编或C语言写成)被称为handler
  • 第一个handler程序由汇编语言编写,被称为向量(vector)

二、RISC-V trap机制

  • 每个CPU核都有一组控制寄存器,

    • 内核通过向这些寄存器写入内容告诉CPU如何处理trap
    • 内核也通过读取这些寄存器来明确发生的trap类型
  • 一些重要的寄存器:

    这些寄存器只能在管理模式下使用,用户模式下不能读写。

    机器模式下也有一组类似的控制寄存器,xv6只在计时器中断使用。

    • stvec:存储trap处理程序的地址,中断时,CPU会跳转到stvec中的地址。

    • sepc:发生trap时,RISC-V会在这里保存当前的pc值,用于之后恢复。

      从trap返回时,使用sret指令,其会将sepc复制到pc。所以内核可通过sepc来控制sret的去向。

    • scause: trap时,RISC-V会在这里放置一个数字,说明trap的原因

    • sscratch: trap handler代码使用sscratch来避免在保存寄存器之前覆盖它们。

      类似于数字交换时的中转站

      比如trap处理的代码

      csrw sscratch, a0li a0, TRAPFRAMEsd ra, 40(a0)
      sd gp, 56(a0)
      

      需要使用a0存储TRAPFRAME作为基地址,保存寄存器的值

      但是a0本身也需要保存,所以先将a0保存到sscratch,然后使用a0,最后将sscratch存储到TRAPFRAME中,这样a0就没有丢失。

    • sstatus

      • SIE位:设备中断是否启用。如果清空SIE,设备中断将停止,直到重新设置。
      • SPP位:trap是来自用户还是管理模式,并控制sret返回的模式。

RISC-V硬件处理trap的流程

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

三、第一种trap:来自用户空间

trampoline page

触发trap后,不会自动切换页表,意味着中断开始处理时,仍然使用用户页表。因此用户页表中必须有中断处理程序的映射

此外,在切换到内核页表后,还要继续处理中断,所以内核页表也要有中断处理程序的映射

Xv6使用trampoline page来解决这些问题。trampoline page映射在每个页表的trampoline地址上,同时没有PTE_U标志,所以trap的管理员模式下可以访问,用户模式不能访问

内核页表:

image-20220921022817320

用户页表:

image-20220919153906893

处理流程

  1. xv6启动时,在trap.c/usertrapret()函数中,有一行w_stvec(trampoline_uservec);,会将uservec的地址写入stvec寄存器,即设置中断处理程序为uservec,uservec位于trampoline.S

  2. 当发生中断时,处理器会自动跳转到uservec进行处理。

  3. uservec的作用

    uservec:# 将a0保存到sscratch,用于之后恢复csrw sscratch, a0# 将TRAPFRAME基地址导入a0li a0, TRAPFRAME# 使用a0+offset保存其他30个寄存器到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)# 读出sscratch中保存的a0值,然后保存到TRAPFRAMEcsrr t0, sscratchsd t0, 112(a0)# initialize kernel stack pointer, from p->trapframe->kernel_spld sp, 8(a0)# make tp hold the current hartid, from p->trapframe->kernel_hartidld tp, 32(a0)# load the address of usertrap(), from p->trapframe->kernel_trapld t0, 16(a0)# fetch the kernel page table address, from p->trapframe->kernel_satp.ld t1, 0(a0)# wait for any previous memory operations to complete, so that# they use the user page table.sfence.vma zero, zero# install the kernel page table.csrw satp, t1# flush now-stale user entries from the TLB.sfence.vma zero, zero# jump to usertrap(), which does not returnjr t0
    

    因为X0寄存器永远是0,所以无需保存

    圖片
  4. usertrap的工作是确定trap的原因,处理它,然后返回

    void usertrap(void) {int which_dev = 0;if((r_sstatus() & SSTATUS_SPP) != 0)panic("usertrap: not from user mode");// 修改stvec,内核中的陷阱由kernelvec()处理w_stvec((uint64)kernelvec);struct proc *p = myproc();// sepc寄存器介绍在下方p->trapframe->epc = r_sepc();if(r_scause() == 8){// trap类型是系统调用if(killed(p))exit(-1);// sepc points to the ecall instruction,// but we want to return to the next instruction.p->trapframe->epc += 4;// 启动设备中断intr_on();// 调用syscall进行处理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();
    }
    
    1. SEPC:"Supervisor Exception Program Counter","监管者异常程序计数器"。是一个特殊寄存器,存储trap发生时的PC值。

      比如程序执行到0x1000处的指令,然后发生了异常。在这种情况下,SEPC寄存器将会存储0x1004,所以sepc存储的就是返回用户态时,应该继续执行的位置

      之所以要保存SEPC,是因为可能会切换到另一个进程,而该进程可能会修改sepc

    2. 关于intr_on

      当xv6进入trap时,默认关闭设备中断响应,因为此时已经进入内核,在内核中执行的中断处理程序是不同的,所以需要先关闭,等到设置好stvec之后,再开启中断响应。

  5. 最后通过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();// 设置stvec指向uservecuint64 trampoline_uservec = TRAMPOLINE + (uservec - trampoline);w_stvec(trampoline_uservec);// 保存内核运行状态,以便下次运行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()// 设置寄存器的值,在使用sret返回到用户态时,需要使用// 设置sstatus的值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);// 获取用户页表的地址uint64 satp = MAKE_SATP(p->pagetable);// 使用trampoline.S\userret,跳转回用户态uint64 trampoline_userret = TRAMPOLINE + (userret - trampoline);((void (*)(uint64))trampoline_userret)(satp);
    }
    

四、系统调用流程

  1. 参数会被放置在a0~a5的寄存器中
  2. 系统调用的编号放置在a7
  3. 在执行ecall后,最终执行到syscall函数,执行对应的系统调用
  4. 返回值放置在a0中,如果系统调用无效,返回-1

五、系统调用参数传递

执行系统调用需要得到用户传来的参数

一般是放在寄存器中,通过argintargaddrargfd等函数从trapframe中检索,并以整数、指针或文件描述符的形式保存。

但是有些参数是指针,内核必须使用指针指向的数据,指针带来了两个挑战:

  1. 用户程序可能有缺陷或恶意,会传递给内核一个无效的指针,或一个旨在欺骗内核访问内核内存而非用户内存的指针。
  2. xv6内核页表映射与用户页表映射不同,因此内核不能使用普通指令从用户提供的地址加载或存储。

内核实现了一些函数,安全地传输数据。fetchstr从用户空间复制字符串到内核空间

copyinstr从用户空间复制数据到内核空间。

六、从内核空间陷入

当xv6执行在内核空间时,stvec指向kernelvec(kernel/kernelvec.S:10)

发生中断时,直接将寄存器保存在线程栈中,这样,被中断线程保存的寄存器值将安全地留在其堆栈上。

保存寄存器后,跳转到kerneltrap。其为两种trap做好了准备:设备中断和异常。调用devintr来处理前者。如果是异常,内核中异常将是致命错误;内核调用panic停止执行。

如果是计时器中断,并且一个进程的内核线程正在运行(而不是调度程序线程),kerneltrap会调用yield,给其他线程一个运行的机会。第7章解释yield中发生的事情。

附:相关寄存器介绍

这些寄存器只能在管理模式下使用,用户模式下不能读写。

机器模式下也有一组类似的控制寄存器,xv6只在计时器中断使用。

1.stvec

存储trap处理程序的地址,中断时,CPU会跳转到stvec中的地址。

2. sepc

发生trap时,RISC-V会在这里保存当前的pc值,用于之后恢复。

从trap返回时,使用sret指令,其会将sepc复制到pc。所以内核可通过sepc来控制sret的去向。

3. scause

trap时,RISC-V会在这里放置一个数字,说明trap的原因

4. sscratch

trap handler代码使用sscratch来避免在保存寄存器之前覆盖它们。

类似于数字交换时的中转站

比如trap处理的代码

csrw sscratch, a0li a0, TRAPFRAMEsd ra, 40(a0)
sd gp, 56(a0)

需要使用a0存储TRAPFRAME作为基地址,保存寄存器的值

但是a0本身也需要保存,所以先将a0保存到sscratch,然后使用a0,最后将sscratch存储到TRAPFRAME中,这样a0就没有丢失。

5.sstatus

SIE位:设备中断是否启用。如果清空SIE,设备中断将停止,直到重新设置。

SPP位:指明trap来自用户还是管理模式,并控制sret返回的模式。比如SPP为0,代表中断来自于用户模式,在之后使用sret返回时,也会切换回用户模式

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hjln.cn/news/22760.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

3.LAB-5 COW

LAB-5 COW 主要是修改fork和缺页中断处理程序 以前fork时,会直接复制原进程的页表内容到新页表,具体过程是 ​ 对于原页表中的每一页,malloc一个新页,然后复制数据到新页,最后把这一页映射到新页表 现在进行修改: 1. fork复制页表对于新进程,为其创建新页表 直接复用旧页…

南昌航空大学-23201406-第一次OOP博客作业

这个Blog,主要是为了将在面向对象的程序设计课程(Java)中的前三次pta题目集进行一个总结。 一、前言总结三次题目集的题目情况关于题目集 这三次题目集的题量都不大,主要内容都集中在最后的判题程序中。且三次的难度循序渐进,充分起到了锻炼的作用。 题目本身这三次题目集…

30 天精通 RxJS (26):简易实作 Observable(一)

因为实在太多读者在问要如何实作 Observable,所以特别调整了本系列文章最后几篇的内容,空出一天的位置来写如何简易实作 Observable。为什么是简易实作而不完整实作呢? 当然这个系列的文章是希望读者能学会如何使用 RxJS,而 实作 Observable 其实只是帮助我们理解 Observab…

Linux共享库、静态库、动态库详解

1. 介绍使用GNU的工具我们如何在Linux下创建自己的程序函数库?一个“程序函数库”简单的说就是一个文件包含了一些编译好的代码和数据,这些编译好的代码和数据可以在事后供其他的程序使用。程序函数库可以使整个程序更加模块化,更容易重新编译,而且更方便升级。 程序函数…

本地部署Llama3-8B/72b 并进行逻辑推理测试

美国当地时间4月18日,Meta开源了Llama3大模型,目前开源版本为8B和70B。Llama 3模型相比Llama 2具有重大飞跃,并在8B和70B参数尺度上建立了LLM模型的新技术。由于预训练和后训练的改进,Llama3模型是目前在8B和70B参数尺度上存在的最好的模型。训练后程序的改进大大降低了错误…

跳跃游戏精细化

跳跃游戏 ​ 给定一个数组,每个元素代表跳跃的距离,判断是否能从起点出发,跳到数组的末尾。 ​ 例如:给定一数组[3,7,8,1,5],从起点出发,可以跳跃3步,跳到位置3,然后跳1步,跳到位置4,跳4步到达末尾 思路分析定义一个变量,用来初始化当前能到达最远位置 遍历数组…

MFC-error C2589: “(”:“::”右边的非法标记

MFC-error C2589: “(”:“::”右边的非法标记错误信息 出错语句 问题原因 解决办法 错误信息 ① 错误 C2589 “(”:“::”右边的非法标记② 错误 C2059 语法错误:“)” 出错语句inline double getFitnessScore (double max_range = std::numeric_limits<double>::max()…

Python库、包、模块

一、什么是库、包、模块 无论是库、包、模块,其核心都在于封装了一系列的功能。 1、库library,这个概念并非Python里的概念,是从C语言过来的。库这个概念其实就是一堆代码一起完成一个或多个任务。非常类似函数,但是是以文件组织在一起。 2、模块module,这个概念就是Pytho…