• 操作系统lab3实验总结


    一、进程相关函数

    在做lab3的实验时,发现函数嵌套的情况很多,首先整理这一块的逻辑。

    箭头表示函数调用

    1.初始化:

    • 申请envs[]的空间;初始化env_free_list(把空闲进程env_status设置为ENV_FREE)

    2.创立进程:设置env_pri

    • env_alloc():
      • 从env_free_list取出一块空闲进程;
      • 设置env_id,env_status,env_parent_id,env_tf.cp0_status,env_tf.regs[29]
      • env_setup_vm():
        • 为进程创建一页页目录,并建立好自映射
        • 设置env_pgdir,env_cr3
    • load_icode():
      • 为进程申请一页作为栈,并建立好映射
      • 设置env_tf.pc(为load_elf返回的binary的入口)
      • load_elf()/load_icode_mapper():

        • 以一个段(segment)为单位,把binary(进程的内容的二进制镜像)的内容复制到所在内存的虚拟地址

        • (load_elf负责找入口,mapper负责copy)

    3.切换进程

    • 保存当前进程的上下文,设置env_tf,env_tf.pc
    • 恢复要启动的进程上下文,并启动新进程,设置env_status,env_pgdir,操作了env_tf,env_id

    二、进程控制块(PCB) 

    进程控制块(PCB) 是系统为了管理进程设置的一个专门的数据结构,用它来记录进程的外部特征,描述进程的运动变化过程。系统利用PCB 来控制和管理进程,所以 PCB 是系统感知进程存在的唯一标志 

    首先贴出PCB的构成。

     1 struct Env {
     2 /* Trapframe 结构体的定义在include/trap.h 中,
     3  * env_tf 的作用就是在进程因为时间片用光不再运行时,
     4  * 将其当时的进程上下文环境保存在env_tf 变量中。
     5  * 当从用户模式切换到内核模式时,内核也会保存进程上下文,
     6  * 因此进程返回时上下文可以从中恢复。
     7  */
     8 struct Trapframe env_tf; // Saved registers
     9 
    10 LIST_ENTRY(Env) env_link; // Free LIST_ENTRY 构造空闲进程链表。
    11 
    12 u_int env_id; // Unique environment identifier
    13 
    14 /*该变量存储了创建本进程的进程id。
    15  *这样进程之间通过父子进程之间的关联可以形成一颗进程树。
    16  */
    17 u_int env_parent_id; // env_id of this env's parent
    18 /*env_status : 该变量只能在以下三个值中进行取值:
    19   – ENV_FREE : 表明该进程是不活动的,即该进程控制块处于进程空闲链表中。
    20 
    21   – ENV_NOT_RUNNABLE : 表明该进程处于阻塞状态,处于该状态的进程往往在
    22     等待一定的条件才可以变为就绪状态从而被CPU 调度。
    23 
    24   – ENV_RUNNABLE : 表明该进程处于就绪状态,正在等待被调度,但处于RUNNABLE 
    25     状态的进程可以是正在运行的,也可能不在运行中。*/
    26 u_int env_status; // Status of the environment
    27 
    28 Pde *env_pgdir; // Kernel virtual address of page dir 这个变量保存了该进程页目录的虚拟地址。
    29 
    30 u_int env_cr3;// 这个变量保存了该进程页目录的物理地址。
    31 
    32 LIST_ENTRY(Env) env_sched_link;//这个变量来构造就绪状态进程链表。
    33 
    34 u_int env_pri;//这个变量保存了该进程的优先级。
    35 
    36 };
    PCB

     1.env_tf

    env_tf的类型是struct Trapframe,定义在trap.h中。

     1 struct Trapframe { //lr:need to be modified(reference to linux pt_regs) TODO
     2         /* Saved main processor registers. */
     3         unsigned long regs[32];
     4 
     5         /* Saved special registers. */
     6         unsigned long cp0_status;
     7         unsigned long hi;
     8         unsigned long lo;
     9         unsigned long cp0_badvaddr;
    10         unsigned long cp0_cause;
    11         unsigned long cp0_epc;
    12         unsigned long pc;
    13 };
    Trapframe

    regs[29]:通用寄存器中的29号是栈寄存器,在env_alloc()的时候设置为USTACKTOP(是用户栈,内核栈在0x8040 0000)

    我们回忆起在load_icode()也申请了一页作为栈映射到了USTACKTOP-BY2PG。刚好是regs[29]所在起始位置的下一页。

    所以在env_alloc()的时候调整了栈指针的位置为USTACKTOP,在env_icode()时为栈专门申请了一页的空间[USTACKTOP-BY2PG,USTACKTOP]。

    pc:程序计数器,用于存放下一条指令的地址

    上面的函数我们一共有两个地方用到了pc。

    第一处在env_alloc()

    e->env_tf.pc = entry_point;
    即将进程的起始地址移动到了binary的e_entry,可执行程序入口点地址。
    第二处在env_run()
    curenv->env_tf.pc = curenv->env_tf.cp0_epc;
    env_tf.cp0_epc存的是下一条指令的地址,则将下一个pc的地址保存了,回复这个进程的时候可以直接跳转到那个位置。
    cp0_status
    在env_alloc()中进行了这样的设置:
    e->env_tf.cp0_status = 0x10001004;
    指导书中提到“MIPSR3000 里的SR(status register) 寄存器就是我们在env_tf里的cp0_ status,R3000 的SR 寄存器的低六位是一个二重栈的结构。”
    二重栈在这个地方应该是指以大小2为单位的栈。所以实际上在中断发生和中断恢复时,会经历这样的倒腾。

    KUo 和IEo 是一组,每当中断发生的时候,硬件自动会将KUp 和IEp 的数值拷贝到这里;KUp 和IEp 是一组,当中断发生的时候,硬件会把KUc 和IEc 的数值拷贝到这里。其中KU 表示是否位于内核模式下,为1 表示位于内核模式下;IE 表示中断是否开启,为1 表示开启,否则不开启2。

    而每当rfe 指令调用的时候,就会进行上面操作的逆操作。---《指导书》

    我后来发现PPT里有,请跳过这部分。
    这一段没有关于KUo、IEo、KUp、IEp、KUc、IEc的解释,我估计是这样的,画图说明。

    下面这一段代码在运行第一个进程前是一定要执行的,所以就一定会执行rfe这条指令。

    lw k0,TF_STATUS(k0) # 恢复CP0_STATUS 寄存器

    mtc0 k0,CP0_STATUS

    j k1

    rfe

    KU:1--内核态,0--用户态;IE:1--开启中断,0--关闭中断。(这里应该是看KUc,IEc)
    rfe会发生类似于中断恢复的操作,往右移动。我们为了设置初始状态为000001b(进入用户态,开启中断),所以我们先设置为000100b,再触发ref指令,使之变成000001b。
    我们之前的设置是e->env_tf.cp0_status = 0x10001004;其中还设置了“第28bit 设置为1,表示处于用户模式下。第12bit 设置为1,表示4 号中断可以被响应。”

    2.lcontext(curenv->env_pgdir);

    curenv->env_pgdir是页目录的内核虚拟地址
    lontext 中有一句指令sw a0,mCONTEXT (a0是第一个参数即新进程的内核虚拟地址)

    在pmap.c中有这样一句mCONTEXT = (int)pgdir;是把全新的页目录kva存到mCONTEXT
    这里就是把curenv的页目录kva存到mCONTEXT,mCONTEXT除了第一次创建页目录的使用,还会在do_refill里使用,这里暂不了解。这里大概是为了开启一个全新的进程时需要创建进程的页目录,需要mCONTEXT。

    三、TIMESTACK

    这一部分不一定正确,欢迎指正!

    在mmu.h中定义了TIMESTACK是0x82000000,在dump中定义了KERNEL_SP是0x80017500(不是很确定,这个位置在Kernel Stack区域)。在stackframe.h中这样地定义了get_sp,当k1不是0的时候,置sp为TIMESTACK,否则1.sp在内核区地址不改变2.sp不在内核区跳转到KERNEL_SP。(内核区仅指ULIM以上)

    get_sp会在SAVE_ALL的时候调用,SAVE_ALL会把真正的sp存进TF_REG29,调用的get_sp是为了找到存TF寄存器的栈的位置。所以或许下面代码中k1就是在判断是否是时钟中断,而TIMESTACK的意义就是时钟中断的栈的位置。

    在env_destroy有这样一条语句

     

    如果当前进程被毁灭的话,在sched_yield选好的下一个进程跳转的时候需要保存的环境应该是KERNEL_STACK的环境,即当前进程被调度之前的环境。所以KERNEL_SP是系统调用后的环境,TIMESTACK是时钟中断后的环境。

     当我们使用sched.c中的函数切换进程时有两种可能:

    1.现在是第一个进程或者要切换进程和现在的进程一样

    2.不是第一个进程也和curenv不一样

    如果是第一种情况的话不需要把TIMESTACK中的环境存到curenv->tf,第二种情况的话需要做这个操作。env_pop_tf里把当前环境设置成取出的进程上一次结束的环境。

    在我们的extra中毁灭一个进程会再一次调度sched_yield,所以环境会正常更新。如果所有进程都被毁灭的话,恢复的环境是Kernel Stack的环境,即第一个进程被调度之前的环境。

    四、异常

    //未完待续,或许就鸽了

  • 相关阅读:
    51Nod
    [HDU-5172] 单点查询线段树
    HihoCoder
    CodeForces
    计蒜客-T1271 完美K倍子数组
    [CodeForces-629A 用阶乘会爆掉
    计蒜客-A1139 dfs
    Codeforces Global Round 7 D2. Prefix-Suffix Palindrome (Hard version)(Manacher算法+输出回文字符串)
    HDU
    操作系统习题——虚地址转换为内存地址计算
  • 原文地址:https://www.cnblogs.com/puublog/p/10707188.html
Copyright © 2020-2023  润新知