ucore lab4 内核线程管理

练习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 contextstruct 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);

你可能感兴趣的:(操作系统,c语言)