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内容结束.

  • 相关阅读:
    [转]对Lucene PhraseQuery的slop的理解
    Best jQuery Plugins of 2010
    15 jQuery Plugins To Create A User Friendly Tooltip
    Lucene:基于Java的全文检索引擎简介
    9 Powerful jQuery File Upload Plugins
    Coding Best Practices Using DateTime in the .NET Framework
    Best Image Croppers ready to use for web developers
    28 jQuery Zoom Plugins Creating Stunning Image Effect
    VS2005 + VSS2005 实现团队开发、源代码管理、版本控制(转)
    禁止状态栏显示超链
  • 原文地址:https://www.cnblogs.com/kangyupl/p/12790220.html
Copyright © 2011-2022 走看看