Advertisement

操作系统ucore lab5实验报告

阅读量:

练习0

填充现有记录
此实验基于前四次测试(即 experiment 1至 experiment 4)。建议将已完成的 experiment 1至 experiment 4 的相关代码整合至当前项目中,并在其中添加 lab1、lab2、lab3 和 lab4 的注释以对应各自的部分。

这里写图片描述
这里写图片描述

未包含的关键代码文件包括kdebug.ctrap.cdefault_pmm.cpmm.cswap_fifo.cvmm.c以及proc.c七个模块的相关实现。经过补充完整后进一步排查发现仍有若干模块需要优化调整。

alloc_proc函数

注释

复制代码
    //LAB4:EXERCISE1 YOUR CODE
    /* * below fields in proc_struct need to be initialized
     *       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
     */
     //LAB5 YOUR CODE : (update LAB4 steps)
    /* * below fields(add in LAB5) in proc_struct need to be initialized    
     *       uint32_t wait_state;                        // waiting state
     *       struct proc_struct *cptr, *yptr, *optr;     // relations between processes
     */

相较于Lab4而言,在实现功能上会多出两行代码。这些新增的两行代码主要是用来 初始化所有的状态指针 。鉴于本实验涉及 用户进程管理 的内容,在操作之前必须对所有 状态指针 进行 初始化 ,以防止后续使用时出现 undefined 或 未置0 的情况。

补充代码

复制代码
    // alloc_proc - alloc a proc_struct and init all fields of proc_struct
    static struct proc_struct *
    alloc_proc(void) {
    struct proc_struct *proc = kmalloc(sizeof(struct proc_struct));
    if (proc != NULL) {
    //LAB4:EXERCISE1 YOUR CODE
    /* * below fields in proc_struct need to be initialized
     *       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
     */
     //LAB5 YOUR CODE : (update LAB4 steps)
    /* * below fields(add in LAB5) in proc_struct need to be initialized    
     *       uint32_t wait_state;                        // waiting state
     *       struct proc_struct *cptr, *yptr, *optr;     // relations between processes
     */
        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);//置空进程名
        proc->wait_state = 0;  //初始化进程等待状态  
        proc->cptr = proc->optr = proc->yptr = NULL;//进程相关指针初始化  
        //*cptr-->children | *yptr-->younger | *optr-->older
    }
    return proc;
    }

do_fork函数

注释

复制代码
    //LAB4:EXERCISE2 YOUR CODE
    /* * Some Useful MACROs, Functions and DEFINEs, you can use them in below implementation.
     * MACROs or Functions:
     *   alloc_proc:   create a proc struct and init fields (lab4:exercise1)
     *   setup_kstack: alloc pages with size KSTACKPAGE as process kernel stack
     *   copy_mm:      process "proc" duplicate OR share process "current"'s mm according clone_flags
     *                 if clone_flags & CLONE_VM, then "share" ; else "duplicate"
     *   copy_thread:  setup the trapframe on the  process's kernel stack top and
     *                 setup the kernel entry point and stack of process
     *   hash_proc:    add proc into proc hash_list
     *   get_pid:      alloc a unique pid for process
     *   wakup_proc:   set proc->state = PROC_RUNNABLE
     * VARIABLES:
     *   proc_list:    the process set's list
     *   nr_process:   the number of process set
     */
    
    //    1. call alloc_proc to allocate a proc_struct
    //    2. call setup_kstack to allocate a kernel stack for child process
    //    3. call copy_mm to dup OR share mm according clone_flag
    //    4. call copy_thread to setup tf & context in proc_struct
    //    5. insert proc_struct into hash_list && proc_list
    //    6. call wakup_proc to make the new child process RUNNABLE
    //    7. set ret vaule using child proc's pid
    
    //LAB5 YOUR CODE : (update LAB4 steps)
       /* Some Functions
    *    set_links:  set the relation links of process.  ALSO SEE: remove_links:  lean the relation links of process 
    *    -------------------
    *    update step 1: set child proc's parent to current process, make sure current process's wait_state is 0
    *    update step 5: insert proc_struct into hash_list && proc_list, set the relation links of process
    */

现对代码进行了更新。其中一项修改是将父进程设为当前进程中,并确保其处于等待状态;另一项调整是针对语句进行的修改。单纯采用计数加法无法满足对进程中调度的管理要求,因此需要使用特定的set_links函数进行操作。

代码

