os-lab3
本文最后更新于 2024年9月6日 下午
1.思考题
3.1
e->env_pgdir[PDX(UVPT)] = PADDR(e->env_pgdir) | PTE_V中,
UVPT:用户页表的起始处的内核虚拟地址
PDX(UVPT):UVPT所处的页目录号(即 UVPT 处于第 PDX(UVPT) 个页目录项所映射的4MB空间;联系 UVPT 的含义,因此页目录也被第PDX(UVPT)映射)
e->env_pgdir:进程 e 的页目录的内核虚拟地址
PADDR(e->env_pgdir):进程 e 的页目录的物理地址
PADDR(e->env_pgdir) | PTE_V:页目录的物理基地址加上权限位
页目录基地址=UVPT+UVPT>>10
页目录的Page number=(UVPT+UVPT>>10)>>12;
页目录中映射到页目录基地址的项A的地址=UVPT+Page number*4=UVPT+UVPT>>10+UVPT>>20
页表项A所在的页目录号=(项的地址-页目录基地址)/4=UVPT>>20>>2=PDX(UVPT)
e->env_pgdir[PDX(UVPT)]:代表项A中存储的物理页号和权限位,即页目录的物理基地址加权限位
3.2
data是传入的进程控制块指针,在load_icode_mapper中有struct Env *env = (struct Env *)data;
在建立虚拟地址到物理地址的页表映射的时候提供env_pgdir和env_asid;
如果没有data,load_icode_mapper就不能知道当前进程空间的页目录基地址和asid。
3.3
- 若
va不按照PAGE_SIZE对齐,算出offset,将offset中的bin数据写入对应的内存地址并建立页表映射关系。 - 将段内的每一页数据写入对应的内存地址并建立页表的映射关系。
- 若发现段在内存的大小
sg_size大于在ELF文件中的大小bin_size,则需要把多余的空间用0填充满
3.4
这里的env_tf.cp0_epc字段指示了进程恢复运行时PC应恢复到的位置,对于CPU来说,cpu直接接触的地址是虚拟地址,所以env_tf.cp0_epc中存的是虚拟地址
3.5
handle_int在genex.S中用mips宏定义实现,而handle_mod和handle_tlb中在genex.S仅仅有宏定义,实际在函数do_tlb_mod和do_tlb_refill中实现。
3.6
在这段代码中,时钟中断的开启和关闭是通过对 CP0_CAUSE 寄存器和 CP0_STATUS 寄存器进行操作来实现的。
时钟中断的开启:
MOS中,时钟中断的初始化发生在调度执行每一个进程之前。从代码角度,就是在env_pop_tf中调用了宏RESET_KCLOCK,随后又在宏 RESTORE_ALL 中恢复了 Status 寄存器,开启了中断。
时钟中断的关闭:
在.text.exc_gen_entry处将UM、EXL和IE位清零,确保处理器处于内核模式,并且禁用中断。
3.7
进程装在两个队列中,一次运行一个进程。定时器周期性产生中断,使得当前进程被迫停止,进入.text.exc_gen_entry,逐步执行到schedule调度函数,若该进程时间片还未用完,则可用时间片数量-1,继续执行该进程,否则会切换到下一个进程,保存上下文,并将原来的进程送到调度队列的末尾,若进程不处于RUNNABLE状态,则会进行其他处理。
难点分析
- 进程的创建
1 | |
在本次实验中,PCB控制块实则以Env的结构体体现出来,实验中前几个函数都涉及到这个结构体的内容,需要认真理解。
- 加载二进制镜像到内存中
其中
load_icode函数的理解较难,调用了elf_load_seg函数,需要理解ELF文件的段是如何加载到内存中的。这里的情况较为复杂,首先需要考虑虚拟地址对齐的问题,其次还需要考虑段在文件中的大小和在内存中的大小问题,如果前者小于后者,需要进行补0操作。同时,这里也涉及lab2内存管理的一些内容,我们需要调用
page_alloc,page_insert等函数,用页的方式加载段到内存中,同时建立起页表中虚拟地址和物理地址的映射关系。
- 时钟中断机制的理解
这里涉及的函数较多,还涉及很多mips的宏定义和函数定义,理解起来十分复杂。需要根据时钟中断的步骤来理清每个函数的调用过程。
RESET_KCLOCK宏将 Count 寄存器清零并将 Compare 寄存器配置为我们所期望的计时器周期数,这就对Timer完成了配置。在设定个时钟周期后,时钟中断将被触发。- MOS中,时钟中断的初始化发生在调度执行每一个进程之前。从代码角度,就是在
env_pop_tf中调用了宏RESET_KCLOCK,随后又在宏RESTORE_ALL中恢复了 Status 寄存器,开启了中断。 - 一旦时钟中断产生,就会触发4KC硬件的异常中断处理流程。系统将 PC指向
0x80000180,
跳转到.text.exc_gen_entry代码段执行。对于时钟引起的中断,通过.text.exc_gen_entry代码段的分发,最终会调用handle_int函数进行处理。 handle_int函数根据 Cause 寄存器的值判断是否是 Timer 对应的 7 号中断位引发的时钟中断,如果是,则执行中断服务函数timer_irq,跳转到schedule中执行。schedule根据不同的中断类型调用不同的处理函数,再调用env_run运行进程。
实验体会
lab3的课下实验相较于lab2较为简单,但是进程的机制理解起来比较复杂,涉及到内存管理,进程切换,进程创建等方方面面。其中特别是对进程调度的核心算法和流程的理解十分重要。
对于进程调度的总结:e->env_pri : 进程每次运行的时间片数量;
curenv全局变量:当前正在运行的进程(在第一 个进程被调度前为 NULL);
count静态变量:当前正在运行进程的剩余时间片数量 = 当前进程剩余的执行次数;
env_sched_list调度队列:存在且只存在所有就绪(状态为 ENV_RUNNABLE)的进程,其中也需要包括正在运行的进程;
当满足下列条件中的任意一个时,需要进程切换:
- 尚未调度过任何进程(
curenv == NULL) - 当前进程已经用完了时间片(
count == 0) - 当前进程不再就绪,如:被阻塞或退出(
e->env_status != ENV_RUNNABLE) yield参数指定必须发生切换(yield != 0)- 无需进程切换时,将剩余时间片数量
count减去 1,然后调用env_run函数;
需要进程切换时,判断当前进程是否仍然就绪,如果是则将其移动到调度链表的尾部。之后,我们选中调度链表首部的进程来调度运行,将剩余时间片数量设置为其优先级。
调度函数 schedule 以及其中逐级调用的 env_run、env_pop_tf 和 ret_from_exception 函数都是不返回(no return)的函数,被调用后会从内核跳转到被调度进程的用户程序中执行。在 MIPS 中通常使用 j 指令而非 jal 调用不返回的函数,因为它们不会再返回到其调用者。
