Mit6.828 Day33 Lab4 PartB

Mit6.828 Day33 Lab4 PartB

摘要:MIT6.828 Lab4 Part B

0x01 写在前面

我想在11月份结束前完成Mit6.828的课程,之前因为比赛等种种原因拖了太长时间了,后面还打算过一遍算法红宝书和计组(计组甚至想再做一个写CPU的Lab…,然后还有课业、CTF比赛、导师这边的项目需要兼顾(难顶。看了下schedule,感觉后面剩下的东西不算太多(其实已经很多了。这一个多月加把劲咯。

0x02 Part B: Copy-on-Write Fork

这个Part看标题是要去处理写时复制的问题。

写时复制(Copy-on-Write)这个问题主要是为了解决从父进程调用fork()到子进程调用exec()时对空间的浪费问题。

所谓的写时复制指的是在fork()的时候并不对实际的父进程内容进行复制,而是对映射进行复制,从而实现内容的共享,这块空享区域被设置为只读。只有在子进程/父进程对共享的内容修改了之后才会重新分配一块新的区域,这个时候会导致缺页错误。

那么这个Part要做的就是去实现一个类Unix的带有写时复制的fork()

User-level page fault handling

写时复制fork()需要获得缺页错误的信息,因此需要先实现一个用户层的缺页错误处理器。

Setting the Page Fault Handler

首先要做的是设置一个缺页错误处理的入口点,用户环境通过sys_env_set_pgfault_upcall这个系统调用来完成这个任务,我们需要往Env结构体中加入一个新的变量env_pgfault_upcall来记录这个信息

Exercise 8

这个Exercise要求我们去实现上面提到的那个系统调用,需要注意的是权限问题(查了半天资料没找到怎么处理这个权限问题。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
static int
sys_env_set_pgfault_upcall(envid_t envid, void *func)
{
// LAB 4: Your code here.
// panic("sys_env_set_pgfault_upcall not implemented");
struct Env * curenv;
int errno;
// Judge if the env is exist or not
if((errno=envid2env(envid, &curenv, 1))<0){
return -E_BAD_ENV;
}
curenv->env_pgfault_upcall=func;
}

同时在syscall()的switch里新增加一个分值:

1
2
case SYS_env_set_pgfault_upcall:
return sys_env_set_pgfault_upcall((env_id)a1, (void*)a2);

Normal and Exception Stacks in User Environments

这个部分主要提到了异常栈的概念,它是位于USTACKTOP-PGSIZE到USTACKTOP-1的那一连串页面。

sys_page_alloc()用于分配这些页面。

Invoking the User Page Fault Handler

调用这个处理器时,内核会按照下面的格式

安置一个类似于UTrapframe的结构体在异常栈中。内核会根据保存在异常栈中的这个结构体里的数据帮助用户环境恢复执行。

需要注意的是,如果在异常栈中执行的时候再次发生了一次异常,这个时候就需要在当前的tf->tf_esp下面新建一个栈帧。

Exercise 9

这个部分需要我们去实现kern/trap.c中的page_fault_handler()函数。这个部分要当心不要栈溢出了。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
struct UTrapframe * utf;
uintptr_t utf_top;

if(curenv->env_pgfault_upcall){
// Judge if the pgfault is happended in exception stack
if((tf->esp>=UXSTACKTOP-PGSIZE)&&(tf->esp<=UXSTACKTOP-1)){
utf_top = UXSTACKTOP-sizeof(struct UTrapframe)-4;
}else{
utf_top = UXSTACKTOP-sizeof(struct UTrapframe);
}
// check the permisson of curenv
user_mem_assert(curenv, utf_top, sizeof(UTrapframe), PTE_W|PTE_U);
// push the register into the stack
utf = (UTrapframe *)utf_top;
utf->utf_fault_va = fault_va;
utf->utf_err = tf->tf_err;
utf->utf_regs = tf->tf_regs;
utf->utf_eip = tf->tf_eip;
utf->utf_eflags = tf->tf_eflags;
utf->utf_esp = tf->tf_esp;
(&(curenv->env_tf))->tf_eip = (uintptr_t)curenv->env_pgfault_upcall;
(&(curenv->env_tf))->tf_esp = utf_top;
env_run(curenv);
}else{
// Destroy the environment that caused the fault.
cprintf("[%08x] user fault va %08x ip %08x\n",
curenv->env_id, fault_va, tf->tf_eip);
print_trapframe(tf);
env_destroy(curenv);
}

这个地方需要注意的点在于:

1)需要判断是否处在异常栈中;

2)需要通过user_mem_assert判断权限;

3)需要懂得陷入帧在栈中的存储(这个在之前的Lab中有提到);

4)需要懂得切换当前环境栈指针esp和调用指针eip。

