Lab4

作者: 彳余斤欠丹彡 | 来源:发表于2017-04-19 22:21 被阅读0次

    Lab4 report

    练习0:填写已有实验

    用meld对比修改了以下文件:

    kdebug.c
    trap.c
    default_pmm.c
    pmm.c
    swap_fifo.c
    vmm.c
    

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

    实验思路

    我们分配的是一个内核线程的进程控制块,其结构体proc_struct在kern/process/proc.h中的定义如下:

    struct proc_struct {
        enum proc_state state;                      // Process state
        int pid;                                    // Process ID
        int runs;                                   // the running times of Proces
        uintptr_t kstack;                           // Process kernel stack
        volatile bool need_resched;                 // bool value: need to be rescheduled to release CPU?
        struct proc_struct *parent;                 // the parent process
        struct mm_struct *mm;                       // Process's memory management field
        struct context context;                     // Switch here to run process
        struct trapframe *tf;                       // Trap frame for current interrupt
        uintptr_t cr3;                              // CR3 register: the base addr of Page Directroy Table(PDT)
        uint32_t flags;                             // Process flag
        char name[PROC_NAME_LEN + 1];               // Process name
        list_entry_t list_link;                     // Process link list 
        list_entry_t hash_link;                     // Process hash list
    };
    

    只需要对其中一部分变量初始化即可。

    实验过程

    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为-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 context和struct trapframe *tf成员变量含义和在本实验中的作用是啥?

    context:进程的上下文,用于进程切换。保存了前一个进程的现场(各个寄存器的状态),由于所有的进程在内核中也是相对独立的,所以使用 context 保存寄存器的作用就是在内核态中能够进行上下文之间的切换。
    tf:中断帧的指针,当进程从用户空间跳到内核空间时,中断帧记录了进程在被中断前的状态;当内核需要跳回用户空间时,需要调整中断帧以恢复让进程继续执行的各寄存器值。除此之外,uCore内核允许嵌套中断,因此为了保证嵌套中断发生时tf 总是能够指向当前的trapframe,uCore 在内核栈上维护了 tf 的链。

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

    实验思路

    1. 分配并初始化进程控制块(alloc_proc 函数);
    2. 分配并初始化内核栈(setup_stack 函数);
    3. 据 clone_flag标志复制或共享进程内存管理结构(copy_mm 函数);
    4. 设置进程在内核(将来也包括用户态)正常运行和调度所需的中断帧和 执行上下文 (copy_thread函数);
    5. 把设置好的进程控制块放入hash_list 和proc_list 两个全局进程链表中;
    6. 把进程状态设置为“就绪”态;
    7. 设置返回码为子进程的 id号。

    实现过程

    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;
        //1. 分配并初始化进程控制块(alloc_proc 函数)
        if ((proc = alloc_proc()) == NULL) {
            goto fork_out;
        }
        proc->parent = current;
        //2. 分配并初始化内核栈(setup_stack 函数)
        if (setup_kstack(proc) != 0) {
            goto bad_fork_cleanup_proc;
        }
        //3. 据 clone_flag标志复制或共享进程内存管理结构(copy_mm 函数)
        if (copy_mm(clone_flags, proc) != 0) {
            goto bad_fork_cleanup_kstack;
        }
        //4. 设置进程在内核(将来也包括用户态)正常运行和调度所需的中断帧和 执行上下文 (copy_thread函数)
        copy_thread(proc, stack, tf);
        bool intr_flag;
        //5. 把设置好的进程控制块放入hash_list 和proc_list 两个全局进程链表中
        local_intr_save(intr_flag);
        {
            proc->pid = get_pid();
            hash_proc(proc);
            nr_process ++;
            list_add(&proc_list, &(proc->list_link));
        }
        local_intr_restore(intr_flag);
        //6. 把进程状态设置为“就绪”态
        wakeup_proc(proc);
        //7. 设置返回码为子进程的 id号
        ret = proc->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可以得到唯一的一个id。

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

    函数的执行过程是:

    1. 让 current 指向 next 内核线程 initproc;
    2. 设置任务状态段 ts 中特权态 0 下的栈顶指针 esp0 为 next 内核线程 initproc 的内核栈的栈顶,即 next->kstack + KSTACKSIZE ;
    3. 设置 CR3 寄存器的值为 next 内核线程 initproc 的页目录表起始地址 next->cr3,这实际上是完成进程间的页表切换;
    4. 由 switch_to函数完成具体的两个线程的执行现场切换,即切换各个寄存器,当 switch_to 函数执行完“ret”指令后,就切换到 initproc 执行了。

    思考题

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

    2个。
    idleproc:第一个内核进程,完成内核中各个子系统的初始化,之后立即调度,执行其他进程。
    initproc:用于完成实验的功能而调度的内核进程。

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

    保护进程切换不会被中断,以免进程切换时其他进程再进行调度。

    相关文章

      网友评论

          本文标题:Lab4

          本文链接:https://www.haomeiwen.com/subject/mwyaattx.html