BUAA OS LAB4 实验报告

Thinking

Thinking 4.1

Q:

思考并回答下面的问题:

•内核在保存现场的时候是如何避免破坏通用寄存器的?

•系统陷入内核调用后可以直接从当时的$a0-$a3参数寄存器中得到用户调用msyscall 留下的信息吗?

•我们是怎么做到让sys开头的函数“认为”我们提供了和用户调用msyscall时同样 的参数的?

•内核处理系统调用的过程对Trapframe做了哪些更改?这种修改对应的用户态的变化是什么?

A:

  1. 在 SAVE_ALL 中:
    move k0,sp,先把通用寄存器的sp复制到$k0
    sw k0,TF_REG29(sp)sw $2,TF_REG2(sp):保存现场需要使用$v0作为协寄存器到内存的中转寄存器,写到内存时需要sp ,所以在正式保存协寄存器和通用寄存器前先保存这两个寄存器。
  2. 可以。
    从用户函数 syscall_*() 到内核函数 sys_*() 时,$a1−$a3 未改变,$a0handle_sys() 的时候被修改为内核函数的地址,但在内核函数 sys_*() 仅为占位符,不会被用到。同时,在内核态中可能使用这些寄存器进行一些操作计算,此时寄存器原有值被改变,因此再次以这些参数调用其他函数时需要重新以sp为基地址,按相应偏移从用户栈中取用这四个寄存器值。
  3. 用户调用时的参数:用户进程的寄存器现场(保存在了内核栈的TF_4-TF_7)的 $a1−$a3
    用户栈(栈指针为用户现场的 sp )的参数 $a4$a5
    把上面两部分参数分别拷贝至内核现场寄存器$a1−$a3 和内核栈。
  4. 第一,将栈中存储的EPC寄存器值增加4,这是因为系统调用后,将会返回下一条指令,而用户程序会保证系统调用操作不在延迟槽内,所以直接加4得到下一条指令的地址;
    第二,将返回值存入 $v0

Thinking 4.2

Q:

思考 envid2env函数:

为什么 envid2env中需要判断 e->env_id != envid 的情况?如果没有这步判断会发生什么情况?

A:

1
2
#define NENV (1 << LOG2NENV)
#define ENVX(envid) ((envid) & (NENV - 1))

观察ENVX定义,发现实际上为取envid后十位,可能会有所重叠,

envid的独一性取决于mkenvid里不断增长的 i ,所以如果不判断envid是否相同,会取到错误的或者本该被销毁的进程控制块。

Thinking 4.3

Q:

思考下面的问题,并对这个问题谈谈你的理解:

请回顾 kern/env.c 文件 中 mkenvid() 函数的实现,该函数不会返回 0,请结合系统调用和 IPC 部分的实现与 envid2env() 函数的行为进行解释。

A:

mkenvid()函数定义为:

1
2
3
4
u_int mkenvid(struct Env *e) {
static u_int i = 0;
return ((++i) << (1 + LOG2NENV)) | (e - envs);
}

++i 保证了envid一定不会为0,而envid2env()的envid为0时返回curenv;

由于curenv为内核态的变量,用户态不能获取 curenvenvid,所以用 0 代表 curenv->envid
目的是方便用户进程调用 syscall_*() 时把当前进程的 envid 作为参数传给内核函数,不通过直接调用而是隐式调用的方法使用内核变量的值。

Thinking 4.4

Q:

关于 fork 函数的两个返回值,下面说法正确的是:

A、fork 在父进程中被调用两次,产生两个返回值

B、fork 在两个进程中分别被调用一次,产生两个不同的返回值

C、fork 只在父进程中被调用了一次,在两个进程中各产生一个返回值

D、fork 只在子进程中被调用了一次,在两个进程中各产生一个返回值

A:

答案为C,要区分调用次数和返回值个数的区别,fork()只在父进程中调用一次。

Thinking 4.5

Q:

