前言

lab4终于完成了。lab4是到目前为止,比较复杂而且代码最多的lab。总共需要完成三个部分。part A添加多核支持,实现RR调度。part B实现Copy-On-Write forkpart C 实现时钟中断以及支持基本的IPC

Part A

Exercise 1

Implement mmio_map_region in kern/pmap.c. To see how this is used, look at the beginning of lapic_init in kern/lapic.c. You’ll have to do the next exercise, too, before the tests for mmio_map_region will run.

实现需要让Jos支持symmetric multiprocessing,也就是所有的cpu都拥有相同的权限去访问资源。Jos使用LAPIC来分发中断,以及通过它来知道当前使用的cpu。而处理器访问LAPIC,需要使用MMIO。所以我们需要对MMIO进行内存映射,这个函数比较简单。

1
2
3
4
5
6
7
8
9
10
11
12
13
void *
mmio_map_region(physaddr_t pa, size_t size) {
static uintptr_t base = MMIOBASE;

size = ROUNDUP(size, PGSIZE);
if(base + size > MMIOLIM){
panic("mmio_map_region: reservation mem overflow");
}
boot_map_region(kern_pgdir, base, size, pa, PTE_W | PTE_PCD | PTE_P);
uintptr_t b = base;
base += size;
return (void *) b;
}

Exercise 2

Read boot_aps() and mp_main() in kern/init.c, and the assembly code in kern/mpentry.S. Make sure you understand the control flow transfer during the bootstrap of APs. Then modify your implementation of page_init() in kern/pmap.c to avoid adding the page at MPENTRY_PADDR to the free list, so that we can safely copy and run AP bootstrap code at that physical address. Your code should pass the updated check_page_free_list() test (but might fail the updated check_kern_pgdir() test, which we will fix soon).

boot_aps()首先把mpentry.S的代码复制到内存MPENTRY_PADDR中。然后对每个cpu都启动一个进程,也就是APS,进程栈保存在percpu_kstacks。最后通过lapic_startap发生中断跳到mpentry.S去执行。然后进行死循环等待cpu进程启动。mpentry.S的功能与bootloader类似,最后会跳转到mp_main。然后就是进行一些初始化,改变cpu状态为启动,通知boot_aps()可以进行boot下一个进程了。

所以这里我门需要为MPENTRY_PADDR留出一块空间来放mpentry.S

1
2
3
mpentry_i = PGNUM(MPENTRY_PADDR);
pages[mpentry_i + 1].pp_link = pages[mpentry_i].pp_link;
pages[mpentry_i].pp_link = NULL;

Question 1

Compare kern/mpentry.S side by side with boot/boot.S. Bearing in mind that kern/mpentry.S is compiled and linked to run above KERNBASE just like everything else in the kernel, what is the purpose of macro MPBOOTPHYS? Why is it necessary in kern/mpentry.S but not in boot/boot.S? In other words, what could go wrong if it were omitted in kern/mpentry.S?
Hint: recall the differences between the link address and the load address that we have discussed in Lab 1.

MPBOOTPHYS的作用是将高地址变为地址。因为根据注释上的说明,此次还处于实模式,但是代码中的地址已经经过重定位,所以需要进行地址翻译。

Exercise 3

Modify mem_init_mp() (in kern/pmap.c) to map per-CPU stacks starting at KSTACKTOP, as shown in inc/memlayout.h. The size of each stack is KSTKSIZE bytes plusKSTKGAP bytes of unmapped guard pages. Your code should pass the new check in check_kern_pgdir().

为每个cpu栈分配内存,比较简单,按照memlayout来就行。

1
2
3
4
5
6
7
8
9
static void
mem_init_mp(void) {
uintptr_t kstacktop_i;
for(int i = 0; i < NCPU; i++){
kstacktop_i = KSTACKTOP - i * (KSTKSIZE + KSTKGAP);
boot_map_region(kern_pgdir, kstacktop_i - KSTKSIZE, KSTKSIZE,
PADDR(percpu_kstacks[i]), PTE_W);
}
}

Exercise 4

The code in trap_init_percpu() (kern/trap.c) initializes the TSS and TSS descriptor for the BSP. It worked in Lab 3, but is incorrect when running on other CPUs. Change the code so that it can work on all CPUs. (Note: your new code should not use the global ts variable any more.)

