LAB3_Part A Multiprocessor Support and Cooperative Multitasking
前言
记录一下自己的学习过程
实验内容翻译:
https://gitee.com/cherrydance/mit6.828
该翻译仅供参考
练习6
按照上述描述,在sched_yield()函数中实现轮询调度。不要忘记修改syscall()函数以调用sys_yield()。
确保在mp_main中调用sched_yield()。
修改kern/init.c以创建三个(或更多)运行程序user/yield.c的环境。
运行make qemu。你应该看到环境在彼此之间交替切换五次后终止,如下所示。
还可以测试多个CPU:make qemu CPUS=2。
在yield程序退出后,系统中将没有可运行的环境,调度器应该调用JOS内核监视器。如果出现任何问题,请在继续之前修复代码。
首先按照要求完善sched_yield()函数,函数要求如下
1,如果当前没有正在运行的环境,从零开始循环
2,如果当前有正在运行的环境,从当前位置往后循环
3,判断当前环境的状态是否为ENV_RUNNALBE,是则运行这个环境
4,如果没有可运行的环境,但之前在该CPU上运行的环境仍然处于ENV_RUNNING状态,选择该环境
实现代码如下:
if(!curenv){
for(size_t i = 0; i < NENV; ++i){
if(envs[i].env_status == ENV_RUNNABLE){
env_run(&envs[i]);
return;
}
}
}else {
uint32_t start = ENVX((curenv->env_id));
for(size_t i = start + 1; i != start; i = (i + 1) % NENV){
if(envs[i].env_status == ENV_RUNNABLE){
env_run(&envs[i]);
return;
}
}
}
if(curenv && curenv->env_status == ENV_RUNNING && curenv->env_cpunum == cpunum()){
env_run(curenv);
return;
}
然后修改syscall()函数以调用sys_yield()。
case SYS_yield:
ys_yield();
break;
修改kern/init.c以创建三个(或更多)运行程序user/yield.c的环境。这里使用了宏定义,user_yield代表user/yield.c,这其中的一些原理不太明白。
//ENV_CREATE(user_primes, ENV_TYPE_USER);
ENV_CREATE(user_yield, ENV_TYPE_USER);
ENV_CREATE(user_yield, ENV_TYPE_USER);
ENV_CREATE(user_yield, ENV_TYPE_USER);
最终结果如下:
问题3:
在你实现的env_run()函数中,你应该调用lcr3()函数。在调用lcr3()之前和之后,你的代码(至少应该)引用了变量e,它是传递给env_run的参数。在加载%cr3寄存器时,MMU使用的寻址上下文会立即改变。但是,虚拟地址(即e)相对于给定的地址上下文具有意义——地址上下文指定虚拟地址映射到的物理地址。为什么指针e在寻址切换之前和之后都可以被解引用?
说是因为两个环境的 e 映射 到了同一个物理地址,因此才能够正确的解引用。其中还有更深刻的原理,没明白。
问题4:
每当内核从一个环境切换到另一个环境时,它必须确保旧环境的寄存器被保存,以便稍后正确恢复。为什么?这是在哪里发生的?
保存寄存器的值以便能正确恢复原来的状态。环境的切换会导致系统调用,在_alltraps中会保存tf的值。
练习7
在kern/syscall.c中实现上述描述的系统调用,并确保syscall()调用它们。你需要使用kern/pmap.c和kern/env.c中的各种函数,特别是envid2env()函数。目前,每当调用envid2env()时,将1作为checkperm参数传递进去。确保检查任何无效的系统调用参数,并在这种情况下返回-E_INVAL。在继续之前,使用user/dumbfork测试你的JOS内核,并确保它能正常工作。
进入kern/syscall.c,按顺序实现要求的五个系统调用。
sys_exofork:
此系统调用创建一个几乎空白的新环境:用户地址空间中没有任何映射,并且它不能运行。新环境将在sys_exofork调用时具有与父环境相同的寄存器状态。在父进程中,sys_exofork将返回新创建环境的envid_t(如果环境分配失败,则返回负错误代码)。然而,在子进程中,它将返回0。(由于子进程最初被标记为不可运行,在父进程通过标记子进程为可运行之前,sys_exofork实际上不会在子进程中返回…)
1,使用env_alloc函数分配一个新环境
2,对返回值进行判断
3,复制父环境的寄存器状态
4,将新环境的status设置为ENV_NOT_RUNNABLE
5,将eax寄存器设置为0,因为eax寄存器保存系统调用的返回值
6,返回新环境的id
代码如下
struct Env *new_e = NULL;
int ret = env_alloc(&new_e, curenv->env_id);
if(ret < 0){
return ret;
}
new_e->env_tf = curenv->env_tf;
new_e->env_status = ENV_NOT_RUNNABLE;
new_e->env_tf.tf_regs.reg_eax = 0;
return new_e->env_id;
sys_env_set_status:
将指定环境的状态设置为ENV_RUNNABLE或ENV_NOT_RUNNABLE。通常使用此系统调用来标记新环境在其地址空间和寄存器状态完全初始化后准备好运行。
1,status必须是ENV_RUNNABLE或ENV_NOT_RUNNABLE,否则返回-E_INVAL
2,使用envid2env函数获取env,且第三个参数设置为1来检查权限
3,如果该envid不存在对应的env,返回-E_BAD_ENV
4,设置status并返回0
代码如下:
if(status != ENV_RUNNABLE && status != ENV_NOT_RUNNABLE){
return -E_INVAL;
}
struct Env *env_store;
int ret = envid2env(envid, &env_store, 1);
if(ret < 0){
return ret;
}
env_store->env_status = status;
return 0;
sys_page_alloc:
在给定环境的地址空间中,分配一页物理内存并将其映射到给定的虚拟地址。
1,判断输入的参数envid,va,perm是否正确
2,使用page_alloc分配一个页面
3,使用page_insert将分配的页面加入到给定环境的pgdir中
代码如下:
if(va != ROUNDDOWN(va, PGSIZE) || va >= (void *)UTOP || perm & (~PTE_SYSCALL)
|| !(perm & (PTE_U | PTE_W))){
return -E_INVAL;
}
struct Env *env_storage = NULL;
int ret = envid2env(envid, &env_storage, 1);
if(ret < 0){
return -E_BAD_ENV;
}
struct PageInfo *new_page = page_alloc(ALLOC_ZERO);
if(NULL == new_page){
-E_NO_MEM;
}
ret = page_insert(env_storage->env_pgdir, new_page, va, perm);
if(ret < 0){
page_free(new_page);
return -E_NO_MEM;
}
return 0;
sys_page_map:
将一页映射(而不是页面内容!)从一个环境的地址空间复制到另一个环境,从而保留内存共享安排,使新的和旧的映射都引用相同的物理内存页。
1,按要求判断参数的合法行
2,得到dstenv和srcenv
3,从srcenv获取page
4,将page插入dstenv
代码如下:
struct Env *srcenv = NULL;
struct Env *dstenv = NULL;
if(envid2env(srcenvid, &srcenv, 1) < 0 || envid2env(dstenvid, &dstenv, 1) < 0){
return -E_BAD_ENV;
}
if(PGOFF(srcva) || srcva >= (void *)UTOP || PGOFF(dstva) ||dstva >= (void *)UTOP){
return -E_INVAL;
}
pte_t *src_pte_store;
struct PageInfo *src_page = page_lookup(srcenv->env_pgdir, srcva, &src_pte_store);
if(!src_page){
return -E_INVAL;
}
if(perm & (~PTE_SYSCALL)){
return -E_INVAL;
}
if((perm & PTE_W) && (*src_pte_store & PTE_W) == 0){
return -E_INVAL;
}
if(page_insert(dstenv->env_pgdir, src_page, dstva, perm) < 0){
-E_NO_MEM;
}
return 0;
sys_page_unmap:
在给定环境中的给定虚拟地址处取消映射的页面。
1,检查参数的合法性
2,调用page_remove
代码如下:
struct Env *env_store = NULL;
if(envid2env(envid, &env_store, 1) < 0){
return -E_BAD_ENV;
}
if(PGOFF(va) || va >= (void *)UTOP){
return -E_INVAL;
}
page_remove(env_store->env_pgdir, va);
return 0;
最后在syscall函数中添加代码:
case SYS_exofork:
ret = sys_exofork();
break;
case SYS_env_set_status:
ret = sys_env_set_status((envid_t)a1, (int)a2);
break;
case SYS_page_alloc:
ret = sys_page_alloc((envid_t)a1, (void*)a2, (int)a3);
break;
case SYS_page_map:
ret = sys_page_map((envid_t)a1, (void *)a2, (envid_t)a3, (void *)a4, (int)a5);
break;
case SYS_page_unmap:
ret = sys_page_unmap((envid_t)a1, (void *)a2);
break;
使用make run-dumbfork
运行结果如下:
使用make grade
成功通过PartA的测试:
总结
完成了lab4的PartA部分。