操作系统实验报告 lab4
来源:互联网 发布:亚马逊索评软件 编辑:程序博客网 时间:2024/05/29 04:45
- 练习0 填写已有实验
- 练习1 分配并初始化一个进程控制块需要编码
- 关键数据结构 struct proc_struct
- 代码填写
- context和tf的作用分析
- 练习2 为新创建的内核线程分配资源
- 执行步骤
- 代码填写
- 练习3 理解proc_run和它调用的函数如何完成进程切换的
- schedule代码分析
- proc_run代码分析
- switch_to函数分析
- 运行截图
- 实验总结
练习0 填写已有实验
本实验依赖实验1~实验3。请把已做的实验1~实验3的代码填入本实验中代码中有lab1,lab2,lab3的注释部分。
运用meld软件进行比较,截图如下:
可以看到,需要补全的文件是
default_pmm.cpmm.cswap_fifo.cvmm.ctrap.c
练习1 分配并初始化一个进程控制块(需要编码)
alloc_proc函数(位于kern/process/proc.c中)负责分配并返回一个新的struct proc_struct结构,用于存储新建立的内核线程的管理信息。ucore需要对这个结构进行最基本的初始化,完成这个初始化过程。
关键数据结构 struct proc_struct
struct proc_struct { enum proc_state state; // Process state int pid; // Process ID int runs; // the running times of Proces uintptr_t kstack; // Process kernel stack volatile bool need_resched; // need to be rescheduled to release CPU? struct proc_struct *parent; // the parent process struct mm_struct *mm; // Process's memory management field struct context context; // Switch here to run process struct trapframe *tf; // Trap frame for current interrupt uintptr_t cr3; // the base addr of Page Directroy Table(PDT) uint32_t flags; // Process flag char name[PROC_NAME_LEN + 1]; // Process name list_entry_t list_link; // Process link list list_entry_t hash_link; // Process hash list };
下面对参数进行简单的讲解
mm:内存管理的信息,包括内存映射列表、页表指针等。
state:进程所处的状态。
parent:用户进程的父进程(创建它的进程)。
kstack:记录了分配给该进程/线程的内核桟的位置。
need_resched:是否需要调度
context:进程的上下文,用于进程切换
tf:中断帧的指针
cr3: cr3 保存页表的物理地址
代码填写
根据题目中的提示填写代码
static struct proc_struct * alloc_proc(void) { struct proc_struct *proc = kmalloc(sizeof(struct proc_struct)); if (proc != NULL) { //LAB4:EXERCISE1 YOUR CODE /* * below fields in proc_struct need to be initialized * enum proc_state state; // Process state * int pid; // Process ID * int runs; // the running times of Proces * uintptr_t kstack; // Process kernel stack * volatile bool need_resched; // bool value: need to be rescheduled to release CPU? * struct proc_struct *parent; // the parent process * struct mm_struct *mm; // Process's memory management field * struct context context; // Switch here to run process * struct trapframe *tf; // Trap frame for current interrupt * uintptr_t cr3; // CR3 register: the base addr of Page Directroy Table(PDT) * uint32_t flags; // Process flag * char name[PROC_NAME_LEN + 1]; // Process name */ proc->state = PROC_UNINIT; //设置进程为“初始”态 proc->pid = -1; //设置进程pid的未初始化值 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; //中断帧指针置为空,总是能够指向中断前的trapframe proc->cr3 = boot_cr3;//设置内核页目录表的基址 proc->flags = 0; //标志位初始化 memset(proc->name, 0, PROC_NAME_LEN); //进程名初始化 } return proc; }
context和*tf的作用分析
①context:进程的上下文,用于进程切换。起到的作用就是保存了现场。在 ucore中,所有的进程在内核中也是相对独立的,因此context 保存寄存器的目的就在于在内核态中能够进行上下文之间的切换。实际利用context进行上下文切换的函数是在kern/process/switch.S中定义switch_to。
② tf:中断帧的指针,总是指向内核栈的某个位置:当进程从用户空间跳到内核空间时,中断帧记录了进程在被中断前的状态。当内核需要跳回用户空间时,需要调整中断帧以恢复让进程继续执行的各寄存器值。除此之外,ucore内核允许嵌套中断。因此为了保证嵌套中断发生时tf 总是能够指向当前的tf,ucore 在内核栈上维护了 tf 的链。
练习2 为新创建的内核线程分配资源
创建一个内核线程需要分配和设置好很多资源。kernel_thread函数通过调用do_fork函数完成具体内核线程的创建工作。do_kernel函数会调用alloc_proc函数来分配并初始化一个进程控制块,但alloc_proc只是找到了一小块内存用以记录进程的必要信息,并没有实际分配这些资源。ucore一般通过do_fork实际创建新的内核线程。do_fork的作用是,创建当前内核线程的一个副本,它们的执行上下文、代码、数据都一样,但是存储位置不同。在这个过程中,需要给新内核线程分配资源,并且复制原进程的状态。完成在kern/process/proc.c中的do_fork函数中的处理过程。
执行步骤
①调用alloc_proc,首先获得一块用户信息块。
②为进程分配一个内核栈。
③复制原进程的内存管理信息到新进程(但内核线程不必做此事)
④复制原进程上下文到新进程
⑤将新进程添加到进程列表
⑥唤醒新进程
⑦返回新进程号(设置子进程号为返回值)
代码填写
根据代码提示填写代码如下,同时标注了对于代码的认识理解
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 * wakup_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 wakup_proc to make the new child process RUNNABLE // 7. set ret vaule using child proc's pid //第一步:申请内存块,如果失败,直接返回处理 if ((proc = alloc_proc()) == NULL) { goto fork_out; } //将子进程的父节点设置为当前进程 proc->parent = current; //第二步:为进程分配一个内核栈 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); //将进程链节点加入进程列表 list_add(&proc_list, &(proc->list_link)); //进程数+1 nr_process ++; } 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; }
在使用 fork 或 clone 系统调用时产生的进程均会由内核分配一个新的唯一的PID值。
具体来说,就是在分配PID时,设置一个保护锁,暂时不允许中断,保证了ID的唯一性。上述操作真正完成了资源分配的工作,与第一步中的工作有着明显的区别。do_fork只是创建当前进程的副本,他们执行的上下文,寄存器,代码都是一样的。
练习3 理解proc_run和它调用的函数如何完成进程切换的
schedule代码分析
在分析 proc_run 函数之前,我们先分析调度函数 schedule() 。
schedule()代码如下:
voidschedule(void) { bool intr_flag; list_entry_t *le, *last; struct proc_struct *next = NULL; local_intr_save(intr_flag); { current->need_resched = 0; last = (current == idleproc) ? &proc_list : &(current->list_link); le = last; do { if ((le = list_next(le)) != &proc_list) { next = le2proc(le, list_link); if (next->state == PROC_RUNNABLE) { break; } } } while (le != last); if (next == NULL || next->state != PROC_RUNNABLE) { next = idleproc; } next->runs ++; if (next != current) { proc_run(next); } } local_intr_restore(intr_flag);}
简单介绍一下schedule的执行过程:
①设置当前内核线程current->need_resched为0;
②proc_list队列存储着所有状态的进程/线程,在其中查找下一个处于“就绪”态的线程或进程next;
③找到这样的进程后,就调用proc_run函数,保存当前进程current的执行现场(进程上下文),恢复新进程的执行现场,完成进程切换。
至此,新的进程next就开始执行了。由于在proc10中只有两个内核线程,且idleproc要让出CPU给initproc执行,我们可以看到schedule函数通过查找proc_list进程队列,只能找到一个处于“就绪”态的initproc内核线程。
proc_run代码分析
void proc_run(struct proc_struct *proc) { if (proc != current) { bool intr_flag; struct proc_struct *prev = current, *next = proc; local_intr_save(intr_flag); { current = proc; load_esp0(next->kstack + KSTACKSIZE); lcr3(next->cr3); switch_to(&(prev->context), &(next->context)); } local_intr_restore(intr_flag); }}
通过proc_run和进一步的switch_to函数完成两个执行现场的切换,具体流程如下:
①让current指向next内核线程initproc;
②设置任务状态段ts中特权态0下的栈顶指针esp0为next内核线程initproc的内核栈的栈顶,即next->kstack + KSTACKSIZE ;
③设置CR3寄存器的值为next内核线程initproc的页目录表起始地址next->cr3,这实际上是完成进程间的页表切换;
由switch_to函数完成具体的两个线程的执行现场切换,即切换各个寄存器,当switch_to函数执行完“ret”指令后,就切换到initproc执行了。
switch_to函数分析
switch_to函数的执行流程:
.globl switch_to switch_to: # switch_to(from, to) # 保存前一个进程的执行现场,前两条汇编指令(如下所示)保存了进程在返回switch_to函数后的指令地址到context.eip中 movl 4(%esp), %eax # eax points to from popl 0(%eax) # esp--> return address, so save return addr in FROM’s # 保存前一个进程的其他7个寄存器到context中的相应成员变量中。 movl %esp, 4(%eax)movl %ebx, 8(%eax)movl %ecx, 12(%eax)movl %edx, 16(%eax)movl %esi, 20(%eax)movl %edi, 24(%eax)movl %ebp, 28(%eax) #再往后是恢复向一个进程的执行现场,这其实就是上述保存过程的逆执行过程,即从 context 的高地址的域 ebp 开始,逐一把相关域的值赋值给对应的寄存器。
设置了initproc->context.eip = (uintptr_t)forkret,这样,当执行switch_to函数并返回后,initproc将执行其实际上的执行入口地址forkret。
运行截图
实验总结
本次实验主要针对内核线程的管理,所有内核线程直接使用共同的ucore内核内存空间,而用户进程需要维护各自的用户内存空间。以及了解到了进程切换的相关细节操作,更加深一步的了解了操作系统。
- 操作系统 lab4 实验报告
- 操作系统实验报告 lab4
- 操作系统ucore lab4实验报告
- ucore操作系统lab4实验报告(理论部分)
- MIT 操作系统实验 MIT JOS lab4
- ucore操作系统实验lab4 -- 内核线程管理
- MIT 6.828 学习笔记6 Lab4实验报告
- 操作系统实验报告
- 操作系统实验报告 lab1
- 操作系统实验报告 lab2
- 操作系统实验报告 lab3
- 操作系统实验报告 lab5
- 操作系统实验报告 lab8
- 操作系统实验报告 lab6
- 操作系统实验报告 lab7
- 操作系统实验一实验报告
- 操作系统实验二实验报告
- 操作系统实验三实验报告
- ORACLE 云上准备预发布环境
- dfs序入门 CF上的几道题
- 《笨办法学python》---更多的变量和打印
- 爬虫-图像网站下载
- 飞机大战游戏实现
- 操作系统实验报告 lab4
- Spring Boot系列(三):Spring Boot转化为json数据格式
- Mac apache Laravel 需要改的一些配置
- Linux sed 命令介绍(一)
- 十年的老代码,你敢动?
- Redis之跳跃表
- Docker Explained: Using Dockerfiles to Automate Building of Images
- 为啥公司愿花更多钱从外面招人
- 学习淘淘商城第四十二课(导入商品数据-service层)