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

相关文章

nginx -t、nginx -s stop 和 nginx -s reload 命令的详细解析(结合应用场景)

《nginx-t、nginx-sstop和nginx-sreload命令的详细解析(结合应用场景)》本文解析Nginx的-t、-sstop、-sreload命令,分别用于配置语法检... 以下是关于 nginx -t、nginx -s stop 和 nginx -s reload 命令的详细解析,结合实际应

MyBatis中$与#的区别解析

《MyBatis中$与#的区别解析》文章浏览阅读314次,点赞4次,收藏6次。MyBatis使用#{}作为参数占位符时,会创建预处理语句(PreparedStatement),并将参数值作为预处理语句... 目录一、介绍二、sql注入风险实例一、介绍#(井号):MyBATis使用#{}作为参数占位符时,会

PostgreSQL的扩展dict_int应用案例解析

《PostgreSQL的扩展dict_int应用案例解析》dict_int扩展为PostgreSQL提供了专业的整数文本处理能力,特别适合需要精确处理数字内容的搜索场景,本文给大家介绍PostgreS... 目录PostgreSQL的扩展dict_int一、扩展概述二、核心功能三、安装与启用四、字典配置方法

深度解析Java DTO(最新推荐)

《深度解析JavaDTO(最新推荐)》DTO(DataTransferObject)是一种用于在不同层(如Controller层、Service层)之间传输数据的对象设计模式,其核心目的是封装数据,... 目录一、什么是DTO?DTO的核心特点:二、为什么需要DTO?(对比Entity)三、实际应用场景解析

深度解析Java项目中包和包之间的联系

《深度解析Java项目中包和包之间的联系》文章浏览阅读850次,点赞13次,收藏8次。本文详细介绍了Java分层架构中的几个关键包:DTO、Controller、Service和Mapper。_jav... 目录前言一、各大包1.DTO1.1、DTO的核心用途1.2. DTO与实体类(Entity)的区别1

Java中的雪花算法Snowflake解析与实践技巧

《Java中的雪花算法Snowflake解析与实践技巧》本文解析了雪花算法的原理、Java实现及生产实践,涵盖ID结构、位运算技巧、时钟回拨处理、WorkerId分配等关键点,并探讨了百度UidGen... 目录一、雪花算法核心原理1.1 算法起源1.2 ID结构详解1.3 核心特性二、Java实现解析2.

使用Python绘制3D堆叠条形图全解析

《使用Python绘制3D堆叠条形图全解析》在数据可视化的工具箱里,3D图表总能带来眼前一亮的效果,本文就来和大家聊聊如何使用Python实现绘制3D堆叠条形图,感兴趣的小伙伴可以了解下... 目录为什么选择 3D 堆叠条形图代码实现:从数据到 3D 世界的搭建核心代码逐行解析细节优化应用场景:3D 堆叠图

深度解析Python装饰器常见用法与进阶技巧

《深度解析Python装饰器常见用法与进阶技巧》Python装饰器(Decorator)是提升代码可读性与复用性的强大工具,本文将深入解析Python装饰器的原理,常见用法,进阶技巧与最佳实践,希望可... 目录装饰器的基本原理函数装饰器的常见用法带参数的装饰器类装饰器与方法装饰器装饰器的嵌套与组合进阶技巧

解析C++11 static_assert及与Boost库的关联从入门到精通

《解析C++11static_assert及与Boost库的关联从入门到精通》static_assert是C++中强大的编译时验证工具,它能够在编译阶段拦截不符合预期的类型或值,增强代码的健壮性,通... 目录一、背景知识:传统断言方法的局限性1.1 assert宏1.2 #error指令1.3 第三方解决

全面解析MySQL索引长度限制问题与解决方案

《全面解析MySQL索引长度限制问题与解决方案》MySQL对索引长度设限是为了保持高效的数据检索性能,这个限制不是MySQL的缺陷,而是数据库设计中的权衡结果,下面我们就来看看如何解决这一问题吧... 目录引言:为什么会有索引键长度问题?一、问题根源深度解析mysql索引长度限制原理实际场景示例二、五大解决