初始化每个cpu进程,这里也没什么好说的,照着注释写即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void
trap_init_percpu(void) {
int i = cpunum();
// Setup a TSS so that we get the right stack
// when we trap to the kernel.
thiscpu->cpu_ts.ts_esp0 = (uintptr_t)percpu_kstacks[i];
//thiscpu->cpu_ts.ts_esp0 = KSTACKTOP - i * (KSTKSIZE + KSTKGAP);
thiscpu->cpu_ts.ts_ss0 = GD_KD;
thiscpu->cpu_ts.ts_iomb = sizeof(struct Taskstate);

// Initialize the TSS slot of the gdt.
gdt[(GD_TSS0 >> 3) + i] = SEG16(STS_T32A, (uint32_t) (&thiscpu->cpu_ts),
sizeof(struct Taskstate) - 1, 0);
gdt[(GD_TSS0 >> 3) + i].sd_s = 0;

// Load the TSS selector (like other segment selectors, the
// bottom three bits are special; we leave them 0)
ltr(GD_TSS0 + (i << 3));

// Load the IDT
lidt(&idt_pd);
}

Exercise 5

Apply the big kernel lock as described above, by calling lock_kernel() and unlock_kernel() at the proper locations.

现在有多个cpu进程都能运行kernel,所以现在必须要解决进程间资源竞争的问题,这里直接套了一个大锁,进程进入kernel的时候上锁,退出的时候释放。。。略邪恶。这里需要贴的地方比较多,但代码比较简单,我就不放了。需要的可以去github上找。

Question 2

It seems that using the big kernel lock guarantees that only one CPU can run the kernel code at a time. Why do we still need separate kernel stacks for each CPU? Describe a scenario in which using a shared kernel stack will go wrong, even with the protection of the big kernel lock.

这个问题采用了AL–Zn的回答。中断发生时会自动压栈,而这时候还没有取得锁,若多个CPU同时发生中断,共享内核栈将会出错。

Exercise 6

Implement round-robin scheduling in sched_yield() as described above. Don’t forget to modify syscall() to dispatch sys_yield().
Make sure to invoke sched_yield() in mp_main.
Modify kern/init.c to create three (or more!) environments that all run the program user/yield.c

实现RR调度。顺序循环进行遍历进程,从当前进程开始找,直到找到第一个ENV_RUNNABLE的进程,并运行它。具体细节参照注释。实现如下。

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
void
sched_yield(void) {
int cur_idx;
if (curenv)
cur_idx = ENVX(curenv->env_id);
else
cur_idx = 0;

if(envs[cur_idx].env_status == ENV_RUNNABLE){
env_run(&envs[cur_idx]);
}
for(int i = cur_idx + 1; i != cur_idx; i = (i + 1) % NENV){
if(envs[i].env_status == ENV_RUNNABLE){
env_run(&envs[i]);
return;
}
}
if(curenv && curenv->env_status == ENV_RUNNING) {
env_run(curenv);
return;
}

// sched_halt never returns
sched_halt();
}

Question 3

In your implementation of env_run() you should have called lcr3(). Before and after the call to lcr3(), your code makes references (at least it should) to the variablee, the argument to env_run. Upon loading the %cr3 register, the addressing context used by the MMU is instantly changed. But a virtual address (namely e) has meaning relative to a given address context–the address context specifies the physical address to which the virtual address maps. Why can the pointer e be dereferenced both before and after the addressing switch?

因为mem_init()时是以kernel pgdir为模板的

Question 4

Whenever the kernel switches from one environment to another, it must ensure the old environment’s registers are saved so they can be restored properly later. Why? Where does this happen?

当然需要保存。不然进程切换时无法恢复啊。

1
2
3
4
// Copy trap frame (which is currently on the stack)
// into 'curenv->env_tf', so that running the environment
// will restart at the trap point.
curenv->env_tf = *tf;

Exercise 7

Implement the system calls described above in kern/syscall.c. You will need to use various functions in kern/pmap.c and kern/env.c, particularly envid2env(). For now, whenever you call envid2env(), pass 1 in the checkperm parameter. Be sure you check for any invalid system call arguments, returning -E_INVAL in that case. Test your JOS kernel with user/dumbfork and make sure it works before proceeding.

这里虽然看起来需要实现的代码很多,其实并不难,因为注释很详细,需要完成的任务也简单。

