Long long time ago, I finished Lab2. And now, let me face Lab3数据结构
目标:创建用户环境,能够追踪进程的运行状况,能够建立一个新的用户环境。也要完成系统调用和可能引起的异常架构
在这门课看来,环境和进程是能够对等的,都指程序运行期间的抽象。不直接叫进程是由于jos中实现的系统调用和UNIX是有差异的函数
描述环境的数据结构线程
首先是ID,定义一个新的类型,32位整型的,使用最低的10位来表示进程ID,也就是最多支持1024个进程/线程3d
进程的状态有如下几种指针
Env数据结构能够完整的描述一个进程code
其中EnvType就一种blog
这个数据结构基本上保存了进程调度与运行所须要的一切,首先是寄存器的全部状态,进程ID, 父进程ID,进程类型和状态,以及很是重要的页表目录的地址进程
JOS的进程描述符仍是相对简陋的,由于如今的JOS不支持多个进程,也不支持多核,在XV6中则有支持ip
进程描述符链表
进程描述符是在系统启动的时候所有生成好了的
将全部的进程描述符所有连成一个链表,这样能够省去不少新建和释放进程描述符的工做
这个和初始化内核区域的页描述符是很是相似的,有了前面的代码,这里也就好说多了
在建立了这部分数据结构以后,须要注意的是,这些描述符属于内核的数据结构了,须要将他们映射到特定的虚拟内存位置上,而且设定为用户可读的
建立于运行进程
因为如今尚未创建文件系统,因此说加载的可执行文件是嵌入到内核中的
在进行编译连接的时候,链接器直接把一个Elf文件连接进了内核的可执行文件中
前面已经为进程描述符表分配了内存空间,如今要初始化这些描述符,就是将全部的描述符的进程id置位0,状态置位free,而后依次的放入到空闲列表中,初始化的工做在循环开始前的memset就直接完成了
就是要为当前的进程分配一个页,用来存放页表目录,同时将内核部分的内存的映射完成
全部的进程,不管是内核仍是用户,在虚地址UTOP之上的内容都是同样的。
而对于内核而言,UTOP之上的映射已经在LAB2中完成了,以下所示
这个函数中完成了虚地址UPAGES和UVPT和内核栈的映射
再看一下内存图
其中的UVPT都是存放页表目录的地方,对于内核而言,是存放内核页表目录的地方,而对于用户是存放用户页表目录的地方
而UPAGES是存放内核页表的地方
因此对应了那句话:UTOP之上的映射都是同样的
下面看看实际函数是怎么实现的
首先申请一个物理页,而后将其做为页表目录的所在,注意虚地址和实地址的转换,由于在UTOP之上都是同样的,因此能够直接把kern_pgdir的内容所有拷贝过来
可是惟独UVPT这个地方是不同的,由于要放的是本身的页表目录
就是根据给定的也表目录,申请长度为len的内存,并将其映射到虚地址va上去
首先须要将va和len进行对齐,而后将申请到的页插入到页表目录中去
这个函数是但愿将以前直接连接进内核的可执行二进制文件取出来执行
那么首先要作的事情,就是解析elf文件的文件头和各段的程序头,而后根据程序头文件中的信息,将指定字节的信息拷贝指定的虚地址处
可是这里须要注意的是:
这里的拷贝到指定的虚地址处,是指用户空间的虚地址,而不是内核空间的虚地址,因此还须要用lcr3函数加载用户空间的页表目录才能将地址转换为用户空间地址
而载入elf文件并开始执行的程序段在boot/main.c中有,能够参考那部分代码
这里的memsz和filesz不一致的缘由,是由于在编译以后,elf文件中的bss段中存在一些没有被初始化的静态变量,这些变量不占用文件存储空间,可是在实际载入以后会占用内存空间
注意上面还须要初始化用户栈
建立一个新的进程须要作的事情基本都在这里了
首先是申请一个进程描述符,调用前面的env_setup_vm函数来完成页表目录的设置和内核区域的映射,而后将制定的二进制文件载入到内存中来
先看env_alloc函数
而后就是env_create函数
最后一句env_pop_tf函数,就是将当前进程的trapframe经过弹栈的形式,切换当前的运行环境
前面这一部分就是进程建立的整个流程,能够参考下面的调用关系图
可是如今这个系统仍是跑不起来,由于尚未异常处理机制
当系统启动完成,会加载连接到内部的那个程序,而后执行,可是由于这个程序内部会调用int指令,因此会产生中断,可是尚未创建任何容许从用户空间到内核空间的方式,因此会产生一次保护异常,而后发现保护异常也处理不了,而后又发生了一次异常,直到发生了三次保护异常,CPU就会重置
为了可以让在中断或者异常发生的时候,当前运行的代码不会随机的选择如何进入内核或者怎样进入内核,而是由内核精细的控制的,基本上基于如下两种方式
指的是用来暂时存储引起中断或者异常的进程的状态。
当发生了终端或者异常的时候,若是发生了从用户态到内核态的转换,也须要切换运行栈。那么任务状态段(task state segment)指明了这个栈的段选择子和地址。处理器会将各类寄存器和错误码都压入到新的栈中。而后从中断描述符中加载CS EIP等,而后让ESP和SS指向新的栈
在ucore中,入口向量是直接生成的,就是把所有的256项都列出来,可是jos不是这么作的,在trapentry.S内先定义了两个宏,而后利用这两个宏来定义相应中断的入口
给定一个全局函数名和中断号,而后所作的就是和ucore中相似的了,若是须要压错误码的话,由CPU来完成这件事,可是若是没有错误码的话,就压入一个0,这样能够保证结构体trapframe得结构保持一致
下面是利用上面给定的宏定义的针对特定中断的处理例程
全部的中断,都是要先压入错误码,而后压入中断号,接下来的事都是同样的了,就是继续压栈,在栈上造成一个结构体,以esp为指针
这里的trapframe的定义与ucore中稍有不一样
trapframe中少了fs和gs
而后就是在trap.c中初始化中断向量表了
注意断点和系统调用的权限设定,SETGATE的最后一个参数表示的是引起该中断须要的特权级,很明显,系统调用和断点是能够在用户态下引起的,而其余的则是由于错误而陷入到了内核中