Ucore lab5操作系统实验

LAB5实验报告


实验相关知识

(主要从教学ppt、gitbook、学堂在线上了解掌握并根据CSDN查询了解更加详细的信息)

在lab4中我们已经实现了内核线程的管理,本次实验我们将实现用户进程的管理,虽然操作系统对二者的管理体制是较为相似的,但是具体实现上还是有很大差别。基于系统的安全性和可扩展性,ucore要提供用户态进程的创建和执行机制,给应用程序执行提供一个用户态运行环境。

内核线程与用户进程的区别:

  • 内核线程只运行在内核态,用户进程会在在用户态和内核态交替运行
  • 所有内核线程共用ucore内核内存空间,不需为每个内核线程维护单独的内存空间;用户进程需要维护各自的用户内存空间

由于进程的执行空间扩展到了用户态空间,所以在进程管理和内存管理上有较大不同。

  • 内存管理方面,增加用户态虚拟内存的管理。限制用户进程可以访问的物理地址空间,且让各个用户进程之间的物理内存空间访问不重叠,这样可以保证不同用户进程之间不能相互破坏各自的内存空间,利用虚拟内存的功能(页换入换出),给用户进程提供了远大于实际物理内存空间的虚拟内存空间。具体实现时,对页表的内容进行扩展,能够把部分物理内存映射为用户态虚拟内存。如果某进程执行过程中,CPU在用户态下执行(在CS段寄存器最低两位包含有一个2位的优先级域,如果为0,表示CPU运行在特权态;如果为3,表示CPU运行在用户态。),则可以访问本进程页表描述的用户态虚拟内存,但由于权限不够,不能访问内核态虚拟内存。
  • 进程管理方面,主要涉及到的是进程控制块中与内存管理相关的部分,包括建立进程的页表和维护进程可访问空间(可能还没有建立虚实映射关系)的信息;加载一个ELF格式的程序到进程控制块管理的内存中的方法;在进程复制(fork)过程中,把父进程的内存空间拷贝到子进程内存空间的技术。另外一部分与用户态进程生命周期管理相关,包括让进程放弃CPU而睡眠等待某事件;让父进程等待子进程结束;一个进程杀死另一个进程;给进程发消息;建立进程的血缘关系链表。

在lab2的实验报告相关知识部分已经介绍了特权级以及相关转换,在此处再详述一下用户级向内核态的转换

通常操作系统会采用软中断或者叫做trap的方式完成。实际上,发生中断时已经实现了从用户态切换到内核态,为了实现这种切换,我们需要建立好中断门,中断门中的中断描述符表指出了中断发生后跳转至何处,并且发生中断时我们必须保存SS、ESP等信息。但是,中断会根据保存的这些信息返回到用户态中,为了实现停留在内核态,我们对CS进行修改,将其指向内核态的代码段,其次,我们将CS的CPL设为0,在此处还需要根据要执行的指令修改EIP,这样最后执行IRET指令时,CPU会将堆栈信息取出并返回到EIP以及CS所指内容去执行,从而便实现了从ring3到ring0的转换。
在这里插入图片描述
为了实现特权级的切换,实际上还需要访问TSS(Task State Segment)任务状态段。简单来说,任务状态段就是内存中的一个数据结构。这个结构中保存着和任务相关的信息。当发生任务切换的时候会把当前任务用到的寄存器内容(CS/ EIP/ DS/SS/EFLAGS…)保存在TSS 中以便任务切换回来时候继续使用。

为了访问TSS,还需要访问全局描述符表。全局描述符表(GDT)保存者TSS的地址,TSS最终会被加载进内存中。其中有一个Task Register 的cache缓存,最终通过基址加上偏移来确定Task所在的具体位置。

实验流程

练习0:填写已有实验

本实验依赖实验1/2/3/4。请把你做的实验1/2/3/4的代码填入本实验中代码中有“LAB1”/“LAB2”/“LAB3”/“LAB4”的注释相应部分。注意:为了能够正确执行lab5的测试应用程序,可能需对已完成的实验1/2/3/4的代码进行进一步改进.

回答虽然作业要求不需要在报告中写实验0,但是这次实验0涉及的方面挺多的,有很多改动,故必须说明。

(实际上每次实验最烦的就是实验0…浪费很长时间,并且经常出现bug -.- )

实验0主要将之前4次实验的代码补充进来,包括 kdebug.c、trap.c、default_pmm.c、pmm.c、swap_fifo.c、vmm.c、proc.c七个文件的相关代码。除了直接补充外,有一些地方需要对代码进行修改或者改进:

  • alloc_proc函数:

    在lab4的基础上,又增添了 wait_state ,proc->cptr , proc->optr , proc->yptr 四个变量,实际上只需要将wait_state初始化为0,三个指针初始化为NULL即可。避免之后由于未定义或未初始化导致管理用户进程时出现错误。

    // 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) 
        {
         
            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;//进程相关指针初始化  
        }
        return proc;
    }
    
  • do_fork函数:

    do_fork函数整体未较大改动,主要修改部分为将子进程的父进程设置为 current process ,并且确保current process 的 wait_state 为0,因此我们可以用一个assert()实现该功能。还有就是插入新进程到进程哈希表和进程链表时,设置好相关进程的链接。设置链接的函数为 set_links这里较为坑的地方在于set_links函数中已经实现了将进程插入链表并将进程总数加1,因此需要删掉lab4中这两句代码。

    修改的代码:

    	//设置父节点为当前进程
        proc->parent = current;
    	//确保当前进程正在等待
        assert(current->wait_state == 0);
    //------------------------------------
        local_intr_save(intr_flag);
        {
         
            proc->pid = get_pid();
            hash_proc(proc);   //将新进程加入hash_list
            // 删除原来的 nr_process++ 和 加入链表 
            set_links(proc);   //执行set_links函数,实现设置相关进程链接
        }
        local_intr_restore(intr_flag);
    
  • trap_dispatch函数:

    主要修改地方在于 当分配给进程的时间片用完时,设置进程为需要被调度

    		ticks ++;
            if (ticks % TICK_NUM == 0)
            {
         
                assert(current != NULL);
                //时间片用完设置为需要调度
                //说明当前进程的时间片已经用完了
                current->need_resched = 1;
            }
    
    
  • idt_init函数:

    增添功能为:设置一个特定中断号的中断门,专门用于用户进程访问系统调用。

    设置一个特殊的中断描述符idt[T_SYSCALL],它的特权级设置为DPL_USER,中断向量处理地址在__vectors[T_SYSCALL]处。这样建立好这个中断描述符后,一旦用户进程执行“INTT_SYSCALL”后,由于此中断允许用户态进程产生(注意它的特权级设置为DPL_USER),所以CPU就会从用户态切换到内核态,保存相关寄存器,并跳转到__vectors[T_SYSCALL]处开始执行

       
    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);
        }
         
  • 10
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值