OS-lab3
OS-lab3
lab2之后,我们能够通过MMU访问内存了,不过操作系统最重要的是能够让进程运行。
include
-
env.h
定义了进程控制相关的变量,如进程数量
NENV
、进程状态ENV_FREE
等、进程控制块Env
、创建进程的宏函数ENV_CREATE
、ENV_CREATE_PRIORITY
等,以及一些在env.c中完成的函数。 -
trap.h
主要定义了用于保存异常现场的结构体
Trapframe
,以及方便取出数据的一些宏定义如TF_REG
、TF_EPC
等。 -
stackframe.h
利用汇编定义了一些处理异常时常用的操作,如开关中断的
STI
和CLI
,保存现场恢复现场的的SAVE_ALL
、RESTORE_ALL
、RESTORE_SOME
等,取出内核栈地址的get_sp
。
lib
-
env.c
首先是定义了一些全局变量,
envs
使进程控制块数组,curenv
指当前的进程,env_free_list
代表空闲的进程控制块,env_shed_list
指正在运行的进程队列,用于进行调度。mkenvid
用于给一个进程建立进程号。具体做法是将该进程的进程控制块偏移和一个静态变量next_env_id
结合起来。
envid2env
用于找出给定的进程号对应的进程控制块。首先判断这个进程号是否为0,即该进程是否为当前进程curenv
,若是则直接将curenv
赋给*penv
,若不是,则利用ENVX
在envs
中找出这个进程号对应的控制块;若这个进程状态为不可运行或env_id
不为指定的envid
则报错;检查checkperm
,如果被置位,则需要检查当前的进程curenv
是否能够操作找到的进程控制块,即检查这个进程是否为curenv
或curenv
的子进程,若不是则报错;将*penv
赋值并返回。上面两个函数是对进程进行处理过程中经常使用的函数。
env_init
函数用于初始化进程控制的一些变量。首先通过LIST_INIT
初始化env_sched_list
和env_free_list
然后将envs
中的控制块状态都设为不可运行,再反向插入env_free_list
中,这样才能够保证顺序。
env_setup_vm
函数用于初始化一个新进程的页表。首先分配一页存放页目录,并设置env_cr3
为页目录物理地址;然后将映射到UTOP
以下的页目录项清零,将UTOP
以上的复制为boot_pgdir
的值,这样在切换为内核态时就能直接使用这一片内存,不需要修改env_cr3
;最后将UVPT
项设置为env_cr3
和相应的标志位。
env_alloc
函数用于创建一个新进程。与分配内存类似,首先需要检查env_free_list
是否为空,然后获得一个空的进程控制块;使用env_setup_vm
给新进程设置页表;给进程控制块的成员赋值;将这个进程控制块从env_free_list
中删掉。上面的三个函数是用于初始化和创建进程的函数,核心是
env_alloc
。load_icode_mapper
函数用于将二进制文件加载到内存中,主要需要处理.text
段、.data
段和.bss
段。这两部分处理方式类似,区别在于
.bss
段需要用bzero
进行清零。具体流程则是用page_alloc
分配一页内存,用page_insert
加入到进程的页表中,使用bcopy
或bzero
对这一页内容进行操作。需要注意的是,这里使用的全部为虚拟地址。
load_icode
函数完成了将二进制文件加载到进程地址中并设置pc值的完整过程。首先是分配一页作为进程的用户栈;然后使用load_elf
加载二进制映像;最后设置env_tf.pc
,即进程开始执行的pc值。
env_create_priority
函数完成了完整的创建进程并加入运行队列的功能。首先是用env_alloc
分配一块空闲的进程;然后给env_pri
赋值;再用load_icode
加载二进制映像;最后使用LIST_INSERT_HEAD
把进程加入到env_sched_list
中。
env_create
函数直接调用了env_create_priority
函数,将优先级设为1。
env_run
函数用于切换当前进程为指定进程。首先是保存curenv
的运行现场,通过bcopy
将TIMESTACK
中存放的运行时信息复制到env_tf
中,并设置env_tf.pc
的值为env_tf.cp0_epc
;接着给curenv
赋值并增加env_runs
;然后调用lcontext
切换进程的地址;最后使用env_pop_tf
恢复准备执行的进程的上下文。到这里为止就完成了从分配一个空闲进程块到加载程序到切换运行的全部过程。
env_free
函数用于释放一个进程的空间。首先是找到UTOP
下已经映射的页目录项,使用page_remove
将这个页目录项对应的页表中所有的映射移除;然后将页目录项清零并减少引用;遍历结束后将页目录也清零并减少页目录所在页面的引用;最后修改进程状态并从env_sched_list
中移除加入到env_free_list
中。
env_destroy
函数用于杀死指定的进程并调度运行一个新进程。先用env_free
杀死进程,再判断如果这个进程为curenv
,则将KERNEL_SP
复制到TIMESTACK
中,执行内核的调度函数。上面这两个函数用于结束进程。
最后的
env_check
用于检查上述函数功能是否正确。 -
env_asm.S
定义了env.c中使用的两个函数
env_pop_tf
和lcontext
。env_pop_tf
用于恢复进程的执行现场。首先是恢复CP0_ENTRYHI
;接着设置CP0_STATUS
关闭全局中断;然后恢复通用寄存器;最后再恢复CP0_STATUS
。
lcontext
函数用于切换地址空间。就是将进程的页目录地址存入到mCONTEXT
中,这个变量专门用于存放页目录地址。 -
kclock.c
这个文件用于设置系统时钟。
-
kclock_asm.S
这里定义完成了设置时钟的函数
set_timer
。主要任务就是将特定的数值写入到时钟的地址,然后设置CP0_STATUS
。 -
sched.c
完成了调度函数
sched_yeild
,采用时间片轮转算法。首先取得当前调度队列的首个进程,判断这个进程是否为空或是否不可执行或时间片是否用完,若满足则需要进行调度;若进程不为空,则将这个进程从当前队列移动到另一个队列尾部;然后判断当前队列是否为空,若为空则需要切换队列,即修改point
;循环查找当前的队列,直到找到不为空且可运行的进程,将时间片的值设为优先级,然后进入env_run
切换执行。在查找过程中,需要注意队列空了之后需要切换到另一个队列继续这个查找,此外还需处理状态为不可运行和已释放的进程。
另外还修改了tools的链接文件,设置了异常处理地址;在start.S中增加了异常分发代码。
实验流程
在init.c中使用了ENV_CREATE
创建了两个进程,接着进入env_create
函数完成了创建进程并加入调度队列。在遇到时钟中断或需要切换的时候,首先会触发异常,跳转到start.S中检查触发异常的原因,即时钟中断,接着通过exception_handlers
载入处理这种中断的函数timer_irq
,接着转到sched_yeild
函数进行调度。