xv6源码解析 014

2024-05-02 10:20
文章标签 源码 解析 xv6 014

本文主要是介绍xv6源码解析 014,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

xv6源码解析 014

今天我们就来看看当进程从用户态陷入的时候是如何返回用户空间的,争取今天结束trap机制。明天开文件系统,五一假期争取结束这个专题。

话不多说我们直接开始。

我们先复习一下usertrap的整个过程。

在这里插入图片描述

我们正式开始


在usertrap的结尾是会调用这样一个函数

void
usertrap(void)
{...usertrapret();
}

这是一个不会返回的函数,正如我们上篇说到的,在trap的整个过程链中的函数都是不会返回的。

void 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.// 保存寄存器的状态到tramframe中,以备下一次trapp->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.// 切换cpu的状态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.// 计算出userret的汇编代码段在trampoline中的起始地址uint64 fn = TRAMPOLINE + (userret - trampoline);// 执行userret((void (*)(uint64,uint64))fn)(TRAPFRAME, satp);
}

结合注释来看,函数很好理解,大致可以概括为:关闭中断 -> 保存内核陷入时的状态,为下一次陷入做准备 -> 将cpu从特权模式切换到用户模式 -> 恢复陷入前的程序计数器的值 -> 切换回用户态页表 -> 计算出并执行userret()汇编函数。

同样userret()也是不会返回的,我们现在就来看看。


userrtet(TRAPFRAME, pagetable)

userret()会接收两个参数,一个是tramframe paged的地址,另一个是页表的地址

.globl userret
userret:# userret(TRAPFRAME, pagetable)# 分别对应参数寄存器a1 和 a2# 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.# 原子的a1寄存器中的值复制到satp中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 TRAPFRAME# 将先前保存在tramframe中的寄存器的状态都加载会寄存器中ld ra, 40(a0)ld sp, 48(a0)ld gp, 56(a0)ld tp, 64(a0)ld t0, 72(a0)ld t1, 80(a0)ld t2, 88(a0)ld s0, 96(a0)ld s1, 104(a0)ld a1, 120(a0)ld a2, 128(a0)ld a3, 136(a0)ld a4, 144(a0)ld a5, 152(a0)ld a6, 160(a0)ld a7, 168(a0)ld s2, 176(a0)ld s3, 184(a0)ld s4, 192(a0)ld s5, 200(a0)ld s6, 208(a0)ld s7, 216(a0)ld s8, 224(a0)ld s9, 232(a0)ld s10, 240(a0)ld s11, 248(a0)ld t3, 256(a0)ld t4, 264(a0)ld t5, 272(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

现在我们就安全的返回了用户空间了,站在用户进程的视角来看,用户进程并没有察觉时间的流逝(一个比喻,对于(分时操作系统的)用户进程来说,消耗了时间片才被视为是时间的流逝),相当于时间暂停了一下下,当返回时,又重新开始了。

现在我们可以看看从内核陷入了,相比于从用户态陷入,从内核陷入要简单很多,因为本来就处在内核态中。

我们直接看代码


kerneltrap()

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);
}

很简单,就相当于一个函数调用的过程,为什么这么说呢?如果当前cpu是在处理用户陷入的流程中,此时我们知道在进入usertrap的时候我们就将陷入处理的向量指向这个函数kerneltrap中;如果我们是在内核中处理其他的事情(我暂时想不出有什么其他的事情),此时陷入处理的向量也是指向这个函数,所以如果有一个无论是什么情况引起的trap,我们都会直接跳转到这个函数中进行处理。处理完之后返回,就这么简单哈哈哈。


时间还有很多,顺便在看看时钟中断和设备中断吧。

首先是时钟中断。一般,时钟中断是以软中断(soft interrupt)的形式出现在内核中的。

软中断和硬中断有什么不同呢?直接上gpt

软中断与硬中断的区别:

  1. 触发源
    • 硬中断(Hardware Interrupt):由外部硬件设备生成,如上述代码中的时钟中断,当系统时钟到达某个预定计数时自动触发。硬中断打断CPU当前正在执行的任务,强制CPU保存当前状态,然后跳转到中断处理程序执行。
    • 软中断(Software Interrupt):由软件内部生成,通过执行特定的中断指令(如系统调用)来触发。它们通常用于在用户态和内核态之间切换,或者请求操作系统服务。
  2. 控制流与上下文
    • 硬中断是异步的,即它们可以在任何时刻发生,不受CPU控制。
    • 软中断往往是同步的,发生在程序执行过程中,其执行时机可以预见。
  3. 处理方式
    • 硬中断处理通常迅速完成必要的即时操作(如记录中断发生),以减少对正常任务的影响,复杂的处理会推迟到软中断(下半部)进行。
    • 软中断(也称为上半部和下半部处理模型中的下半部)处理那些非紧急但可能较耗时的任务,可以在更宽松的时间条件下执行,有时可以在多个CPU上并行处理。
  4. 中断嵌套与屏蔽
    • 硬中断可以被其他硬中断嵌套(取决于硬件设计),并且可以通过中断屏蔽位来控制是否响应新的硬中断。
    • 软中断一般不支持嵌套,但同类软中断可以在不同的CPU核心上并行执行。

