dasqweq L18 2020-04-28 14:49:15 lab5 :用户进程
202 0

“操作系统实验-基于uCore OS(厦门大学)”实验报告

lab5 :用户进程

操作系统实验报告

实验五 lab5

系别:网络空间安全系

实验者姓名:吴名

学号:22920172204233

目录

一、实验目的

二、实验内容

三、实验练习

1: 填写已有实验

2: 加载应用程序并执行( 需要编码)

3: 父进程复制自己的内存空间给子进程( 需要编码)

4: 阅读分析源代码, 理解进程执行 fork/exec/wait/exit 的实现, 以及系统调用的实现( 不需要编码)

四、实验总结

一、实验目的


  • 了解第一个用户进程创建过程
  • 了解系统调用框架的实现机制
  • 了解ucore如何实现系统调用sys_fork/sys_exec/sys_exit/sys_wait来进行进程管理

二、实验内容


​ 实验4完成了内核线程, 但到目前为止, 所有的运行都在内核态执行。 实验5将创建用户进程, 让用户进程在用户态执行, 且在需要ucore支持时, 可通过系统调用来让ucore提供服务。 为此需要构造出第一个用户进程, 并通过系统调sys_fork / sys_exec / sys_exit / sys_wait 来支持运行不同的应用程序, 完成对用户进程的执行过程的基本管理。

三、实验练习


练习0: 填写已有实验

​ 本实验依赖实验1/2/3/4。请把你做的实验1/2/3/4的代码填入本实验中代码中 有 “LAB1”, “LAB2” ,“LAB3” “LAB4” 的注释相应部分。

​ 我使用Meld Diff Viewer工具,通过对lab4lab5的文件进行对比,将实验4中缺少的代码填写进去。操作与lab4相似。由于lab5是基于 lab1lab2lab3lab4完成的,所以虽然lab4需要 lab1lab2lab3 的代码,但是由于lab4中已经复制了 lab1lab2lab3的代码,因此lab5中不需要再将 lab1lab2lab3进行拷贝。

​ 对于软件的使用,我们这里只需要将文件的导入进行文件夹对比,然后找到两份代码的不同,进行一个个的比较,然后就可以完成练习0

​ 本次实验需要进行复制的文件有:

kdebug.c
trap.c
default_pmm.c
pmm.c
swap_fifo.c
vmm.c
proc.c
copy

​ 但lab5还需要对四个函数进行修改

1、alloc_proc() 函数

在原来的实验基础上,新增了 2 行代码:

copy

改进后的 alloc_proc 函数:

copy
2、do_fork() 函数

在原来的实验基础上,新增了 2 行代码:

copy

set_links函数:

copy

改进后的 do_fork 函数:

copy
3、idt_init() 函数

在原来的实验基础上,新增了 1 行代码:

copy

改进后的 idt_init 函数:

copy
4、trap_dispatch() 函数

在原来的实验基础上,新增了 1 行代码:

copy

改进后的 trap_dispatch 函数如下:

copy

练习1: 加载应用程序并执行( 需要编码)

do_execv函数调用load_icode( 位于kern/process/proc.c中)来加载并解析一个处于内存中的ELF执行文件格式的应用程序,建立相应的用户内存空间来放置应用程序的代码段、 数据段等,且要设置好proc_struct结构中的成员变量trapframe中的内容,确保在执行此进程后,能够从应用程序设定的起始执行地址开始执行。需设置正确的trapframe内容。

​ 请在实验报告中简要说明你的设计实现过程。

​ 请在实验报告中描述当创建一个用户态进程并加载了应用程序后,CPU是如何让这个应用程序最终在用户态执行起来的。即这个用户态进程被ucore选择占用CPU执行( RUNNING态)到具体执行应用程序第一条指令的整个经过。

