## 前言

lab4终于完成了。lab4是到目前为止，比较复杂而且代码最多的lab。总共需要完成三个部分。part A添加多核支持，实现RR调度。part B实现Copy-On-Write forkpart C 实现时钟中断以及支持基本的IPC

## Part A

#### Exercise 1

Implement mmio_map_region in kern/pmap.c. To see how this is used, look at the beginning of lapic_init in kern/lapic.c. You’ll have to do the next exercise, too, before the tests for mmio_map_region will run.

#### Exercise 2

Read boot_aps() and mp_main() in kern/init.c, and the assembly code in kern/mpentry.S. Make sure you understand the control flow transfer during the bootstrap of APs. Then modify your implementation of page_init() in kern/pmap.c to avoid adding the page at MPENTRY_PADDR to the free list, so that we can safely copy and run AP bootstrap code at that physical address. Your code should pass the updated check_page_free_list() test (but might fail the updated check_kern_pgdir() test, which we will fix soon).

boot_aps()首先把mpentry.S的代码复制到内存MPENTRY_PADDR中。然后对每个cpu都启动一个进程，也就是APS，进程栈保存在percpu_kstacks。最后通过lapic_startap发生中断跳到mpentry.S去执行。然后进行死循环等待cpu进程启动。mpentry.S的功能与bootloader类似，最后会跳转到mp_main。然后就是进行一些初始化，改变cpu状态为启动，通知boot_aps()可以进行boot下一个进程了。

#### Question 1

Compare kern/mpentry.S side by side with boot/boot.S. Bearing in mind that kern/mpentry.S is compiled and linked to run above KERNBASE just like everything else in the kernel, what is the purpose of macro MPBOOTPHYS? Why is it necessary in kern/mpentry.S but not in boot/boot.S? In other words, what could go wrong if it were omitted in kern/mpentry.S?

MPBOOTPHYS的作用是将高地址变为地址。因为根据注释上的说明，此次还处于实模式，但是代码中的地址已经经过重定位，所以需要进行地址翻译。

#### Exercise 3

Modify mem_init_mp() (in kern/pmap.c) to map per-CPU stacks starting at KSTACKTOP, as shown in inc/memlayout.h. The size of each stack is KSTKSIZE bytes plusKSTKGAP bytes of unmapped guard pages. Your code should pass the new check in check_kern_pgdir().

#### Exercise 4

The code in trap_init_percpu() (kern/trap.c) initializes the TSS and TSS descriptor for the BSP. It worked in Lab 3, but is incorrect when running on other CPUs. Change the code so that it can work on all CPUs. (Note: your new code should not use the global ts variable any more.)

#### Exercise 5

Apply the big kernel lock as described above, by calling lock_kernel() and unlock_kernel() at the proper locations.

#### Question 2

It seems that using the big kernel lock guarantees that only one CPU can run the kernel code at a time. Why do we still need separate kernel stacks for each CPU? Describe a scenario in which using a shared kernel stack will go wrong, even with the protection of the big kernel lock.

#### Exercise 6

Implement round-robin scheduling in sched_yield() as described above. Don’t forget to modify syscall() to dispatch sys_yield().
Make sure to invoke sched_yield() in mp_main.
Modify kern/init.c to create three (or more!) environments that all run the program user/yield.c

#### Question 3

In your implementation of env_run() you should have called lcr3(). Before and after the call to lcr3(), your code makes references (at least it should) to the variablee, the argument to env_run. Upon loading the %cr3 register, the addressing context used by the MMU is instantly changed. But a virtual address (namely e) has meaning relative to a given address context–the address context specifies the physical address to which the virtual address maps. Why can the pointer e be dereferenced both before and after the addressing switch?

#### Question 4

Whenever the kernel switches from one environment to another, it must ensure the old environment’s registers are saved so they can be restored properly later. Why? Where does this happen?

#### Exercise 7

Implement the system calls described above in kern/syscall.c. You will need to use various functions in kern/pmap.c and kern/env.c, particularly envid2env(). For now, whenever you call envid2env(), pass 1 in the checkperm parameter. Be sure you check for any invalid system call arguments, returning -E_INVAL in that case. Test your JOS kernel with user/dumbfork and make sure it works before proceeding.

sys_exofork这里的fork，只是单纯分配了一块地址空间给进程，但里面是空的，所以目前进程还是不可运行。具体做法也很直观，下面是实现，注意要把子进程返回值的寄存器设为0.

sys_env_set_status设置状态，不多说。

sys_page_alloc 分配页的系统调用，不难，比较烦的是权限检查。

sys_page_map，页的映射，用来两个进程通信时使用，使两块地址空间的部分内容映射到同一块物理地址。也不难。。还是烦在地址检查。

sys_page_unmap不多说

## Part B

Part B的任务主要是实现Copy-on-Write Fork，也就是所谓的写时拷贝技术。传统的fork函数，调用时会直接将父进程的内存内容拷贝到子进程当中了，但是大多数是程序，当fork之后就直接调用exec()填充新的内存内容了。所以拷贝这一步很浪费时间。所以现在采用的做法是COW fork，也就是把拷贝的时间推迟到子进程需要写入的时候。这种做法用的很多，比如cache更新的情况。

#### Exercise 8

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

#### Exercise 9

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?)

• 用户进程发生page_fault用户栈 -> kernel栈 -> 用户异常栈
• page_fault处理时又发生page_fault。虽然已经在用户异常栈了，但还是会继续陷入到内核中，重走一遍上面的流程，这里需要注意，压入UTrapframe时，需要空4个字节。所以栈切换顺序为，用户异常栈 -> kernel栈 -> 用户异常栈

#### Exercise 10

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.

#### Exercise 11

Finish set_pgfault_handler() in lib/pgfault.c.

#### Exercise 12

Implement fork, duppage and pgfault in lib/fork.c.

fork

duppage

pafault

## Part C

#### Exercise 13

Modify kern/trapentry.S and kern/trap.c to initialize the appropriate entries in the IDT and provide handlers for IRQs 0 through 15. Then modify the code inenv_alloc() in kern/env.c to ensure that user environments are always run with interrupts enabled.

#### Exercise 14

Modify the kernel’s trap_dispatch() function so that it calls sched_yield() to find and run a different environment whenever a clock interrupt takes place.

You should now be able to get the user/spin test to work: the parent environment should fork off the child, sys_yield() to it a couple times but in each case regain control of the CPU after one time slice, and finally kill the child environment and terminate gracefully.

#### Exercise 15

Implement sys_ipc_recv and sys_ipc_try_send in kern/syscall.c. Read the comments on both before implementing them, since they have to work together. When you call envid2env in these routines, you should set the checkperm flag to 0, meaning that any environment is allowed to send IPC messages to any other environment, and the kernel does no special permission checking other than verifying that the target envid is valid.

Then implement the ipc_recv and ipc_send functions in lib/ipc.c.

sys_ipc_recv，这里等待接受信息，只需要改变进程状态，直接调度就会阻塞了。我一开始没反应过来还弄了死循环。

sys_ipc_try_send需要注意一点就是共享内存时，不能直接使用sys_page_map，因为sys_page_map查找env时会检查权限。

ipc_recv

ipc_send

lab4完。