lab5用户进程管理实现

不同的进程有各自的页表,所以即使不同进程的用户态虚拟地址相同,但由于页表把虚拟页映射到了不同的物理页帧,所以不同进程的虚拟内存空间是被隔离开的,相互之间无法直接访问。
ucore把用户进程的虚拟地址空间分了两块,一块与内核线程一样,是所有用户进程都共享的内核虚拟地址空间,映射到同样的物理内存空间中,这样在物理内存中只需放置一份内核代码,使得用户进程从用户态进入核心态时,内核代码可以统一应对不同的内核程序;另外一块是用户虚拟地址空间,虽然虚拟地址范围一样,但映射到不同且没有交集的物理内存空间中。

引入进程前后虚拟内存布局

引入进程前的内核虚拟内存布局
在这里插入图片描述
0xC0000000——0xF8000000:对实际物理空间的映射,(只是相差0xC0000000的一个差值映射,建立内核二级页表时使用到了)
0xFAC00000——0xFB000000:页表的建立,采取页目录表自映射机制,一级页表与二级页表都在此,管理整个内核空间的一个映射关系,引入进程以后,页表需要扩展,需要增加管理用户态的空间

引入进程后,进程的虚拟内存布局,就是上图的0xC0000000以下的低地址空间
在这里插入图片描述
0x00800000——0xB0000000:用户空间的堆栈,代码段,数据段和堆
0x00200000——0x00800000:放的是应用程序的调试信息

用户进程是如何创建执行的?

为什么要创建用户进程?(将应用程序都运行在内核会有什么问题)
如果把应用软件都作为内核线程来执行,那系统的安全性就无法得到保证了。

第一个用户进程创建过程
第一个用户进程是由第二个内核线程initproc通过把hello应用程序执行码覆盖到initproc的用户虚拟内存空间来创建的
do_execve函数将initproc所占内存清空:
1、将当前进程页表指向内核页表
2、将当前进程的内存管理区域清空
load_icode函数向上述清空的进程壳中填写新内容:
1、重新创建进程的内存管理空间
2、建立一个新的页目录表,并将内核页表拷贝到此新页目录表中
3、填上执行代码的内容(执行代码hello world已经被bootloader加载ucore的时候一起加载到内存中来了,所以只需要获取它在内存中的起始地址即可)拷贝执行代码到新创建的进程空间中并建立相应的页表映射关系
4、建立一个用户栈及页表映射关系
5、将页表的起始地址从内核页表的起始地址转移到我们新建好的页表起始地址。至此用户空间就已经创建完毕。
因为需要让用户进程到用户态去执行,所以涉及到特权级的切换,从特权级0切换到特权级3
如何构造一个trapframe使得用户程序可以从内核态跳到用户态去执行?
进程中有两个栈,一个是内核栈,一个是用户栈,这样可以确保在内核中运行时用的是内核栈,在用户中运行时用的是用户栈
其实就是将内核栈中的ss,esp设置为用户栈地址,然后将cs和eip设置为用户态程序的起始地址,这样程序就会跳到用户态去执行了

有了第一个用户进程以后,其他的进程怎么用这个进程创建出来
父进程通过fork()复制出一个子进程,
具体过程:
1、创建子进程的进程控制块
2、建立子进程的内核栈
3、为子进程创建一个新的虚存空间,将父进程的代码段数据段复制到子进程并建立映射关系(关系到页表的创建与修改),之后子进程有自己的mm_struct及自己的页表
4、拷贝父进程的trapframe到子进程,并设置好子进程的内核栈地址(esp),trapframe中主要保存一些寄存器的信息,例如需要放入堆栈的cs,eip,eflags,ss,esp等等
5、将新fork()的子进程挂到进程队列上,挂到进程队列上就表明可以被调度执行了。唤醒子进程,子进程进入running态,可以去执行了
6、父进程返回子进程的pid,而子进程返回的他自己的eax被设置为0

进程是如何退出的?如何等待的?

