下面我们来介绍进程控制
在进程控制当中呢
我们会涉及这样几个问题
一个是在内核当中的进程切换
也就是说我一个进程
在运行过程当中
内核如何来实现它从一个进程
到另一个进程的切换
后面的三个呢
实际上为用户提供的系统调用服务
那是用户在执行它的应用的过程当中
它有需求要创建一个新的进程
好在这里头呢
我如何来创建一个新的进程
在这里头运行一个新的程序
那这个是进程加载
父进程创建子进程之后
它们俩之间呢
需要有一些协调的关系
你比如说子进程结束之后
父进程负责回收它所占用的资源
那这个时候
它们之间有一个通讯关系
这就是进程的等待和退出
下面我们一个一个的来
首先是进程切换
进程切换呢
有时候也称之为叫上下文切换
它是暂停当前运行进程
把这个进程呢
从运行状态变成其它状态
这里所谓的其它状态呢
可能会是由于进行IO操作
或者等待事件而进入等待状态
也可能是由于被抢先
又或者说时间片运行完
那转回到就绪状态
另一个是说
我把当前的进程停下来之后
那我会调度另一个进程
让它从就绪状态变成运行状态
也就是说我从就绪队列里
找一个新的进程然后恢复
并且让它继续运行
这是进程切换所要做的事情
好那具体说起来
有一些切换的时候
我们有一些什么要求呢
那在切换之前
需要去保存进程的上下文
也就是说
你下一个进程在执行过程当中
需要用到这些CPU的寄存器
状态寄存器
好那这些呢你需要先做保存
好切换到下一个进程运行的时候
在切换过来之后
下一个进程开始执行之前
我还要继续把它上一次
暂停时候的当时的
寄存器的状态恢复回来
好恢复回来之后
那它才能够从
上一次暂停的位置继续往下执行
还有一个要求是说
由于我们现在计算机系统里
进程的切换是非常频繁的
通常情况下是10毫秒左右
就会有一次进程切换
那这样以来的话
为了保证系统运行的效率
这个切换的速度必须非常快
所以通常情况下
它都是由汇编来实现的
那具体说起来我们在这里
要保存一些什么样的信息呢
实际上保存就是进程在整个
生命周期当中它所维护的信息
大致说起来呢
包括这样几个方面
寄存器
那我们在CPU里头通用寄存器
好CPU里头状态信息
然后就内存地址空间里的信息
那你说通常情况下我们
在切换的时候
内存里头的这些信息
是不会被另外一个进程
因为它各占一段区域
是不会被另一个进程所替代的
所以在这里头呢
那内存里的地址
空间内容大部分不用保存
下面我们通过一个图式
来说明进程的切换过程
那在这里头呢
我们假定系统当中有
P0和P1两个进程
它们的执行过程当中呢
先是P0处于用户态执行
那执行过程当中
P1处于空闲状态
那可能会是就绪
也可能会是等待
好那在这里头呢
执行的过程当中
碰着一个系统调用
或者是中断
假定我们这是时钟中断
那它的时间片用完了
到这儿来之后切换到内核
那切换过程当中
它需要保护现场到PCB0
也就进程零的进程控制块里头
把它的当时执行到状态
都保存下来
保存下来之后那这时候说
如果说时钟中断
时间片用完
那这时候我们需要选择
下一个就绪进程
假定这时候
你选择下一个进程是P1
好那到P1需要恢复P1现场
恢复完之后
那这时候呢
状态就已经切到进程P1了
好那这时候P1开始运行
继续运行
好运行到一段时间之后呢
假定说它的时间片又用完了
这时候又产生时钟中断
时钟中断又切回到内核状态
好那这时候它要保存
进程P1的现场
到它对应的进程控制块PCB1
好那在这里头我们假定你又决策
我需要选下一个进程
这时候又选P0
那这时候恢复P0的现场
那这时候从PCB0里头
这两个是对应的
PCB0里头恢复现场
好恢复进程状态之后
它切到进程0
然后继续执行
到这个地方呢
我们一个从进程零切换过去
再切换回来一个完整的过程
就在这儿展现出来了
跟它相关的呢
执行正常执行
然后这个地方呢
是保存现场到PCB0
从PCB0里恢复现场
好中间是PCB1
这边恢复执行到保存
那从这一过程来讲
它是比较清楚了
那这时候我们要在PCB里
记住一些什么信息了
记录进程的运行状态
好那状态呢是放这里头
然后我们把状态呢
它相同的状态
我放到一个队列里头
这时候维护出若干个队列
比如说就绪队列
它有一个起头
然后相应的进程串在一起
那可以用各种各样的办法
来形成这样一个队列
好如果是等待呢
我们就会把它分成
若干种不同类型的等待
比如说在这里头每个设备
等待的设备不同
那它有各自一个队列
比如这里列出来的
磁带01和磁盘0
好然后还有一类呢
就是处于这个
运行到退出的状态的
这时候它的收尾状态
这时候僵尸队列那它对应的
在这里头那处于这样一种状态
这是进程块里头
保存信息和它们如何来组织
好那实际上接下来
我们是说到底会有
一些什么样信息保存到
进程控制块里头呢
那不同的系统里
进程控制块呢是不一样的
比如说在我们 ucore和ucore plus里头
有一个数据结构叫PROC STRUCT
好在这个数据结构里头呢
它保存了进程的相关信息
大致的信息
我们可以把它分成这样几类
一类是进程的标识信息
比如说它的执行
是哪一个可执行文件
它的ID是多少
它的进程ID
好然后它的父进程是谁
好这是一类
然后再一类是进程的状态信息
比如说我们在这里头CPU里头
状态寄存器的相关信息
然后地址空间的起头的位置
第一级页表的起始地址在哪
然后进程的状态和它
是否允许调度等这样一些
执行状态的信息
还有一类呢是进程所占用的资源
比如说它占用的存储资源
那所有分配给
它存储那组织成
相关的数据结构MM
好那上边那个是
它占用的内核堆栈
还有一类呢
是我们保护现场用的
那在中断和进程切换的时候
它都需要保护现场
那这些现场的内容呢
我们刚才说是保存到
进程控制块里头
实际上就是指这个地方
好你两个进程需要复用的部分
就需要在这儿做保存
除此之外呢
这些进程它在
不同的时候处于不同状态
这些状态组成不同队列
那在这里还有几个
相关的指针结构
用于描述我当前进程
到底是在哪一个队列当中
好有了这些信息之后
那我们这个操作系统
就对进程的执行状态呢
有了一个准确的把握了
好我们在ucore里用到
还是比较简单的
对于实际的像Linux windows
那么这时候它的数据结构
比我们要多得多
这是进程控制块的数据结构
在ucore里的完整定义
在我们这里头呢
大家需要注意到的是
我们刚开始说的
这是进程的状态
进程的ID信息
进程ID 线程ID 组ID
然后是进程执行的相关信息
是否需要调度它的父进程是谁
然后这个地方是
进程的内存管理的数据结构
然后下边两个呢
是进程的现场保护的
上下文现场和中断保护现场
然后底下是CR3
CR3呢实际上是页表的起始地址
然后它的标志位
可执行文件的进程的名字
然后是进程的哈希表和链表
那这和我们前面讲的
这里的数据结构呢
是能够完全对得上的
我们在这儿呢是介绍的大致情况
在这里头呢实际上是完整的列表
希望同学下去之后有机会
把这个完整的列表呢
能够有一个基本的了解
好那在这里
我们需要特别说明呢
是内存地址空间的数据结构mm_struct
那在这个数据结构当中呢
我们关心的内容是说
第一个它到底有哪些内存块
内存逻辑地址空间里头
映射是在哪个地方
对应的地址空间是啥样的
好这是第一个
在这里呢
是它的第一级页表的起始地址
pde_t *pgdir
那这是地址空间的起头
然后说如果它有共享的话
那么它又共享了几次
然后如果说在这里头
有跟外存之间的置换
那这时候置换相关数据结构
好有了这些之后
那我们对进程的运行情况就了解了
这是内存管理的内存的数据结构
对应 到代码当中呢
是这个数据结构mm_struct
在这里头我们关注的几个内容呢
是它也会组织成映射的链表
然后这是页表的起始地址的指针
然后这是引用次数
和对mm数据结构进行写的时候
它相应的锁标志信号量
好然后再一个进程里头
ucore里头的进程队列
它怎么来组织呢
那在这里头呢
我们看到基本上是链表
你比如说在这里头
我们有双向 链表
然后说如果说
你这个链表很长
那这时候检索的开销是非常大的
所以在ucore里头又加了hash list
如果说这里链表长的话
先加一级hash队列
然后每一级队列里头呢
hash值相同的
再组成相应的自己的队列
那有了这些办法之后
我们就知道它在这里头
到底它的队列是如何来组织的
好那么具体的在ucore里头
它的一个切换过程是什么样子呢
不管由于什么原因
最后只要你导致切换
都会到schedule内核函数里头来
好到这个函数里头
它干些啥呢
它首先清楚调度标志
那我现在正在调度
不能再进行改调度 标志
然后我这儿呢
查找新的就绪进程
一种可能情况
就是查找完了之后我有可能
还是找回来还是我自己
那这种情况是存在的
好找着一个新的进程
然后说我修改进程的状态标志
那把前头那一个改成
是就绪或者是等待
然后把新那一个
改成是运行状态
好然后接下来进行切换
切换这儿有一个switch_to
这是最后切换的代码
好那在这里头
这个过程大致的流程就有了
好那么实际说起来我们看看这里头
这个图里说的呢
是ucore里头的函数调用关系
那不管是你由于
各种各样的情况出来之后呢
它最后都会到schedule这地方来
比如说在我们这里
是由于退出所过来的情况
好到这个schedule里头来了之后呢
它到这个地方去做选择
和这地方是在做切换
那到这个地方
最后到switch_to这一段汇编呢
它是完成切换的
而准确的切换代码
是什么样的呢
准确的切换代码是跟你平台相关的
每一个CPU平台上
它所需要保存的寄存器是不一样的
而为了保存的速度比较快
恢复速度比较快
那在这里头呢
我们所有切换代码switch_to()
都是用汇编来写成的
那大致的格局呢
是前半段保存切换过去之后
实际上改CR3
改完之后下半段恢复
然后就继续执行了
好这是进程切换的函数调用关系图
那对应到我们实际系统当中呢
这是我们的切换函数它的实现
那我们需要关注的问题
是在于这个地方在切换的时候
它会去改进程的状态
然后选择下一个被调度的进程
从调度队列当中把这个进程取出来
然后后面进行一系列的判断
那我们在这儿呢
这一部分内容呢
我们直接看关注的是它的调用图
那在这儿我们可以看到
schedule()这个函数它调用的地方呢
基本上是我们各种事件出现的时候
你比如说数据发送
然后这个后面会讲到信号量的触发
然后接收到事件
CPU处于空闲状态
发送消息等等一些信息
最后都会导致到我这schedule()
schedule()里呢我们刚才说到proc_run()
这是我们这里关键的函数
到这个地方下来之后有一个
switch_to()
在这个函数里头呢
实际上我们通过跳转
转到我们的汇编代码上了
这个汇编代码呢
就是我们这里的这一段
在这里头呢前面是保存
上一个进程的寄存器的工作
下边是从堆栈当中恢复
进入运行状态的进程的寄存器的值
恢复完毕之后然后返回
那就新的进程就开始继续运行了
那在刚才那个地方呢
switch实际上是通过这里头
这一句C语言的函数
最后跳转到刚才的
这个汇编代码当中来