练习0:填写已有实验
请把你做的实验1/2/3/4的代码填入本实验中代码中有“LAB1”,“LAB2”,“LAB3”,”LAB4”的注释相应部分。注意:为了能够正确执行lab5的测试应用程序,可能需对已完成的实验1/2/3/4的代码进行进一步改进。
| 12
 
 | 这几个文件补上 说真的最烦这个步骤 但是这次 不只是直接复制 还要补充vmm.c trap.c default_pmm.c pmm.c proc.c
 
 | 
以下为补充的代码块
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 
 | proc.c: static struct proc_struct * alloc_proc(void) {
 proc->state = PROC_UNINIT;
 proc->pid = -1;
 proc->runs = 0;
 proc->kstack = 0;
 proc->need_resched = NULL;
 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->yptr = proc-> optr = NULL;
 |   cptr: proc is parent          |
 |   yptr: proc is younger sibling |
 |   optr: proc is older sibling   |
 }
 int do_fork(uint32_t clone_flags, uintptr_t stack, struct trapframe *tf) {
 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;
 }
 
 trap.c:
 static void trap_dispatch(struct trapframe *tf) {
 
 if (++ticks % TICK_NUM == 0) {
 assert(current != NULL);
 current->need_resched = 1;
 }
 }
 void idt_init(void) {
 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);
 }
 
 | 
练习1: 加载应用程序并执行
do_execv函数调用load_icode(位于kern/process/proc.c中)来加载并解析一个处于内存中的ELF执行文件格式的应用程序,建立相应的用户内存空间来放置应用程序的代码段、数据段等,且要设置好proc_struct结构中的成员变量trapframe中的内容,确保在执行此进程后,能够从应用程序设定的起始执行地址开始执行。需设置正确的trapframe内容。
请在实验报告中简要说明你的设计实现过程。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | 以下为要填写的内容 首先清空进程原先的中断帧 然后再将 中断帧中的 代码段 和 数据段修改为 用户态的段选择子 栈指针设置为 用户栈顶 eip 设置为 用户程序的入口地址
 最后 确保在用户进程中能够响应中断
 static int load_icode(unsigned char *binary, size_t size) {
 tf->tf_cs = USER_CS;
 tf->tf_ds = tf->tf_es = tf->tf_ss = USER_DS;
 tf->tf_esp = USTACKTOP;
 tf->tf_eip = elf->e_entry;
 tf->tf_eflags = FL_IF;
 }
 
 | 
请在实验报告中描述当创建一个用户态进程并加载了应用程序后,CPU是如何让这个应用程序最终在用户态执行起来的。即这个用户态进程被ucore选择占用CPU执行(RUNNING态)到具体执行应用程序第一条指令的整个经过。
在此之前先理一下 用户态进程是怎么来的
- 创建了一个 硬构造了 第0个 内核线程 idleproc
- idleproc 通过 kernel_thread创建了 第1个内核线程 initproc 在 lab 4 中只是打印字符串
- initproc 通过 kernel_execve将 hello应用程序执行码覆盖到了 initproc 用户虚拟内存空间 来创建 用户态进程
| 12
 3
 4
 5
 6
 
 | 创建一个用户态进程并加载了应用程序之后 调度器 schedule 调用 proc_run设置 指针 current 为当前执行PCB 并加载 该进程的 内核栈和页目录表
 调用 switch_to 因为 当前进程的 context 其中的 eip 被设置为 forkret(copy_thread 拷贝父进程的中断帧时设置的) 因此 switch_to ret 后
 会跳转到 forkret 处 forkret 又会 将栈 设置为 当前进程的trapframe 然后跳到 __trapret
 此时 __trapret 会根据当前进程的trapframe 恢复上下文 最后 退出中断 iret 从系统调用的函数调用路径 返回
 切换到用户进程 hello 第一句语句 _start 处 开始执行
 
 | 
练习2: 父进程复制自己的内存空间给子进程
创建子进程的函数do_fork在执行中将拷贝当前进程(即父进程)的用户内存地址空间中的合法内容到新进程中(子进程),完成内存资源的复制。具体是通过copy_range函数(位于kern/mm/pmm.c中)实现的,请补充copy_range的实现,确保能够正确执行。
| 12
 3
 4
 5
 6
 7
 8
 
 | 这个函数是用来 拷贝父进程的用户内存地址空间到子进程中 share 此处没用到 不管它int copy_range(pde_t *to, pde_t *from, uintptr_t start, uintptr_t end, bool share) {
 
 void* src_kvaddr = page2kva(page);
 void* dst_kvaddr = page2kva(npage);
 memcpy(dst_kvaddr, src_kvaddr, PGSIZE);
 ret = page_insert(to, npage, start, perm);
 }
 
 | 
练习3: 阅读分析源代码,理解进程执行 fork/exec/wait/exit 的实现,以及系统调用的实现
请在实验报告中简要说明你对 fork/exec/wait/exit函数的分析。并回答如下问题:
- 请分析fork/exec/wait/exit在实现中是如何影响进程的执行状态的?
| 12
 3
 4
 5
 6
 
 | fork 创建新的 PCB 进程状态为 UNINITexec 将当前进程的内存布局清除 再调用 load_icode 读出 ELF映像中的内存布局 并填写 进程状态不改变
 wait 当前进程若无子进程 则返回错误 若有子进程 则判定 是否为 ZOMBIE 子进程 有则释放子进程的资源 并返回子进程的返回状态码
 若无 ZOMBIE 状态子进程 则进入 SLEEPING 状态 等子进程唤醒
 exit 清除当前进程几乎所有资源(PCB和内核栈不清除) 将所有子进程(如果有的话)设置为 init 进程(内核) 将当前进程状态设置为 ZOMBIE
 若有父进程在等待当前进程exit 则 唤醒父进程
 
 | 
- 请给出ucore中一个用户态进程的执行状态生命周期图(包执行状态,执行状态之间的变换关系,以及产生变换的事件或函数调用)
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 
 |                                              RUNNING----------------+A |                  |
 | |                  |
 proc_run()            exit()
 | |                  |
 | V                  V
 --alloc_page()--> UNINIT --wakeup_proc()--> RUNNABLE --exit()--> ZOMBIE
 A                    A
 |                    |
 子进程exit()            exit()
 |                    |
 |                    |
 SLEEPING----------------+
 
 
 | 
最后附上运行结果 做这个 lab 的时候 给绕的很晕 要特别注意 各个函数 分别修改了 中断帧 和 上下文的 eip esp 的哪些内容
比如 kernel_thread 将 tf->eip 设置为 kernel_thread_entry 当此进程被调度上去的时候
就会跳到 kernel_thread_entry 去执行 fn 这个函数
copy_thread 将 tf->eip 设置为 forkret 而 forkret 会执行这条指令 movl 4(%esp), %esp 将 context->esp 当做 栈顶
而 context->esp 又在 copy_thread 中 被设置为 中断帧的栈顶
![lab5_finish]()