练习1:分配并初始化一个进程控制块(需要编码)
alloc_proc
分配内存 -> 初始化(清空)->返回进程块地址
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; // 初始化id
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 , sizeof(proc->name));//初始化名字
}
return proc;
}
请说明proc_struct中struct context context
和struct trapframe *tf
成员变量含义和在本实验中的作用是啥?(提示通过看代码和编程调试可以判断出来)
context:报存的是进程运行时的上下文(各个寄存器状态),用于进程切换时,不丢失进程运行环境
tf: 中断帧的指针,总是指向内核栈的某个位置:当进程从用户空间跳到内核空间时,中断帧记录了进程在被中断前的状态。当内核需要跳回用户空间时,需要调整中断帧以恢复让进程继续执行的各寄存器值。除此之外,uCore内核允许嵌套中断。因此为了保证嵌套中断发生时tf 总是能够指向当前的trapframe,uCore 在内核栈上维护了 tf 的链。
练习2:为新创建的内核线程分配资源(需要编码)
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;
if((proc = alloc_proc()) == NULL){// 1. 创建pcb
goto fork_out;
}
proc->parent = current; // 设置父进程为current
if(setup_kstack(proc) != 0 ){// 2. 调用setup_kstack分配内核栈
goto bad_fork_cleanup_proc;
}
if(copy_mm(clone_flags , proc) != 0 ){//3.调用copy_mm 复制父进程内存信息(也可能是共享)
goto bad_fork_cleanup_kstack;
}
copy_thread(proc , stack ,tf); // 4. 调用copy_thread 复制上下文和中断帧
bool intr_flag;
local_intr_save(intr_flag);// 关中断
{
proc->pid = get_pid();//分配id
hash_proc(proc);//放入hash链表
list_add(&proc_list , &proc->list_link); // 5. 插入proc_list
nr_process++;//进程数++
}
local_intr_restore(intr_flag);//开中断
wakeup_proc(proc);// 6. 唤醒新进程
ret = proc->pid;// 7. 返回pid
fork_out:
return ret;
//分配失败收场工作
bad_fork_cleanup_kstack:
put_kstack(proc);
bad_fork_cleanup_proc:
kfree(proc);
goto fork_out;
请说明ucore是否做到给每个新fork的线程一个唯一的id?请说明你的分析和理由。
会的
get_pid
static int
get_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) {//超过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;
}
last_pid=上一次分配的pid.当分配超过MAX_PID时从1开始重新分配
(last_pid,next_safe)指定了一段连续的未分配的pid区间.如果last_pid < next_safe时直接分配last_pid+1,否则以1为单位增加pid,每次增加都遍历整个proc_list查重,并更新next_safe,如果冲突了就再增1,从头再判断
练习3:阅读代码,理解 proc_run 函数和它调用的函数如何完成进程切换的。(无编码工作)
下面是调度函数
1 调度开始时,先屏蔽中断。
2 在进程链表中,查找第一个可以被调度的程序
3 运行新进程,允许中断
void
schedule(void) {
bool intr_flag;
struct proc_struct *next;
local_intr_save(intr_flag);//保存中断开关状态
{
current->need_resched = 0;
if (current->state == PROC_RUNNABLE) {
sched_class_enqueue(current);
}
if ((next = sched_class_pick_next()) != NULL) {
sched_class_dequeue(next);
}
if (next == NULL) {//没有就调内核线程
next = idleproc;
}
next->runs ++;
if (next != current) {
proc_run(next);
}
}
local_intr_restore(intr_flag);//恢复中断开关状态
}
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;//设置当前进程为proc
load_esp0(next->kstack + KSTACKSIZE);//更新tss的特权态0下的栈顶指针esp0为新进程的栈顶
lcr3(next->cr3);//更新CR3位新进程页目录表物理地址,完成进程间页表切换
switch_to(&(prev->context), &(next->context));//切换当前进程和新进程的上下文
}
local_intr_restore(intr_flag);//恢复中断开关状态
}
}
在本实验的执行过程中,创建且运行了几个内核线程?
俩个
idleproc: ucore: 第一个内核进程,完成内核中各个子系统的初始化,之后立即调度,执行其他进程。
initproc:用于完成实验的功能而调度的内核进程。
语句local_intr_save(intr_flag);....local_intr_restore(intr_flag);
在这里有何作用?请说明理由
作用分别是屏蔽中断和打开中断,以免进程切换时其他进程再进行调度。也就是保护进程切换不会被中断,以免进程切换时其他进程再进行调度,相当于互斥锁。之前在第六步添加进程到列表的时候也需要有这个操作,是因为进程进入列表的时候,可能会发生一系列的调度事件,比如我们所熟知的抢断等,加上这么一个保护机制可以确保进程执行不被打乱。
static inline bool
__intr_save(void) {
if (read_eflags() & FL_IF) {//看看关了没,没关就关,并且如果这里关了就返回1,没关就返回0
intr_disable();
return 1;
}
return 0;
}
static inline void
__intr_restore(bool flag) {//根据上次关时候的返回值看看打不打开中断(这样中断就可以嵌套)
if (flag) {
intr_enable();
}
}
#define local_intr_save(x) do { x = __intr_save(); } while (0)
#define local_intr_restore(x) __intr_restore(x);