Exercise 3.1

这个很简单,照着之前初始化 pages 的方法来就行了。

Exercise 3.2

env_init() 这个嘛很简单,就是要注意链表的顺序要和数组的顺序一样,所以链表得倒着插入。

env_setup_vm()0~UTOP 先弄成空,然后把 UTOP~FULL 弄成跟内核一样的,另外页表是存在 UVPT 要设置成该进程自己的。要注意 notes 里面说的,内核那边的映射并不增加 pp_ref,而是增加页表的 p->pp_ref

region_alloc() 就是要想清楚现在操作内存应该用什么函数,其实就是 lab2 做的一系列 page_* 函数。这里用 page_alloc() 分配一个页,用 page_insert() 把这个页插入页表。值得注意的是,这个范围应该是 ROUNDDOWN(va, PGSIZE) <= vaddr < ROUNDUP(va+len, PGSIZE),我写的时候右边也写成了等号,于是后面 user/hello 总是无法触发 page fault。

load_icode() 这个基本上按照 boot/main.c:bootmain 抄就好了。注意到ELF里面写的是虚拟地址,所以我们肯定要把页表切换过去。可是怎么切换呢?往下翻一翻,看到了 env_free() 里面有一句话:lcr3(PADDR(kern_pgdir)); 这才想起来在 inc/x86.h 里面有 lcr3() 这么个函数,可以把页表载入到 CR3 里面。

env_create() 没啥好说的。就是这个 panic() 里面用 %e 蛮开心的。

env_run() 也没啥好说的了。回过头来说前面 load_icode(),在抄 boot/main.c:bootmain 的时候我就在想,程序入口要放在哪呢?提示说 See env_run() and env_pop_tf() below。现在到了这里其实就很明白了,env_pop_tf() 里面有个 popal,这里就包含了寄存器 %EIP,这才想起来下一条指令是放在 %EIP 里面的。所以说我们把程序入口放在 e->env_tf.tf_eip 就对了。

Exercise 3.4

trapentry.S 的时候怎么知道每个中断有没有返回值呢?在 Schedule 页面有一张 IDT 表,跟着这个来就行了。

trap.c 里面,首先把 trapentry.S 里面对应的函数名先 extern 声明一遍,然后调用 SETGATE 就好了。SETGATE 里面 istrap 据说都要设置成0,但是为什么是这样的我好像一直找到??

在写这两个文件的时候,善用宏还是很重要的,比如用 ## 拼接字符串这样的小伎俩就很好用。另外,我实在没学会怎么在汇编器里面一会儿跳到 .text 一会儿又跳到 .data,所以我还是很丑陋地写了一排 T_*。不过反正 Sublime Text 有块选择嘛,写起来蛮舒服的呀。

另外,我这才知道原来 make grade 是可以运行的,里面有测试……以前竟然从来不知道。测了一下,lab2 竟然是过的。

做这题的时候发现了好多个前面的 BUG,不得不找了别人的代码来对比。

What is the purpose of having an individual handler function for each exception/interrupt? (i.e., if all exceptions/interrupts were delivered to the same handler, what feature that exists in the current implementation could not be provided?)

因为在触发中断的时候,处理器是会把 trapno 推到栈里面的,所以弄成一个统一的入口函数再来派发也不是不可以。但是这样一来,所有中断的 DPL 都得设置成3了,这样用户就可以触发所有中断。当然你说我们在入口函数里面再来判断 %CS & 3 也不是不可以,但是这样好麻烦,而且既然处理器都能帮我们做这个事情了,为啥我们还要自己来做呢。

Did you have to do anything to make the user/softint program behave correctly? The grade script expects it to produce a general protection fault (trap 13), but softint’s code says int $14. Why should this produce interrupt vector 13? What happens if the kernel actually allows softint’s int $14 instruction to invoke the kernel’s page fault handler (which is interrupt vector 14)?

T_PGFLT 要求 DPL=0,于是用户态想要触发这个中断的时候直接就触发了 general protection fault,也就是13。

Exercise 3.5

trap_dispatch() 写个 switch 就好了。

Exercise 3.6

T_BRKPTDPL 要改成3,然后 trap_dispatch() 里面再加个 case

The break point test case will either generate a break point exception or a general protection fault depending on how you initialized the break point entry in the IDT (i.e., your call to SETGATE from trap_init). Why? How do you need to set it up in order to get the breakpoint exception to work as specified above and what incorrect setup would cause it to trigger a general protection fault?

  • 设置 T_BRKPTDPL=3,则触发 T_BRKPT
  • 设置 T_BRKPTDPL=0,则触发 T_GPFLT

What do you think is the point of these mechanisms, particularly in light of what the user/softint test program does?

限定用户态只能做很少的底层相关的事情:只能做系统调用和断点。

Exercise 3.7

trap_dispatch() 里面调用 syscall 的参数哪来的呢?是来自寄存器,但不是让我们直接读取 %eax,而是注意到在触发中断的时候,处理器把寄存器都存到了 tf->tf_regs 里面了。所以说,我们从 tf->tf_regs 获取参数的值,再把返回值写回 tf->tf_regs.eax

kern/syscall.c:syscall 也就一个 switch 的事情,但是我却把自己坑了,调了三个小时都没看出来错在哪。我忘写 case,于是编译器就直接当作 label 处理了,所以这个函数完全不正常。我先是用 gdb 跟踪,感觉完全不对劲,然后又看 obj/user/hello.asm,发现那个 switch 翻译得不对。我还以为是编译器有问题,还把 gcc-5.4 换成了 gcc-4.8,还是这样。最后突然发现自己忘记写 case 了!编译器竟然连个 warning 都不给!

Exercise 3.8

注意得到了 envid_t 之后还要调用 ENVX() 才是 envs[] 中的下标。

Exercise 3.9

pgdir_walk() 可以得到 page table entry,然后判断就好了。注意一点就是在设置 user_mem_check_addr 的时候,不要直接把当前正在循环的虚拟地址赋值过去,而是要和传入的 va 取一个最大值。