进程的概念
进程是具有独立功能的程序关于某个数据集合上的一次运行活动,进程是系统资源分配和调度的独立单位,又被称为任务
进程特点
- 程序的一次执行过程
- 正在运行的抽象程序
- 将一个CPU变成多个虚拟的CPU
- 每个进程有独立的地址空间
- 将CPU控制权调度给进程
进程控制块(PCB)
操作系统用于管理进程的数据结构,主要包括进程的各种属性和进程的动态变化,是操作系统感知进程存在的标志
所有进程对应的进程控制块组成进程表,放在内存中存储,一般进程的数量是确定的
进程控制块的组成
从四个方面来介绍进程控制块:
- 进程描述信息
- 进程标识符,唯一的
- 进程名
- 用户标识符
- 进程组
- 进程控制信息
- 当前进程状态
- 优先级
- 代码执行入口
- 程序的磁盘地址
- 运行统计信息(执行时间、页面调度)
- 进程间同步和通信
- 进程的队列指针
- 进程的的消息队列
- 拥有的资源和使用情况
- 虚拟地址空间的状况
- 打开文件
- CPU现场信息
- 寄存器值(栈指针、通用寄存器、程序计数器、程序状态字)
- 指向该进程的页表指针
进程状态
进程三种基本状态:
- 运行态:占用CPU,并在CPU上运行
- 就绪态:已经具备运行条件,由于没有空闲CPU暂时不能运行
- 等待态(阻塞态、睡眠态):因为等待某一个事件而不能运行
三状态进程模型的状态之间的转化如下:
其他几种状态:
- 创建态:已完成创建工作,比如PID、PCB的创建,但是没有执行该进程,可能是因为资源有限等原因
- 终止态:进程执行结束之后进入该状态,可以完成一些数据统计工作并进行资源的回收
- 挂起态:用于调节系统负载(比如操作系统中的进程数量太多时),与等待态不同,将进程的内存空间回收同时将其内容送到磁盘上
五状态进程模型的转化如下图:
七状态进程模型的转化如下图:
Linux进程状态转换如下图:
fork是创建了一个进程,进程变成就绪态,通过调度进程占用CPU,可能是等待某个事件,进程进入了睡眠态,当这个事件发生时,就又回到了就绪态,如果正在运行的进程时间片到了,进程就重新进入就绪队列,可能在运行过程中还会等其他的条件,就进入了另一种睡眠。 有深度睡眠和浅度睡眠两种睡眠状态,浅度睡眠在睡眠过程中会接收信号,而深度睡眠的进程是不接收信号的。然后,正在运行的进程可能因为调试,设定了断点,所以处于暂停状态,然后又接着执行又进入就绪态,正在执行的进程如果结束,就进入终止态,把终止态称之为僵尸态。
进程队列
操作系统中有很多的进程,每个进程的转态不同,为了方便管理这些进程,会根据进程的状态建立进程队列。进程队列的每一个元素就是PCB,伴随着进程的状态的改变,PCB从一个队列进入到另一个队列。
- 就绪队列可以有多个
- 等待态因为等待的原因不同,可以有多个队列
五状态进程队列模型
进程控制
进程控制原语主要分为以下几个:
- 进程创建原语
- 进程撤销原语
- 阻塞原语
- 唤醒原语
- 挂起原语
- 激活原语
- 改变进程优先级
以上操作都是原子操作
进程创建原语
- 给每一个新的进程分配一个标识ID以及进程控制块
- 为进程分配它所需要的地址空间
- 初始化进程控制块,设置相应的内容的默认值
- 把它插入到相应的队列当中,设置相应的队列指针
进程撤销原语
- 回收进程占用的资源
- 关闭打开文件、断开网络连接、回收分配内存
- 撤销该进程的PCB
进程阻塞
当被等待的时间未发生时,由进程自己执行阻塞原语,使进程由运行态变成阻塞态
UNIX中几个进程控制操作
- fork是创建新的进程,它的创建过程是通过复制调用进程本身来创建,那么调用进程我们称之为父进程,也就是通过复制父进程,来创建子进程
- exec是一个系列的系统调用,通过用一段新的程序来覆盖原来的地址空间,也就是子进程用一些新的程序代码,把父进程拷贝过来的内容给它覆盖掉
- wait实际上是一个初级的一个同步操作,它能使得一个进程等待另外一个进程的结束
- exit是用来终止一个进程的运行
UNIX中fork
- 子进程分配一个空闲的进程描述符(PCB)
- 分配了一个唯一的标识 pid
- 分配子进程分配地址空间,以一次一页的方式把父进程的地址空间内容完全地拷贝给子进程
- 从父进程那里继承各种共享资源,比如:打开的文件啊、当前工作目录等等
- 子进程的状态设置为就绪态,并且把它插入到了就绪队列
- 为子进程返回一个值0
- 为父进程返回一个值,是子进程的pid
上述第三部中以一次一页的方式来复制父进程的地址空间,但是其实父进程的内存地址空间对于子进程来说是无意义的,因为子进程会接着执行 exec来把父进程拷贝过来的这些地址空间给覆盖掉,所以这个拷贝的过程花费了很多无用的时间和空间。在Linux中采用Copy-on-Write技术(写时复制技术)来避免这个问题,在使用写时复制技术时,只需要父进程把地址空间的指针传递给子进程,再把地址空间设置为只读,那么当子进程要往地址空间里写数据时。这个信号被操作系统接受,然后呢操作系统会为子进程单独再开辟一块空间,把相应的内容放进去,这样节省了之前复制父进程地址空间的时间,加快了fork的实现速度。