xv6源码分析 013

2024-04-30 06:44
文章标签 分析 源码 013 xv6

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

xv6源码分析 013

从这一章开始我们就开始学习xv6内核的陷入机制(trap)。其中主要的函数是usertrapkerneltrap,这两个函数的实现有点像。

我们需要了解用用户态陷入内核态并从内核态返回用户态,从内核态进行陷入并返回的整个过程,以及会调用那些函数,陷入之前的进程的上下文如何被保存,保存在哪里,陷入过程中需要改变那些寄存器的值,从陷入中恢复之后需要如何恢复这些寄存器的值,等等。

xv6_book/chapter4.md · Prim./mit6.S086 - 码云 - 开源中国 (gitee.com)

全局变量

struct spinlock tickslock;
uint ticks;extern char trampoline[], uservec[], userret[];// in kernelvec.S, calls kerneltrap().
void kernelvec();

struct spinlock tickslockuint ticks是用于时钟中断的

extern char trampoline[], uservec[], userret[]是汇编函数

void kernelvec()也是一个汇编函数

初始化函数

void
trapinit(void)
{initlock(&tickslock, "time");
}

从用户态陷入

我先用一个图来表示从用户态陷入内核,再从内核返回用户态的整个过程。

在这里插入图片描述

现在我们来看看具体的函数

陷入函数

陷入过程由两部分组成,trampoline page中存放的跳转代码(在trampoline.S中),和usertrap(void)

我们先来看看trampoline.S中陷入内核的部分

uservec:	# 这是一个全局标识,在文件外部也能够使用这个标识# swap a0 and sscratch# so that a0 is TRAPFRAMEcsrrw a0, sscratch, a0  # 这条汇编指令下面会讲解# save the user registers in TRAPFRAME# 将当前cpu中寄存器的状态保存到tramframe中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)# save the user a0 in p->trapframe->a0# 获取本次陷入内核的缘由csrr t0, sscratchsd t0, 112(a0)# restore kernel stack pointer from p->trapframe->kernel_sp# 从trapframe中加载内核栈的栈指针到sp寄存器ld sp, 8(a0)# make tp hold the current hartid, from p->trapframe->kernel_hartidld tp, 32(a0)# load the address of usertrap(), p->trapframe->kernel_trapld t0, 16(a0)# restore kernel page table from p->trapframe->kernel_satpld t1, 0(a0)# satp寄存器:# 用于管理内存地址转换和页表相关的设置,# 比如建立和更改虚拟地址到物理地址的映射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 returnjr t0

我们先来解释一下csrrw a0, sscratch, a0这句汇编。

  1. csrrw表示“读-改-写”(Read-Set-Write)操作,它回显读取一个系统控制寄存器的值,然后用给定的寄存器更新该寄存器,并返回寄存器原始的值。我们需要注意的是:这是一条原子指令,基本用途是:在软件中管理和控制处理器的行为和状态,比如中断功能,计时器或调试功能等。

  2. 参数:

    csrrw rd, csr, rs1
    
    • rd:目标寄存器,用于存放从CSR中读取的原始值
    • csr:要访问的控制状态寄存器的编码
    • rs1:源寄存器,其内容将被写入CSR中。
  3. 指令执行的过程:

    • 读取csr当前的值,并将该值写入到寄存器rd中。
    • 将寄存器rs1中的值写入csr中,完成修改操作

所以这条指令的作用就是原子地交换了两个寄存器的值。我们也可以去了解一条cas指令xchg,这个也是一个原子指令,用在互斥锁中的。

ok,当执行完上面这段trampoline汇编之后,我们就跳转到了内核中,并处于usertrap()函数的入口处,我们现在来执行这个函数,这个函数的主要的功能是路由(routine):判断陷入的原因,并执行相应的处理程序。

usertrap(void)
void
usertrap(void)
{int which_dev = 0;if((r_sstatus() & SSTATUS_SPP) != 0)panic("usertrap: not from user mode");// send interrupts and exceptions to kerneltrap(),// since we're now in the kernel.// 主要的作用是用来处理用户态陷入执行时可能产生的异常或中断w_stvec((uint64)kernelvec); // 这句代码我们在下面解释struct proc *p = myproc();// save user program counter.p->trapframe->epc = r_sepc();if(r_scause() == 8){// system call// 系统调用if(p->killed)exit(-1);// sepc points to the ecall instruction,// but we want to return to the next instruction.// 跳过ecall指令执行下一条指令p->trapframe->epc += 4;  // 下面再细讲// an interrupt will change sstatus &c registers,// so don't enable until done with those registers.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();// 陷入机制的函数一般都是一去不复返的。
}

