实验目的:

  • 了解内核线程创建/执行的管理过程
  • 了解内核线程的切换和基本调度过程

本次实验将首先接触的是内核线程的管理。内核线程是一种特殊的进程,内核线程与用户进程的区别有两个:

  • 内核线程只运行在内核态
  • 用户进程会在在用户态和内核态交替运行
  • 所有内核线程共用ucore内核内存空间,不需为每个内核线程维护单独的内存空间
  • 而用户进程需要维护各自的用户内存空间

进程是资源分配单位,线程是CPU调度单位。

练习0:填写已有实验

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

和前面一样,直接合并代码即可

练习1:分配并初始化一个进程控制块(需要编码)

alloc_proc函数(位于kern/process/proc.c中)负责分配并返回一个新的struct proc_struct结构,用于存储新建立的内核线程的管理信息。ucore需要对这个结构进行最基本的初始化,你需要完成这个初始化过程。

【提示】在alloc_proc函数的实现中,需要初始化的proc_struct结构中的成员变量至少包括:state/pid/runs/kstack/need_resched/parent/mm/context/tf/cr3/flags/name。

代码如下(按照提示,需要初始化的成员变量:state/pid/runs/kstack/need_resched/parent/mm/context/tf/cr3/flags/name):

// alloc_proc - alloc a proc_struct and init all fields of proc_struct
static struct proc_struct *
alloc_proc(void) {struct proc_struct *proc = kmalloc(sizeof(struct proc_struct));
if (proc != NULL) {proc->state =  (enum 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);}return proc;
}

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

其实就是按照提示,初始化相应的成员变量

请回答如下问题:

  • 请说明proc_struct中struct context contextstruct trapframe *tf成员变量含义和在本实验中的作用是啥?(提示通过看代码和编程调试可以判断出来)

    struct context context

    进程的上下文,用于进程切换(参见switch.S)。在 uCore中,所有的进程在内核中也是相对独立的(例如独立的内核堆栈以及上下文等等)。使用 context 保存寄存器的目的就在于在内核态中能够进行上下文之间的切换。实际利用context进行上下文切换的函数是在kern/process/switch.S中定义switch_to。

    需要注意的是,与trapframe保存用户态内核态的上下文不同,context保存的是线程当前的上下文,可能是执行用户代码的上下文,也可能是执行内核代码的上下文。

    // Saved registers for kernel context switches.
    // Don't need to save all the %fs etc. segment registers,
    // because they are constant across kernel contexts.
    // Save all the regular registers so we don't need to care
    // which are caller save, but not the return register %eax.
    // (Not saving %eax just simplifies the switching code.)
    // The layout of context must match code in switch.S.
    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 trapframe tf

    中断帧的指针,总是指向内核栈的某个位置:当进程从用户空间跳到内核空间时,中断帧记录了进程在被中断前的状态。当内核需要跳回用户空间时,需要调整中断帧以恢复让进程继续执行的各寄存器值。除此之外,uCore内核允许嵌套中断。因此为了保证嵌套中断发生时tf 总是能够指向当前的trapframe,uCore 在内核栈上维护了 tf 的链,可以参考trap.c::trap函数做进一步的了解。

  • 两者关系:以kernel_thread函数为例,尽管该函数设置了proc->trapframe,但在fork函数中的copy_thread函数里,程序还会设置proc->context。两个上下文看上去好像冗余,但实际上两者所分的工是不一样的。

    进程之间通过进程调度来切换控制权,当某个fork出的新进程获取到了控制流后,首当其中执行的代码是current->context->eip所指向的代码,此时新进程仍处于内核态,但实际上我们想在用户态中执行代码,所以我们需要从内核态切换回用户态,也就是中断返回。此时会遇上两个问题:

    • 新进程如何执行中断返回? 这就是proc->context.eip = (uintptr_t)forkret的用处。forkret会使新进程正确的从中断处理例程中返回。
    • 新进程中断返回至用户代码时的上下文为? 这就是proc_struct->tf的用处。中断返回时,新进程会恢复保存的trapframe信息至各个寄存器中,然后开始执行用户代码。

练习2:为新创建的内核线程分配资源(需要编码)

创建一个内核线程需要分配和设置好很多资源。kernel_thread函数通过调用do_fork函数完成具体内核线程的创建工作。do_kernel函数会调用alloc_proc函数来分配并初始化一个进程控制块,但alloc_proc只是找到了一小块内存用以记录进程的必要信息,并没有实际分配这些资源。ucore一般通过do_fork实际创建新的内核线程。do_fork的作用是,创建当前内核线程的一个副本,它们的执行上下文、代码、数据都一样,但是存储位置不同。在这个过程中,需要给新内核线程分配资源,并且复制原进程的状态。你需要完成在kern/process/proc.c中的do_fork函数中的处理过程。它的大致执行步骤包括:

  • 调用alloc_proc,首先获得一块用户信息块。
  • 为进程分配一个内核栈。
  • 复制原进程的内存管理信息到新进程(但内核线程不必做此事)
  • 复制原进程上下文到新进程
  • 将新进程添加到进程列表
  • 唤醒新进程
  • 返回新进程号

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;//LAB4:EXERCISE2 YOUR CODEif ((proc = alloc_proc()) == NULL) {goto fork_out;}proc->parent = current;if (setup_kstack(proc) != 0) {goto bad_fork_cleanup_kstack;}if (copy_mm(clone_flags, proc) != 0) {goto bad_fork_cleanup_proc;}copy_thread(proc, stack, tf);int intr_flag;local_intr_save(intr_flag);{proc->pid = get_pid();hash_proc(proc);list_add(&proc_list, &proc->list_link);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;
}