sys_exofork这里的fork,只是单纯分配了一块地址空间给进程,但里面是空的,所以目前进程还是不可运行。具体做法也很直观,下面是实现,注意要把子进程返回值的寄存器设为0.

1
2
3
4
5
6
7
8
9
10
11
12
static envid_t
sys_exofork(void) {
struct Env *child_env;
int r;
r = env_alloc(&child_env, curenv->env_id);
if(r!=0)
return r;
child_env->env_status = ENV_NOT_RUNNABLE;
child_env->env_tf = curenv->env_tf;
child_env->env_tf.tf_regs.reg_eax = 0;
return child_env->env_id;
}

sys_env_set_status设置状态,不多说。

1
2
3
4
5
6
7
8
9
10
11
12
static int
sys_env_set_status(envid_t envid, int status) {
struct Env *env;
int r = envid2env(envid, &env, 1);
if(r!=0)
return r;
if(env->env_status == ENV_RUNNABLE || env->env_status == ENV_NOT_RUNNABLE){
env->env_status = status;
return 0;
}
return -E_INVAL;
}

sys_page_alloc 分配页的系统调用,不难,比较烦的是权限检查。

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
static int
sys_page_alloc(envid_t envid, void *va, int perm) {
struct Env *env;
int r = envid2env(envid, &env, 1);
if(r!=0)
return r;
if (va > (void *) UTOP || va != ROUNDDOWN(va, PGSIZE))
return -E_INVAL;
int flag = PTE_U | PTE_P;

if ((perm & flag) != flag)
return -E_INVAL;

if (perm & (~(PTE_U | PTE_P | PTE_AVAIL | PTE_W)))
return -E_INVAL;

struct PageInfo *pp = page_alloc(1);
if(pp == NULL)
return -E_NO_MEM;
pp->pp_ref ++;
r = page_insert(env->env_pgdir, pp, va, perm);
if(r!=0){
page_free(pp);
return r;
}
return 0;
}

sys_page_map,页的映射,用来两个进程通信时使用,使两块地址空间的部分内容映射到同一块物理地址。也不难。。还是烦在地址检查。

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
static int
sys_page_map(envid_t srcenvid, void *srcva,
envid_t dstenvid, void *dstva, int perm) {
struct Env *srcenv, *dstenv;
int r = envid2env(srcenvid, &srcenv, 1);
if(r!=0)
return r;
r = envid2env(dstenvid, &dstenv, 1);
if(r!=0)
return r;

if (srcva >= (void *) UTOP || dstva >= (void *) UTOP ||
srcva != ROUNDDOWN(srcva, PGSIZE) || dstva != ROUNDDOWN(dstva, PGSIZE))
return -E_INVAL;

pte_t *pte;
struct PageInfo *pp = page_lookup(srcenv->env_pgdir, srcva, &pte);
if(pp == NULL)
return -E_INVAL;

if ((!(perm & (PTE_U | PTE_P))) || (perm & (~(PTE_U | PTE_P | PTE_AVAIL | PTE_W))))
return -E_INVAL;

if ((perm & PTE_W) && !(*pte & PTE_W))
return -E_INVAL;

r = page_insert(dstenv->env_pgdir, pp, dstva, perm);
return r;
}

sys_page_unmap不多说

1
2
3
4
5
6
7
8
9
10
11
12
static int
sys_page_unmap(envid_t envid, void *va) {
struct Env *env;
int r = envid2env(envid, &env, 1);
if(r!=0)
return r;
if (va > (void *)UTOP || va != ROUNDDOWN(va, PGSIZE))
return -E_INVAL;

page_remove(env->env_pgdir, va);
return 0;
}

Part B

Part B的任务主要是实现Copy-on-Write Fork,也就是所谓的写时拷贝技术。传统的fork函数,调用时会直接将父进程的内存内容拷贝到子进程当中了,但是大多数是程序,当fork之后就直接调用exec()填充新的内存内容了。所以拷贝这一步很浪费时间。所以现在采用的做法是COW fork,也就是把拷贝的时间推迟到子进程需要写入的时候。这种做法用的很多,比如cache更新的情况。

具体做法是,当fork调用时,把子进程的用户空间的地址都映射到父进程时,并且设置权限位,直到子进程需要写内存的时候,权限判断失败,并触发page fault,然后进行系统分配页,把需要写入的那一页内存copy出来,并分配到子进程的地址空间中。

