北航操作系统课程lab3实验报告

OS lab3 实验报告

实验思考题

Thinking 3.1

思考envid2env 函数:

为什么envid2env 中需要判断e->env_id != envid 的情况?如果没有这步判断会发生什么情况?

在判断前,实际上已经有了e = &envs[ENVX(envid)];这表明e->env_id一定与envid在低10位上相同,因为e的产生就是通过低10位的序号来访问struct Env结构体的。但在ASID上却并不一定相同。若没有这步判断,则会查询到一个不存在的进程,导致程序出错。

Thinking 3.2

结合include/mmu.h 中的地址空间布局,思考env_setup_vm 函数:

• UTOP 和ULIM 的含义分别是什么,UTOP 和ULIM 之间的区域与UTOP以下的区域相比有什么区别?

• 请结合系统自映射机制解释代码中pgdir[PDX(UVPT)]=env_cr3的含义。

• 谈谈自己对进程中物理地址和虚拟地址的理解。

UTOP = 0x7f400000,是为用户所能操纵的地址空间的最大值;ULIM = 0x80000000,是操作系统分配给用户地址空间的最大值。两者之间的区域对用户进程而言是一个只读片段,保存着页表、struct Pagestruct Env,显然这些都是用户程序不允许动的。UTOP以下的区域就可以被用户程序所读写。

由自映射机制可得,pgdir[PDX(UVPT)]就代表页表起始地址所对应的页目录项env_cr3是该进程的页目录物理地址,这样即可通过页表项的虚拟地址准确找到物理地址。

可以理解为多个进程都可以使用UTOP以下的虚拟地址,但若进程间没有共享段的话,相同的虚拟地址对应于不同的物理地址。进程直接操作虚拟地址,操作系统则需要建立起不同进程的虚拟地址对物理地址的映射关系。

Thinking 3.3

找到 user_data 这一参数的来源,思考它的作用。没有这个参数可不可以?为什么?(可以尝试说明实际的应用场景,举一个实际的库中的例子)

 //最先调用的是load_icode函数
 load_elf(binary, size, &entry_point, (void*)e, load_icode_mapper);
 //在调用的load_elf函数中,将(void *) e 转化为 (void *) user_data,这体现在函数的定义上
 int load_elf(u_char *binary, int size, u_long *entry_point, void *user_data,
              int (*map))
 //也体现对load_icode_mapper函数的调用上
     map(phdr->p_vaddr, phdr->p_memsz, binary + phdr->p_offset, phdr->p_filesz, user_data)
     //可见最后一个参数赫然是原先的进程指针
 //在函数load_icode_mapper中也证实了这一点
 struct Env *env = (struct Env *)user_data;

如果没有这个进程指针,那么后续步骤将无法完成。感觉绕这么大一个弯子好像就是想要将这个进程指针一直传递下去。

Thinking 3.4

结合load_icode_mapper 的参数以及二进制镜像的大小,考虑该函数可能会面临哪几种复制的情况?你是否都考虑到了?

可以直接考虑最复杂的情况,即va不页对齐,va+bin_size也不页对齐,va+sg_size也不页对齐。在加载.text && .data段和.bss段时先判断初始是否页对齐,然后再判断结尾是否页对齐。

Thinking 3.5

思考上面这一段话,并根据自己在lab2 中的理解,回答:

你认为这里的 env_tf.pc 存储的是物理地址还是虚拟地址?

• 你觉得entry_point其值对于每个进程是否一样?该如何理解这种统一或不同?

pc当然是存的虚拟地址呀,计组做过无数遍了(-!-)

对每个进程都一样。*entry_point = ehdr->e_entry;尽管不同进程其实虚拟地址一样,但加载的二进制文件、页表肯定是不一样的。每个进程起始地址统一会降低操作系统的复杂度,但在部分相同虚拟地址中的内容不同也区分了不同的进程。

Thinking 3.6

请查阅相关资料解释,上面提到的epc是什么?为什么要将env_tf.pc设置为epc呢?

EPC寄存器是CP0寄存器组中的一个寄存器,用来存放异常中断发生时进程正在执行的指令地址(一般该地址对应的指令还未被执行)。切换进程时,相当于施加了一个异常,这时硬件会自动帮我们把当前pc保存在EPC寄存器中,所以下次再轮到这个进程执行时,直接从该pc对应地址开始执行,而不是从头执行。