我们看两点,可能会有疑惑的地方:

  • w_stvec((uint64)kernelvec);

    这行代码的作用是设置系统陷入处理的入口地址(interrupt service routine)。在RISC-V架构中,stvec寄存器存储着中断或异常向量表的基地址。这意味着后续的中断和异常不在由usertrap()来处理,而是交给了kernelvec指向的汇编函数来处理,这是内核在响应这些是将时从用户态转到内核态的一个关键步骤。

  • p->trapframe->epc += 4;

    这行代码理解起来就相对简单,我们知道,调用系统调用进而陷入内核是通过ecall指令,所以ecall是陷入的开始(对内核来说),也是上一次上下文切换的最后一条指令(对用户态进程来说)。在内核中,ecall相当于一条没用的指令,所以我们需要跳过这条指令,指向下一条需要执行的指令,注意这两条指令不一定是在连续的物理内存中的。

明天在讲解剩下的内容。

通过ecall指令,所以ecall是陷入的开始(对内核来说),也是上一次上下文切换的最后一条指令(对用户态进程来说)。在内核中,ecall相当于一条没用的指令,所以我们需要跳过这条指令,指向下一条需要执行的指令,注意这两条指令不一定是在连续的物理内存中的。

明天在讲解剩下的内容。

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



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

相关文章

Java程序进程起来了但是不打印日志的原因分析

《Java程序进程起来了但是不打印日志的原因分析》:本文主要介绍Java程序进程起来了但是不打印日志的原因分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Java程序进程起来了但是不打印日志的原因1、日志配置问题2、日志文件权限问题3、日志文件路径问题4、程序

Java 正则表达式URL 匹配与源码全解析

《Java正则表达式URL匹配与源码全解析》在Web应用开发中,我们经常需要对URL进行格式验证,今天我们结合Java的Pattern和Matcher类,深入理解正则表达式在实际应用中... 目录1.正则表达式分解:2. 添加域名匹配 (2)3. 添加路径和查询参数匹配 (3) 4. 最终优化版本5.设计思

Java字符串操作技巧之语法、示例与应用场景分析

《Java字符串操作技巧之语法、示例与应用场景分析》在Java算法题和日常开发中,字符串处理是必备的核心技能,本文全面梳理Java中字符串的常用操作语法,结合代码示例、应用场景和避坑指南,可快速掌握字... 目录引言1. 基础操作1.1 创建字符串1.2 获取长度1.3 访问字符2. 字符串处理2.1 子字

Python 迭代器和生成器概念及场景分析

《Python迭代器和生成器概念及场景分析》yield是Python中实现惰性计算和协程的核心工具,结合send()、throw()、close()等方法,能够构建高效、灵活的数据流和控制流模型,这... 目录迭代器的介绍自定义迭代器省略的迭代器生产器的介绍yield的普通用法yield的高级用法yidle

C++ Sort函数使用场景分析

《C++Sort函数使用场景分析》sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变,如果某些场景需要保持相同元素间的相对顺序,可使... 目录C++ Sort函数详解一、sort函数调用的两种方式二、sort函数使用场景三、sort函数排序

Java调用C++动态库超详细步骤讲解(附源码)

《Java调用C++动态库超详细步骤讲解(附源码)》C语言因其高效和接近硬件的特性,时常会被用在性能要求较高或者需要直接操作硬件的场合,:本文主要介绍Java调用C++动态库的相关资料,文中通过代... 目录一、直接调用C++库第一步:动态库生成(vs2017+qt5.12.10)第二步:Java调用C++

kotlin中const 和val的区别及使用场景分析

《kotlin中const和val的区别及使用场景分析》在Kotlin中,const和val都是用来声明常量的,但它们的使用场景和功能有所不同,下面给大家介绍kotlin中const和val的区别,... 目录kotlin中const 和val的区别1. val:2. const:二 代码示例1 Java

Go标准库常见错误分析和解决办法

《Go标准库常见错误分析和解决办法》Go语言的标准库为开发者提供了丰富且高效的工具,涵盖了从网络编程到文件操作等各个方面,然而,标准库虽好,使用不当却可能适得其反,正所谓工欲善其事,必先利其器,本文将... 目录1. 使用了错误的time.Duration2. time.After导致的内存泄漏3. jsO

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

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

Spring事务中@Transactional注解不生效的原因分析与解决

《Spring事务中@Transactional注解不生效的原因分析与解决》在Spring框架中,@Transactional注解是管理数据库事务的核心方式,本文将深入分析事务自调用的底层原理,解释为... 目录1. 引言2. 事务自调用问题重现2.1 示例代码2.2 问题现象3. 为什么事务自调用会失效3