[MIT6.828] LAB4 Part B: Copy-on-Write Fork

来源:互联网 发布:英语单词测试软件 编辑:程序博客网 时间:2024/04/26 23:53

Exercise 3. Implement the sys_env_set_pgfault_upcall system call. Be sure to enable permission checking when looking up the environment ID of the target environment, since this is a "dangerous" system call.

修改kern/syscall.c文件,添加注册页故障处理函数的功能:
1、修改sys_env_set_pgfault_upcall()函数

在代码中有两行被注释掉了,刚开始的时候我是用这两行代码检测传入的func是否合法,但是评分程序过不了才知道这里不要求验证func的有效性。不过我还是觉得在这里验证最好,毕竟往内核传入指针了阿。

2、修改syscal函数,把sys_env_set_pgfault_upcall()注册给适当的调用号。

 

Exercise 4. Implement the code in page_fault_handler in kern/trap.c required to dispatch page faults to the user-mode handler. Be sure to take appropriate precautions when writing into the exception stack. (What happens if the user environment runs out of space on the exception stack?)


修改kern/trap.c,在异常处理中加入调用用户注册的页错误处理函数的功能

注意检查用户页错误处理函数是否注册,用户异常栈是否分配,是否溢出,是否页错误嵌套。并注意给新的UTrapframe和嵌套返回值保留空间。

 

Exercise 5. Implement the _pgfault_upcall routine in lib/pfentry.S. The interesting part is returning to the original point in the user code that caused the page fault. You'll return directly there, without going back through the kernel. The hard part is simultaneously switching stacks and re-loading the EIP.

修改lib/pfentry.S文件,在用户层加入用户页错误处理函数的入口功能。

step 0是调用用户注册的用户页错误处理函数。step 1 是用来设置返回到发生页错误(原始)的地址。 step 2是用来弹出通用寄存器。 step 3 是用来弹出状态寄存器。 step 4用来切换esp到原始栈。 step 5就是返回到原始地址。
各种注意事项代码的注释中已有说明,一定得认真看了再去写代码。


 

 

Exercise 6. Finish set_pgfault_handler() in lib/pgfault.c.

修改lib/pgfault.c文件的set_pgfault_handler()函数

注意这里饶了个弯子,没有直接注册handler而是注册了练习5中的汇编函数_pgfault_upcall,这样是因为要从handler直接返回到用户页错误发生处,需要汇编函数_pgfault_upcall来制造栈环境(普通C代码无能为力)。

 

Challenge! Extend your kernel so that not only page faults, but all types of processor exceptions that code running in user space can generate, can be redirected to a user-mode exception handler. Write user-mode test programs to test user-mode handling of various exceptions such as divide-by-zero, general protection fault, and illegal opcode.

给系统异常加入注册用户异常处理函数的系统调用。这个最简单的实现方法是只写一个系统调用,用参数中的异常号来分辨不同的异常,代码可以精简很多。如果觉得这样写用户调用不方便(还得记异常号)那就给每个异常写一个用户层的lib接口。这样还可以保持上面写的set_pgfault_handler函数不动。 没啥技术含量和挑战性,咱就不写了,以后有机会再说。

 

Exercise 7. Implement fork, duppage and pgfault in lib/fork.c.
Test your code with the forktree program. It should produce the following messages, with interspersed 'new env', 'free env', and 'exiting gracefully' messages. The messages may not appear in this order, and the environment IDs may be different.

 

修改lib/fork.c文件

 

pgfault()函数:检查页属性,然后分配页,复制数据。

 

duppage()函数:注意区别写/写时复制 和 只读页面。在这里想到,其实在kern/env.c的load_inode()函数里面我们都是用可写的方式处理页面。所以需要注意改进阿。

 

fork()函数:这里需要注意,由于新建进程时,自进程会把自己的pgfault_handler设置成空,所以要重新注册一下。
不过不能在子进程注册,因为运行子进程时候调用函数或者写入变量会导致页错误,这时候还没有注册pgfault_handler,所以就默认进程销毁了。 只能在父进程设置子进程的句柄。我有个疑问,为什么不在新建进程的时候直接把pgfault_handler继承过来呢,不知道设计者怎么想的。


 

 

Challenge! Implement a shared-memory fork() called sfork(). This version should have the parent and child share all their memory pages (so writes in one environment appear in the other) except for pages in the stack area, which should be treated in the usual copy-on-write manner. Modify user/forktree.c to use sfork() instead of regular fork(). Also, once you have finished implementing IPC in part C, use your sfork() to run user/pingpongs. You will have to find a new way to provide the functionality of the global env pointer.

修改lib/fork.c。把sfork()函数变成一个读写共享全局数据和代码的fork()

和fork()函数基本一致,只需要注意共享UTEXT到end之间的数据时老老实实的按照原来的属性重新映射一个到子进程就可以了,注意权限哦

 

Challenge! Your implementation of fork makes a huge number of system calls. On the x86, switching into the kernel using interrupts has non-trivial cost. Augment the system call interface so that it is possible to send a batch of system calls at once. Then change fork to use this interface.

How much faster is your new fork?

You can answer this (roughly) by using analytical arguments to estimate how much of an improvement batching system calls will make to the performance of your fork: How expensive is an int 0x30 instruction? How many times do you execute int 0x30 in your fork? Is accessing the TSS stack switch also expensive? And so on...

Alternatively, you can boot your kernel on real hardware and really benchmark your code. See the RDTSC (read time-stamp counter) instruction, defined in the IA32 manual, which counts the number of clock cycles that have elapsed since the last processor reset. QEMU doesn't emulate this instruction faithfully (it can either count the number of virtual instructions executed or use the host TSC, neither of which reflects the number of cycles a real CPU would require).

这次的挑战是要求测试下fork的速度,可以通过RDTSC指令阿或者指令计数器来计算。这次的fork调用了N多个系统调用,每个系统调用都要切换栈空间,压入弹出N多寄存器,尤其是后来我还加入了浮点寄存器的保护,这都512个字节呢。速度会奇慢无比,具体代码不写了。RDTSC指令格式满大街都是,大家自己google吧。

原创粉丝点击