User-mode Page Fault Entrypoint

下面我们需要做的是实现一个将会处理页面错误处理器的调用和从出现缺页指令处恢复的汇编程序,它将会使用sys_env_set_pgfault_upcall()来在内核中注册。

Exercise 10

这个部分需要我们去实现_pgfault_upcall程序,我们会直接返回到导致缺页错误的地方。比较困难的地方在于同时换栈和EIP的重加载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#include <inc/mmu.h>
#include <inc/memlayout.h>

// Page fault upcall entrypoint.

// This is where we ask the kernel to redirect us to whenever we cause
// a page fault in user space (see the call to sys_set_pgfault_handler
// in pgfault.c).
//
// When a page fault actually occurs, the kernel switches our ESP to
// point to the user exception stack if we're not already on the user
// exception stack, and then it pushes a UTrapframe onto our user
// exception stack:
//
// trap-time esp
// trap-time eflags
// trap-time eip
// utf_regs.reg_eax
// ...
// utf_regs.reg_esi
// utf_regs.reg_edi
// utf_err (error code)
// utf_fault_va <-- %esp
//
// If this is a recursive fault, the kernel will reserve for us a
// blank word above the trap-time esp for scratch work when we unwind
// the recursive call.
//
// We then have call up to the appropriate page fault handler in C
// code, pointed to by the global variable '_pgfault_handler'.

.text
.globl _pgfault_upcall
_pgfault_upcall:
// Call the C page fault handler.
pushl %esp // function argument: pointer to UTF
movl _pgfault_handler, %eax
call *%eax
addl $4, %esp // pop function argument

// Now the C page fault handler has returned and you must return
// to the trap time state.
// Push trap-time %eip onto the trap-time stack.
//
// Explanation:
// We must prepare the trap-time stack for our eventual return to
// re-execute the instruction that faulted.
// Unfortunately, we can't return directly from the exception stack:
// We can't call 'jmp', since that requires that we load the address
// into a register, and all registers must have their trap-time
// values after the return.
// We can't call 'ret' from the exception stack either, since if we
// did, %esp would have the wrong value.
// So instead, we push the trap-time %eip onto the *trap-time* stack!
// Below we'll switch to that stack and call 'ret', which will
// restore %eip to its pre-fault value.
//
// In the case of a recursive fault on the exception stack,
// note that the word we're pushing now will fit in the
// blank word that the kernel reserved for us.
//
// Throughout the remaining code, think carefully about what
// registers are available for intermediate calculations. You
// may find that you have to rearrange your code in non-obvious
// ways as registers become unavailable as scratch space.
//
// LAB 4: Your code here.
addl $8, %esp
movl 0x20(%esp), %eax

subl $4, 0x28(%esp)
movl 0x28(%esp), %ebx
movl %eax, (%ebx)
// Restore the trap-time registers. After you do this, you
// can no longer modify any general-purpose registers.
// LAB 4: Your code here.
popal // restore
// Restore eflags from the stack. After you do this, you can
// no longer use arithmetic operations or anything else that
// modifies eflags.
// LAB 4: Your code here.
addl $4, %esp
popfl // pop eflas
// Switch back to the adjusted trap-time stack.
// LAB 4: Your code here.
popl %esp
// Return to re-execute the instruction that faulted.
// LAB 4: Your code here.
ret

汇编太难了,这部分全部看的别人的代码。

Exercise 11

这部分需要我们去完成C用户库中的用户级页面错误处理机制

我们需要去设置我们的页面错误处理器,这里其实就是将我们在pfentry.S中写的汇编对接起来了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void
set_pgfault_handler(void (*handler)(struct UTrapframe *utf))
{
int r;

if (_pgfault_handler == 0) {
// First time through!
// LAB 4: Your code here.
// panic("set_pgfault_handler not implemented");
r = sys_page_alloc(thisenv->env_id, (void)* (UXSTACKTOP-PGSIZE), PTE_W|PTE_U);
if(r!=0)
panic("fail to alloc a page for UXSTACKTOP!\n");
r = sys_env_set_pgfault_upcall(this->env_id, _pgfault_handler);
if(r!=0)
panic("handler register failed!\n")
}

// Save handler pointer for assembly to call.
_pgfault_handler = handler;
}

评论