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

相关文章

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟 开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚 第一站:海量资源,应有尽有 走进“智听

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

OWASP十大安全漏洞解析

OWASP(开放式Web应用程序安全项目)发布的“十大安全漏洞”列表是Web应用程序安全领域的权威指南,它总结了Web应用程序中最常见、最危险的安全隐患。以下是对OWASP十大安全漏洞的详细解析: 1. 注入漏洞(Injection) 描述:攻击者通过在应用程序的输入数据中插入恶意代码,从而控制应用程序的行为。常见的注入类型包括SQL注入、OS命令注入、LDAP注入等。 影响:可能导致数据泄

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动

Spring 源码解读:自定义实现Bean定义的注册与解析

引言 在Spring框架中,Bean的注册与解析是整个依赖注入流程的核心步骤。通过Bean定义,Spring容器知道如何创建、配置和管理每个Bean实例。本篇文章将通过实现一个简化版的Bean定义注册与解析机制,帮助你理解Spring框架背后的设计逻辑。我们还将对比Spring中的BeanDefinition和BeanDefinitionRegistry,以全面掌握Bean注册和解析的核心原理。

CSP 2023 提高级第一轮 CSP-S 2023初试题 完善程序第二题解析 未完

一、题目阅读 (最大值之和)给定整数序列 a0,⋯,an−1,求该序列所有非空连续子序列的最大值之和。上述参数满足 1≤n≤105 和 1≤ai≤108。 一个序列的非空连续子序列可以用两个下标 ll 和 rr(其中0≤l≤r<n0≤l≤r<n)表示,对应的序列为 al,al+1,⋯,ar​。两个非空连续子序列不同,当且仅当下标不同。 例如,当原序列为 [1,2,1,2] 时,要计算子序列 [