复制代码
    /* do_fork -     parent process for a new child process
     * @clone_flags: used to guide how to clone the child process
     * @stack:       the parent's user stack pointer. if stack==0, It means to fork a kernel thread.
     * @tf:          the trapframe info, which will be copied to child process's proc->tf
     */
    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;
    //LAB4:EXERCISE2 YOUR CODE
    /* * Some Useful MACROs, Functions and DEFINEs, you can use them in below implementation.
     * MACROs or Functions:
     *   alloc_proc:   create a proc struct and init fields (lab4:exercise1)
     *   setup_kstack: alloc pages with size KSTACKPAGE as process kernel stack
     *   copy_mm:      process "proc" duplicate OR share process "current"'s mm according clone_flags
     *                 if clone_flags & CLONE_VM, then "share" ; else "duplicate"
     *   copy_thread:  setup the trapframe on the  process's kernel stack top and
     *                 setup the kernel entry point and stack of process
     *   hash_proc:    add proc into proc hash_list
     *   get_pid:      alloc a unique pid for process
     *   wakup_proc:   set proc->state = PROC_RUNNABLE
     * VARIABLES:
     *   proc_list:    the process set's list
     *   nr_process:   the number of process set
     */
    
    //    1. call alloc_proc to allocate a proc_struct
    //    2. call setup_kstack to allocate a kernel stack for child process
    //    3. call copy_mm to dup OR share mm according clone_flag
    //    4. call copy_thread to setup tf & context in proc_struct
    //    5. insert proc_struct into hash_list && proc_list
    //    6. call wakup_proc to make the new child process RUNNABLE
    //    7. set ret vaule using child proc's pid
    
    //LAB5 YOUR CODE : (update LAB4 steps)
       /* Some Functions
    *    set_links:  set the relation links of process.  ALSO SEE: remove_links:  lean the relation links of process 
    *    -------------------
    *    update step 1: set child proc's parent to current process, make sure current process's wait_state is 0
    *    update step 5: insert proc_struct into hash_list && proc_list, set the relation links of process
    */
    if ((proc = alloc_proc()) == NULL)
    {
        goto fork_out;
    }
    //设置父节点为当前进程
    proc->parent = current;
    assert(current->wait_state == 0);//确保当前进程正在等待
    //分配内核栈
    if (setup_kstack(proc) != 0) {
        goto bad_fork_cleanup_proc;
    }
    //调用copy_mm()函数复制父进程的内存信息到子进程
    if (copy_mm(clone_flags, proc) != 0) {
        goto bad_fork_cleanup_kstack;
    }
    //调用copy_thread()函数复制父进程的中断帧和上下文信息
    copy_thread(proc, stack, tf);
    
    bool intr_flag;
    local_intr_save(intr_flag);
    {
        proc->pid = get_pid();
        hash_proc(proc);//将新进程加入hash_list
        set_links(proc);//执行set_links函数,实现设置相关进程链接
    }
    local_intr_restore(intr_flag);
    //唤醒进程,等待调度
    wakeup_proc(proc);
    //返回子进程的pid
    ret = proc->pid;
    fork_out:
    return ret;
    
    bad_fork_cleanup_kstack:
    put_kstack(proc);
    bad_fork_cleanup_proc:
    kfree(proc);
    goto fork_out;
    }

idt_init函数

通过注释可知添加一行代码,用来设置相关的中断门
代码

复制代码
    /* idt_init - initialize IDT to each of the entry points in kern/trap/vectors.S */
    void
    idt_init(void) {
     /* LAB1 YOUR CODE : STEP 2 */
     /* (1) Where are the entry addrs of each Interrupt Service Routine (ISR)?
      *     All ISR's entry addrs are stored in __vectors. where is uintptr_t __vectors[] ?
      *     __vectors[] is in kern/trap/vector.S which is produced by tools/vector.c
      *     (try "make" command in lab1, then you will find vector.S in kern/trap DIR)
      *     You can use  "extern uintptr_t __vectors[];" to define this extern variable which will be used later.
      * (2) Now you should setup the entries of ISR in Interrupt Description Table (IDT).
      *     Can you see idt[256] in this file? Yes, it's IDT! you can use SETGATE macro to setup each item of IDT
      * (3) After setup the contents of IDT, you will let CPU know where is the IDT by using 'lidt' instruction.
      *     You don't know the meaning of this instruction? just google it! and check the libs/x86.h to know more.
      *     Notice: the argument of lidt is idt_pd. try to find it!
      */
     /* LAB5 YOUR CODE */ 
     //you should update your lab1 code (just add ONE or TWO lines of code), let user app to use syscall to get the service of ucore
     //so you should setup the syscall interrupt gate in here
    extern uintptr_t __vectors[];
    int i;
    for (i = 0; i < sizeof(idt)  sizeof(struct gatedesc); i ++) {
        SETGATE(idt[i], 0, GD_KTEXT, __vectors[i], DPL_KERNEL);
    }
    SETGATE(idt[T_SYSCALL], 1, GD_KTEXT, __vectors[T_SYSCALL], DPL_USER);//设置相应的中断门
    lidt(&idt_pd);
    }