ok,看代码

void
clockintr()
{acquire(&tickslock);ticks++;wakeup(&ticks);release(&tickslock);
}

设备中断

int
devintr()
{uint64 scause = r_scause();if((scause & 0x8000000000000000L) &&(scause & 0xff) == 9){// this is a supervisor external interrupt, via PLIC.// irq indicates which device interrupted.int irq = plic_claim();// 判断时什么样的中断if(irq == UART0_IRQ){uartintr();} else if(irq == VIRTIO0_IRQ){virtio_disk_intr();} else if(irq){printf("unexpected interrupt irq=%d\n", irq);}// the PLIC allows each device to raise at most one// interrupt at a time; tell the PLIC the device is// now allowed to interrupt again.if(irq)plic_complete(irq);return 1;} else if(scause == 0x8000000000000001L){// software interrupt from a machine-mode timer interrupt,// forwarded by timervec in kernelvec.S.if(cpuid() == 0){clockintr();}// acknowledge the software interrupt by clearing// the SSIP bit in sip.w_sip(r_sip() & ~2);return 2;} else {return 0;}
}

这篇关于xv6源码解析 014的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/954029

相关文章

使用Jackson进行JSON生成与解析的新手指南

《使用Jackson进行JSON生成与解析的新手指南》这篇文章主要为大家详细介绍了如何使用Jackson进行JSON生成与解析处理,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 核心依赖2. 基础用法2.1 对象转 jsON(序列化)2.2 JSON 转对象(反序列化)3.

Springboot @Autowired和@Resource的区别解析

《Springboot@Autowired和@Resource的区别解析》@Resource是JDK提供的注解,只是Spring在实现上提供了这个注解的功能支持,本文给大家介绍Springboot@... 目录【一】定义【1】@Autowired【2】@Resource【二】区别【1】包含的属性不同【2】@

SpringCloud动态配置注解@RefreshScope与@Component的深度解析

《SpringCloud动态配置注解@RefreshScope与@Component的深度解析》在现代微服务架构中,动态配置管理是一个关键需求,本文将为大家介绍SpringCloud中相关的注解@Re... 目录引言1. @RefreshScope 的作用与原理1.1 什么是 @RefreshScope1.

Java并发编程必备之Synchronized关键字深入解析

《Java并发编程必备之Synchronized关键字深入解析》本文我们深入探索了Java中的Synchronized关键字,包括其互斥性和可重入性的特性,文章详细介绍了Synchronized的三种... 目录一、前言二、Synchronized关键字2.1 Synchronized的特性1. 互斥2.

Python实现无痛修改第三方库源码的方法详解

《Python实现无痛修改第三方库源码的方法详解》很多时候,我们下载的第三方库是不会有需求不满足的情况,但也有极少的情况,第三方库没有兼顾到需求,本文将介绍几个修改源码的操作,大家可以根据需求进行选择... 目录需求不符合模拟示例 1. 修改源文件2. 继承修改3. 猴子补丁4. 追踪局部变量需求不符合很

Java的IO模型、Netty原理解析

《Java的IO模型、Netty原理解析》Java的I/O是以流的方式进行数据输入输出的,Java的类库涉及很多领域的IO内容:标准的输入输出,文件的操作、网络上的数据传输流、字符串流、对象流等,这篇... 目录1.什么是IO2.同步与异步、阻塞与非阻塞3.三种IO模型BIO(blocking I/O)NI

Python 中的异步与同步深度解析(实践记录)

《Python中的异步与同步深度解析(实践记录)》在Python编程世界里,异步和同步的概念是理解程序执行流程和性能优化的关键,这篇文章将带你深入了解它们的差异,以及阻塞和非阻塞的特性,同时通过实际... 目录python中的异步与同步:深度解析与实践异步与同步的定义异步同步阻塞与非阻塞的概念阻塞非阻塞同步

Redis中高并发读写性能的深度解析与优化

《Redis中高并发读写性能的深度解析与优化》Redis作为一款高性能的内存数据库,广泛应用于缓存、消息队列、实时统计等场景,本文将深入探讨Redis的读写并发能力,感兴趣的小伙伴可以了解下... 目录引言一、Redis 并发能力概述1.1 Redis 的读写性能1.2 影响 Redis 并发能力的因素二、

Spring MVC使用视图解析的问题解读

《SpringMVC使用视图解析的问题解读》:本文主要介绍SpringMVC使用视图解析的问题解读,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Spring MVC使用视图解析1. 会使用视图解析的情况2. 不会使用视图解析的情况总结Spring MVC使用视图

利用Python和C++解析gltf文件的示例详解

《利用Python和C++解析gltf文件的示例详解》gltf,全称是GLTransmissionFormat,是一种开放的3D文件格式,Python和C++是两个非常强大的工具,下面我们就来看看如何... 目录什么是gltf文件选择语言的原因安装必要的库解析gltf文件的步骤1. 读取gltf文件2. 提