实现过程如下:

  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 process RUNNABLE
  7. set ret vaule using child proc’s pid

请在实验报告中简要说明你的设计实现过程。请回答如下问题:

  • 请说明ucore是否做到给每个新fork的线程一个唯一的id?请说明你的分析和理由。

    也就是get_pid函数是否能返回一个唯一的id。

    从代码可以看到,

    第一次被调用时,next_safe,last_pid刚开始赋值为8192,first time last_pid = 1,也就如果last_pid小于next_safe,那么是安全的。

    如果last_pid大于next_safe/MAX_PID,不一定是安全的,此时就需要遍历proc_list,重新对last_pidnext_safe进行设置,为下一次的get_pid调用打下基础。

    // get_pid - alloc a unique pid for process
    static int
    get_pid(void) {// MAX_PID = 8192static_assert(MAX_PID > MAX_PROCESS);struct proc_struct *proc;// proc 链表list_entry_t *list = &proc_list, *le;// next_safe = 8192, last_pid = 8192static int next_safe = MAX_PID, last_pid = MAX_PID;// first time last_pid = 1if (++ 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);// last_pid 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;
    }
    

练习3:阅读代码,理解 proc_run 函数和它调用的函数如何完成进程切换的。(无编码工作)

请在实验报告中简要说明你对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代码如上所示,从init.c的kern_init->cpu_idle->schedule从proc_list找到需要执行的proc

主要步骤:

  • current = proc,把当前proc设置为将要运行的proc
  • load_esp0, 设置ts的ts_esp0(设置TSS中ring0的内核栈地址)为kstack + KSTACKSIZE
  • lcr3加载cr3为将要运行的proc的cr3
  • switch_to切换context,从当前运行的proc,切换为即将要运行的proc

回答如下问题:

  • 在本实验的执行过程中,创建且运行了几个内核线程?

    两个,idle_proc, init_proc

  • 语句local_intr_save(intr_flag);....local_intr_restore(intr_flag);在这里有何作用?请说明理由

    保证两个语句中间的代码是原子操作不被打断,即disable_interrupt和enable_interrupt。

完成代码编写后,编译并运行代码:make qemu

如果可以得到如 附录A所示的显示内容(仅供参考,不是标准答案输出),则基本正确。

ucore内核进程的初始化与切换

实验指导书大概讲了一下内核进程切换的过程,但是并不能一眼就看出来是如何完成切换的。在调试过程中跟踪执行流程、寄存器和堆栈信息,这样能更清晰的看到ucore是如何完成进程切换的。

ucore两个进程的初始化都在proc_init里面完成。

idle_proc的初始化:

init_main的初始化:

ucore内核进程的切换(如何从idle切换到init_main):

可以清楚的看到,switch_to切换了上下文,重新设置EIP后,ret会跳转到forkret,而forkret会调用到forkrets,之后forkrets利用中断返回iret,返回到tf里面设置的EIP指向的地址,也就是kernel_thread_entry,在kernel_thread_entry里面会调用此线程设置的将要真正执行的方法init_main,执行完成后再调用do_exit结束lab4的运行。

扩展练习Challenge:实现支持任意大小的内存分配算法

这不是本实验的内容,其实是上一次实验内存的扩展,但考虑到现在的slab算法比较复杂,有必要实现一个比较简单的任意大小内存分配算法。可参考本实验中的slab如何调用基于页的内存分配算法(注意,不是要你关注slab的具体实现)来实现first-fit/best-fit/worst-fit/buddy等支持任意大小的内存分配算法。。

后面补上

【注意】下面是相关的Linux实现文档,供参考

SLOB

http://en.wikipedia.org/wiki/SLOB http://lwn.net/Articles/157944/

SLAB

https://www.ibm.com/developerworks/cn/linux/l-linux-slab-allocator/

参考:

设计关键数据结构 – 进程控制块 · ucore_os_docs (gitbooks.io)

uCore实验 - Lab4 | Kiprey’s Blog

ucore进程上下文切换关键代码分析_111尽力而为的博客-CSDN博客