​ load_icode函数的主要工作就是给用户进程建立一个能够让用户进程正常运行的用户环境。 此函数有一百多行, 完成了如下重要工作:

  1. 调用mm_create函数来申请进程的内存管理数据结构mm所需内存空间, 并对mm进行初 始化;
  2. 调用setup_pgdir来申请一个页目录表所需的一个页大小的内存空间, 并把描述ucore内核虚空间映射的内核页表( boot_pgdir所指) 的内容拷贝到此新目录表中, 最后让mm- >pgdir指向此页目录表, 这就是进程新的页目录表了, 且能够正确映射内核虚空间;
  3. 根据应用程序执行码的起始位置来解析此ELF格式的执行程序, 并调用mm_map函数根据ELF格式的执行程序说明的各个段( 代码段、 数据段、 BSS段等) 的起始位置和大小建立对应的vma结构, 并把vma插入到mm结构中, 从而表明了用户进程的合法用户态虚拟地址空间;
  4. 调用根据执行程序各个段的大小分配物理内存空间, 并根据执行程序各个段的起始位置确定虚拟地址, 并在页表中建立好物理地址和虚拟地址的映射关系, 然后把执行程序各 个段的内容拷贝到相应的内核虚拟地址中, 至此应用程序执行码和数据已经根据编译时 设定地址放置到虚拟内存中了;
  5. 需要给用户进程设置用户栈, 为此调用mm_mmap函数建立用户栈的vma结构, 明确用户栈的位置在用户虚空间的顶端, 大小为256个页, 即1MB, 并分配一定数量的物理内存且建立好栈的虚地址<-->物理地址映射关系;
  6. 至此,进程内的内存管理vma和mm数据结构已经建立完成, 于是把mm->pgdir赋值到cr3 寄存器中, 即更新了用户进程的虚拟内存空间, 此时的initproc已经被hello的代码和数据覆盖, 成为了第一个用户进程, 但此时这个用户进程的执行现场还没建立好;创建用户进程250
  7. 先清空进程的中断帧, 再重新设置进程的中断帧, 使得在执行中断返回指令“iret”后, 能够让CPU转到用户态特权级, 并回到用户态内存空间, 使用用户态的代码段、 数据段和堆栈, 且能够跳转到用户进程的第一条指令执行, 并确保在用户态能够响应中断;

do_execve函数:

copy

load_icode函数:

copy

请在实验报告中描述当创建一个用户态进程并加载了应用程序后,CPU是如何让这个应用程序最终在用户态执行起来的。即这个用户态进程被ucore选择占用CPU执行( RUNNING态)到具体执行应用程序第一条指令的整个经过。

答:按照load_icode函数的工作流程:

  1. 调用 mm_create 函数来申请进程的内存管理数据结构 mm 所需内存空间,并对 mm 进行初始化;
  2. 调用 setup_pgdir来申请一个页目录表所需的一个页大小的内存空间,并把描述ucore内核虚空间映射的内核页表(boot_pgdir所指)的内容拷贝到此新目录表中,最后让mm->pgdir指向此页目录表,这就是进程新的页目录表了,且能够正确映射内核虚空间;
  3. 根据可执行程序的起始位置来解析此 ELF 格式的执行程序,并调用 mm_map函数根据 ELF格式执行程序的各个段(代码段、数据段、BSS段等)的起始位置和大小建立对应的vma结构,并把vma 插入到 mm结构中,表明这些是用户进程的合法用户态虚拟地址空间;
  4. 根据可执行程序各个段的大小分配物理内存空间,并根据执行程序各个段的起始位置确定虚拟地址,并在页表中建立好物理地址和虚拟地址的映射关系,然后把执行程序各个段的内容拷贝到相应的内核虚拟地址中,至此应用程序执行码和数据已经根据编译时设定地址放置到虚拟内存中了;
  5. 需要给用户进程设置用户栈,为此调用 mm_mmap 函数建立用户栈的 vma 结构,明确用户栈的位置在用户虚空间的顶端,大小为 256 个页,即1MB,并分配一定数量的物理内存且建立好栈的虚地址<-->物理地址映射关系;
  6. 至此,进程内的内存管理 vma 和 mm 数据结构已经建立完成,于是把 mm->pgdir 赋值到 cr3 寄存器中,即更新了用户进程的虚拟内存空间,此时的 init 已经被 exit 的代码和数据覆盖,成为了第一个用户进程,但此时这个用户进程的执行现场还没建立好;
  7. 先清空进程的中断帧,再重新设置进程的中断帧,使得在执行中断返回指令iret后,能够让 CPU转到用户态特权级,并回到用户态内存空间,使用用户态的代码段、数据段和堆栈,且能够跳转到用户进程的第一条指令执行,并确保在用户态能够响应中断;

练习2: 父进程复制自己的内存空间给子进程( 需要编码)

