6.828 Lab3: User Environments
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_BRKPT
的 DPL
要改成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_BRKPT
的DPL=3
,则触发T_BRKPT
。 - 设置
T_BRKPT
的DPL=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
取一个最大值。