ucore lab5
实验目的:
- 了解第一个用户进程创建过程
- 了解系统调用框架的实现机制
- 了解ucore如何实现系统调用sys_fork/sys_exec/sys_exit/sys_wait来进行进程管理
实验内容:
实验4完成了内核线程,但到目前为止,所有的运行都在内核态执行。实验5将创建用户进程,让用户进程在用户态执行,且在需要ucore支持时,可通过系统调用来让ucore提供服务。为此需要构造出第一个用户进程,并通过系统调用sys_fork/sys_exec/sys_exit/sys_wait来支持运行不同的应用程序,完成对用户进程的执行过程的基本管理。相关原理介绍可看附录B。
练习
练习0:填写已有实验
本实验依赖实验1/2/3/4。请把你做的实验1/2/3/4的代码填入本实验中代码中有“LAB1”/“LAB2”/“LAB3”/“LAB4”的注释相应部分。注意:为了能够正确执行lab5的测试应用程序,可能需对已完成的实验1/2/3/4的代码进行进一步改进。
这里首先使用meld来对比目录和文件内部的内容,修改在实验1/2/3/4的代码填入实验五中。
- kdebug.c
- trap.c
- default_pmm.c
- pmm.c
- swap_fifo.c
- vmm.c
- proc.c
有一些代码需要进行进一步改进:
改进的alloc_proc函数:
static struct proc_struct *
alloc_proc(void) {
struct proc_struct *proc = kmalloc(sizeof(struct proc_struct));
if (proc != NULL) {
proc->state = PROC_UNINIT;
proc->pid = -1;
proc->runs = 0;
proc->kstack = 0;
proc->need_resched = 0;
proc->parent = NULL;
proc->mm = NULL;
memset(&(proc->context), 0, sizeof(struct context));
proc->tf = NULL;
proc->cr3 = boot_cr3;
proc->flags = 0;
memset(proc->name, 0, PROC_NAME_LEN);
proc->wait_state = 0; //PCB新增的条目,初始化进程等待状态
proc->cptr = proc->optr = proc->yptr = NULL;//设置指针
}
return proc;
}
改进的内容为:
这两行代码主要是初始化进程等待状态、和进程的相关指针,例如父进程、子进程、同胞等等。其中的wait_state是进程控制块中新增的条目。避免之后由于未定义或未初始化导致管理用户进程时出现错误。
proc->wait_state = 0; //初始化进程等待状态
proc->cptr = proc->optr = proc->yptr = NULL; //指针初始化
改进的do_fork函数:
int
do_fork(uint32_t clone_flags, uintptr_t stack, struct trapframe *tf) {
int ret = -E_NO_FREE_PROC;
struct proc_struct *proc;
if (nr_process >= MAX_PROCESS) {
goto fork_out;
}
ret = -E_NO_MEM;
if ((proc = alloc_proc()) == NULL) {
goto fork_out;
}
proc->parent = current;
assert(current->wait_state == 0); //确保进程在等待
if (setup_kstack(proc) != 0) {
goto bad_fork_cleanup_proc;
}
if (copy_mm(clone_flags, proc) != 0) {
goto bad_fork_cleanup_kstack;
}
copy_thread(proc, stack, tf);
bool intr_flag;
local_intr_save(intr_flag);
{
proc->pid = get_pid();
hash_proc(proc);
set_links(proc); //设置进程链接
}
local_intr_restore(intr_flag);
wakeup_proc(proc);
ret = proc->pid;
fork_out:
return ret;
bad_fork_cleanup_kstack:
put_kstack(proc);
bad_fork_cleanup_proc:
kfree(proc);
goto fork_out;
}
改进的内容为:
assert(current->wait_state == 0); //确保进程在等待
set_links(proc); //设置进程链接
第一行代码需要确保当前进程正在等待,我们在alloc_proc中初始化wait_state为0。
改进trap_dispatch函数:
设置每100次时间中断后,当前正在执行的进程准备被调度。同时,注释掉原来的"100ticks"输出
ticks ++;
if (ticks % TICK_NUM == 0) {
assert(current != NULL);
current->need_resched = 1;
//原代码中的print_ticks();改成上面两句
}
break;
这里主要是将时间片设置为需要调度,说明当前进程的时间片已经用完了。
改进 idt_init 函数:
void idt_init(void) {
extern uintptr_t __vectors[];
int i;
for (i = 0;i<sizeof(idt)/sizeof(struct gatedesc);i ++) {
SETGATE(idt[i],0,GD_KTEXT,__vectors[i], DPL_KERNEL);
}
SETGATE(idt[T_SYSCALL], 1, GD_KTEXT, __vectors[T_SYSCALL], DPL_USER);
lidt(&idt_pd);
}
改进的内容为:
SETGATE(idt[T_SYSCALL], 1, GD_KTEXT, __vectors[T_SYSCALL], DPL_USER);////这里主要是设置相应的中断门
设置一个特定中断号的中断门,专门用于用户进程访问系统调用,即设置中断T_SYSCALL的触发特权级为DPL_USER。
练习1: 加载应用程序并执行(需要编码)
do_execv函数调用load_icode(位于kern/process/proc.c中)来加载并解析一个处于内存中的ELF执行文件格式的应用程序,建立相应的用户内存空间来放置应用程序的代码段、数据段等,且要设置好proc_struct结构中的成员变量trapframe中的内容,确保在执行此进程后,能够从应用程序设定的起始执行地址开始执行。需设置正确的trapframe内容。
请在实验报告中简要说明你的设计实现过程。
请在实验报告中描述当创建一个用户态进程并加载了应用程序后,CPU是如何让这个应用程序最终在用户态执行起来的。即这个用户态进程被ucore选择占用CPU执行(RUNNING态)到具体执行应用程序第一条指令的整个经过。
首先,我们看到do_execv函数:
do_execve函数主要做的工作就是先回收自身所占用户空间,然后调用load_icode,用新的程序覆盖内存空间,形成一个执行新程序的新进程。
//主要目的在于清理原来进程的内存空间,为新进程执行准备好空间和资源
int do_execve(const char *name, size_t len, unsigned char *binary, size_t size)
{
struct mm_struct *mm = current->mm;
if (!user_mem_check(mm, (uintptr_t)name, len, 0)) {
return -E_INVAL;
}
if (len > PROC_NAME_LEN) {
len = PROC_NAME_LEN;
}
char local_name[PROC_NAME_LEN + 1];
memset(local_name, 0, sizeof(local_name));
memcpy(local_name, name, len);
//如果mm不为NULL,则不执行该过程
if (mm != NULL)
{
//将cr3页表基址指向boot_cr3,即内核页表
lcr3(boot_cr3);//转入内核态
if (mm_count_dec(mm) == 0)
{
//下面三步实现将进程的内存管理区域清空
exit_mmap(mm);//清空内存管理部分和对应页表
put_pgdir(mm);//清空页表
mm_destroy(mm);//清空内存
}
current->mm = NULL;//最后让它当前的页表指向空,方便放入自己的东西
}
int ret;
//填入新的内容,load_icode会将执行程序加载,建立新的内存映射关系,从而完成新的执行
if ((ret = load_icode(binary, size)) != 0) {
goto execve_exit;
}
//给进程新的名字
set_proc_name(current, local_name);
return 0;
execve_exit:
do_exit(ret);
panic("already exit: %e.\n", ret);
}
接下来看到load_icode函数:
下面是loab_icode函数的主要实现思路:
- 为用户进程创建新的mm结构
- 创建页目录表
- 校验ELF文件的魔数是否正确
- 创建虚拟内存空间,即往mm结构体添加vma结构
- 分配内存,并拷贝ELF文件的各个program section到新申请的内存上
- 为BSS section分配内存,并初始化为全0
- 分配用户栈内存空间
- 设置当前用户进程的mm结构、页目录表的地址及加载页目录表地址到cr3寄存器
- 设置当前用户进程的tf结构
static int
load_icode(unsigned char *binary, size_t size) {
if (current->mm != NULL) {
//当前进程的内存为空
panic("load_icode: current->mm must be empty.\n");
}
int ret = -E_NO_MEM; //记录错误信息:未分配内存
struct mm_struct *mm;
//(1) create a new mm for current process
if ((mm = mm_create()) == NULL) {
//分配内存
goto bad_mm; <

本文介绍ucoreOS中用户态进程的创建、管理和系统调用机制,涵盖进程状态转换、ELF文件加载、Copy-on-Write机制实现等内容。
最低0.47元/天 解锁文章
1686

被折叠的 条评论
为什么被折叠?