ucore_lab4实验报告相关推荐

  1. 2019春第二次课程设计实验报告

    2019春第二次课程设计实验报告 一.实验项目名称: 贪吃蛇游戏编写: 二.实验项目功能描述: 这个实验主要是实现游戏的正常运行,实现的目标是对小蛇移动的控制, 同时对小蛇数据的保存,如何实现转弯的效 ...

  2. JAVA第二次验证设计性实验报告

    [实验任务一]:素数输出 (3)实验报告中要求包括程序设计思想.程序流程图.源代码.运行结果截图.编译错误分析等内容. 1.   实验内容 (1)计算并输出3~100之间的素数. (2)编程满足下列要 ...

  3. 计算机网络实验报告建立校园网,计算机网络实验报告

    设计性实验报告 一.实验目的 通过对网络设备的连通和对拓扑的分析,加深对常见典型局域网拓扑的理解:通过路由建立起网络之间的连接,熟悉交换机.路由器的基本操作命令,了解网络路由的设计与配置. 二.背景描 ...

  4. c语言链表最高响应比优先,操作系统--最高响应比优先调度算法实验报告..doc

    操作系统--最高响应比优先调度算法实验报告. 进程调度一.实验题目与要求 编写程序完成批处理系统中的作业调度,要求采用响应比高者优先的作业调度算法.实现具体包括:首先确定作业控制块的内容和组成方式:然 ...

  5. 计算机网络实验可变长子网掩码,计算机网络实验3-子网掩码与划分子网实验报告.docx...

    PAGE PAGE # / 5 上机实验报告三 -.实验目的 (1 )掌握子网掩码的算法. 了解网关的作用. 熟悉模拟软件 packet tracer5.3的使用. 二.实验内容 1.( 1) 172 ...

  6. 实验报告Linux操作系统基本命令,linux操作系统实验报告全部.doc

    linux操作系统实验报告全部 计算机操作系统 实验报告 学 号:姓 名:提交日期:2014.12.15成 绩: 东北大学秦皇岛分校 [实验题目]熟悉Linux/UNIX操作系统[实验目的]1.熟悉L ...

  7. 递归下降文法C语言实验报告,递归下降语法分析器实验报告.doc

    递归下降语法分析器实验报告 编译原理实验报告 题目: 递归下降语法分析器 学 院 计算机科学与技术 专 业 xxxxxxxxxxxxxxxx 学 号 xxxxxxxxxxxx 姓 名 宁剑 指导教师 ...

  8. labview简易计算机实验报告,labview实验报告..doc

    学院:电气工程学院 班级:自112班 姓名:何富裕 学号:1112011060 实验一 一.实验目的 熟悉LabVIEW软件的基本编程环境. 二.实验内容 创建并保存一个VI程序.此VI要实现的功能是 ...

  9. c语言实验报告管理系统,C语言实验报告-学生信息资管理系统.doc

    C语言实验报告-学生信息资管理系统 C语言实验报告 院系: 数学与计算科学学院 班级: 信息与计算科学2班 姓名: 学号: 2011年12月21日 一.问题描述 编写一个信息管理系统,包括姓名.性别. ...

最新文章

  1. SpringMVC教程--Validation校验
  2. Android(五)——dex文件动态调试
  3. ImageLoader加载图片
  4. 互联网日报 | 6月27日 星期日 | B站举办十二周年演讲;特斯拉在华召回285520辆汽车;小鹏汽车将于7月7日在港上市...
  5. 系统架构师学习笔记-系统开发基础知识(二)
  6. linux系统下安装和配置redis(2021版)
  7. c语言三个杠的等号是什么,数学3个横杠的等号表示什么意思?比如这个定 – 手机爱问...
  8. JS原生Ajax的使用
  9. 敏捷开发“松结对编程”实践之一:人员结构篇(大型研发团队,学习型团队,139团队,师徒制度)...
  10. Struts学习笔记总结
  11. SSH框架之-hibernate 三种状态的转换
  12. 【Alpha】“北航社团帮”小程序v1.0发布声明
  13. MySQL导出sql脚本文件
  14. 计算机boot进入u盘启动,深度u盘装系统进入boot设置教程
  15. 正则应用之--日期正则表达式
  16. Python+OpenCV中的Shi-Tomasi角点检测实现(附代码)
  17. 基于GMap.NET库实现的Windows桌面地图工具软件分享
  18. 赠书!Python 安全攻防,终于来了!
  19. Python 3.6 使用wordcloud制作词云(可设背景图像)
  20. Android 头像选择(拍照、相册裁剪),含7.0的坑

热门文章

  1. NLP--优化器(Optimizer)总结【分析】
  2. RFID 基础/分类/编码/调制/传输
  3. 天下3 无法打开服务器列表文件,《天下3》2021年中资料片“万象归宗”上线!——网易《天下3》官方网站...
  4. html中加个有颜色横线,关于html:更改下划线颜色
  5. 选择结构习题:根据月份、旅客订票张数和票价按优惠率计算费用
  6. 基于orCAD Capture CIS下的有刷直流电机驱动仿真
  7. Minecraft 1.19.2 Fabric模组开发 10.建筑生成
  8. macOS 隐藏文件夹
  9. 人际交往里的一些真相:切莫交浅言深
  10. 简明Python教程(A Byte of Python中文版)