所以在写COW Fork之前,首先需要处理在用户空间page fault的情况。

Exercise 8

Implement the sys_env_set_pgfault_upcall system call. Be sure to enable permission checking when looking up the environment ID of the target environment, since this is a “dangerous” system call

这个很简单了,就是设置pgfault时需要调用的函数。

1
2
3
4
5
6
7
8
9
static int
sys_env_set_pgfault_upcall(envid_t envid, void *func) {
struct Env *env;
int r = envid2env(envid, &env, 1);
if(r!=0)
return r;
env->env_pgfault_upcall = func;
return 0;
}

Exercise 9

Implement the code in page_fault_handler in kern/trap.c required to dispatch page faults to the user-mode handler. Be sure to take appropriate precautions when writing into the exception stack. (What happens if the user environment runs out of space on the exception stack?)

这个函数比较复杂。因为这里涉及到栈的切换。当kernel处理page_fault时,并不是在kernel栈或者用户栈处理,而是启用了一个新的栈,用户异常栈。并且使用一个新的数据结构UTrapframe来保存触发page_fault的进程信息。所以现在用户空间的缺页流程是这样的的,用户空间发生缺页,产生中断,陷入到内核中,分发到page_fault_handler中,在用户异常栈保存错误进程的信息,以及错误地址(保存到UTrapframe中),切换到用户异常栈,然后调用用户自定义的pgfault_upcall,最后再切换到原来错误的地方继续运行。

栈的切换分成两种情况。

  • 用户进程发生page_fault用户栈 -> kernel栈 -> 用户异常栈
  • page_fault处理时又发生page_fault。虽然已经在用户异常栈了,但还是会继续陷入到内核中,重走一遍上面的流程,这里需要注意,压入UTrapframe时,需要空4个字节。所以栈切换顺序为,用户异常栈 -> kernel栈 -> 用户异常栈

好,现在情况应该比较清楚了。所以目前这个函数需要做的就是在用户异常栈中压入UTrapframe数据结构,并保存错误进程的信息,以便之后恢复重新运行。这里有一点要注意,就是有可能发生上面说的第二种情况,所以需要判断进程的栈地址是不是已经在用户异常栈了,然后需要多压4字节,另外别忘了检查地址用完的情况。代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
if(curenv->env_pgfault_upcall!=NULL){
struct UTrapframe *utf;
uintptr_t utf_addr;
if(tf->tf_esp >= UXSTACKTOP - PGSIZE && tf->tf_esp < UXSTACKTOP){
utf_addr = tf->tf_esp - sizeof(struct UTrapframe) - 4;
}
else{
utf_addr = UXSTACKTOP - sizeof(struct UTrapframe);
}
user_mem_assert(curenv, (void *) utf_addr, sizeof(struct UTrapframe), PTE_W);

utf = (struct UTrapframe *) utf_addr;
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;

tf->tf_eip = (uintptr_t) curenv->env_pgfault_upcall;
tf->tf_esp = utf_addr;
env_run(curenv);
}

Exercise 10

Implement the _pgfault_upcall routine in lib/pfentry.S. The interesting part is returning to the original point in the user code that caused the page fault. You’ll return directly there, without going back through the kernel. The hard part is simultaneously switching stacks and re-loading the EIP.

这是我最头疼的部分。这段汇编先调用了用户自定义的page_handler函数,然后需要根据之前压入的UTrapframe来切换为发生的错误的进程继续运行。这里我用了PKU张弛的代码。。他貌似也是用了别人的代码。。。我没有过多去纠结这段代码。大致是把返回的错误地址填到之前空的4个字节处,再加点trick,就可以同时切换espeip。下面是代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
movl 0x28(%esp), %eax
subl $0x4, 0x30(%esp)
movl 0x30(%esp), %edx
movl %eax, (%edx)
addl $0x8, %esp

// Restore the trap-time registers. After you do this, you
// can no longer modify any general-purpose registers.
popal

// Restore eflags from the stack. After you do this, you can
// no longer use arithmetic operations or anything else that
// modifies eflags.
addl $0x4, %esp
popfl
// Switch back to the adjusted trap-time stack.
// LAB 4: Your code here.
popl %esp
// Return to re-execute the instruction that faulted.
ret

Exercise 11

Finish set_pgfault_handler() in lib/pgfault.c.

