进程
最开始的计算机只能运行单道程序这是毋庸置疑的,但是随着计算机发展很显著的就会出现一个问题,如果我的计算机想要去运行多道程序,那么串行执行肯定是可以的一种方式,但是如果计算机想同时运行多道程序(并行执行多道程序),这就成为了一个必然的发展趋势和必须要解决的问题。
但是一开始计算机的资源是有限的,也就是可以理解为只有一个CPU,需要解决的问题就变成了如何通过一个CPU对多个程序实现并行?这时候从理解上就无法理解了,一个人处理一件事,n个人同时处理n件事这个并行的概念很容易去理解,但是一个人处理多件事,不就成了一心多用了吗?
但是显然CPU和人是不一样的,CPU有一个非常重要的特点,CPU运行的效率和速率都很高,对于CPU来说,任何的程序其实都是一个顺序的指令序列罢了,我只要按着这个执行就行,不管是跳转指令还是什么,我很快的执行就行了,如果以GUI程序好理解的角度来看,只要多个GUI在用户看来是并行的那就是并行的,是真并行还是假并行意义并不大。
当然真实的情况不是这个逻辑,但是也就是说在很短的单位时间内,如果通过某种合适的策略,让一个CPU在不同的程序之间不停的去切换,就会产生一种并行的错觉,但本质上其实是伪并行
这个很显然的明白了目的和手段,那么就要聚焦于策略是什么?策略往往一定是从个体单位上来分析和构建的,那么这里的问题就转换为了,伪并行策略应用的单位元素是什么?
所以提出了一种概念模型——顺序进程
进程模型
在计算机上所有可运行的软件,不管是属于操作系统的软件,还是用户的软件,都可以理解为一个正在执行的实例。
而这个**实例有自己的程序计数器(记录我这个程序的指令序列执行到了哪里),寄存器,变量,也就是存储空间,从这个角度,因为CPU的运行就需要这些因素,或者说描述CPU某一个瞬间的状态就是基于这些因素的,就可以理解为每个实例都有自己的虚拟CPU,而真实的物理CPU通过装入这些记录了状态的因素运行
,而这么一个个记录了CPU的状态的实例就是一个进程**
理解:
- 对于CPU而言其实无所谓执行哪个指令序列,所有的程序都是指令序列也就没有程序的概念了,从这个角度上来说程序是一个逻辑模型
- 每个进程存储了当时CPU运行的状态,而实际上物理CPU只有
运行
这么一个状态,不管是阻塞还是就绪其实都是虚拟CPU的状态,对于物理CPU来说这个写状态的区别只是一个switch(signal) 的区别,因为CPU本身是不停的运行的。阻塞不是物理的CPU阻塞掉了,而是逻辑上的CPU某种条件不被满足而被阻塞掉了,如果要让进程继续运行,就必须把这个没有满足的条件满足,才能得到一个结果,从阻塞转到就绪态- 抽象出物理CPU和逻辑CPU的概念,进程从就绪到运行只是需要CPU时间片的概念就比较好理解了,其实就是向物理CPU注入了对应数据
- 另一个证明这个观点的就是:当进程进行切换的时候,会存储物理程序计数器的数据,在运行前会注入记录程序技术器的数据,同时类似一系列的操作统一叫做恢复上下文
总之:进程就是存储了CPU某一时间状态的实例
在概念上其实也可以将进程和进程上运行的程序进行一个分离区分,进程是一个CPU运行的状态,是包含同时也隔离运行的程序的。
创建进程
抽象在什么情况下就需要(可以)创建进程?
- 系统初始化
- 操作系统启动的时候不仅会启动一些可视的进程,也会启动一些不可视的进程,也就是类似后台运行的概念,比如说一个邮件APP会启动一个后台进程去维护接受邮件的操作,当邮件来临,也能主动做出接收并提醒用户。
- 这些程序平时往往都是睡眠的状态,当需要的时候就会被唤醒然后进行处理
- 类似这样的程序也叫做守护进程
- 程序通过调用系统调用进行进程的创建
- 程序通过创建进程来辅助自己的工作
- 用户请求创建进程
- 例如用户点击图形界面上的快捷方式启动一个程序时会创建进程
- 批处理作业的初始化
- 仅在大型机的批处理系统中应用
- 当系统存在可以运行下一个作业的资源时,就创建一个下一个作业的进程
上面的这些情况再抽象一下,创建进程的本质就是:由一个已存在的进程执行一个用于创建进程的系统调用
创建进程的系统调用:
- UNIX
- fork(只有一个)
- 1)创建一个与调用进程相同(相同的存储映像,环境字符串,同样打开的文件)的副本
- 2)执行execve或者类似的一个系统调用,修改存储映像并运行生成一个新进程
- 从调用系统调用的进程孪生出子进程,
- fork(只有一个)
- win32(还有100多个用于进程管理相关的系统调用)
- CreatProcess
- 和fork不同它不仅负责创建进程,还负责程序和进程的关联(程序的装入)
- 调用参数:
- 执行的程序
- 执行程序的命令行参数
- 各种安全属性
- 打开的文件是否继承的控制位
- 优先级信息
- (若有的话)所需要创建的窗口规格
- 指向一个结构的指针
- 然后会将创建好的这个进程的信息返回给调用者
- CreatProcess
- 这里有个关键:其实可以从不管是UNIX还是win32在创建进程的角度上去看,其实就是填充好进程需要的信息,而区分子进程和父进程是两个不同进程的判别标准就是:两个不同的地址空间,两种系统下的创建方式都会保证这一点
进程终止
什么情况下会造成进程终止:
以被终止的进程为参照对象,终止操作可以分为:
- 主动终止
- 正常退出:工作已经完成
- 程序的指令序列运行完并删除了临时文件等等的操作之后,通过系统调用告诉系统工作已经完成,然后修改对应的进程的控制位
- UNIX是exit系统调用
- windows是ExitProcess系统调用
- 出异常退出:
- 可能是因为程序本身有问题无法找到或者其他的一些异常引发程序的退出
- 正常退出:工作已经完成
- 被动终止
- 严重错误
- 比如说非法指令,引用了不存在的内存,或者除数是0,等等程序无法处理的错误
- 当遇到这些问题的时候,进程可以通知操作系统自行处理某些类型的错误,这个时候进程会**收到信号(被中断信号),而不是立马终止**
- 被其他进程杀死
- 通过一些系统调用或者函数被系统授权,用于通知系统杀死某个进程
- 严重错误
层次结构
进程的层次结构并不复杂,因为本身上不会有太多的依赖关系,但是同样也可以创建进程组(比如说发送一个信号同时发给一系列的进程)
父进程和子进程之间也不会有太大的关联,所有进程的地位相同,当然也会提供一些操作(令牌,也叫句柄,可以在进程间传递)给父进程,让父进程可以控制子进程。
进程状态
- 运行态:
- 进程独占CPU
- 就绪态:
- 可以运行,但是因为有别的进程正在使用CPU而无法运行
- 阻塞态:
- 在逻辑上不能运行,即使CPU空闲也不能运行,必须发生外部事件,才能恢复为就绪态
进程调度程序:
- 进程调度程序是操作系统的一部分,也是一个进程,主要作用的位置是2<–>3之间的转换
- 进程调度程序主要的作用就是公平的决定**运行哪个进程,何时运行,运行多长事件**
- 形成清晰的组织形式:
进程管理实现
操作系统会维护一张表格,从结构上讲就是一个结构数组,也叫做进程表,每个进程占用一个表项,这些表项也成为**程序控制块**,顾名思义就是记录程序相关控制关键信息的内容:
再次回顾进程本身记录的就是CPU运行的一些状态,又因为进程本身是实体和操作系统或者其他进程交互会有一些相应的信息的产生,在这里面处于关键的判断信息等被记录下来,就可以控制这个进程的一些行为,而这些信息的一个整体就是**程序控制块**
通过这些信息可以更好的解释,系统是如何处理中断的?
和每一个IO类关联的行目叫做中断向量,它往往存储在内存底部的固定区域:
- 包含中断服务程序的入口地址
- 假如中断在程序3中发生时,中断硬件就会把程序计数器,程序状态字,一个或多个寄存器压入堆栈
- CPU随即跳转到中断向量指向的内存地址,上面的这些操作由硬件完成,然后软件,特别是中断服务例程就接管了一切剩余的工作
- 中断服务例程结束之后,它会调用一个C过程处理某个特定的中断类型剩下的工作
- 完成有关工作之后就使某些进程就绪,接着调用调度程序,决定随后该运行哪个进程
- 然后将控制转给一段汇编代码,为当前的进程装入寄存器值以及内存映射并启动该进程运行
- 当该进程结束时,操作系统显示一个提示符并等待新的命令。一旦它接到新命令,就装入新的程序进内 存,覆盖前一个程序。
多道程序设计模型
内存不是无限的,当然线程间也不是无限切换的,即使是无限切换的,也存在相应的效率问题,通过针对多道程序的专门设计,可以一定程度上的提高CPU的利用率。
总结出来就是:
- 增加内存并不一定能改善CPU的利用率
- 如果要考虑系统的性能就要考虑如何能尽可能的提高CPU的利用率