/* Step 1: Add the EPC in 'tf' by a word (size of an instruction). */ /* Exercise 4.2: Your code here. (1/4) */ //将EPC寄存器值自增4,也就是一个指令的长度 tf->cp0_epc += 4; /* Step 2: Use 'sysno' to get 'func' from 'syscall_table'. */ /* Exercise 4.2: Your code here. (2/4) */ //依据相应的系统调用号在syscall_table中索引得到相应的系统调用 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) */ //此处注意最后两个参数是存放在栈底偏16、20字节的位置 //也就是需要通过解引用获取相应地址对应的值的形式来获取 //这里是先将tf->regs[]的u_long转化成(u_int *)的形式,注意先转换,不然很有可能出现地址不对齐的情况, //对应读取内存的话,可能会触发mips中的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); }
intenvid2env(u_int envid, struct Env **penv, int checkperm) { structEnv *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) */ //根据相应的索引获取对应的进程控制块 e = &envs[ENVX(envid)]; //根据提示填写 if (envid == 0) { *penv = curenv; return0; } //如果获取的进程压根不在运行或者已被销毁,返回-E_BAD_ENV 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) */ //根据checkperm位对可进行操作的权限进行检查,不符合同样返回-E_BAD_ENV if (checkperm != 0 ) { if ( e != curenv && e->env_parent_id != curenv->env_id) { return -E_BAD_ENV; } } /* Step 3: Assign 'e' to '*penv'. */ *penv = e; return0; }
Exercise 4.4
题面
1
实现 kern/syscall_all.c 中的 intsys_mem_alloc(u_int envid,u_int va, u_int perm) 函数。
intsys_mem_unmap(u_int envid, u_int va) { structEnv *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函数的监控 try(envid2env(envid, &e, 1)); /* Step 3: Unmap the physical page at 'va' in the address space of 'envid'. */ //调用page_remove函数来完成相应的删除映射工作 //至于此处为啥没有异常处理,那是因为原来没有建立映射的话,不就直接就完成了工作了() page_remove(e->env_pgdir, e->env_asid, va); return0; }
Exercise 4.7
题面
1
实现 kern/syscall_all.c 中的 voidsys_yield(void) 函数。
回答
代码如下:
1 2 3 4 5 6
//注意该函数也是noreturn的属性,调用其的函数中在其后续运行的语句将会无效 void __attribute__((noreturn)) sys_yield(void) { // Hint: Just use 'schedule' with 'yield' set. /* Exercise 4.7: Your code here. */ schedule(1); }
intsys_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) */ //阻塞当前进程,注意此处需要手动将其从调度队列中删除,为了保证调度队列的一致性, //因此在`schedule()`函数运行时对于当前进程阻塞的情况不需要去手动删除了 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. */ //放弃CPU,阻塞直到接收到数据 ((struct Trapframe *)KSTACKTOP - 1)->regs[2] = 0; schedule(1); }
intsys_ipc_try_send(u_int envid, u_int value, u_int srcva, u_int perm) { structEnv *e; structPage *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对应的PCB try(envid2env(envid, &e, 0)); /* Step 3: Check if the target is waiting for a message. */ /* Exercise 4.8: Your code here. (6/8) */ //如果接受进程并不是在准备接受数据的状态,那么就返回异常值-E_IPC_NOT_RECV if (e->env_ipc_recving != 1) { 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) */ //在srcva不为0的情况下,利用page_lookup找到对应的物理页 //由于page_lookup函数不自带异常返回值的处理 //因此,需要单独判断 p = page_lookup(curenv->env_pgdir, srcva, NULL); if (p == NULL) { return -E_INVAL; } //利用page_insert来完成curenv中的相应物理页与e中的接受虚地址e->env_ipc_dstva的映射关系的建立 //同时perm是相应的给定参数 try(page_insert(e->env_pgdir, e->env_asid, p, e->env_ipc_dstva, perm)); } return0; }
intsys_exofork(void) { structEnv *e; /* Step 1: Allocate a new env using 'env_alloc'. */ /* Exercise 4.9: Your code here. (1/4) */ //在完成此函数的时候需要默认的一点是,当前进程curenv应该是作为新创建进程的父进程 //不然该函数将毫无意义 //因此此处的parentid应该是当前结点的env_id 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) */ //由于在栈结构中,栈顶一般指向的是填入数据末尾的下一段空空间 //因此需要先将其转化为对应的结构指针然后自减1,回到栈内容末尾 //并通过解引用的形式获取相应的结构体变量值 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) */ //由于子进程的fork返回值是通过相应栈空间中的$v0寄存器的值来存储 //因此需要进行相应的赋值 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; //返回的子进程id,说明父进程的fork返回值通过该函数的返回值决定 return e->env_id; }
staticvoid __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 &= (~PTE_COW); perm |= PTE_D; /* Step 3: Allocate a new page at 'UCOW'. */ /* Exercise 4.13: Your code here. (3/6) */ //在UCOW(0x003f f000)处申请分配一块临时的物理页,用于存放va所在页上的数据 try(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) */ //将发生异常地址va映射到临时页面上,设定相应的新的权限位 try(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) */ //解除UCOW对于临时页面的内存映射,相当于进行了一个置换,用UCOW去申请一个新页面,将va映射到新页面后,完成使命,UCOW解除映射关系 try(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); }
//user/lib/libos.c ... constvolatilestructEnv *env; externintmain(int, char **); voidlibmain(int argc, char **argv) { // set env to point at our env structure in envs[]. env = &envs[ENVX(syscall_getenvid())];
// call user main routine main(argc, argv);
// exit gracefully exit(); }
intfork(void) { u_int child; u_int i; /* Step 1: Set our TLB Mod user exception entry to 'cow_entry' if not done yet. */ //此处的env并不是凭空出现的,其定义在user/lib/libos.c中的libmain()函数中,作为初始的进程 if (env->env_user_tlb_mod_entry != (u_int)cow_entry) { 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. //通过syscall_exofork()函数获取对应的子进程号 child = syscall_exofork(); if (child == 0) { //子进程在被syscall_exofork创建后, //直接执行该if判断,由于提前将其进程上下文中的返回值$v0设置为0 //因此子进程中会直接入该if判断中执行如下操作, //并返回子进程下的pid,0 env = envs + ENVX(syscall_getenvid()); return0; } /* 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) */ //此处只能通过如下朴素的遍历方式来判断相应二级页表项的有效性 //不能使用page_lookup()来寻找对应有效的物理页的原因是page_lookup()是内核态下的方法 //而该函数实现在用户态下,压根没有相应的声明和定义 for (i = 0; i <= PDX(USTACKTOP); i++) { if ((vpd[i] & PTE_V) != 0) { for (u_int j = 0; j < 1024; j++) { u_long va = (i * 1024 + j) << PGSHIFT; if (va >= USTACKTOP) { break; } if ((vpt[VPN(va)] & PTE_V) != 0) { //将父进程中有效的页面共享给子进程 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); //返回子进程号,完成fork()函数 return child; }
//进程传递的具体数值 u_int env_ipc_value; // the value sent to us //发送方ID u_int env_ipc_from; // envid of the sender //接收方接收数据状态,1表示等待接受、0表示不可接受 u_int env_ipc_recving; // whether this env is blocked receiving //接收到的页面需与自身的哪个需页面完成映射 u_int env_ipc_dstva; // va at which the received page should be mapped //传递页面的权限位设置 u_int env_ipc_perm; // perm in which the received page should be mapped