lab3 实验报告

思考题

Thinking 3.1

image-20220427224819093

env_id = (asid << 11) | (1 << 10) | index,其保证了每一个进程控制块的id唯一。

在进行env_id != envid判断前仅仅只利用了index后10位进行偏移找到进程控制块e,无法保证高6位的asid信息相同。如果不判断,无法保证所取到的env为所需要的。

Thinking 3.2

image-20220428080549794

A1:
UTOP是用户读写部分的最高地址,ULIM是用户进程的最高地址。
UTOP和ULIM之间的区域为用户进程的进程块还有页表,只能读。

A2:
env_cr3是页目录的物理地址
pgdir[PDX(UVPT)]=env_cr3利用了页目录自映射,对应了页目录的页目录项。

A3:
进程对应的都是虚拟地址;对于进程而言,物理地址只能通过在查询页表获得。
不同的进程有着自己独立的虚拟地址空间和映射关系。不同的进程中相同的虚拟地址可能映射到不同的物理地址,不同的虚拟地址可能映射到相同的物理地址。
在有一些OS中,MMU的填写是由硬件自动完成的,设计师无需花费太多精力考虑物理地址。在我们的MOS中,需要花大量的时间考虑物理地址的填写,是因为其硬件无法自己实现重填,需要软件的参与。
CPU运行和进程内部保存的几乎都是虚拟地址,对于程序而言,使用虚拟地址来运行,虚拟地址到物理地址的转换这一步由OS负责,对进程程序不可见。

Thinking 3.3

image-20220428100155594

来源:通过阅读代码,发现load_elf()load_icode_mapper()存在user_data,使用时,两个函数传入的数据都是进程控制块e。

不能删去此参数,因为user_data是向内层函数int *map()load_icode_mapper()传递的参数。
若缺失,load_icode_mapper()就无法获取所需的参数

实际例子:C语言标准库stdlib.h 中的qsort()函数。第3个参数size是为了给函数内部调用compar()提供信息。qsort()的声明如下

void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))

Thinking 3.4

image-20220428102051534

根据vava + bin_size是否页对齐,一共可以分成4种情况。

都考虑到了。

Thinking 3.5

image-20220428102336310

A1:
我认为是虚拟地址:理由有3:

  1. 此pc值会供CPU使用,而CPU只会发出虚拟地址
  2. pc指向了一段连续的地址空间,这种“连续性”只有虚拟地址才具有
  3. 通过阅读函数load_icode(),有:env_tf.pc = entry_point,在load_elf()中,有*entry_point = ehdr->e_entry,对于一个elf可执行文件,其内部存储的是虚拟地址

A2:

​ 我认为其对每个进程都是一样的。
​ entry_point的值为一个虚拟地址,将其值确定下来相当于规定了文件入口位置。虚拟地址相同意味着页表项相同,只是其对应的物理地址不同。OS可以对每个可执行文件执行相同的操作,同时找到正确的物理地址。
​ 因此可知其能降低elf可执行文件和操作系统的复杂度,方便操作。

Thinking 3.6

image-20220429005051801

epccp0_epc,因为其内部保存的是进程发生中断时的指令地址。当恢复进程时,应从发生中断的指令开始往下执行,因此要将pc设为cp0_epc

Thinking 3.7

image-20220429005148329

A1:

操作系统在发生时钟中断时将原进程env_tf中的内容保存到TIMESTACK区域

handle_int函数中通过SAVE_ALL实现功能。

SAVE_ALL中会根据CP0_CAUSE寄存器中的值选择栈顶,对于时钟中断,会设置栈顶为0x82000000,即TIMESTACK

image-20220510091122759

A2:

TIMESTACK时发生时钟中断时的栈顶指针,KERNEL_SP是发生其他中断时的栈顶指针。

Thinking 3.8

image-20220504091508748

handle_int:在./lib/genex.S

handle_sys:在./lib/syscall.S

handle_mod和handle_tlb两者都是通过genex.S文件中的宏函数BUILD_HANDLER实现的。

Thinking 3.9

image-20220504091524717

LEAF(set_timer)
    li t0, 0xc8
    sb t0, 0xb5000100	#在地址0xb5000100中写入0xc8,设置时钟频率为200
    sw  sp, KERNEL_SP	#保存sp值到KERNEL_SP
setup_c0_status STATUS_CU0|0x1001 0	 #设置CP0的SR寄存器,第1,13位置1
    jr ra				#函数结束,返回
    nop 
END(set_timer)
timer_irq:
    sb zero, 0xb5000110		#在地址0xb5000100中写入0,响应时间中断,暂停时钟
1:  j   sched_yield			#跳转到调度函数,进行调度
    nop 
    j   ret_from_exception	#跳转到ret_from_exception
    nop 

Thinking 3.10

image-20220504091555846

有两个env_sched_list用于存储进程队列。

当进程被创建时,插入env_sched_list[0]的队头。

当发生时间片中断,在handle_int函数的最后会跳转到sched_yield函数。

在sched_yield()中,若当前进程还能继续运行,则继续。否则就会选出下一个要运行的进程。(具体选择next_env的方式此处略过)

实验难点展示

lab3实验难点

时钟中断的过程梳理

个人认为这是本次实验的核心和难点:理解OS处理异常的过程。

lab3时钟中断

时间片轮转算法

  • 若当前进程还能运行,就接着运行
  • 若当前进程的时间片已经运行完
    • 将要结束的进程的进程控制块插入另一列表的尾部
    • env_sched_list[cur_head_index]中寻找下一进程
      • 当前队列若空,则切换队列,cur_head_index = 1 - cur_head_index
      • 找到env_status == ENV_RUNNALE的进程,跳出寻找过程
      • 对于env_status != ENV_RUNNALE的进程,将其插入另一队列的开头(之后就都会移回来)
    • 如果没有找到,接着在另一队列中寻找下一进程
      • 当前队列若空,则返回错误值
      • 找到env_status == ENV_RUNNALE的进程,跳出寻找过程
      • 对于env_status != ENV_RUNNALE的进程,将其插入另一队列的开头
    • 设置时间片运行次数,并运行选中的进程

体会与感想

本次实验让我们能够创建相关进程并且通过异常处理实现进程调度(时间片轮转)。

lab3与之前的实验有着较强的关联,需要大量使用之前lab中完成的函数,且有很多需要相互调用的新函数,阅读起来难度较大。只有事先了解清楚函数的大概作用,捋清了它和整体之间的关系,才能比较高效的理解代码。感受最明显的就是在load_icode(),load_icode_mapper(),load_elf()三个函数中,如果在写函数之前不知道每一个函数的功能和其在整体之中大概的作用,就很难理解。

此外,在使用函数时还要弄明白使用每个函数的限制条件。例如bzero(start, len),其要求start与4对齐,否则会直接循环卡死。在无法保证起始地址与4对齐的地方,可以复制bzero()的代码块,仅使用其中按字节置0的部分。

posted @ 2022-05-20 20:15  tantor  阅读(88)  评论(0编辑  收藏  举报