创建一个专用的中断门用于实现用户的系统调用接口,在该设计中,在加载lidt指令之前需要预先定义一个独特的中断描述符idt[T_SYSCALL]。该特殊描述符具有DPL_USER级别的权限,并将对应的向量地址定位在__vectors[T_SYSCALL]位置上。配置完成后,在后续的操作中当用户态进程触发INT T_SYSCALL指令时(其特权级别配置为DPL_USER),CPU将切换至内核态模式以便完成相关的任务处理,并直接跳转至该向量地址进行后续操作。

复制代码
    vector128(vectors.S)--\>
    \_\_alltraps(trapentry.S)--\>trap(trap.c)--\>trap\_dispatch(trap.c)----\>syscall(syscall.c)-

syscall 中,根据系统调用号来完成不同的系统调用服务。

trap_dispatch函数

通过注释信息可知,在每次循环执行过程中,在时间片耗尽后将其被指定为需要调度的任务。

复制代码
    ticks ++;
        if (ticks % TICK_NUM == 0) {
            assert(current != NULL);
            current->need_resched = 1;//时间片用完设置为需要调度
        }

当全部修改完后就可以正式进行实验

练习1

通过调用do_execv函数加载并解析一个位于kern/process/proc.c中的ELF格式可执行文件,并为该程序创建相应的用户级内存空间。随后将代码段和数据段存放在其中,并配置相关参数至proc_struct结构体中以确保该进程能够从预先指定的起始地址正确运行。

load_icode函数

注释

复制代码
    /* LAB5:EXERCISE1 YOUR CODE
     * should set tf_cs,tf_ds,tf_es,tf_ss,tf_esp,tf_eip,tf_eflags
     * NOTICE: If we set trapframe correctly, then the user level process can return to USER MODE from kernel. So
     *          tf_cs should be USER_CS segment (see memlayout.h)
     *          tf_ds=tf_es=tf_ss should be USER_DS segment
     *          tf_esp should be the top addr of user stack (USTACKTOP)
     *          tf_eip should be the entry point of this binary program (elf->e_entry)
     *          tf_eflags should be set to enable computer to produce Interrupt
     */

The function's primary responsibility is to establish an environment that allows the user process to run smoothly.

The function's primary responsibility is to create a user environment that ensures the user process can run effectively.

首先使用 mm_create 函数来获取进程所需的内存管理数据结构,并对该数据结构进行初始化配置。

  • 2、通过调用函数 setup_pgdir 来申请所需的内存空间以创建一个页面目录表,并将描述ucore内核虚空间映射的内核页表(由变量 boot_pgdir 指向)的内容进行复制。最终使得变量 mm 的 pgdir 指针指向该新建的页面目录表,并实现对内核虚拟空间的有效映射。

    • 3、根据可执行程序的起始位置来解析此 ELF 格式的执行程序,并调用 mm_map函数根据 ELF格式执行程序的各个段(代码段、数据段、BSS段等)的起始位置和大小建立对应的vma结构,并把vma 插入到 mm结构中,表明这些是用户进程的合法用户态虚拟地址空间;

4.为可执行程序各段的大小而分配物理内存空间,并同时计算其起始位置以确定相应的虚拟地址;随后,在页表中预先建立了物理与虚拟地址之间的对应关系,并将各段内容按顺序复制至对应的内核区域;至此,在编译阶段设定好的位置上完成了应用程序代码及数据文件在虚拟内存中的存放工作。

  • 配置用户进程以建立其vma结构;通过调用mm_mmap函数创建该vma结构,并确定其位于用户的虚拟空间顶端位置;该结构由256个页组成(相当于1MB),同时预留相应的物理内存,并配置好了虚拟地址与物理地址之间的对应关系;

  • 6.至此,vma和mm的数据结构已建成,因此将mm→pgdir赋值至cr3寄存器从而实现了用户的虚拟内存空间更新。此时init已被退出代码及数据覆盖标志着成为第一个用户进程,但此时该用户的执行环境尚未完全搭建。

