• ucore lab5 用户进程管理 学习笔记


    近几日睡眠质量不佳,脑袋一困就没法干活,今天总算时补完了.LAB5难度比LAB4要高,想要理解所有细节时比较困难.但毕竟咱不是要真去写一个OS,所以一些个实现细节就当成黑箱略过了.
    这节加上了用户进程,主要逻辑是:idle_proc内核线程--子进程-->init_proc内核线程--子进程-->user_main内核线程--load_icode-->exit用户进程--子进程-->新的用户进程,然后再逐级释放

    init_main: 在init内核线程中创建user_main内核线程

    user_mem_check: 顾名思义.

    copy_mm: 把当前进程的mm和对应的物理内存全部做拷贝,添加到指定进程的mm中.使用了dup_mmap

    dup_mmap: 把mm从from拷贝到to,使用了copy_range

    copy_range: 把pde_t的对应物理内存从from拷贝到to

    接下来就是各种状态的转换:

    /*
    process state changing:
                                                
      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----------------------------------
    */
    

    do_wait: 如果指定子进程为僵尸态,则释放子进程资源(线程表,PCB,内核栈);否则自身进入等待态

    do_execve: 释放当前进程的mm,调用load_icode载入指定的程序

    do_fork: 创建当前进程的子进程,并拷贝对应内存区域.trapframe,stack可以单独指定

    do_exit:
    释放当前进程资源
    如果当前进程的父进程为WT_CHILD状态,则唤醒之
    把当前进程的所有子进程都过继给initproc,如果哪个子进程是个ZOMBIE,则唤醒initproc

    do_kill: 将指定进程的flags设为PF_EXITING,等待被exit

    do_yield: 将当前进程的need_resched置一

    load_icode: 当前进程的mm为空时,将内存中指定的ELF程序文件进行展开并作为当前进程的新内容,并更新为用户态特权级

    syscall巧妙的运用了函数指针数组便于用户态使用

    static int (*syscalls[])(uint32_t arg[]) = {
        [SYS_exit]              sys_exit,
        [SYS_fork]              sys_fork,
        [SYS_wait]              sys_wait,
        [SYS_exec]              sys_exec,
        [SYS_yield]             sys_yield,
        [SYS_kill]              sys_kill,
        [SYS_getpid]            sys_getpid,
        [SYS_putc]              sys_putc,
        [SYS_pgdir]             sys_pgdir,
    };
    
    #define NUM_SYSCALLS        ((sizeof(syscalls)) / (sizeof(syscalls[0])))
    
    void
    syscall(void) {
        struct trapframe *tf = current->tf;
        uint32_t arg[5];
        int num = tf->tf_regs.reg_eax;
        if (num >= 0 && num < NUM_SYSCALLS) {
            if (syscalls[num] != NULL) {
                arg[0] = tf->tf_regs.reg_edx;
                arg[1] = tf->tf_regs.reg_ecx;
                arg[2] = tf->tf_regs.reg_ebx;
                arg[3] = tf->tf_regs.reg_edi;
                arg[4] = tf->tf_regs.reg_esi;
                tf->tf_regs.reg_eax = syscalls[num](arg);
                return ;
            }
        }
        print_trapframe(tf);
        panic("undefined syscall %d, pid = %d, name = %s.
    ",
                num, current->pid, current->name);
    }
    
    

    承接LAB4,当执行到init_main时,会创建user_main内核线程,并使init_main进入等待态调度user_main
    调度完成后的函数调用栈为:
    kernel_thread_entry->user_main->__alltraps->trap->trap_dispatch->syscall->sysexec->do_execve
    这一过程最终将user/exit.c的代码载入到了user_main内核线程中,并将其转化为了用户进程exit,且拥有独立的mm
    载入完成后输出信息:
    kernel_execve: pid = 2, name = "exit".
    因为载入代码这一过程是运行时发生的,所以GDB无法跟踪调试
    通过分析可知,ELF程序的入口代码位于user/libs/initcode.S中,主要功能是调用了umain
    umain.c功能是调用main函数,并在返回后exit()
    main函数则位于exit.c中

    int
    main(void) {
        int pid, code;
        cprintf("I am the parent. Forking the child...
    ");
        if ((pid = fork()) == 0) {
            cprintf("I am the child.
    ");
            yield();
            yield();
            yield();
            yield();
            yield();
            yield();
            yield();
            exit(magic);
        }
        else {
            cprintf("I am parent, fork a child pid %d
    ",pid);
        }
        assert(pid > 0);
        cprintf("I am the parent, waiting now..
    ");
    
        assert(waitpid(pid, &code) == 0 && code == magic);
        assert(waitpid(pid, &code) != 0 && wait() != 0);
        cprintf("waitpid %d ok.
    ", pid);
    
        cprintf("exit pass.
    ");
        return 0;
    }
    

    首先输出信息:
    I am the parent. Forking the child...
    调用fork()创建子进程,调用栈为:
    user/exic.c::main->fork->sys_fork->syscall(SYS_fork)->触发中断SYS_CALL,参数为SYS_fork
    以上部分都在user/文件夹下,即在用户态下运行,触发系统调用类型的中断后切换到内核态(对应kern/文件夹)继续执行,__alltraps->trap->dispatch_trap->syscall->sys_fork
    此时创建出了3号进程,二者都运行到了if ((pid = fork()) == 0) {这行代码.
    对于父进程:fork()返回值为子进程PID
    对于子进程:fork()返回值为0
    输出信息:
    I am parent, fork a child pid 3
    I am the parent, waiting now..
    父进程调用wait(pid,&code),以fork()类似的形式触发系统调用,最终进入等待态
    子进程被调度,输出信息:
    I am the child.
    然后子进程调用exit()触发系统调用,释放自身资源,并进入僵尸态.
    因为父进程处于WT_CHILD状态,所以被唤醒,有如下信息:
    waitpid 3 ok.
    exit pass.
    main()返回到umain()中,调用exit回收进程资源,并重新调度到init_proc,输出如下信息:
    all user-mode processes have quit.
    init check memory pass.
    kernel panic at kern/process/proc.c:447:
    initproc exit.

    至此整个LAB 5分析完毕

  • 相关阅读:
    Ajax 的 GET 和 POST 模式
    AJax中post与get请求注意事项
    代理模式 (Proxy)
    装饰模式 (Decoratory)
    抽象工厂模式( Abstract Factory )
    单例模式(Singleton)
    原型设计模式
    Intro.js的简介和用法
    mysql 分片
    数据分片(二)如何为数据分片
  • 原文地址:https://www.cnblogs.com/kangyupl/p/12813285.html
Copyright © 2020-2023  润新知