ucore lab4(内核线程管理)
一、实验目的
1.1了解内核线程创建/执行的管理过程
1.2了解内核线程的切换和基本调度过程
二、实验内容
实验2/3完成了物理和虚拟内存管理,这给创建内核线程(内核线程是一种特殊的进程)打下了提供内存管理的基础。当一个程序加载到内存中运行时,首先通过ucore OS的内存管理子系统分配合适的空间,然后就需要考虑如何分时使用CPU来“并发”执行多个程序,让每个运行的程序(这里用线程或进程表示)“感到”它们各自拥有“自己”的CPU。
本次实验将首先接触的是内核线程的管理。内核线程是一种特殊的进程,内核线程与用户进程的区别有两个:
·内核线程只运行在内核态
·用户进程会在在用户态和内核态交替运行
·所有内核线程共用ucore内核内存空间,不需为每个内核·线程维护单独的内存空间
·而用户进程需要维护各自的用户内存空间
三、实验步骤及流程
3.0 练习0:填写已有实验
本实验依赖实验1/2/3。请把你做的实验1/2/3的代码填入本实验中代码中有“LAB1”,“LAB2”,“LAB3”的注释相应部分。
3.1 练习1:分配并初始化一个进程控制块(需要编码)
3.1.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。
请在实验报告中简要说明你的设计实现过程。请回答如下问题:
· 请说明proc_struct中struct context context和struct trapframe *tf成员变量含义和在本实验中的作用是啥?(提示通过看代码和编程调试可以判断出来)
3.1.2关键数据结构及知识点
我们需要实现的alloc_proc()函数它主要定义了一个结构体proc_struct,我们需要初始化这样一个结构体的一个对象并返回它。这个函数的返回语句是“return proc”,其中proc就是这个proc_struct结构体的一个对象。
这里我们需要初始化的一个东西就是proc_struct的一个对象,分配的是一个内核线程的PCB,它通常只是内核中的一小段代码或者函数,没有用户空间。而由于在操作系统启动后,已经对整个核心内存空间进行了管理,所以内核中的所有线程都不需要再建立各自的页表,只需共享这个核心虚拟空间就可以访问整个物理内存了。
proc_struct结构体如下(kern/process/proc.h):
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;
};
**(1)state:**进程所处的状态,在proc.h有定义,具体如下:
enum proc_state {
//进程状态
PROC_UNINIT = 0, // 未初始状态
PROC_SLEEPING, //睡眠(阻塞)状态
PROC_RUNNABLE, //运行与就绪态
PROC_ZOMBIE, //僵死状态
};
**(2)pid:**进程id号。
**(3)runs:**进程运行的时间。
**(4)kstack:**记录了分配给该进程/线程的内核桟的位置。这里记录的是分配给该进程在内存中的栈位置,相关操作应该和lab2和lab3有关。
**(5)need_resched:**是否需要调度,目前实验未涉及这一部分,可以暂时搁置不作处理。
**(6)parent:**用户进程的父进程,这是一个指针变量,记录它的父进程是谁。在所有进程中,只有一个进程没有父进程,就是内核创建的第一个内核线程。
**(7)mm:**负责管理进程的虚拟内存,其实就是内存管理的信息,包括内存映射列表、页表指针等。mm成员变量在lab3中用于虚存管理。但在实际OS中,内核线程常驻内存,不需要考虑替换页问题,mm应该和lab2和lab3有关。
**(8)context:**进程的上下文,用于进程切换。使用Switch.S汇编文件中的定义。
**(9)tf:**中断帧的指针,指导书上说,它是中断帧的指针,总是指向内核栈的某个位置:当进程从用户空间跳到内核空间时,中断帧记录了进程在被中断前的状态。当内核需要跳回用户空间时,需要调整中断帧以恢复让进程继续执行的各寄存器值。除此之外,uCore内核允许嵌套中断。
**(10)cr3:**记录了当前使用的页表的地址。
**(11)name[PROC_NAME_LEN + 1]:**这是内核线程(进程)的名称。
3.1.3代码实现(alloc_proc(kern/process/proc.c))
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;//内核栈地址,该进程分配的地址为0,因为还没有执行,也没有被重定位,因为默认地址都是从0开始的。
proc->need_resched = 0;//不需要调度
proc->parent = NULL;//父进程为空
proc->mm = NULL;//虚拟内存为空
memset(&(proc->context), 0, sizeof(struct context));//初始化上下文
proc->tf