【MIT6.S081】xv6学习整理

一、写在前面

    xv6是MIT为了操作系统实验教学开发的小型类Unix操作系统。我们可以在这个课程中初步了解操作系统的代码实现,深入理解理论课上的抽象概念,比如,用户态/内核态,系统调用,进程控制块,进程上下文。为了更深入理解操作系统,学一学xv6是很有必要的。

二、学习资源

    xv6官方网站(Lab):链接
    MIT6.S081课程视频:链接
    MIT6.S081课程翻译文本:链接

三、学习建议

    在学习初期建议跟着MIT课程学习,并完成对应Lab。英文好的小伙伴可以观看视频学习,对于英文差一些的同学推荐直接看翻译文本(节约时间,可以反复阅读理解)。

1. 学习Lec1, 3

    了解xv6基础知识,认识系统调用,并完成Lab2。

2. 学习Lec4, 5, 6

    分析xv6的三级页表机制和内存管理,熟悉内存相关函数用法。详细追踪系统调用切换用户态/内核态的过程,理解trap机制。

3. 阅读源码,分析进程管理

    阅读xv6启动过程,main函数中有关进程的部分。对xv6的进程创建、装载、调度、运行、出让CPU的过程有一个了解。
    下面图中是推荐学习路线,及每个阶段完成的任务。
推荐学习路线

四、关于系统调用

    在分析trap机制前,我们把trap看作一个黑盒,分析系统调用的过程。以ps为例,整个过程的起点是用户程序调用ps系统调用,然后跳转到ps在用户态的入口usys.S/ps,通过ecall进入trap;trap从用户态切换到内核态,之后执行内核函数syscall.c/syscall,通过函数指针数组syscalls调用ps system call的具体实现sysproc.c/sys_ps;执行一些内核代码,完成系统调用被要求的操作;再次通过trap从内核态切换回用户态。
系统调用过程
    实际上,我们在xv6 shell里面使用的ps是应用工具,它是由ps.c编译得到的。为了实现在shell输入ps,终端打印进程信息,我们实际需要编写应用工具ps.c和系统调用ps。在ps.c中使用ps system call完成用户需求。对于所有为xv6扩展的功能基本都是这个逻辑,所以我们既要修改kernel代码,也要修改user代码。添加过程如下图所示。结合系统调用的执行过程,可以理解每个添加文件在系统调用执行过程中发挥的作用。
添加系统调用过程
    建议先完成Lab2 trace和sysinfo的添加。参考网上的资料添加trace和sysinfo,理解系统调用执行过程。在学习内存管理、进程管理、文件管理后,再为xv6添加更多系统调用。

五、关于内存管理

    xv6采用三级页表结构,将39位的虚拟地址映射到56位的物理地址。(由于RISC-V机器字长64位,所以虚拟地址和物理地址实际的表示都是64位,虚拟地址取64位中的低39位,物理地址则取56位)。虚拟地址39位被分成27位index和12位offset,其中index用于索引页表中的PTE(页表项),offset用于在index翻译的物理块中索引存储单元。
    一个物理页大小为4KB,每个页表占用一个物理页。一个PTE占64bit(8B),所以一个页表有512个PTE。PTE从高位到低位的内容是10位空闲位、44位PPN(物理块号)、10位标志位。
xv的三级页表

Task1 分析用户进程页表(init进程)

    控制vmprint函数打印init进程页表项信息,添加于kernel/exec.c
the position of vmprint
    GDB调试,设置断点于init.c/dup系统调用(查看user/init.asm得到断点地址),在QEMU窗口使用info mem命令查看页表映射关系。
init.asm
    根据打印出的信息,分析用户进程(init进程)页表。手工模拟三级页表翻译虚拟地址到物理地址的过程,确定左图索引的PTE编号是正确的。
分析页表
    此外,分析右图的虚拟地址和标志位,我们可以将这6个页对应到用户虚拟地址空间分布图。6个页从低地址到高地址分别对应text、data、guard page、stack、trapframe、trampoline
用户地址空间分布

六、关于trap机制

    trap机制在两个阶段发挥作用,一个是user mode到kernel mode切换的过程,另一个是kernel mode到user mode的过程。换句话说,在xv6中只要涉及到user/kernel mode的切换,必定需要trap机制提供支持。所以,不仅仅是系统调用,在很多关键的位置trap机制都要发挥作用。
    简单理解,从user到kernel,trap机制提供运行内核代码需要的环境;从kernel到user,trap机制恢复用户代码运行环境。
trap机制
    从user到kernel,起点是系统调用的ecall指令ecall指令修改cpu mode为kernel,保存用户程序被中断的位置(当前PC)到sepc寄存器,加载trampoline.S/uservec函数的入口地址(stvec寄存器的内容)到PC,实际上这可以看作程序跳转的底层实现。
    在trampoline.S/uservec函数中,保护32个用户寄存器、设置内核代码运行环境、切换页表。最后跳转到trap.c/usertrap函数
    trap.c/usertrap函数是成功切换到内核后第一个被执行的C程序,它会根据触发trap机制的原因,将我们带到不同的功能函数中去。在这里我们进入syscall,完成内核中关于系统调用的操作。
user到kernel
    syscall返回后,通过trap机制从kernel切换到user。在trap.c/usertrap中调用usertrapret;在usertrapret函数中,保存内核环境、做一些恢复设置。最后调用函数trampoline.S/userret;在trampoline.S/userret中,切换页表、恢复用户寄存器,最后通过sret指令返回用户态。
kernel到user
    值得注意的是,user/kernel两次页表切换都在trampoline.S文件中。这是因为trampoline.S所在的物理页,被同时映射到内核页表和所有用户页表,并且在内核页表和用户页表中拥有相同的虚拟地址。对于trampoline.S,在页表切换前后没有变化,cpu可以不间断的执行其中的程序。所以只有在trampoline.S中切换页表,程序才能不崩溃。

七、关于进程管理

    xv6中共有6中进程状态:UNUSED, USED, RUNNABLE, RUNNING, SLEEPING, ZOMBIE。
进程状态
    不同进程状态可以相互转换,实现转换的函数都是内核实现。其中相当重要的状态是RUNNING,因为进程的状态转换都发生在内核,但进程的运行,也即RUNNING状态,是在用户态发生的。不管是RUNNABLE->RUNNING,还是RUNNING切换到其他状态,出让cpu,都会涉及到cpu状态的切换。汇编函数swtch在状态切换过程中发挥关键作用,进程在swtch中切换上下文,接着通过trap机制实现cpu状态的切换。
进程转换图
    在开始下面的内容前,需要先知道一个概念。xv6中预先设置了系统中用户进程的最大数量NPROC,并通过PCB表proc[NPROC]统一管理所有可用的PCB。在最开始,一个用户进程都没有的时候,proc中所有进程控制块的状态都是UNUSED,它们都是可分配的PCB(pid可用)。
预先定义
pcb表

    一个进程从无到有的过程大致是这样:系统发出创建进程的请求(userinit或者fork),alloproc为进程创建页表、分配PCB,一个UNUSED进程切换状态为USED;接着在userinit或者fork中,为新创建的进程装载代码和数据,进程状态从USED变成RUNNABLE;通过scheduler调度程序,进程状态被设置为RUNNING,切换回用户态运行进程;时间片到,或因其他原因需要出让cpu时,用户态切换回内核态,进程状态从RUNNING切换成或RUNNABLE,或SLEEPING,或ZOMBIE,视引发状态切换的原因而不同。至此,便是进程从创建到运行的过程。
进程的诞生


未完待续...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值