最后就简单了,为用户异常栈分配页,已经设置upcall调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void
set_pgfault_handler(void (*handler)(struct UTrapframe *utf)) {
int r;
if (_pgfault_handler == 0) {
if(sys_page_alloc(thisenv->env_id,
(void *) (UXSTACKTOP - PGSIZE), PTE_U|PTE_P|PTE_W) != 0){
panic("set_pgfault_handler: sys_page_alloc failed");
}
if(sys_env_set_pgfault_upcall(thisenv->env_id, _pgfault_upcall) != 0){
panic("set_pgfault_handler: sys_env_set_pgfault_upcall failed");
}
}
// Save handler pointer for assembly to call.
_pgfault_handler = handler;
}

整个个过程非常trick,十分有趣。其实绕这么一大圈,最终的目的还是为了保护kernel

Exercise 12

Implement fork, duppage and pgfault in lib/fork.c.

还没完。。。高潮才刚刚到来。前面搞了这么多,都只是为了实现COW Fork!。因为我门需要把COW Fork作为自定义函数库来实现。所以前面弄了这么多,只有一个目的,那就是在用户进程中handle page fault

关与具体实现,其实mit的讲义上已经很清楚了。大致就是说把UTOP以下的地址都通过duppage进行映射到同一块物理地址上,当子进程发生缺页时,才通过pgfault来分配页。具体细节不赘述。

下面是代码。

fork

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
envid_t fork(void) {
envid_t envid;
uintptr_t addr;

set_pgfault_handler(&pgfault);
envid = sys_exofork();
if(envid < 0)
panic("fork: sys_exofork failed");
if(envid == 0){
thisenv = &envs[ENVX(sys_getenvid())];
return 0;
}

for (addr = 0; addr < USTACKTOP; addr += PGSIZE) {
if ((uvpd[PDX(addr)] & PTE_P) && (uvpt[PGNUM(addr)] & PTE_P))
duppage(envid, PGNUM(addr));
}

if (sys_page_alloc(envid, (void *) (UXSTACKTOP - PGSIZE),
PTE_P | PTE_U | PTE_W) < 0)
panic("fork: sys_page_alloc failed");

extern void _pgfault_upcall();
sys_env_set_pgfault_upcall(envid, _pgfault_upcall);

if (sys_env_set_status(envid, ENV_RUNNABLE) != 0)
panic("fork: sys_env_set_status");

return envid;
}

duppage

1
2
3
4
5
6
7
8
9
10
11
12
static int duppage(envid_t envid, unsigned pn) {
int r;
void *addr = (void *)(pn * PGSIZE);
if ((uvpt[pn] & PTE_W) || (uvpt[pn] & PTE_COW)) {
if ((r = sys_page_map(0, addr, envid, addr, PTE_P | PTE_U | PTE_COW)) < 0)
panic("duppage: %e", r);
if ((r = sys_page_map(0, addr, 0, addr, PTE_P | PTE_U | PTE_COW)) < 0)
panic("duppage: %e", r);
} else if ((r = sys_page_map(0, addr, envid, addr, PTE_P | PTE_U)) < 0)
panic("duppage: %e", r);
return 0;
}

pafault

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static void
pgfault(struct UTrapframe *utf) {
void *addr = (void *) utf->utf_fault_va;
uint32_t err = utf->utf_err;
int r;

pte_t pte = ((pte_t *) uvpt)[PGNUM(addr)];
if(!( (err & FEC_WR) != 0 && (pte & PTE_COW)!=0 )){
panic("pgfault: not write and not a COW page");
return;
}

addr = ROUNDDOWN(addr, PGSIZE);
envid_t envid = sys_getenvid();
if ((r = sys_page_alloc(envid, PFTEMP, PTE_P | PTE_U | PTE_W)) < 0)
panic("pgfault: %e", r);
memcpy(PFTEMP, addr, PGSIZE);
if ((r = sys_page_map(envid, PFTEMP, envid, addr, PTE_P | PTE_U | PTE_W)) < 0)
panic("pgfault: %e", r);
if ((r = sys_page_unmap(envid, PFTEMP)) < 0)
panic("pgfault: %e", r);

return;
}

精彩的Part B

Part C

实现基本的IPC通信,这部分代码还比较简单。

Exercise 13

Modify kern/trapentry.S and kern/trap.c to initialize the appropriate entries in the IDT and provide handlers for IRQs 0 through 15. Then modify the code inenv_alloc() in kern/env.c to ensure that user environments are always run with interrupts enabled.

