OS-lab3

OS-lab3

lab2之后,我们能够通过MMU访问内存了,不过操作系统最重要的是能够让进程运行。

include

  • env.h

    定义了进程控制相关的变量,如进程数量NENV、进程状态ENV_FREE等、进程控制块Env、创建进程的宏函数ENV_CREATEENV_CREATE_PRIORITY等,以及一些在env.c中完成的函数。

  • trap.h

    主要定义了用于保存异常现场的结构体Trapframe,以及方便取出数据的一些宏定义如TF_REGTF_EPC等。

  • stackframe.h

    利用汇编定义了一些处理异常时常用的操作,如开关中断的STICLI,保存现场恢复现场的的SAVE_ALLRESTORE_ALLRESTORE_SOME等,取出内核栈地址的get_sp

lib

  • env.c

    首先是定义了一些全局变量,envs使进程控制块数组,curenv指当前的进程,env_free_list代表空闲的进程控制块,env_shed_list指正在运行的进程队列,用于进行调度。

    mkenvid用于给一个进程建立进程号。具体做法是将该进程的进程控制块偏移和一个静态变量next_env_id结合起来。
    envid2env用于找出给定的进程号对应的进程控制块。首先判断这个进程号是否为0,即该进程是否为当前进程curenv,若是则直接将curenv赋给*penv,若不是,则利用ENVXenvs中找出这个进程号对应的控制块;若这个进程状态为不可运行或env_id不为指定的envid则报错;检查checkperm,如果被置位,则需要检查当前的进程curenv是否能够操作找到的进程控制块,即检查这个进程是否为curenvcurenv的子进程,若不是则报错;将*penv赋值并返回。

    上面两个函数是对进程进行处理过程中经常使用的函数。

    env_init函数用于初始化进程控制的一些变量。首先通过LIST_INIT初始化env_sched_listenv_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加入到进程的页表中,使用bcopybzero对这一页内容进行操作。需要注意的是,这里使用的全部为虚拟地址。
    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的运行现场,通过bcopyTIMESTACK中存放的运行时信息复制到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_tflcontext

    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函数进行调度。

posted @ 2020-06-23 17:43  p_wk  阅读(177)  评论(0编辑  收藏  举报