操作系统 Lab3:进程与调度
一、实验思考题
Thinking 3.1
思考 envid2env 函数: 为什么 envid2env 中需要判断 e->env_id !=envid 的情况?如果没有这步判断会发生什么情况?
mkenvid(struct Env *e)
中生成的envid为(asid << (1 + LOG2NENV)) | (1 << LOG2NENV) | idx
,即asid|有效位|env结构体在envs数组的偏移
。- 而
envid2env(u_int envid, struct Env **penv, int checkperm)
中通过envid的偏移找到结构体e=envs+ENVX(envid);
。而此时不能确保所给envid中asid的有效性。所以需要通过再次判断e->env_id !=envid
保证进程块的asid. - 若此index所对的env已经更换,则新env结构体会对应新的ASID。如果没有这步判断,可能会出现:①所给envid所对的结构体已经被free后再次使用,即旧结构体所代表的PCB被淘汰,而此时得到的新env与envid想找的旧结构体不匹配;②env结构体没有成功切换为新进程的PCB,导致asid匹配出现错误。
Thinking 3.2 结合 include/mmu.h 中的地址空间布局,思考 env_setup_vm 函数:
UTOP 和 ULIM 的含义分别是什么?
- ULIM: user limit:kuseg用户态上限,0x8000 0000区分内核态和用户态的地址
- UTOP: userSpace top:用户可分配虚拟内存上限
UTOP 和 ULIM 之间的区域与 UTOP 以下的区域相比有什么区别?
- UTOP 和 ULIM 之间的区域:
- 用与存放页表和内核结构体,用户不可自由分配,即env中结构体数组的pgdir被清零后不会被赋值
- 其中UPAGES和ENVS其所占的物理页面,除了映射到用户态的这一部分虚拟地址,还与内核中的虚拟地址有一一映射关系。
- UTOP 以下的区域:占用物理内存,但是以页表形式,没有物理内存和内核地址的一一映射
请结合系统自映射机制解释 Step4 中pgdir[PDX(UVPT)]=env_cr3的含义。
- 内核态中的虚拟页目录的第PDX(UVPT)个页目录项中保存着此页目录所在页表的基地址的物理地址env_cr3
谈谈自己对进程中物理地址和虚拟地址的理解。
- 每个进程都有kuseg的2G虚拟空间,但是实际所有进程共享相同的内存空间。不同的进程有相同的虚拟地址,但是相同虚拟地址映射到不同物理地址
Thinking 3.3
找到 user_data 这一参数的来源,思考它的作用。没有这个参数可不可以?为什么?(可以尝试说明实际的应用场景,举一个实际的库中的例子)
- 来源:env_create_priority()函数中alloc的Env结构体指针
- 这个参数的作用是传给load_elf函数,并作为load_elf的int* map函数的参数,获得结构体的页目录
- c语言库函数中qsort()的回调函数
int(*cmp)(const void* e1,const void* e2))
使用qsort()的参数width实现多类型的比较:cmp((char*)base + j*width,(char*)base+(j+1)*width) > 0))
Thinking 3.4
结合 load_icode_mapper 的参数以及二进制镜像的大小,考虑该函
数可能会面临哪几种复制的情况?你是否都考虑到了?
Thinking 3.5
你认为这里的 env_tf.pc 存储的是物理地址还是虚拟地址?
- 物理地址。大概在0x400000+
你觉得entry_point其值对于每个进程是否一样?该如何理解这种统一或不同?
- entry_point是一样的虚拟地址,但是进程PCB不同,可以映射到各自不同的物理地址。
Thinking 3.6
请查阅相关资料解释,上面提到的 epc 是什么?为什么要将env_tf.pc 设置为 epc 呢?
- epc是发生异常中断时执行到的pc值,保存在curenv->env_tf.cp0_epc。
- env_tf.pc保存进程上下文,pc保存了返回该进程时开始执行的pc值,即为发生异常时的epc
Thinking 3.7
操作系统在何时将什么内容存到了 TIMESTACK 区域
- handle_int()处理时钟中断时,SAVE_ALL保存当前寄存器值的栈顶地址get_sp为0x8200_0000,即为TIMESTACK.
TIMESTACK 和 env_asm.S 中所定义的 KERNEL_SP 的含义有何不同
- TIMESTACK是产生时钟中断异常时用的存放CPU寄存器状态的栈顶指针,KERNEL_SP是非时钟中断异常用的栈指针。
Thinking 3.8
试找出上述 5 个异常处理函数的具体实现位置。
- genex.S中有 handle_int()的实现,有do_refill()即handle_tlb()的实现
- syscall.S有handle_sys()的实现
Thinking 3.9
阅读 kclock_asm.S 和 genex.S 两个文件,并尝试说出 set_timer 和time_irq 函数中每行汇编代码的作用
.text
LEAF(set_timer)
li t0, 0xc8
sb t0, 0xb5000100
//实时钟绑定4号中断,触发4号中断
sw sp, KERNEL_SP
//内核栈保存栈寄存器值
setup_c0_status STATUS_CU0|0x1001 0
//设置SR寄存器的状态:
//1.STATUS_CU0:打开CP0寄存器使用权
//2.SR[16]:IsC:软件能够访问并使指令高速缓存条目无效
//3.SR[4]:IEo
jr ra
nop
END(set_timer)
.extern delay
timer_irq:
sb zero, 0xb5000110
//关闭时钟中断
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
//异常返回
nop
Thinking 3.10
阅读相关代码,思考操作系统是怎么根据时钟周期切换进程的。
二、实验难点与指导书反馈
- 初始化进程目录时,没有理解为什么要复制UTOP上部分的内核目录,也不知道这部分的权限该如何设置。建议建议指导书把权限位的作用在头文件中指明,或者在lab教程中指明本实验中用到的权限位及其说明
- 汇编代码读起来比较晦涩,可以把R3000中的一些部分的汇编知识抽离出来,辅助理解;比如:
- SR寄存器各个位的作用。因为个人感觉虽然自己强硬地把R3000中那部分阅读翻译了一遍,但实际上还是比较困惑,不是很能读懂
- 使用到的伪指令的作用。伪指令对阅读理解造成了比较大的理解障碍。
load_icode_mapper()
,纠结了很久段尾要不要考虑和后面段的共享页面。希望指导书可以提前说明段按地址顺序由小到大加载,只用考虑段首的页面共用情况- de的第一个Bug就是
load_elf()
里面的binary地址,开始没有给binary+shdr_offset
的段偏移 - TIMESTACK和SP两个概念的辨析:这个思考题一开始卡住了,原因是
grep -r TIMESTACK
的时候没有查到汇编代码get_sp出现了0x8200_0000。 - 可以强调一下
INSERT_TAIL(env_sche_list,e,sche_link)
要在env_create()
而不是alloc时插入 - 进程调度算法时为
INSERT_TAIL
三、体会与感想
个人对Lab3的整体感受是比Lab2体验差很多。主要是以下原因:
- 课下:
- Lab2对存储管理的结构比较浑然一体,Lab3的知识相对零碎,比如很多变量的设置并不能直接看到其作用效果或范围,所以debug的时候也比较困难,比如Lab2debug还可以通过输出地址判断本步骤的正确性等,Lab3设计很多汇编函数和寄存器的操作,不管是理解还是测试都不是很容易
- 从Lab3遇到Bug会比较瞻前顾后,不知道是本次Lab还是之前Lab的bug,排查Bug所在范围的过程比较头疼
- 上机时的Exam和Extra题面都比较模糊,具体是以下几点:
- Exam对于“版本号相同、ASID空闲”这一分支并未显式说明要分配此ASID,而另一分支显式说明“找到最小的未使用ASID并分配”,且还强调“不要做任何无关操作”,虽然按题意是要分配,但是在阅读上的体验并不好,会在是否需要分配的问题上绊住
- Exam对于“模拟位图”和“运行进程”的界定可以更清晰些,避免歧义
- Extra对PV操作资源的个数并未叙述清楚造成理解偏差