os-lab3笔记


lab3

实验目的:

  • 创建一个进程,并成功运行
  • 实现时钟中断,通过时钟中断内核获得执行权
  • 实现进程调度,创建两个进程,通过时钟中断切换进程执行

使用进程控制卡Env来跟踪用户进程,并建立一个简单的用户进程,加载一个程序镜像到指定的内存空间,并让它运行起来。

进程

  • 由于没有实现线程,进程既是基本的分配单元,也是基本的执行单元。
  • 每个进程都是一个实体,有自己的地址空间,通常包括代码段、数据段和堆栈。
  • 程序是一个没有声明的实体,只有被操作系统执行时才能称为一个活动的实体,而执行中的程序,就是进程

PCB进程控制块

本质就是一个数据结构

env_init

traverse 是遍历的意思

段地址映射

base_pgdir分配了一页物理内存,将其转换为内核虚拟地址,并使用map_segment来进行映射。

  • pages映射到UPAGES
  • envs映射到UENVS

其中补全的部分我是这么写的

struct Page* pp = pa2page(pa + i);
page_insert(pgdir, asid, pp, va + i, perm);

其实可以把pp直接替换为中间那一部分。

注意是va+ipa+i

进程的标识

就是env_id

这里用自己的话解释一下为什么还需要一个asid

实际上是为了让每一个进程有一个自己的tlb,这样当这个进程结束时(或者进程切换的时候)只需要把这个进程的tlb刷新掉即可。

既然需要有一个自己的tlb那就得有一个标识符,那为什么不直接用env_id呢,我认为主要是为了让tlb只有有限个,太多了就炸内存了,这波属于是宁愿少点,不能多了。

创建进程

终于开始创建进程了

实际上创建进程在不考虑进程的交互性(也就是说这是个活的实体),实际上就是个数据结构,那么相应的数据结构就是PCB进程控制块。

因此也可以理解为设置一个进程控制块。

  • 设置进程控制块实际上是告诉了我们这个进程是什么
  • 那么进程还要有自己的地址空间,包括代码段、数据段和堆栈

OK,这里开始解释后面的进程调度和中断的开启。

在MIPS处理器中,中断是由硬件控制的,也就是CP0寄存器中保存着我们需要的信息。

那么具体是如何设置的呢?

这里我们考虑的相对简单,还只开启了时钟中断。

这里就是需要将IM7设置为1,表示时钟中断可以被响应,同时还有IE位,也就是Interupt Enable中断使能位要打开才能正确的中断

还有第二个问题,中断需要调度进程,同时需要进入内核态。

害,理论是真的很重要!!!

用户态的标志:EXL=0, UM=1

其他都是内核态

那么如果我们刚开始在内核态接受到了时钟中断,中断结束后我们还应当是在内核态,但是由于

eret

这个命令在所有进程调度的最后都会被执行,同时eret会进EXL设置为0(也就是希望返回到用户态),因此我们还需要手动维护一下EXL使得保持在内核态。

elf简单复习一下

ELF文件结构为:

  • ELF头,包括程序的基本信息,比如体系结构和操作系统,同时也把包括了节头表和段头表相对文件的偏移量offset
  • 段头表,program header table ,主要包含程序中各个段segment的信息,段的信息需要在运行时刻使用
  • 节头表,section header table,主要包括程序中各个节section的信息,节的信息需要在程序编译和链接的时候使用

加载二进制文件

ELF文件的类型有三种:可重定位文件,可执行文件,可被共享的对象文件。

这里指可执行文件

要加载一个ELF文件到内存,只需要将其中所有需要加载的program segment程序段到对应的虚拟地址上即可。(这里我认为主要是因为这里是可执行文件,所以只需要将各个需要在运行时刻使用的信息存放过去即可)

目前还是在内核态,alloc的一个页面是一个物理页面。

要得到其物理地址,由于是内核态,因此需要调用page2kva来得到物理地址。

这一点从page_alloc()中的memset((void*)page2kva(pp),0,PAGE_SIZE);可以初见端倪。

而且本身也非常合理。

给进程分配地址空间,实际上是将可执行文件的信息进行复制。

这里用到了一个回调函数,还算比较好理解。

load_icode函数最后需要将env_tf.cp0_epc设置为e_entry,这个是说当执行进程时还是需要从最开始这个可执行文件的开始位置开始执行。

创建进程

进程控制块也设置好了,地址空间也安排好了,终于可以创建进程了。

这里的创建进程是在内核初始化时直接创建进程。

  1. 分配一个新的Env结构体
  2. 设置进程控制块
  3. 将程序载入到进程的地址空间

image-20240422043001149

这里可以修改为

if (env_alloc(&e,0) < 0) return NULL;

进程运行与切换

要运行一个进程:

  • 需要保存当前进程的上下文(如果当前没有进程就pass)
  • 恢复要启动的进程的上下文,然后运行该进程

进程的上下文朴素的理解就是当时运行这个进程时硬件的情况,也就是各个寄存器的状态,也就是Trapframe的内容

包括

  • 通用寄存器
  • HILO
  • CP0的StatusEPCCauseBadVAddr寄存器

在这里寄存器状态保存的地方是KSTACKTOP以下的大小为TrapFrame的区域

image-20240422044027836

运行一个进程:

  1. 保存当前进程的上下文信息(没有就开摆)
  2. 切换curenv为即将运行的进程(这里还没有切换)
  3. 设置全局变量cur_pgdir为当前进程的页目录地址,在TLB重填是将用到这个全局变量
  4. 调用env_top_tf函数,恢复现场(就是即将运行的进程的现场/上下文信息)、异常返回

中断与异常

CP0寄存器

  • Status 状态寄存器,包括中断引脚使能,其他CPU模式等位域
  • Cause 记录导致异常的原因
  • EPC 异常结束后程序恢复执行的位置

处理异常是由硬件完成的,MIPS CPU处理一个异常的步骤包括

  1. 设置EPC指向从异常返回的地址
  2. 设置EXL位,强制CPU进入内核态,并禁止中断
  3. 设置Cause寄存器,记录异常发生的原因
  4. CPU从异常入口位置取指,此后交给软件处理,也即是我们的操作系统来处理

梳理

这里再把整个流程进行梳理

image-20240424160152458

进程创建

几个问题

  1. tests/lab3_*这几个里面的pre_env_run.c是干嘛用的,tests/lab3_4还有个quick_sort.c是干嘛的

    第一直觉感觉是创建一个进程,但是仔细一看还是不太清楚


文章作者: hugo
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 hugo !
  目录