首先清除进程中的中断帧后继行重新配置这些帧.这样就能保证当执行完iret返回指令时.系统能够让CPU切换至用户的特权级并返回至用户的内存空间区域.随后将利用用户的代码块.数据块以及栈结构来进行后续操作.这不仅能让系统直接转向用户的第一条指令.而且还能确保了在用户态下正常处理中断.

至此完成用户的上下文环境搭建工作。当系统调用发生时,initproc将按照产生该系统调用的函数调用路径直接返回,并在执行中断返回指令 iret 后立即转移到程序入口点 _start 处继续执行。

首先相关的定义如下:

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

基于注释的主要任务是实现proc_struct结构中tf结构体变量的配置。为了使程序能够顺利地从内核态过渡至用户态并得以执行

在调用trap函数之前必须先完成一些准备工作:首先必须将系统调用之前的执行状态进行记录。这种记录的具体内容应包括与当前进程继续执行所需的全部信息即与用户进程中断相关的所有寄存器等当前状态信息并将这些内容存储到当前进程中断(trapframe)中。特别注意,在为新进程分配内存时要把这个trapframe字段放置于内核栈顶端位置上这样才能确保后续操作能够顺利进行。整个软件的工作就是在专门的数据结构区域中完成了这一任务具体来说就是在vector128__alltraps这两个特殊区域的起始部分实现了这一功能

复制代码
    vectors.S::vector128起始处:
      pushl $0
      pushl $128
    ......
    trapentry.S::__alltraps起始处:
    pushl %ds
      pushl %es
      pushal
    ……

在此阶段中完成了对用于保存用户态运行过程执行现场(即trapframe)的所有填写工作,并使操作系统能够开始执行特定的操作系统调用服务流程。在sys_getpid函数的设计中,默认将当前进程中的PID字段直接赋值为函数返回值的方式是一种典型的系统调用实现方法。所有操作完成后,在按照调用关系路径回滚至__alltraps记录点之后,在恢复执行现场的过程中需要利用当前进程中的中断帧信息来进行相关操作。这一过程实质上是将trapframe中的相关信息部分存储至寄存器中以备后续使用。当完成寄存器内容恢复并调整完成后,则应当将内核堆栈指针设置至中断帧中的$tf_eip位置处以完成此次内核栈的状态更新

复制代码
    /* below here defined by x86 hardware */
    uintptr_t tf_eip;
    uint16_t tf_cs;
    uint16_t tf_padding3;
    uint32_t tf_eflags;
    /* below here only when crossing rings */
    uintptr_t tf_esp;
    uint16_t tf_ss;
    uint16_t tf_padding4;

当执行IRET指令时,CPU会根据内核栈的状态返回到用户态,并将其EIP指针指向tf_eip的值;随后的那条指令即为此处处理的结果。因此整个系统调用过程便完成。

代码

复制代码
    tf->tf_cs = USER_CS;
    tf->tf_ds = tf->tf_es = tf->tf_ss = USER_DS;
    tf->tf_esp = USTACKTOP;//0xB0000000
    tf->tf_eip = elf->e_entry;
    tf->tf_eflags = FL_IF;//FL_IF为中断打开状态 
    ret = 0;

练习2

父进程通过复制自身内存空间至子进程中实现资源传递(编码环节不可忽视)。在创建子进程中,函数do_fork执行时会将当前进程中(即父进程中)的有效用户内存地址空间内容复制至新进程中(子进程中),从而完成内存资源的转移。具体实现则依赖于内核提供的copy_range函数(见于kern/mm/pmm.c文件),建议补充对此函数的实现以确保其正确运行。

注释

复制代码
    /* LAB5:EXERCISE2 YOUR CODE
         * replicate content of page to npage, build the map of phy addr of nage with the linear addr start
         * * Some Useful MACROs and DEFINEs, you can use them in below implementation.
         * MACROs or Functions:
         *    page2kva(struct Page *page): return the kernel vritual addr of memory which page managed (SEE pmm.h)
         *    page_insert: build the map of phy addr of an Page with the linear addr la
         *    memcpy: typical memory copy function
         * * (1) find src_kvaddr: the kernel virtual address of page
         * (2) find dst_kvaddr: the kernel virtual address of npage
         * (3) memory copy from src_kvaddr to dst_kvaddr, size is PGSIZE
         * (4) build the map of phy addr of  nage with the linear addr start
         */