我们并不应该对所有的用户空间页都使用duppage进行映射。那么究竟哪 些用户空间页应该映射,哪些不应该呢?请结合kern/env.c中env_init函数进行的页 面映射、include/mmu.h里的内存布局图以及本章的后续描述进行思考。

A:

0 ~ USTACKTOP 范围的内存需要使用 duppage 进行映射;
USTACKTOP 到 UTOP 之间的 user exception stack 是用来进行页写入异常的,不会在处理COW异常时调用 fork() ,所以 user exception stack 这一页不需要共享;
UTOP以上页面的内存与页表是所有进程共享的,且用户进程无权限访问,不需要做父子进程间的duppage

其上范围的内存要么属于内核,要么是所有用户进程共享的空间,用户模式下只可以读取。除只读、共享的页面外都需要设置 PTE_COW进行保护。

Thinking 4.6

Q:

在遍历地址空间存取页表项时你需要使用到vpd和vpt这两个指针,请参 考user/include/lib.h中的相关定义,思考并回答这几个问题:

•vpt和vpd的作用是什么?怎样使用它们?

•从实现的角度谈一下为什么进程能够通过这种方式来存取自身的页表?

•它们是如何体现自映射设计的?

•进程能够通过这种方式来修改自己的页表项吗?

A:

作用:在用户态下通过访问进程自己的物理内存获取用户页的页目录项页表项的 perm,用于 duppage 根据不同的 perm 类型在父子进程间执行不同的物理页映射;

使用:

vpd是页目录首地址,以vpd为基地址,加上页目录项偏移数即可指向va对应页目录项
vpt是页表首地址,以vpt为基地址,加上页表项偏移数即可指向va对应的页表项
自映射设计体现:

1
2
#define vpt ((volatile Pte *)UVPT)
#define vpd ((volatile Pde *)(UVPT + (PDX(UVPT) << PGSHIFT)))

(PDX(UVPT) << PGSHIFT) <==> UVPT >> 10

vpd的地址在UVPT和UVPT + PDMAP之间,说明将页目录映射到了某一页表位置(即实现了自映射);

不能。该区域对用户只读不写,若想要增添页表项,需要陷入内核进行操作。

Thinking 4.7

Q:

在 do_tlb_mod 函数中,你可能注意到了一个向异常处理栈复制 Trapframe 运行现场的过程,请思考并回答这几个问题:

• 这里实现了一个支持类似于“异常重入”的机制,而在什么时候会出现这种“异常重 入”?

• 内核为什么需要将异常的现场 Trapframe复制到用户空间

A:

当出现COW异常时,需要使用用户态的系统调用发生中断,即重入;

由于处理COW异常时调用的 handle_mod() 函数把epc改为用户态的异常处理函数 env_user_tlb_mod_entry ,退出内核中断后跳转到epc所在的用户态的异常处理函数。

所以将异常的现场 Trapframe复制到用户空间,用户态的异常处理函数从用户空间的用户异常栈读取相关数据进行异常处理。

Thinking 4.8

Q:

在用户态处理页写入异常,相比于在内核态处理有什么优势?

A:

内核不需要执行大量的页面拷贝工作;
内核态处理失误可能会使得操作系统崩溃;
用户状态下权限较低,避免改变不必要的内存空间;
在微内核的模式下,用户态进行新页面的分配映射也更加灵活方便。

Thinking 4.9

Q:

请思考并回答以下几个问题:

• 为什么需要将 syscall_set_tlb_mod_entry的调用放置在 syscall_exofork之前?

• 如果放置在写时复制保护机制完成之后会有怎样的效果?

A:

syscall_exofork()返回后父子进程各自执行自己的进程,子进程需要修改entry.S中定义的env指针,涉及到对COW页面的修改,会触发COW写入异常,COW中断的处理机制依赖于syscall_set_tlb_mod_entry,所以将 syscall_set_tlb_mod_entry 的调用放置在 syscall_exofork 之前;
父进程在调用写时复制保护机制可能会引发缺页异常,而异常处理未设置好,则不能正常处理。