​ 创建子进程的函数do_fork在执行中将拷贝当前进程( 即父进程) 的用户内存地址空间中的合法内容到新进程中( 子进程) , 完成内存资源的复制。 具体是通过copy_range函数( 位于kern/mm/pmm.c中) 实现的, 请补充copy_range的实现, 确保能够正确执行。

​ 请在实验报告中简要说明如何设计实现”Copy on Write 机制“, 给出概要设计, 鼓励给出详细设计。

do_fork函数,它完成的工作主要如下:

1、分配并初始化进程控制块( alloc_proc 函数);

2、分配并初始化内核栈,为内核进程(线程)建立栈空间( setup_stack 函数);

3、根据 clone_flag 标志复制或共享进程内存管理结构( copy_mm 函数);

4、设置进程在内核(将来也包括用户态)正常运行和调度所需的中断帧和执行上下文 ( copy_thread 函数);

5、为进程分配一个 PID( get_pid() 函数);

6、把设置好的进程控制块放入 hash_list 和 proc_list 两个全局进程链表中;

7、自此,进程已经准备好执行了,把进程状态设置为“就绪”态;

8、设置返回码为子进程的 PID 号。

do_fork函数中调用了copy_mm函数,copy_mm函数调用dup_mmap函数,dup_mmap函数调用copy_range函数。

copy

请在实验报告中简要说明如何设计实现”Copy on Write 机制“, 给出概要设计, 鼓励给出详细设计。

答:在创建子进程时,将父进程的PDE直接赋值给子进程的PDE,但是需要将允许写入的标志位置0;当子进程需要进行写操作时,再次出发中断调用do_pgfault(),此时应给子进程新建PTE,并取代原先PDE中的项,然后才能写入。

练习3: 阅读分析源代码, 理解进程执行 fork/exec/wait/exit 的实现, 以及系统调用的实现( 不需要编码)

请在实验报告中简要说明你对 fork/exec/wait/exit函数的分析。 并回答如下问题:

  • 请分析fork/exec/wait/exit在实现中是如何影响进程的执行状态的?
  • 请给出ucore中一个用户态进程的执行状态生命周期图( 包执行状态, 执行状态之间的变换关系, 以及产生变换的事件或函数调用) 。 ( 字符方式画即可)

执行: make grade。 如果所显示的应用程序检测都输出ok, 则基本正确。

在本实验中, 与进程相关的各个系统调用属性如下所示:

系统调用名 含义 具体完成服务的函数
SYS_exit process exit do_exit
SYS_fork create child process, dup mm do_fork->wakeup_proc
SYS_wait wait process do_wait
SYS_exec after fork, process execute a program load a program and refresh the mm
SYS_clone create child thread do_fork->wakeup_proc
SYS_yield process flag itself need resecheduling proc->need_sched=1, then scheduler will rescheule this process
SYS_sleep process sleep do_sleep
SYS_kill kill process do_kill->proc->flags |= PF_EXITING->wakeup_proc->do_wait->do_exit
SYS_getpid get the process's pid

通过这些系统调用, 可方便地完成从进程/线程创建到退出的整个运行过程。

fork:

调用过程:
fork->SYS_fork->do_fork+wakeup_proc
copy
实验代码:

```c 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; //LAB4:EXERCISE2 YOUR CODE / * Some Useful MACROs, Functions and DEFINEs, you can use them in below implementation. * MACROs or Functions: * alloc_proc: create a proc struct and init fields (lab4:exercise1) * setup_kstack: alloc pages with size KSTACKPAGE as process kernel stack * copy_mm: process "proc" duplicate OR share process "current"'s mm according clone_flags * if clone_flags & CLONE_VM, then "share" ; else "duplicate" * copy_thread: setup the trapframe on the process's kernel stack top and * setup the kernel entry point and stack of process * hash_proc: add proc into proc hash_list * get_pid: alloc a unique pid for process * wakeup_proc: set proc->state = PROC_RUNNABLE * VARIABLES: * proc_list: the process set's list * nr_process: the number of process set */

//    1. call alloc_proc to allocate a proc_struct
//    2. call setup_kstack to allocate a kernel stack for child process
//    3. call copy_mm to dup OR share mm according clone_flag
//    4. call copy_thread to setup tf & context in proc_struct
//    5. insert proc_struct into hash_list && proc_list
//    6. call wakeup_proc to make the new child
copy
最新评论
暂无评论~