参考注释信息可知, 其含义较为明确. 主要任务包括定位父进程中及其对应的内存地址, 并通过函数接口实现数据复制过程, 最终建立起了父、子进程中对应关系.

相关函数定义:
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

代码

复制代码
    void * src_kvaddr = page2kva(page);//找寻父进程的内核虚拟页地址  
    void * dst_kvaddr = page2kva(npage);//找寻子进程的内核虚拟页地址  
    
    memcpy(dst_kvaddr, src_kvaddr, PGSIZE);//复制父进程到子进程  
    
    ret = page_insert(to, npage, start, perm);//建立物理地址与子进程的页地址起始位置的映射关系
    //perm是权限

练习3

深入分析源代码以掌握进程执行机制(fork、exec、wait、exit)的具体实现细节,并熟悉系统调用的实现过程(无需深入编码)

fork

该函数通过调用 SYS_fork 来实现其功能。其中主要依赖于两个关键函数: do_fork() 和 wakeup_proc() 来执行相应的操作。其中 wake_up_proc 的作用是将进程的状态设置为 wait 状态(wait_state字段设为0)。即 proc->wait_state = 0。在lab4中我们已经对它进行了全面介绍,在此仅作简要回顾。在此上下文中我们仅需回顾其基本功能即可。

  • 配置并建立进程控制块(alloc\_proc函数);
  • 配置和建立内核栈表(setup\_stack函数);
  • 根据 clone\_flag 标志进行进程内存管理结构的复制或共享(copy\_mm函数);
  • 为进程在其正常运行及调度过程中所需设置中断帧以及相应的执行上下文配置相关参数(copy\_thread函数);
  • 将配置好的进程控制块添加至全局进程链表 hash\_listproc\_list 中;
  • 此时, 进程已准备好运行, 将其状态设置为'就绪'态;
  • 最后, 设置返回码为子进程中使用的标识符编号。

exec

在应用程序运行的过程中, 系统会自动触发SYS_exec这一系统调用. 当ucore接收此系统调用时, 会立即通过调用do_execve()函数来完成相应的操作. 该功能的主要职责是为新启动的用户提供必要的上下文信息初始化, 同时负责创建用户进程的资源空间和堆栈空间, 并最终使得该用户提供独立的资源空间和堆栈空间进行分配. 此外, 该模块还负责将该用户的程序代码和数据区域映射到内存中指定的位置, 并在此基础上完成对文件系统的访问控制.
主要工作流程如下:

  1. 初始化新用户的上下文信息
  2. 创建独立的资源空间与堆栈空间
  3. 映射程序代码与数据区域至内存相应位置并实现文件系统的访问控制
  • 首先,在开始加载新的执行码之前,请做好相应的用户态内存清理工作。当 mm 不为空时,请将页表设置为内核专用页表,并进一步进行判断:若 mm 中记录的引用计数减一后结果等于零,则表示没有其他进程需要释放与当前进程相关联的内存空间;此时,请根据 mm 中的信息释放该进程占用的所有用户态内存,并释放其自身的页面表占用的空间。最后,请将当前进程对应的 mm 内存管理指针设为空值。
  • 随后的操作涉及将应用程序执行代码加载到当前进程中 newly created 的用户态虚拟存储区域中。
    具体来说,请完成以下操作:
    (1) 读取 ELF 格式文件;
    (2) 申请必要的内存空间;
    (3) 建立并配置该存储区域;
    (4) 加载程序代码。
    这些步骤都由 load_icode 函数负责实现。

wait

在执行wait功能时,在操作系统层面上会触发一系列特定的操作流程以实现功能性需求。其中,在执行wait功能时,在操作系统层面上会触发一系列特定的操作流程以实现功能性需求. 在此过程中, 系统将首先激活一个专门负责处理该操作的虚拟化进程中. 随后启动相应的父进程中. 这些父进程中将依次完成以下任务: 首先, 他们会根据当前系统的运行状态动态地生成相应的虚拟化结构; 其次, 他们会根据当前系统的运行状态动态地生成相应的虚拟化结构; 最后, 他们将负责管理和回收所有与子进程相关联的内存资源.

    1. 若进程ID不为零,则仅查找该进程中对应该PID号的退出子进程;否则可以选择任何一个处于退出状态下的子进程中;
    1. 当被检定子进程中执行态不在Zombie态时,则将当前进程中执行态设为Sleeping(睡眠),并记录睡眠原因WT_CHILD(即等待子进程中结束),随后调用schedule函数选择新的中继进程启动执行;如果被唤醒,则返回步骤1继续;
    1. 当被检定子进程中执行态已变为Zombie态时,则表示该进程中已结束运行;此时需本进程中完成对其最终回收工作:首先从proc_list和hash_list两个队列中删除其控制块,并释放其内核栈及相关过程块;至此该进程中所占的所有资源均已释放完毕。

