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_pgdirenv_asid
如果没有dataload_icode_mapper就不能知道当前进程空间的页目录基地址和asid。

3.3

  1. va不按照PAGE_SIZE对齐,算出offset,将offset中的bin数据写入对应的内存地址并建立页表映射关系。
  2. 将段内的每一页数据写入对应的内存地址并建立页表的映射关系。
  3. 若发现段在内存的大小sg_size大于在ELF文件中的大小bin_size,则需要把多余的空间用0填充满

3.4

这里的env_tf.cp0_epc字段指示了进程恢复运行时PC应恢复到的位置,对于CPU来说,cpu直接接触的地址是虚拟地址,所以env_tf.cp0_epc中存的是虚拟地址

3.5

handle_intgenex.S中用mips宏定义实现,而handle_modhandle_tlb中在genex.S仅仅有宏定义,实际在函数do_tlb_moddo_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. 进程的创建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Control block of an environment (process).
struct Env {
struct Trapframe env_tf; // saved context (registers) before switching
LIST_ENTRY(Env) env_link; // intrusive entry in 'env_free_list'
u_int env_id; // unique environment identifier
u_int env_asid; // ASID of this env
u_int env_parent_id; // env_id of this env's parent
u_int env_status; // status of this env
Pde *env_pgdir; // page directory
TAILQ_ENTRY(Env) env_sched_link; // intrusive entry in 'env_sched_list'
u_int env_pri; // schedule priority

// Lab 4 IPC
u_int env_ipc_value; // the value sent to us
u_int env_ipc_from; // envid of the sender
u_int env_ipc_recving; // whether this env is blocked receiving
u_int env_ipc_dstva; // va at which the received page should be mapped
u_int env_ipc_perm; // perm in which the received page should be mapped

// Lab 4 fault handling
u_int env_user_tlb_mod_entry; // userspace TLB Mod handler

// Lab 6 scheduler counts
u_int env_runs; // number of times we've been env_run'ed
};

在本次实验中,PCB控制块实则以Env的结构体体现出来,实验中前几个函数都涉及到这个结构体的内容,需要认真理解。

  1. 加载二进制镜像到内存中
  • 其中load_icode函数的理解较难,调用了elf_load_seg函数,需要理解ELF文件的段是如何加载到内存中的。这里的情况较为复杂,首先需要考虑虚拟地址对齐的问题,其次还需要考虑段在文件中的大小和在内存中的大小问题,如果前者小于后者,需要进行补0操作。

  • 同时,这里也涉及lab2内存管理的一些内容,我们需要调用page_alloc,page_insert等函数,用页的方式加载段到内存中,同时建立起页表中虚拟地址和物理地址的映射关系。

  1. 时钟中断机制的理解


这里涉及的函数较多,还涉及很多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_tfret_from_exception 函数都是不返回(no return)的函数,被调用后会从内核跳转到被调度进程的用户程序中执行。在 MIPS 中通常使用 j 指令而非 jal 调用不返回的函数,因为它们不会再返回到其调用者。