子进程本身与父进程一起完成子进程的资源回收工作。
当进程执行完它的工作后,就需要执行退出操作,释放进程占用的资源。ucore分了两步来完成这个工作,首先由进程本身完成大部分资源的占用内存回收工作,然后由此进程的父进程完成剩余资源占用内存的回收工作。为何不让进程本身完成所有的资源回收工作呢?这是因为进程要执行回收操作,就表明此进程还存在,还在执行指令,这就需要内核栈的空间不能释放,且表示进程存在的进程控制块不能释放。所以需要父进程来帮忙释放子进程无法完成的这两个资源回收工作。

进程如何退出?
通过系统调用exit进行进程退出工作。
1、current->mm != NULL,表示是用户进程,则开始回收此用户进程所占用的用户态虚拟内存空间;
将当前进程页表切换到内核页表,判断当前进程的内存空间是否被共享,若没有共享了, 则开始回收其内存空间,
2、设置当前进程的执行状态为僵尸状态current->state=PROC_ZOMBIE,此时当前进程已经不能被调度了,需要此进程的父进程来做最后的回收工作(即回收描述此进程的内核栈和进程控制块);等着父进程调用wait
3、如果当前进程的父进程current->parent处于等待子进程状态:则唤醒父进程(即执行“wakup_proc(current->parent)”),让父进程帮助自己完成最后的资源回收;
4、如果当前进程还有子进程,则需要把这些子进程的父进程指针设置为内核线程initproc,且各个子进程指针需要插入到initproc的子进程链表中。如果某个子进程的执行状态是PROC_ZOMBIE,则需要唤醒initproc来完成对此子进程的最后回收工作。
5、执行schedule()函数,选择新的进程执行。

进程如何等待?
那么父进程如何完成对子进程的最后回收工作呢?这要求父进程要执行wait用户函数或wait_pid用户函数,这两个函数的区别是,wait函数等待任意子进程的结束通知,而wait_pid函数等待进程id号为pid的子进程结束通知。这两个函数最终访问sys_wait系统调用接口让ucore来完成对子进程的最后回收工作,即回收子进程的内核栈和进程控制块所占内存空间
具体流程:

  1. 如果pid!=0,表示只找一个进程id号为pid的退出状态的子进程,否则找任意一个处于退出状态的子进程;
  2. 如果此子进程的执行状态不为PROC_ZOMBIE,表明此子进程还没有退出,则当前进程只好设置自己的执行状态为PROC_SLEEPING,睡眠原因为WT_CHILD(即等待子进程退出),调用schedule()函数选择新的进程执行,自己睡眠等待,如果被唤醒,则重复跳回步骤1处执行;
  3. 如果此子进程的执行状态为PROC_ZOMBIE,表明此子进程处于退出状态,需要当前进程(即子进程的父进程)完成对子进程的最终回收工作,即首先把子进程控制块从两个进程队列proc_list和hash_list中删除,并释放子进程的内核堆栈和进程控制块。自此,子进程才彻底地结束了它的执行过程,消除了它所占用的所有资源。

系统调用

操作系统为什么需要实现系统调用呢?其实这是实现了用户进程后,自然引申出来需要实现的操作系统功能。用户进程只能在操作系统给它圈定好的“用户环境”中执行,但“用户环境”限制了用户进程能够执行的指令,即用户进程只能执行一般的指令,无法执行特权指令。如果用户进程想执行一些需要特权指令的任务,比如通过网卡发网络包等,只能让操作系统来代劳了。于是就需要一种机制来确保用户进程不能执行特权指令,但能够请操作系统“帮忙”完成需要特权指令的任务,这种机制就是系统调用。

系统调用名 含义 具体完成服务的函数
SYS_exit process exit do_exit
SYS_fork create child process, dup mm do_fork–>wakeup_proc
SYS_wait wait child process do_wait
SYS_exec after fork, process execute a new program load a program and refresh the mm
SYS_yield process flag itself need resecheduling proc->need_sched=1, then scheduler will rescheule this process
SYS_kill kill process do_kill–>proc->flags |= PF_EXITING, -->wakeup_proc–>do_wait–>do_exit
SYS_getpid get the process’s pid

至此,实验五中的主要工作完毕。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值