exit

该库在用户态实现了一个名为exit的功能,并通过调用SYS_exit系统调用接口协助操作系统完成进程退出时的部分资源回收目标。具体来说,在进程退出过程中,该功能不仅能够实现资源回收目标,并且确保程序在终止时能够清理其相关资源。

  • 当current->mm不为空时,则表示该内存块属于用户空间;此时应启动回收该用户进程占用的用户态虚拟内存空间。
  • 在此状态下,请将当前进程的状态字段current->state设为PROC_ZOMBIE,并将其退出码current->exit_code设定为error_code值。此时该过程已无法被调度运行,请由该过程的父进程负责完成最后阶段的回收工作(即释放该过程对应的内核栈以及相关进程中断单元)。
  • 当前父进程中若已处于等待子进程中状态(即parent_process->wait_state已被置位WT_CHILD),则应立即唤醒该父进程并指示其参与完成最终资源回收工作。
  • 若当前进程中仍有未处理的子进程中,则需先将这些剩余子进程中设置其父进程中指针字段值为内核线程init;同时需将这些子进程中指针依次追加到init所管理的子进程中链表中。特别地,在某些特殊情况下(如某个待回收子进程中状态标记为PROC_ZOMBIE),还需主动唤醒init线程以完成对其最终阶段资源的回收处理。
  • 通过调用schedule()调度函数,系统将选择新的任务进行执行。

据分析而言, 该函数的主要功能就是负责回收当前进程所占的绝大多数内存资源, 并同时通知父进程完成最终的内存回收任务。

在本实验中,与进程相关的各个系统调用属性如下所示:

系统调用名 含义 具体完成服务的函数
SYS_exit process exit do_exit
SYS_fork create child process, dup mm do_fork–>wakeup_proc
SYS_wait wait child process do_wait
SYS_exec after fork, process execute a new program load a program and refresh the mm
SYS_yield process flag itself need resecheduling proc->need_sched=1, then scheduler will rescheule this process
SYS_kill kill process do_kill–>proc->flags
SYS_getpid get the process’s pid

基于'用户环境'的约束条件限制了用户进程在执行指令时的能力范围, 因此在特殊指令(如特权指令)的操作上存在局限性. 为了克服这一挑战, 可以采用系统调用机制作为统一的服务接口, 这样不仅简化了用户的实现难度, 而且把与硬件操作相关的复杂工作以及与特权指令处理相关的繁琐流程转移到操作系统层面进行集中管理. 当一个系统函数被调用时, CPU会按照预先定义好的中断描述符切换到内核态, 并启动操作系统对系统的响应过程. 在此过程中, 系统会在进入操作系统的前一刻保存当前进程的状态信息, 包括当前线程的栈帧和相关寄存器值等关键数据. 接着操作系统会根据这些预先保存的信息完成具体的任务处理服务. 完成服务之后, 系统再通过IRET指令将CPU切换回用户态, 同时EIP指针也会指向被保留下来的tf_eip值位置. 这一完整的过程就实现了系统的功能服务传递.

实验截图

这里写图片描述

实验成功!!

收获

在本次实验中, 我掌握了用户进程管理的相关知识. 通过查阅教材和参考资料, 我对第一个用户进程创建过程有了基本的认识. 对系统调用框架的工作原理有了初步的理解, 并了解到可以通过系统调用sys_fork、sys_exec、sys_exit和sys_wait来支持多个应用程序的运行. 完成了对用户进程执行流程的基本管理. 在学习过程中也遇到了一些难以理解的地方. 对虚拟内存空间的概念仍不够清晰, 并不完全明白代码中tf->tf_cs = USER_CS等参数的作用. 此外,在中断处理方面的知识掌握程度还有待提高. 以后会更加重视中断相关知识的学习与深入理解.

全部评论 (0)

还没有任何评论哟~