OS-lab4
OS-lab4
上机
lab4-1
exam
发现这届课程组确实很贴心,担心大家过不了exam,每次都给伪代码。lab3上机要自己实现异常处理函数,lab4是实现系统调用。这次exam特别特别简单(尤其还给伪代码,想错都难),实现进程组通讯。
extra
实现父进程给所有的后代进程(子进程、子子进程、…),可以在用户态实现,也可以写新的系统调用。
要遍历所有的后代进程可以用dfs,我是在用户态实现的
1 |
|
md气死我了,我 dfs(syscall_getenvid(), val, srcva, perm);
调用syscall_getenvid的时候没加括号,结果相当于传的是syscall_getenvid这个函数的地址,气死了,气死了,到最后也没找出来bug
lab4-2
exam
实现父进程,阻塞所有的后代进程。准确来说,父进程申请一个信号量,可以最多阻塞n个进程,然后后代进程会阻塞,当阻塞的进程数达到n的时候,解除阻塞,同时信号量失效。
保证
- 只有一个根进程,其他都是后代进程,所以我们的信号量可以直接定义一个全局变量,但如果有多个父进程,就需要把信号量设成进程的属性,在fork的时候父进程把它传给子进程
- 只会进行一次申请信号量,保证先申请,才会fork
我的阻塞实现在用户态,首先在ipc.c中定义void barrier_alloc(int n)
,void barrier_wait(void)
1 |
|
实现系统调用
1 |
|
但其实我的实现是错的:
1 |
|
实验报告
一、thinking
thinking 4.1
- k0寄存器在MIPS规范中是留给内核操作的,保存现场的时候,对于状态寄存器可以用k0寄存器做最中介进行传递
- 如果切换内核态的时候没有修改a0-a3应该是可以访问的,但do_syscall传递了tf这个参数,会修改寄存器的值,所以不能
- do_syscall 的时候我们保存了系统调用前的现场,按照SAVEALL的规格,便可以找到传递的参数
- 4
- EPC+=4,返回到系统调用的下一条指令
- 如果系统调用号不存在,通过修改v0返回错误值
thinking 4.2
进程号是独一无二的,801和001返回的是同一个进程块,而001是非法的,所以必须判断一下
thinking 4.3
envid2env函数中,如果进程号为0,则返回当前进程块
thinking 4.4
C,子进程并没有调用fork,但其调度时返回的现场和父进程相同,除了返回值是0
thinking 4.5
USTACKTOP以上的用户空间不能映射,包括每个进程的页表和页目录,每个进程都不一样。
pages,envs在进程申请的时候进行了映射,也不需要
thinking 4.6
- vpd和vpt是页目录和页表的基地址,我们取某个页表项的时候可以vpt[index]即可
- 进程申请的时候完成了页表的自映射
- e->env_pgdir[PDX(UVPT)] = PADDR(e->env_pgdir) | PTE_V;
- 由上述自映射我们发现,我们只有有效位,并没有PTE_D,所以不能修改
thinking 4.7
真正的异常处理函数在用户空间,我们必须保证在异常处理完之后回到异常发生的现场
如果有多个异常进程,如果保存的现场都在内核态同一个位置,可能会出差错,保存在用户空间解决了这个问题
thinking 4.8
更符合微内核的设计,能在用户空间处理的问题就在用户空间处理,但是在COW处理的过程中,进行了多次系统调用,为什么不在一次系统调用中完成呢,切换内核态不是很大的开销吗?
thinking 4.9
我们在exofork系统调用中,已经设置了子进程的写入异常函数,所以应在exofork之前就进行set_tlb_mod_entry系统调用
二、实验难点分析
1. 系统调用是特殊的异常
都是保存现场进入特定的异常处理函数do_syscall,然后根据传进来的系统调用号再进入特定的系统调用函数。除此,系统调用并不会切换进程,可以理解为用户程序执行了一段内核的代码
2. envid2env
了解创建系统调用号的过程,确保了所有的进程的进程号都不同,所以才有ENVX这个宏。在envid2env中我们特地检查了e的进程id是否和传进来的参数一样,若一样则没问题。如果不一样,确实会有不一样的情况:801和001这两个返回的进程块一样,但001的进程id是非法的。
3. 进程通信
在try_send系统调用中,我一开始使用了sys_mem_map函数,这样是不对的,因为sys_mem_map中envid2env传入了检查位。
4. 写入异常函数
写入异常的函数在用户空间中,所以处理完异常后需要恢复现场。
COW机制确保了父子进程能够最大共享数据
三、实验感想
- 总的来说,lab4的难度又有提升,工作量很大,需要在内核代码和用户代码来回切换
- 初始了进程通信和fork,明白了简化的实现机制,只有先理解了简单的机制,才能有优化提升的空间,在进程尝试发消息的时候一直处于轮询状态,这样很浪费资源