zoukankan      html  css  js  c++  java
  • ucore lab4 内核线程管理 学习笔记

    越学越简单,真是越学越简单啊
    看视频的时候着实被那复杂的函数调用图吓到了.看代码的时候发现条理还是很清晰的,远没有没想象的那么复杂.
    这节创建了俩内核线程,然后运行第一个线程,再由第一个切换到第二个.

    kern_init:
    在vmm_init后加了一个proc_init
    在最末位加了个cpu_idel

    proc.c&.h
    枚举类proc_state定义了进程生命周期里的各种状态

    // process's state in his life cycle  
    enum proc_state {  
        PROC_UNINIT = 0,  // uninitialized  
        PROC_SLEEPING,    // sleeping  
        PROC_RUNNABLE,    // runnable(maybe running)  
        PROC_ZOMBIE,      // almost dead, and wait parent proc to reclaim his resource  
    };  
    

    各状态间的转化:

      alloc_proc                                 RUNNING  
          +                                   +--<----<--+  
          +                                   + proc_run +  
          V                                   +-->---->--+   
    PROC_UNINIT -- proc_init/wakeup_proc --> PROC_RUNNABLE -- try_free_pages/do_wait/do_sleep --> PROC_SLEEPING --  
                                               A      +                                                           +  
                                               |      +--- do_exit --> PROC_ZOMBIE                                +  
                                               +                                                                  +   
                                               -----------------------wakeup_proc----------------------------------  
    

    proc_struct,就课程里讲的那个进程控制块(PCB)

    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  
    };  
    

    list_entry_t proc_list 链表形式的进程集合
    list_entry_t hash_list[1024] 散列表形式的进程集合
    proc_struct *idleproc 0号进程,作用是不断检查当前有无处于就绪状态的进程,有则立即运行
    proc_struct *initproc 本实验中测试用的进程,打印一句 hello world
    static int nr_process 线程计数器

    alloc_proc:分配一个PCB并初始化
    各种成员变量清零
    state设为UNINIT
    pid=-1
    cr3=内核页目录表基址(物理地址)

    kernel_thread(fn,arg,clone_flag) :创建内核线程
    创建一个临时trapframe
    CS,DS,SS,ES均取内核态的对应值
    ebx=fn
    edx=arg
    eip=kernel_thread_entry //中断返回时从kernel_thread_entry继续
    kernel_thread_entry在entry中定义:把arg做参数调用fn,把fn返回值做参数调用do_exit
    调用do_fork(clone_flags|CLONE_VM,0,&tf)

    do_fork: 根据tf,stack,clone_tf创建新线程

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

    copy_thread:

    proc->tf = (struct trapframe *)(proc->kstack + KSTACKSIZE) - 1;  
    //在内核堆栈的顶部设置中断帧大小的一块栈空间  
    *(proc->tf) = *tf; //拷贝在kernel_thread函数建立的临时中断帧的初始值  
    proc->tf->tf_regs.reg_eax = 0;  
    //设置子进程/线程执行完do_fork后的返回值  
    proc->tf->tf_esp = esp; //设置中断帧中的栈指针esp  
    proc->tf->tf_eflags |= FL_IF; //使能中断  
    proc->context.eip = (uintptr_t)forkret; //trapentry.s定义,把esp压栈,调用__trapet  
    proc->context.esp = (uintptr_t)(proc->tf); //context.esp赋值为当前栈顶  
    

    get_pid:分配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,从头再判断.

    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) {
            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;
    }
    

    proc_init:

    void  proc_init(void) {  
        int i;  
          
        //初始化proc_list和hash_list  
        list_init(&proc_list);  
        for (i = 0; i < HASH_LIST_SIZE; i ++) {  
            list_init(hash_list + i);  
        }  
      
        //给idleproc分配一个PCB  
        if ((idleproc = alloc_proc()) == NULL) {  
            panic("cannot alloc idleproc.
    ");  
        }  
      
        idleproc->pid = 0;	//设为0号进程  
        idleproc->state = PROC_RUNNABLE;	//可运行  
        idleproc->kstack = (uintptr_t)bootstack;	//kstack指向全句内核栈,在entry.S里用汇编定义,大小8KB  
        idleproc->need_resched = 1;	//需要重新调度  
        set_proc_name(idleproc, "idle");	//命名  
        nr_process ++;	//进程数+1  
      
        current = idleproc;  
      
        int pid = kernel_thread(init_main, "Hello world!!", 0);  
        if (pid <= 0) {  
            panic("create init_main failed.
    ");  
        }  
      
        initproc = find_proc(pid);  
        set_proc_name(initproc, "init");  
      
        assert(idleproc != NULL && idleproc->pid == 0);  
        assert(initproc != NULL && initproc->pid == 1);  
    }  
    

    cpu_idle: 无限循环检查当前线程的need_resched,为真时调用schedule()

    schedule:基于FIFO的调度算法
    保存中断开关状态
    从当前进程往后遍历,选择下一个RUNNABLE的进程调用proc_run
    恢复中断开关状态

    proc_run:
    更新tss的特权态0下的栈顶指针esp0为新进程的栈顶
    更新CR3位新进程页目录表物理地址,完成进程间页表切换
    switch切换当前进程和新进程的上下文

    switch_to:

    .text  
    .globl switch_to  
    switch_to:                      # switch_to(from, to)  
          
        # save from's registers  
        movl 4(%esp), %eax          # eax points to from  
        popl 0(%eax)                # save eip !popl  
        movl %esp, 4(%eax)          # save esp::context of from  
        movl %ebx, 8(%eax)          # save ebx::context of from  
        movl %ecx, 12(%eax)         # save ecx::context of from  
        movl %edx, 16(%eax)         # save edx::context of from  
        movl %esi, 20(%eax)         # save esi::context of from  
        movl %edi, 24(%eax)         # save edi::context of from  
        movl %ebp, 28(%eax)         # save ebp::context of from  
      
        # restore to's registers  
        movl 4(%esp), %eax          # not 8(%esp): popped return address already  
                                    # eax now points to to  
        movl 28(%eax), %ebp         # restore ebp::context of to  
        movl 24(%eax), %edi         # restore edi::context of to  
        movl 20(%eax), %esi         # restore esi::context of to  
        movl 16(%eax), %edx         # restore edx::context of to  
        movl 12(%eax), %ecx         # restore ecx::context of to  
        movl 8(%eax), %ebx          # restore ebx::context of to  
        movl 4(%eax), %esp          # restore esp::context of to  
      
        pushl 0(%eax)               # push eip  
      
        ret  
    

    当调用switch_to(&(from->context), &(to->context)),进入它的第一行代码时,此时的栈布局为:

    |to.context   |高地址  
    |from.context |  
    |ret address  |<---esp  
    

    整个switch_to的功能为:
    令eax=from.context
    把各个寄存器保存到from.context里
    令eax=to.context
    把to.context恢复到各个寄存器里,把to.context.eip压栈
    此时进行ret,栈顶出栈作为eip,返回到的地址就变成了to.context.eip,进程切换完成

    对于进程init而言,我们前面把它的context.eip设为了forkret,具体功能为,把esp压栈,调用中断返回函数__trapet,进而将trapframe中的值恢复到的各个寄存器中.eip再次变更为trapframe.eip,即kernel_thread_entry函数,作用为把edx做参数调用ebx对应的函数,edx和ebx也在trapframe中分别指定为"hello world"和init_main,调用完init_main后再把返回值做参数调用do_exit.而do_exit负责退出进程,整个lab4内容结束.

  • 相关阅读:
    Practice II 字符串
    Euleriar Path 入门
    2-SAT 入门
    Practice I 图论
    游戏中寻找学习JAVA的乐趣之坦克大战系列5-坦克的动态参数
    JQuery教程:实现轮播图效果
    HTML表格应用
    菜鸟Vue学习笔记(三)
    Java成神路上之设计模式系列教程之一
    JVM垃圾回收机制之对象回收算法
  • 原文地址:https://www.cnblogs.com/kangyupl/p/12790220.html
Copyright © 2011-2022 走看看