Thinking 3.7

关于 TIMESTACK,请思考以下问题:

操作系统在何时将什么内容存到了 TIMESTACK 区域

TIMESTACK 和 env_asm.S 中所定义的 KERNEL_SP 的含义有何不同

env_destoryenv_run函数中利用到了TIMESTACK区域,前者将自身进程栈中存放的内容赋值到该区域,后者将该区域的内容复制到当前进程的状态中,以便切换到下一进程。

 //在stackframe.S中,异常处理会将栈指针置于TIMESTACK处
 //这样就能在发生异常时将当前进程状态存入TIMESTACK
 //env_destory销毁本身进程时也是如此
 li  sp, 0x82000000
 lw  sp, KERNEL_SP

Thinking 3.8

试找出上述 5 个异常处理函数的具体实现位置。

handle_int函数在genex.S文件中,handle_sys函数在syscall.S文件中。另外三个函数handle_reservedhandle_tlbhandle_mod都在genex.S文件中,没有直接明确的函数名,是靠拼接而成,具体声明位于最后,但定义在最开始。

Thinking 3.9

阅读 kclock_asm.S 和 genex.S 两个文件,并尝试说出 set_timer 和timer_irq 函数中每行汇编代码的作用

 LEAF(set_timer)
 //对定时器的初始化
 •    li t0, 0xc8
 •    sb t0, 0xb5000100
 //向0xb5000100地址写入0xc8
 •    sw  sp, KERNEL_SP
 //保存当前栈指针
 setup_c0_status STATUS_CU0|0x1001 0
 //把CP0_STATUS第12位和第0位置1,允许4号中断,并表示开启了中断,禁止再次响应中断
 •    jr ra
 //函数返回
 •    nop
 ​
 END(set_timer)
 ​
 ​
 timer_irq:
 ​
     sb zero, 0xb5000110
 //向地址0xb5000110写入0,不理解为什么
 1:  j   sched_yield
 //展开对进程的调度
     nop
     /*li t1, 0xff
     lw    t0, delay
     addu  t0, 1
     sw  t0, delay
     beq t0,t1,1f    
     nop*/
     j   ret_from_exception
 //跳转到ret_from_exception函数,执行rfe指令
     nop

Thinking 3.10

阅读相关代码,思考操作系统是怎么根据时钟周期切换进程的。

进程装在两个队列中,一次运行一个进程。定时器周期性产生中断,使得当前进程被迫停止,通过执行sched_yield函数,来进行进程的调度,若该进程时间片还未用完,则可用时间片数量-1,否则会切换到下一个进程,保存上下文。并将原来的进程送到另一个队列的末尾,若进程不处于RUNNABLE状态,则会进行其他处理。

实验难点展示

env_setup_vm函数

想要填好这个函数,就必须得明白用户虚拟地址空间的一些宏定义的段所代表的含义。

  o      ULIM     -----> +----------------------------+------------0x8000 0000
  o                      |         User VPT           |     PDMAP                 
  o      UVPT     -----> +----------------------------+------------0x7fc0 0000    
  o                      |         PAGES              |     PDMAP                 
  o      UPAGES   -----> +----------------------------+------------0x7f80 0000    
  o                      |         ENVS               |     PDMAP                 
  o  UTOP,UENVS   -----> +----------------------------+------------0x7f40 0000    

UVPT相当于是新进程的页表了,在对页目录更新时,我们需要将这部分的物理地址给存到自映射下的页目录项里,方便我们对页表进行访问。而PAGES和ENVS分别对应着1024个页面和1024个进程,映射到固定的物理地址,在进程切换过程中是不能由进程本身去更改的,相对于各个进程各自的地址空间,这部分可以理解为全局变量,所以在对页目录的更新时,不能像对UTOP以下的地址空间那样直接赋值0,而是和boot_pgdir一样映射到固定的那部分物理地址。

这个函数初始化了进程的地址空间的内核部分,用户部分由于还没用,所以页目录项都为0。

load_icode_mapper函数

