环境创建系统调用 学习笔记

107 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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;
}

解除指定进程中的一个映射,注意检查地址安全性问题。