IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> xv6源码阅读——中断与异常 -> 正文阅读

[嵌入式]xv6源码阅读——中断与异常

说明

  • 阅读的代码是 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_write
 ecall
 ret

调用逻辑

在这里插入图片描述

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 TRAPFRAME
        csrrw a0, sscratch, a0

        # 将寄存器保存到当前进程的 trapframe 中
        sd ra, 40(a0)
        # ... 保存寄存器
        sd t6, 280(a0)

        # 同时也保存 a0
        csrr t0, sscratch
        sd t0, 112(a0)

        # 从 user mode 的 traptable 中恢复一些内核的信息
        ld sp, 8(a0)
        ld tp, 32(a0)
        ld t0, 16(a0)
        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
  • 在进行系统调用之前,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 为 kernelvec
  w_stvec((uint64)kernelvec);

  struct proc *p = myproc();

  // 保存PC, 否则可能会有其他的 usertrap 修改它
  p->trapframe->epc = r_sepc();

  if(r_scause() == 8){
    // system call

    if(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.S
  w_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 table
  p->trapframe->kernel_sp = p->kstack + PGSIZE; // process's kernel stack
  p->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 mode
  x |= SSTATUS_SPIE; // enable interrupts in user mode
  w_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, 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 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
  • 切换到用户页表
  • 将之前保存的寄存器恢复
  • 将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, -256
        sd 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源码
  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2022-09-21 00:44:29  更:2022-09-21 00:44:54 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/28 17:43:02-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计