lab4难点

T1

1
2
3
4
5
6
7
8
9
#include <asm/asm.h>

LEAF(msyscall)
// Just use 'syscall' instruction and return.

/* Exercise 4.1: Your code here. */
syscall
jr ra
END(msyscall)

T2

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
void do_syscall(struct Trapframe *tf) {
int (*func)(u_int, u_int, u_int, u_int, u_int);
int sysno = tf->regs[4];
if (sysno < 0 || sysno >= MAX_SYSNO) {
tf->regs[2] = -E_NO_SYS;
return;
}

/* Step 1: Add the EPC in 'tf' by a word (size of an instruction). */
/* Exercise 4.2: Your code here. (1/4) */
tf->cp0_epc += 4;

/* Step 2: Use 'sysno' to get 'func' from 'syscall_table'. */
/* Exercise 4.2: Your code here. (2/4) */

func = syscall_table[sysno];

/* Step 3: First 3 args are stored in $a1, $a2, $a3. */
u_int arg1 = tf->regs[5];
u_int arg2 = tf->regs[6];
u_int arg3 = tf->regs[7];

/* Step 4: Last 2 args are stored in stack at [$sp + 16 bytes], [$sp + 20 bytes]. */
u_int arg4, arg5;
/* Exercise 4.2: Your code here. (3/4) */

arg4 = *((u_int *)tf->regs[29] + 4);
arg5 = *((u_int *)tf->regs[29] + 5);

/* Step 5: Invoke 'func' with retrieved arguments and store its return value to $v0 in 'tf'.
*/
/* Exercise 4.2: Your code here. (4/4) */

tf->regs[2] = func(arg1, arg2, arg3, arg4, arg5);
}

T3

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
int envid2env(u_int envid, struct Env **penv, int checkperm) {
struct Env *e;

/* Step 1: Assign value to 'e' using 'envid'. */
/* Hint:
* If envid is zero, set 'penv' to 'curenv' and return 0.
* You may want to use 'ENVX'.
*/
/* Exercise 4.3: Your code here. (1/2) */
if (envid == 0) {
*penv = curenv;
return 0;
}
e = &envs[ENVX(envid)];

if (e->env_status == ENV_FREE || e->env_id != envid) {
return -E_BAD_ENV;
}

/* Step 2: Check when 'checkperm' is non-zero. */
/* Hints:
* Check whether the calling env has sufficient permissions to manipulate the
* specified env, i.e. 'e' is either 'curenv' or its immediate child.
* If violated, return '-E_BAD_ENV'.
*/
/* Exercise 4.3: Your code here. (2/2) */
if (checkperm && e != curenv && e->env_parent_id != curenv->env_id) {
return -E_BAD_ENV;
}

/* Step 3: Assign 'e' to '*penv'. */
*penv = e;
return 0;
}

T4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int sys_mem_alloc(u_int envid, u_int va, u_int perm) {
struct Env *env;
struct Page *pp;

/* Step 1: Check if 'va' is a legal user virtual address using 'is_illegal_va'. */
/* Exercise 4.4: Your code here. (1/3) */

if (is_illegal_va(va)) {
return -E_INVAL;
}

/* Step 2: Convert the envid to its corresponding 'struct Env *' using 'envid2env'. */
/* Hint: **Always** validate the permission in syscalls! */
/* Exercise 4.4: Your code here. (2/3) */
try(envid2env(envid, &env, 1));

/* Step 3: Allocate a physical page using 'page_alloc'. */
/* Exercise 4.4: Your code here. (3/3) */
try(page_alloc(&pp));

/* Step 4: Map the allocated page at 'va' with permission 'perm' using 'page_insert'. */
return page_insert(env->env_pgdir, env->env_asid, pp, va, perm);
}