这个函数写的我很有成就感,因为可以很轻易的搞懂这部分想要干什么,其他函数或许得仰仗往届学长了。前一段是要用bcopy,后一段是要用bzero。函数的目的就是把&entry_point地址的数据给加载到一个段的起始地址上。途中会有三处地址不对齐的地方,分别是起始地址、bin_size结束地址、sg_size结束地址。具体实现部分不再赘述,但有一点需要意识到,当初始地址不对齐时,需要检测这个初始地址对应页面是否已经被分配且使用了,如果是,则不需要额外分配,如不是,则需要重新分配。

env_run函数

注释让我参照env_destory函数,但还是有很大不同的。这里如果是用结构体进行赋值的话是没有问题的,而且不用bcopy使得函数看起来还很简洁,但当我使用bcopy的时候,却没有过得了测试点,原因我现在也没搞清楚,但在我看来两者应该是等价的才对。在这个函数中,需要设置要运行的这个进程状态为RUNNABLE,env_run这个参数表明的是该进程运行次数,e->env_run++可以放在这个函数里,也可以放在sched_yield函数里,因为正常情况下后者都会调用一次前者,当然,最主要的是env_run这个参数目前其实也用不到,无所谓了。

sched_yield函数

这是中断异常模块唯一一个让我们写的函数。这个函数有很多种写法,最主要的是要考虑取出来的进程如果不是RUNNABLE状态时,应该怎么办,我的想法是如果函数时FREE状态,则直接将其从所在的列表删掉,如果是NOTRUNNABLE状态,则把它当成时间片用完的进程处理,将其放到另一个列表队尾,等待其获得所需资源改变状态。反正就是要找到一个能够运行的进程出来,至少应该有一个处于RUNNABLE状态的进程才能使得这个函数正常运行,此外,该函数的实现表明也有可能有如下情况出现:即当前进程的时间片用完后,该调度函数仍然选择当前进程作为下一个执行的进程。这也与JAVA里的yield()函数有异曲同工之妙啊。

流程图

 

这是PPT里的一张函数调用关系图,感觉我如果要画可能应该也不会比这个详细了,主要是懒^_^

创建一个进程需要如下步骤:

1、从空闲PCB链表中申请一个PCB块(env_alloc)

2、初始化新进程的地址空间(env_setup_vm),这需要申请页面作为新进程的页目录(page_alloc)

3、加载二进制镜像并设置堆栈、pc(load_icode),这需要申请页面来储存二进制文件(page_alloc)

运行一个进程之前要保存原进程的上下文(将 TIMESTACK区域的值存入自己的PCB中),设置要运行进程的上下文。上下文包括了页目录位置、寄存器值、pc值。

中断异常的产生与处理:

1、kclock_init调用set_timer初始化时钟产生周期性中断

2、这个中断异常被except_vec3所检测到,CAUSE寄存器应该是硬件设置的,该函数检测异常类型,并跳到相应的异常处理地址。

3、handle_int用于处理中断异常,保存进程上下文(将寄存器的值存入TIMESTACK区域中)

4、若产生的是4号中断,则进入time_irq中,调用sched_yield,表明当前进程已经用完了一个时间片。

体会与感想

这部分的难点主要在调试,跳板机上多进程的调试让人很头大,也不知道什么时候会切换进程,所以debug是要虚空de了。

这部分的Exercise比lab 2简单,中断异常的地方甚至只需要复制代码就行。但在填写过程中还是遇到了很多问题,对PCB的初始化设置究竟需要考虑到哪些参数是没有明说的,以及进程调度函数中的代码有些是不是真的需要也不太知道,就是说删掉后也可以,可能没有相应的测试点,亦或许根本不重要。

这部分最难的是汇编函数,但没有让我们写,所以整体上还比较快乐的。其实我们没必要知道太多关于寄存器的细节,甚至在中断异常时,寄存器的各个位的意义,如何变化这对我而言都不太能理解,也不太想理解,而且感觉也不怎么用得到。

在上述说到的这种情况下,很难在这个lab中找到bug,希望不要带到后续实验中吧。

posted @ 2022-05-02 23:06  南风北辰  阅读(1302)  评论(0编辑  收藏  举报