持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第17天,点击查看活动详情
接着上一文中的内容,这节的内容是环境创建系统调用,虽然你的内核现在有能力运行和切换多用户级进程,但是它仍然只能跑内核初始创建的进程。你现在将实现必要的JOS系统调用来运行用户进程来创建和启动其它新的用户进程。下面细说
虽然你的内核现在有能力运行和切换多用户级进程,但是它仍然只能跑内核初始创建的进程。你现在将实现必要的JOS系统调用来运行用户进程来创建和启动其它新的用户进程。
Unix提供了fork系统调用来创建进程,它拷贝父进程的整个地址空间到新创建的子进程。两个进程之间唯一的区别是它们的进程ID,在父进程fork返回的是子进程ID,而在子进程fork返回的是0。
你将实现1个不同的更原始的JOS系统调用来创建进程。利用这些系统调用能实现类似Unix的fork函数。
Exercise 7. Implement the system calls described above in kern/syscall.c and make sure syscall() calls them. 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.
static envid_t
sys_exofork(void)
{
// LAB 4: Your code here.
struct Env *child;
int ret = env_alloc(&child, curenv->env_id);
if (ret < 0) return ret;
child->env_tf = curenv->env_tf;
child->env_status = ENV_NOT_RUNNABLE;
child->env_tf.tf_regs.reg_eax = 0;
return child->env_id;
}
创建新的进程,如果空间没有映射则无法运行。调用函数的子进程寄存器状态和父进程相同,在父进程返回子进程的ID,子进程返回0。将子进程的eax寄存器设置为0从而让系统调用的返回值为0。
static int
sys_env_set_status(envid_t envid, int status)
{
struct Env *env;
int state = envid2env(envid, &env, 1);
if (state < 0) return state;
if (status != ENV_NOT_RUNNABLE && status != ENV_RUNNABLE) {
return -E_INVAL;
}
env->env_status = status;
return 0;
}
设置进程的状态为ENV_NOT_RUNNABLE或者ENV_RUNNABLE,标记准备的新环境。
static int
sys_page_alloc(envid_t envid, void *va, int perm)
{
// LAB 4: Your code here.
struct Env *env;
int ret = envid2env(envid, &env, 1);
if (ret < 0) return ret;
if ((size_t) va >= UTOP || ((size_t) va % PGSIZE) != 0) return -E_INVAL;
if ((perm & PTE_U) != PTE_U || (perm & PTE_P) != PTE_P) return -E_INVAL;
if ((perm & ~(PTE_U | PTE_P | PTE_W | PTE_AVAIL)) != 0) return -E_INVAL;
struct PageInfo *pp = page_alloc(1);
if (pp == NULL) return -E_NO_MEM;
if (page_insert(env->env_pgdir, pp, va, perm) < 0) {
page_free(pp);
return -E_NO_MEM;
}
return 0;
}
分配一个物理页并指定映射到给定进程的进程空间虚拟地址。
static int
sys_page_map(envid_t srcenvid, void *srcva,
envid_t dstenvid, void *dstva, int perm)
{
// LAB 4: Your code here.
if ((size_t) srcva >= UTOP || ((size_t) srcva % PGSIZE) != 0 || (size_t) dstva >= UTOP || ((size_t) dstva % PGSIZE) != 0) {
return -E_INVAL;
}
struct Env *senv, *denv;
int state1, state2;
state1 = envid2env(srcenvid, &senv, 1);
state2 = envid2env(dstenvid, &denv, 1);
if (state1 < 0) return state1;
if (state2 < 0) return state2;
pte_t *pte;
struct PageInfo *pp = page_lookup(senv->env_pgdir, srcva, &pte);
if (pp == NULL) return -E_INVAL;
if (((*pte & PTE_W) == 0) && (perm & PTE_W)) return -E_INVAL;
return page_insert(denv->env_pgdir, pp, dstva, perm);
}
复制页面映射(不是页面内容)从一个环境的地址空间到另一个环境的地址空间,将进程secenvid的进程映射到detenvid的进程。
static int
sys_page_unmap(envid_t envid, void *va)
{
// Hint: This function is a wrapper around page_remove().
// LAB 4: Your code here.
struct Env *env;
if (envid2env(envid, &env, 1) < 0) return -E_BAD_ENV;
if ((size_t) va >= UTOP || ((size_t) va % PGSIZE) != 0) {
return -E_INVAL;
}
page_remove(env->env_pgdir, va);
return 0;
}
解除指定进程中的一个映射,注意检查地址安全性问题。