T5

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
int sys_mem_map(u_int srcid, u_int srcva, u_int dstid, u_int dstva, u_int perm) {
struct Env *srcenv;
struct Env *dstenv;
struct Page *pp;

/* Step 1: Check if 'srcva' and 'dstva' are legal user virtual addresses using
* 'is_illegal_va'. */
/* Exercise 4.5: Your code here. (1/4) */

if (is_illegal_va(srcva) || is_illegal_va(dstva)) {
return -E_INVAL;
}

/* Step 2: Convert the 'srcid' to its corresponding 'struct Env *' using 'envid2env'. */
/* Exercise 4.5: Your code here. (2/4) */
try(envid2env(srcid, &srcenv, 1));

/* Step 3: Convert the 'dstid' to its corresponding 'struct Env *' using 'envid2env'. */
/* Exercise 4.5: Your code here. (3/4) */
try(envid2env(dstid, &dstenv, 1));

/* Step 4: Find the physical page mapped at 'srcva' in the address space of 'srcid'. */
/* Return -E_INVAL if 'srcva' is not mapped. */
/* Exercise 4.5: Your code here. (4/4) */
pp = page_lookup(srcenv->env_pgdir, srcva, NULL);
if (pp == NULL) {
return -E_INVAL;
}

/* Step 5: Map the physical page at 'dstva' in the address space of 'dstid'. */
return page_insert(dstenv->env_pgdir, dstenv->env_asid, pp, dstva, perm);
}

T6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int sys_mem_unmap(u_int envid, u_int va) {
struct Env *e;

/* Step 1: Check if 'va' is a legal user virtual address using 'is_illegal_va'. */
/* Exercise 4.6: Your code here. (1/2) */
if (is_illegal_va(va)) {
return -E_INVAL;
}

/* Step 2: Convert the envid to its corresponding 'struct Env *' using 'envid2env'. */
/* Exercise 4.6: Your code here. (2/2) */

try(envid2env(envid, &e, 1));

/* Step 3: Unmap the physical page at 'va' in the address space of 'envid'. */
page_remove(e->env_pgdir, e->env_asid, va);
return 0;
}

T7

void schedule(int yield) 指定yield为1指定切换

1
2
3
4
5
void __attribute__((noreturn)) sys_yield(void) {
// Hint: Just use 'schedule' with 'yield' set.
/* Exercise 4.7: Your code here. */
schedule(1);
}

T8

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
int sys_ipc_recv(u_int dstva) {
/* Step 1: Check if 'dstva' is either zero or a legal address. */
if (dstva != 0 && is_illegal_va(dstva)) {
return -E_INVAL;
}

/* Step 2: Set 'curenv->env_ipc_recving' to 1. */
/* Exercise 4.8: Your code here. (1/8) */

curenv->env_ipc_recving = 1;

/* Step 3: Set the value of 'curenv->env_ipc_dstva'. */
/* Exercise 4.8: Your code here. (2/8) */

curenv->env_ipc_dstva = dstva;

/* Step 4: Set the status of 'curenv' to 'ENV_NOT_RUNNABLE' and remove it from
* 'env_sched_list'. */
/* Exercise 4.8: Your code here. (3/8) */

curenv->env_status = ENV_NOT_RUNNABLE;
TAILQ_REMOVE(&env_sched_list, curenv, env_sched_link);

/* Step 5: Give up the CPU and block until a message is received. */
((struct Trapframe *)KSTACKTOP - 1)->regs[2] = 0;
schedule(1);
}

注意此处目标不要求为当前进程和子进程:

