一、进程相关函数
在做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的构成。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 };
1.env_tf
env_tf的类型是struct Trapframe,定义在trap.h中。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 };
①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()
KUo 和IEo 是一组,每当中断发生的时候,硬件自动会将KUp 和IEp 的数值拷贝到这里;KUp 和IEp 是一组,当中断发生的时候,硬件会把KUc 和IEc 的数值拷贝到这里。其中KU 表示是否位于内核模式下,为1 表示位于内核模式下;IE 表示中断是否开启,为1 表示开启,否则不开启2。
而每当rfe 指令调用的时候,就会进行上面操作的逆操作。---《指导书》
![](https://img2018.cnblogs.com/blog/1615774/201904/1615774-20190415001837074-677574821.png)
下面这一段代码在运行第一个进程前是一定要执行的,所以就一定会执行rfe这条指令。
lw k0,TF_STATUS(k0) # 恢复CP0_STATUS 寄存器
mtc0 k0,CP0_STATUS
j k1
rfe
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的环境,即第一个进程被调度之前的环境。
四、异常
//未完待续,或许就鸽了