操作系统lab5实验报告
到实验四为止,ucore还一直在核心态“打转”,没有到用户态执行。创建用户进程,让用户进程在用户态执行,且在需要ucore支持时,可通过系统调用来让ucore提供服务。而本实验将进程的执行空间扩展到了用户态空间,出现了创建子进程执行应用程序等。即实验五主要是分析用户进程的整个生命周期来阐述用户进程管理的设计与实现。
练习0 填写已有实验
这里和前几个实验一样,照样运用meld
软件进行对比,大致的截图如下:
这里简单将我们需要修改的地方罗列如下:
proc.c
default_pmm.c
pmm.c
swap_fifo.c
vmm.c
trap.c
另外根据试验要求,我们需要对部分代码进行改进,这里讲需要改进的地方的代码和说明罗列如下:
alloc_proc函数
改进后的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;
proc->cptr = proc->optr = proc->yptr = NULL;
}
return proc;
}
比起改进之前多了这两行代码:
proc->wait_state = 0;//初始化进程等待状态
proc->cptr = proc->optr = proc->yptr = NULL;//进程相关指针初始化
这里解释proc的几个新指针:
parent: proc->parent (proc is children)
children: proc->cptr (proc is parent)
older sibling: proc->optr (proc is younger sibling)
younger sibling: proc->yptr (proc is older sibling)
就像注释所写的,这两行代码主要是初始化进程等待状态、和进程的相关指针,例如父进程、子进程、同胞等等。
因为这里涉及到了用户进程,自然需要涉及到调度的问题,所以进程等待状态和各种指针需要被初始化。
do_fork函数
改进后的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);//将原来简单的计数改成来执行set_links函数,从而实现设置进程的相关链接
}
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;
}
改动主要是上述代码中含注释的两行,第一行是为了确定当前的进程正在等待,第二行是将原来的计数换成了执行一个set_links
函数,因为要涉及到进程的调度,所以简单的计数肯定是不行的。
idt_init函数
改进后的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);////这里主要是设置相应的中断门
trap_dispatch函数
改进后的部分函数如下:
ticks ++;
if (ticks % TICK_NUM == 0) {
assert(current != NULL);
current->need_resched = 1;
}
break;
相比与原来主要是多了这一行代码
current->need_resched = 1;
这里主要是将时间片设置为需要调度,说明当前进程的时间片已经用完了。
练习1 加载应用程序并执行
根据实验说明书,我们需要完善的函数是load_icode
函数。
这里介绍下这个函数的功能:load_icode
函数主要用来被do_execve
调用,将执行程序加载到进程空间(执行程序本身已从磁盘读取到内存中),这涉及到修改页表、分配用户栈等工作。
该函数主要完成的工作如下:
1、调用
mm_create
函数来申请进程的内存管理数据结构 mm 所需内存空间,