try(envid2env(envid, &e, 0);

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
int sys_ipc_try_send(u_int envid, u_int value, u_int srcva, u_int perm) {
struct Env *e;
struct Page *p;

/* Step 1: Check if 'srcva' is either zero or a legal address. */
/* Exercise 4.8: Your code here. (4/8) */

if (srcva != 0 && is_illegal_va(srcva)) {
return -E_INVAL;
}

/* Step 2: Convert 'envid' to 'struct Env *e'. */
/* This is the only syscall where the 'envid2env' should be used with 'checkperm' UNSET,
* because the target env is not restricted to 'curenv''s children. */
/* Exercise 4.8: Your code here. (5/8) */
try(envid2env(envid, &e, 0));

/* Step 3: Check if the target is waiting for a message. */
/* Exercise 4.8: Your code here. (6/8) */
if (e->env_ipc_recving == 0) {
return -E_IPC_NOT_RECV;
}

/* Step 4: Set the target's ipc fields. */
e->env_ipc_value = value;
e->env_ipc_from = curenv->env_id;
e->env_ipc_perm = PTE_V | perm;
e->env_ipc_recving = 0;

/* Step 5: Set the target's status to 'ENV_RUNNABLE' again and insert it to the tail of
* 'env_sched_list'. */
/* Exercise 4.8: Your code here. (7/8) */
e->env_status = ENV_RUNNABLE;
TAILQ_INSERT_TAIL(&env_sched_list, e, env_sched_link);

/* Step 6: If 'srcva' is not zero, map the page at 'srcva' in 'curenv' to 'e->env_ipc_dstva'
* in 'e'. */
/* Return -E_INVAL if 'srcva' is not zero and not mapped in 'curenv'. */
if (srcva != 0) {
/* Exercise 4.8: Your code here. (8/8) */
p = page_lookup(curenv->env_pgdir, srcva, NULL);
if (p == NULL) {
return -E_INVAL;
}
try(page_insert(e->env_pgdir, e->env_asid, p, e->env_ipc_dstva, perm));
}
return 0;
}

T9

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
int sys_exofork(void) {
struct Env *e;

/* Step 1: Allocate a new env using 'env_alloc'. */
/* Exercise 4.9: Your code here. (1/4) */
try(env_alloc(&e, curenv->env_id));

/* Step 2: Copy the current Trapframe below 'KSTACKTOP' to the new env's 'env_tf'. */
/* Exercise 4.9: Your code here. (2/4) */

e->env_tf = *((struct Trapframe *)KSTACKTOP - 1);

/* Step 3: Set the new env's 'env_tf.regs[2]' to 0 to indicate the return value in child. */
/* Exercise 4.9: Your code here. (3/4) */

e->env_tf.regs[2] = 0;

/* Step 4: Set up the new env's 'env_status' and 'env_pri'. */
/* Exercise 4.9: Your code here. (4/4) */

e->env_status = ENV_NOT_RUNNABLE;
e->env_pri = curenv->env_pri;

return e->env_id;
}

T10

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
static void duppage(u_int envid, u_int vpn) {
int r;
u_int addr;
u_int perm;

/* Step 1: Get the permission of the page. */
/* Hint: Use 'vpt' to find the page table entry. */
/* Exercise 4.10: Your code here. (1/2) */

addr = vpn << PGSHIFT;
perm = vpt[vpn] & ((1 << PGSHIFT) - 1);

/* Step 2: If the page is writable, and not shared with children, and not marked as COW yet,
* then map it as copy-on-write, both in the parent (0) and the child (envid). */
/* Hint: The page should be first mapped to the child before remapped in the parent. (Why?)
*/
/* Exercise 4.10: Your code here. (2/2) */
if ((perm & PTE_D) == 0 || (perm & PTE_LIBRARY) || (perm & PTE_COW)) {
if ((r = syscall_mem_map(0, (void *)addr, envid, (void *)addr, perm)) < 0) {
user_panic("user panic mem map error: %d", r);
}
} else {
if ((r = syscall_mem_map(0, (void *)addr, envid, (void *)addr,
(perm & ~PTE_D) | PTE_COW)) < 0) {
user_panic("user panic mem map error: %d", r);
}
if ((r = syscall_mem_map(0, (void *)addr, 0, (void *)addr,
(perm & ~PTE_D) | PTE_COW)) < 0) {
user_panic("user panic mem map error: %d", r);
}
}
}

T11

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void do_tlb_mod(struct Trapframe *tf) {
81 struct Trapframe tmp_tf = *tf;
82
83 if (tf->regs[29] < USTACKTOP || tf->regs[29] >= UXSTACKTOP) {
84 tf->regs[29] = UXSTACKTOP;
85 }
86 tf->regs[29] -= sizeof(struct Trapframe);
87 *(struct Trapframe *)tf->regs[29] = tmp_tf;
88 Pte *pte;
89 page_lookup(cur_pgdir, tf->cp0_badvaddr, &pte);
90 if (curenv->env_user_tlb_mod_entry) {
91 tf->regs[4] = tf->regs[29];
92 tf->regs[29] -= sizeof(tf->regs[4]);
93 // Hint: Set 'cp0_epc' in the context 'tf' to 'curenv->env_user_tlb_mod_entry'.
94 /* Exercise 4.11: Your code here. */
95 tf->cp0_epc = curenv->env_user_tlb_mod_entry;
96 } else {
97 panic("TLB Mod but no user handler registered");
98 }
99 }

T12

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int sys_set_tlb_mod_entry(u_int envid, u_int func) {
struct Env *env;

/* Step 1: Convert the envid to its corresponding 'struct Env *' using 'envid2env'. */
/* Exercise 4.12: Your code here. (1/2) */
try(envid2env(envid, &env, 1));

/* Step 2: Set its 'env_user_tlb_mod_entry' to 'func'. */
/* Exercise 4.12: Your code here. (2/2) */

env->env_user_tlb_mod_entry = func;

return 0;
}

T13

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
static void __attribute__((noreturn)) cow_entry(struct Trapframe *tf) {
u_int va = tf->cp0_badvaddr;
u_int perm;

/* Step 1: Find the 'perm' in which the faulting address 'va' is mapped. */
/* Hint: Use 'vpt' and 'VPN' to find the page table entry. If the 'perm' doesn't have
* 'PTE_COW', launch a 'user_panic'. */
/* Exercise 4.13: Your code here. (1/6) */
perm = PTE_FLAGS(vpt[VPN(va)]);
if ((perm & PTE_COW) == 0) {
user_panic("PTE_COW not found, va=%08x, perm=%08x", va, perm);
}

/* Step 2: Remove 'PTE_COW' from the 'perm', and add 'PTE_D' to it. */
/* Exercise 4.13: Your code here. (2/6) */
perm = (perm & ~PTE_COW) | PTE_D;

/* Step 3: Allocate a new page at 'UCOW'. */
/* Exercise 4.13: Your code here. (3/6) */

syscall_mem_alloc(0, (void *)UCOW, perm);

/* Step 4: Copy the content of the faulting page at 'va' to 'UCOW'. */
/* Hint: 'va' may not be aligned to a page! */
/* Exercise 4.13: Your code here. (4/6) */

memcpy((void *)UCOW, (void *)ROUNDDOWN(va, PAGE_SIZE), PAGE_SIZE);

// Step 5: Map the page at 'UCOW' to 'va' with the new 'perm'.
/* Exercise 4.13: Your code here. (5/6) */

syscall_mem_map(0, (void *)UCOW, 0, (void *)va, perm);

// Step 6: Unmap the page at 'UCOW'.
/* Exercise 4.13: Your code here. (6/6) */
syscall_mem_unmap(0, (void *)UCOW);

// Step 7: Return to the faulting routine.
int r = syscall_set_trapframe(0, tf);
user_panic("syscall_set_trapframe returned %d", r);
}

T14

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
int sys_set_env_status(u_int envid, u_int status) {
struct Env *env;

/* Step 1: Check if 'status' is valid. */
/* Exercise 4.14: Your code here. (1/3) */

if (status != ENV_RUNNABLE && status != ENV_NOT_RUNNABLE) {
return -E_INVAL;
}

/* Step 2: Convert the envid to its corresponding 'struct Env *' using 'envid2env'. */
/* Exercise 4.14: Your code here. (2/3) */

try(envid2env(envid, &env, 1));

/* Step 3: Update 'env_sched_list' if the 'env_status' of 'env' is being changed. */
/* Exercise 4.14: Your code here. (3/3) */

if (status == ENV_RUNNABLE && env->env_status != ENV_RUNNABLE) {
TAILQ_INSERT_TAIL(&env_sched_list, env, env_sched_link);
} else if (status == ENV_NOT_RUNNABLE && env->env_status != ENV_NOT_RUNNABLE) {
TAILQ_REMOVE(&env_sched_list, env, env_sched_link);
}

/* Step 4: Set the 'env_status' of 'env'. */
env->env_status = status;

/* Step 5: Use 'schedule' with 'yield' set if ths 'env' is 'curenv'. */
if (env == curenv) {
schedule(1);
}
return 0;
}

T15

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
int fork(void) {
u_int child;
u_int i;

/* Step 1: Set our TLB Mod user exception entry to 'cow_entry' if not done yet. */
if (env->env_user_tlb_mod_entry != (u_int)cow_entry) {
try(syscall_set_tlb_mod_entry(0, cow_entry));
}

/* Step 2: Create a child env that's not ready to be scheduled. */
// Hint: 'env' should always point to the current env itself, so we should fix it to the
// correct value.
child = syscall_exofork();
if (child == 0) {
env = envs + ENVX(syscall_getenvid());
return 0;
}

/* Step 3: Map all mapped pages below 'USTACKTOP' into the child's address space. */
// Hint: You should use 'duppage'.
/* Exercise 4.15: Your code here. (1/2) */
for (i = 0; i < PDX(UXSTACKTOP); i++) {
if (vpd[i] & PTE_V) {
for (u_int j = 0; j < PAGE_SIZE / sizeof(Pte); j++) {
u_long va = (i * (PAGE_SIZE / sizeof(Pte)) + j) << PGSHIFT;
if (va >= USTACKTOP) {
break;
}
if (vpt[VPN(va)] & PTE_V) {
duppage(child, VPN(va));
}
}
}
}

/* Step 4: Set up the child's tlb mod handler and set child's 'env_status' to
* 'ENV_RUNNABLE'. */
/* Hint:
* You may use 'syscall_set_tlb_mod_entry' and 'syscall_set_env_status'
* Child's TLB Mod user exception entry should handle COW, so set it to 'cow_entry'
*/
/* Exercise 4.15: Your code here. (2/2) */
syscall_set_tlb_mod_entry(child, cow_entry);
syscall_set_env_status(child, ENV_RUNNABLE);

return child;
}

实验体会总结

注意栈帧的用法

栈帧:进入函数体时会通过对栈指针做减法(压栈)的方式为该函数自身的局部变量、返回地址、调用函数的参数分配存储空间,在函数调用结束之后会**对栈指针做加法(弹栈)**来释放这部分空间 ,该空间就是栈帧。

调用方在自身栈帧的底部预留被调用函数的参数存储空间,由被调用方从调用方的栈帧中读取参数
寄存器 $a0-$a3 用于存放函数调用的前四个参数(但在栈中仍然需要为其预留空间),剩余的参数仅存放在栈中。

例子:msyscall 函数一共有 6 个参数,前 4 个参数会被 syscall_* 的函数分别存入 $a0-$a3 寄存器(寄存器传参的部分)同时栈帧底部保留 16 字节的空间(不要求存入参数的值),后 2 个参数只会被存入在预留空间之上的 8 字节空间内(没有寄存器传参),于是总共 24 字节的空间用于参数传递。

在本次实验中,一定要清楚现在是在改内核还是改用户,因为之前写的都是内核,而内核函数是不能在用户空间调用的,这一点要注意区分。

在用户态调用函数陷入内核态时,注意是否需要保存上下文。