ucore lab4
来源:互联网 发布:ubuntu mate 17.10 编辑:程序博客网 时间:2024/05/21 13:54
练习0:填写已有实验
本实验依赖实验1/2/3。请把你做的实验1/2/3的代码填入本实验中代码中有“LAB1”,“LAB2”,“LAB3”的注释相应部分。
练习1:分配并初始化一个进程控制块(需要编码)
说明proc_struct中 struct context context 和 struct trapframe *tf 成员变量含义和在本实验中的作用是啥?
结构体定义:
enum proc_state { //进程状态 PROC_UNINIT = 0, //未初始状态 PROC_SLEEPING, //睡眠(阻塞)状态 PROC_RUNNABLE, //运行与就绪态 PROC_ZOMBIE, //僵死状态};struct context { //进程上下文 uint32_t eip; uint32_t esp; uint32_t ebx; uint32_t ecx; uint32_t edx; uint32_t esi; uint32_t edi; uint32_t ebp;};struct proc_struct { //进程控制块 enum proc_state state; //进程状态 int pid; //进程ID int runs; //运行时间 uintptr_t kstack; //内核栈位置 volatile bool need_resched; //是否需要调度 struct proc_struct *parent; //父进程 struct mm_struct *mm; //进程的虚拟内存 struct context context; //进程上下文 struct trapframe *tf; //当前中断帧的指针 uintptr_t cr3; //当前页表地址 uint32_t flags; //进程 char name[PROC_NAME_LEN + 1];//进程名字 list_entry_t list_link; //进程链表 list_entry_t hash_link; };
宏定义:
#define PROC_NAME_LEN 15
alloc()函数的实现:
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; //进程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; //标志位为0 memset(proc->name, 0, PROC_NAME_LEN);//进程名为0 } return proc;}
总结一下过程:
1 在堆上分配一块内存空间用来存放进程控制块
2 初始化进程控制块内的各个参数
3 返回分配的进程控制块
问题一:struct context context和struct trapframe *tf 成员 变量的含义和作用
context作用:
进程的上下文,用于进程切换。主要保存了前一个进程的现场(各个寄存器的状态)。在uCore中,所有的进程在内核中也是相对独立的。使用context 保存寄存器的目的就在于在内核态中能够进行上下文之间的切换。实际利用context进行上下文切换的函数是在kern/process/switch.S中定义switch_to。
tf:中断帧的指针,总是指向内核栈的某个位置:当进程从用户空间跳到内核空间时,中断帧记录了进程在被中断前的状态。当内核需要跳回用户空间时,需要调整中断帧以恢复让进程继续执行的各寄存器值。除此之外,uCore内核允许嵌套中断。因此为了保证嵌套中断发生时tf 总是能够指向当前的trapframe,uCore 在内核栈上维护了 tf 的链。
练习2:为新创建的内核线程分配资源(需要编码)
请说明ucore是否做到给每个新fork的线程一个唯一的id?请说明你的分析和理由。
函数定义:
static int //内核栈复制函数setup_kstack(struct proc_struct *proc) { struct Page *page = alloc_pages(KSTACKPAGE); if (page != NULL) { proc->kstack = (uintptr_t)page2kva(page); return 0; } return -E_NO_MEM;}static int //该函数在本次实验并没有实现copy_mm(uint32_t clone_flags, struct proc_struct *proc) { assert(current->mm == NULL); //判断当前函数的虚拟内存非空 return 0;}static voidcopy_thread(struct proc_struct *proc, uintptr_t esp, struct trapframe *tf) { proc->tf = (struct trapframe *)(proc->kstack + KSTACKSIZE) - 1; *(proc->tf) = *tf; proc->tf->tf_regs.reg_eax = 0; proc->tf->tf_esp = esp; proc->tf->tf_eflags |= FL_IF; proc->context.eip = (uintptr_t)forkret; proc->context.esp = (uintptr_t)(proc->tf);}voidintr_disable(void) { //禁止中断函数 cli(); //禁止中断}static inline bool__intr_save(void) { if (read_eflags() & FL_IF){ //如果允许屏蔽中断,即IF=1.则中断 intr_disable(); //禁止中断 return 1; } return 0;}static inline void__intr_restore(bool flag) { //如果中断被屏蔽,则恢复中断 if (flag) { intr_enable(); //恢复中断 }}
eflags寄存器
宏定义:
#define local_intr_save(x) \ do { x = __intr_save(); } while (0)#define FL_IF 0x00000200 #define local_intr_restore(x) __intr_restore(x);
do_fork()函数的实现:
intdo_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) { //分配进程数大于4096,返回 goto fork_out; //返回 } ret = -E_NO_MEM; //因内存不足而分配失败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); //屏蔽中断,intr_flag置为1 { proc->pid = get_pid(); //获取当前进程PID hash_proc(proc); //建立hash映射 list_add(&proc_list,&(proc->list_link));//加入进程链表 nr_process ++; //进程数加一 } local_intr_restore(intr_flag); //恢复中断 wakeup_proc(proc); //唤醒新进程 ret = proc->pid; //返回当前进程的PIDfork_out: //已分配进程数大于4096 return ret;bad_fork_cleanup_kstack: //分配内核栈失败 put_kstack(proc);bad_fork_cleanup_proc: kfree(proc); goto fork_out;}
问题:ucore是否做到给每个新fork的线程一个唯一的id?
查看get_pid函数
static intget_pid(void) { static_assert(MAX_PID > MAX_PROCESS); struct proc_struct *proc; list_entry_t *list = &proc_list, *le; static int next_safe = MAX_PID, last_pid = MAX_PID; if (++ last_pid >= MAX_PID) { last_pid = 1; goto inside; } if (last_pid >= next_safe) { inside: next_safe = MAX_PID; repeat: le = list; while ((le = list_next(le)) != list) { proc = le2proc(le, list_link); if (proc->pid == last_pid) { if (++ last_pid >= next_safe) { if (last_pid >= MAX_PID) { last_pid = 1; } next_safe = MAX_PID; goto repeat; } } else if (proc->pid > last_pid && next_safe > proc->pid) { next_safe = proc->pid; } } } return last_pid;}
从上面的代码可以看出。在使用 fork 或 clone 系统调用时产生的进程均会由内核分配一个新的唯一的PID值。具体来说,就是在分配PID时,设置一个保护锁,暂时不允许中断,这样在就唯一地分配了一个PID。
练习3:阅读代码,理解 proc_run 函数和它调用的函数如何完成进程切换的。(无编码工作)
在本实验的执行过程中,创建且运行了几个内核线程?
语句 local_intr_save(intr_flag);….local_intr_restore(intr_flag); 在这里有何作用?请说明理由。
首先分析schedule函数的源码:
/* 宏定义: #define le2proc(le, member) \ to_struct((le), struct proc_struct, member)*/voidschedule(void) { bool intr_flag; //定义中断变量 list_entry_t *le, *last; //当前list,下一list struct proc_struct *next = NULL; //下一进程 local_intr_save(intr_flag); //中断禁止函数 { current->need_resched = 0; //设置当前进程不需要调度 //last是否是idle进程(第一个创建的进程),如果是,则从表头开始搜索 //否则获取下一链表 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; //找到一个可以调度的进程,break } } } while (le != last); //循环查找整个链表 if (next == NULL || next->state != PROC_RUNNABLE) { next = idleproc; //未找到可以调度的进程 } next->runs ++; //运行次数加一 if (next != current) { proc_run(next); //运行新进程,调用proc_run函数 } } local_intr_restore(intr_flag); //允许中断}
可以看到ucore实现的是FIFO调度算法:
1 调度开始时,先屏蔽中断。
2 在进程链表中,查找第一个可以被调度的程序
3 运行新进程,允许中断
再分析switch_to函数
switch_to: # switch_to(from, to) # save from's registers movl 4(%esp), %eax #保存from的首地址 popl 0(%eax) #将返回值保存到context的eip movl %esp, 4(%eax) #保存esp的值到context的esp movl %ebx, 8(%eax) #保存ebx的值到context的ebx movl %ecx, 12(%eax) #保存ecx的值到context的ecx movl %edx, 16(%eax) #保存edx的值到context的edx movl %esi, 20(%eax) #保存esi的值到context的esi movl %edi, 24(%eax) #保存edi的值到context的edi movl %ebp, 28(%eax) #保存ebp的值到context的ebp # restore to's registers movl 4(%esp), %eax #保存to的首地址到eax movl 28(%eax), %ebp #保存context的ebp到ebp寄存器 movl 24(%eax), %edi #保存context的ebp到ebp寄存器 movl 20(%eax), %esi #保存context的esi到esi寄存器 movl 16(%eax), %edx #保存context的edx到edx寄存器 movl 12(%eax), %ecx #保存context的ecx到ecx寄存器 movl 8(%eax), %ebx #保存context的ebx到ebx寄存器 movl 4(%eax), %esp #保存context的esp到esp寄存器 pushl 0(%eax) #将context的eip压入栈中 ret
所以switch_to函数主要完成的是进程的上下文切换,先保存当前寄存器的值,然后再将下一进程的上下文信息保存到对于寄存器中。
proc_run函数的源码:
voidproc_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); //修改esp lcr3(next->cr3); //修改页表项 //上下文切换 switch_to(&(prev->context), &(next->context)); } local_intr_restore(intr_flag); //允许中断 }}
所以整个函数的执行过程为:
1 屏蔽中断
2 修改esp0,页表项和进行上下文切换
3 允许中断
问题一:在本实验的执行过程中,创建且运行了几个内核线程?
idleproc: ucore: 第一个内核进程,完成内核中各个子系统的初始化,之后立即调度,执行其他进程。
initproc:用于完成实验的功能而调度的内核进程。
问题二:语句A在这里有何作用?请说明理由。
屏蔽中断和打开中断,以免进程切换时其他进程再进行调度。
实验结果:
- ucore lab4
- ucore lab4
- 操作系统ucore lab4实验报告
- ucore操作系统实验lab4 -- 内核线程管理
- ucore操作系统lab4实验报告(理论部分)
- lab4
- lab4
- Lab4 Authentication
- JOS-lab4
- Lab4: bootloader
- ucore OS
- ucore lab1
- ucore lab2
- ucore lab3
- Ucore lab2
- ucore lab5
- ucore-lab7
- ucore lab1
- java连接数据库操作2--防止sql注入
- 深度遍历和广度遍历
- 文章标题
- 项目遇到的麻烦
- 16. 求两点之间的最短路径
- ucore lab4
- “荒谬”的等式:1+2+3+4+…+∞= -1/12;1^2+2^2+3^2+4^2+…+∞^2=0
- 时光倒流我这么学java
- 微软亚洲研究院童欣:VR/AR 里的手势交互到底难在哪儿
- 高性能服务器编程之epoll
- 使用lombok消除冗余代码
- 期末考试 编程题#3:计算数列平方和(Coursera 程序设计与算法 专项课程3 C++程序设计 郭炜、刘家瑛;函数对象作参数)
- Idea2017部署项目到tomcat时没有artifacts解决办法
- Two Sum