## Part A

Jos中 我们用Env结构体来描述进程，关于Env，讲义中已经很清楚了，这里不赘述。关键点是通过envs数组和env_free_list来维护数组，这里需要注意的是env_free_list，不是像之前free_page_list那样是反向。这里需要和envs的顺序相同。

#### Exercise 1

Modify mem_init() in kern/pmap.c to allocate and map the envs array. This array consists of exactly NENV instances of the Env structure allocated much like how you allocated the pages array. Also like the pages array, the memory backing envs should also be mapped user read-only at UENVS (defined in inc/memlayout.h) so user processes can read from this array.

#### Exercise 2

In the file env.c, finish coding the following functions:

• env_init()
Initialize all of the Env structures in the envs array and add them to the env_free_list. Also calls env_init_percpu, which configures the segmentation hardware with separate segments for privilege level 0 (kernel) and privilege level 3 (user).
• env_setup_vm()
Allocate a page directory for a new environment and initialize the kernel portion of the new environment’s address space.
• region_alloc()
Allocates and maps physical memory for an environment
• load_icode()
You will need to parse an ELF binary image, much like the boot loader already does, and load its contents into the user address space of a new environment.
• env_create()
Allocate an environment with env_alloc and call load_icode to load an ELF binary into it.
• env_run()
Start a given environment running in user mode.

env_init() 初始化envs，并且连接env_free_list。和之前的page_init做法基本一样，除了顺序相反。

env_setup_vm为进程分配页目录，这里做法是copykernel的页目录。

region_alloc()为用户空间分配页。类似于lab2 中的boot_map_region

load_icode()这个函数注释好多，一开始看了半天。其实说白了就是把elf程序加载到用户内存空间。正常来讲用户程序应该从磁盘上读取，但是目前jos还没有文件系统。mit直接链接了一些用户程序到kernel中。所以这里不需要读取，更加方便一点。具体我们可以参考bootloader的做法。因为需要对用户空间进行内存操作，这里需要用lcr3()切换页目录。最后需要注意的是设置进程的入口点为这个程序的入口点。最后的最后是为用户程序栈初始化分配一页。

env_create这个简单，综合前面的函数，先创建进程，然后加载用户程序。

env_run运行进程。这个也比较简单，照着注释来就行。切换当前进程为新的进程。切换地址空间。最后调用env_pop_tf来保存现场，并且跳转到用户程序的入口点，不返回。

#### 中断、异常和系统调用

中断、异常和系统调用是用户程序或者外部设备和kernel进行交互的方式。比如说当敲击键盘时会产生中断，让操作系统知道这时候有字符可读。在比如用户程序运行时，发生错误，比如除0，无法运行下去，这会产生异常，让kernel来处理，系统调用就更不用说了，每时每刻都在发生，比如printf就是一个系统调用。这里说的中断、异常和系统调用，每一种都有些细微的不同，其实根据上述的例子就能看出来，中断是异步的，异常是同步的，系统调用同步异步都有可能。之后文章中说的中断，是广义上的中断，也就是一个统称，不细分为中断、异常和系统调用。操作系统用int n指令来说明中断产生，当中断产生时，操作系统会根据中断向量表，来索引n，然后跳到相应的处理函数。

#### Exercise 4

Edit trapentry.S and trap.c and implement the features described above. The macros TRAPHANDLER and TRAPHANDLER_NOEC in trapentry.S should help you, as well as the T_* defines in inc/trap.h. You will need to add an entry point in trapentry.S (using those macros) for each trap defined in inc/trap.h, and you’ll have to provide_alltraps which the TRAPHANDLER macros refer to. You will also need to modify trap_init() to initialize the idt to point to each of these entry points defined in trapentry.S; the SETGATE macro will be helpful here.

Your _alltraps should:

1. push values to make the stack look like a struct Trapframe
2. load GD_KD into %ds and %es
3. pushl %esp to pass a pointer to the Trapframe as an argument to trap()
4. call trap (can trap ever return?)

Consider using the pushal instruction; it fits nicely with the layout of the struct Trapframe.

## Part B

#### Exercise 5 && Exercise 6

Modify trap_dispatch() to dispatch page fault exceptions to page_fault_handler(). You should now be able to get make grade to succeed on the faultread,faultreadkernel, faultwrite, and faultwritekernel tests. If any of them don’t work, figure out why and fix them.

Modify trap_dispatch() to make breakpoint exceptions invoke the kernel monitor.

#### Exercise 7

Add a handler in the kernel for interrupt vector T_SYSCALL. You will have to edit kern/trapentry.S and kern/trap.c’s trap_init(). You also need to changetrap_dispatch() to handle the system call interrupt by calling syscall() (defined in kern/syscall.c) with the appropriate arguments, and then arranging for the return value to be passed back to the user process in %eax. Finally, you need to implement syscall() in kern/syscall.c. Make sure syscall() returns -E_INVAL if the system call number is invalid. You should read and understand lib/syscall.c (especially the inline assembly routine) in order to confirm your understanding of the system call interface. Handle all the system calls listed in inc/syscall.h by invoking the corresponding kernel function for each call.

系统调用 传入需要调用函数号，以及参数。所以当分发系统调用是，只要按照说明传入相应的参数，并且在/kern/syscall.c中按照函数号，分发下去即可。最后把返回值保存在eax中。

/kern/syscall.c

#### Exercise 8

Add the required code to the user library, then boot your kernel. You should see user/hello print “hello, world” and then print “i am environment 00001000”.user/hello then attempts to “exit” by calling sys_env_destroy() (see lib/libmain.c and lib/exit.c). Since the kernel currently only supports one user environment, it should report that it has destroyed the only environment and then drop into the kernel monitor.

#### Exercise 9

Change kern/trap.c to panic if a page fault happens in kernel mode.

Hint: to determine whether a fault happened in user mode or in kernel mode, check the low bits of the tf_cs.

Read user_mem_assert in kern/pmap.c and implement user_mem_check in that same file.

/kern/trap.c

‘/kern/pmap.c’

/kern/syscall.c 最后需要在sys_cputs 添加检查，因为只有这个调用访问到地址。