很简单,像之前注册中断门,这里需要参考intel手册注册相应的IPC门。代码略去。

完成之后,Jos就能进行时钟中断了。

Exercise 14

Modify the kernel’s trap_dispatch() function so that it calls sched_yield() to find and run a different environment whenever a clock interrupt takes place.

You should now be able to get the user/spin test to work: the parent environment should fork off the child, sys_yield() to it a couple times but in each case regain control of the CPU after one time slice, and finally kill the child environment and terminate gracefully.

处理时钟中断。防止进程死循环一直霸占cpu,所以需要在时钟中断处,重新调度进程。

1
2
3
4
5
if (tf->tf_trapno == IRQ_OFFSET + IRQ_TIMER) {
lapic_eoi();
sched_yield();
return;
}

Exercise 15

Implement sys_ipc_recv and sys_ipc_try_send in kern/syscall.c. Read the comments on both before implementing them, since they have to work together. When you call envid2env in these routines, you should set the checkperm flag to 0, meaning that any environment is allowed to send IPC messages to any other environment, and the kernel does no special permission checking other than verifying that the target envid is valid.

Then implement the ipc_recv and ipc_send functions in lib/ipc.c.

这几个函数都很好写,因为注释很详细!

sys_ipc_recv,这里等待接受信息,只需要改变进程状态,直接调度就会阻塞了。我一开始没反应过来还弄了死循环。

1
2
3
4
5
6
7
8
9
10
11
12
13
static int
sys_ipc_recv(void *dstva) {
if(dstva < (void *)UTOP && dstva != ROUNDDOWN(dstva, PGSIZE)){
return -E_INVAL;
}
curenv->env_ipc_recving =true;
curenv->env_ipc_dstva = dstva;
curenv->env_status = ENV_NOT_RUNNABLE;
sys_yield();

return 0;

}

sys_ipc_try_send需要注意一点就是共享内存时,不能直接使用sys_page_map,因为sys_page_map查找env时会检查权限。

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
static int
sys_ipc_try_send(envid_t envid, uint32_t value, void *srcva, unsigned perm) {
struct Env *env;
int r = envid2env(envid, &env, 0);
int flag = PTE_U | PTE_P;
if(r!=0)
return r;
if(!env->env_ipc_recving)
return -E_IPC_NOT_RECV;
if(srcva < (void *) UTOP){
if(srcva != ROUNDUP(srcva, PGSIZE))
return -E_INVAL;

pte_t *pte;
struct PageInfo *pp = page_lookup(curenv->env_pgdir, srcva, &pte);
if(pp == NULL)
return -E_INVAL;

if ((!(perm & (PTE_U | PTE_P))) ||
(perm & (~(PTE_U | PTE_P | PTE_AVAIL | PTE_W))))
return -E_INVAL;

if ((perm & PTE_W) && !(*pte & PTE_W))
return -E_INVAL;

r = page_insert(env->env_pgdir, pp, env->env_ipc_dstva, perm);
if(r!=0)
return r;
}
env->env_ipc_value = value;
env->env_ipc_recving = false;
env->env_ipc_from = curenv->env_id;
env->env_status = ENV_RUNNABLE;
env->env_tf.tf_regs.reg_eax = 0;
return 0;
}

然后就是函数库的两个函数,是对上面两个系统调用的包装,让用户使用更为方便。没啥可说的,看代码把。

ipc_recv

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int32_t
ipc_recv(envid_t *from_env_store, void *pg, int *perm_store) {
int r;
if(!pg){
pg = (void *) -1;
}
r = sys_ipc_recv(pg);
if(r==0){
if(from_env_store)
*from_env_store = thisenv->env_ipc_from;
if(perm_store)
*perm_store = thisenv->env_ipc_perm;
return thisenv->env_ipc_value;
}
else{
*from_env_store = 0;
*perm_store = 0;
return r;
}
return 0;
}

ipc_send

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void
ipc_send(envid_t to_env, uint32_t val, void *pg, int perm) {
int r;
if(!pg)
pg = (void *) -1;

while(1){
r = sys_ipc_try_send(to_env, val, pg, perm);
if(r == 0)
break;
if(r != -E_IPC_NOT_RECV)
panic("ipc_send:%e", r);
sys_yield();
}
}

lab4完。