进程

进程相关的数据结构

Posted by 邹盛富 on June 10, 2018

进程的概念

进程是具有独立功能的程序关于某个数据集合上的一次运行活动,进程是系统资源分配和调度的独立单位,又被称为任务

进程特点

  • 程序的一次执行过程
  • 正在运行的抽象程序
  • 将一个CPU变成多个虚拟的CPU
  • 每个进程有独立的地址空间
  • 将CPU控制权调度给进程

进程控制块(PCB)

操作系统用于管理进程的数据结构,主要包括进程的各种属性进程的动态变化,是操作系统感知进程存在的标志

所有进程对应的进程控制块组成进程表,放在内存中存储,一般进程的数量是确定的

进程控制块的组成

从四个方面来介绍进程控制块:

  • 进程描述信息
    • 进程标识符,唯一的
    • 进程名
    • 用户标识符
    • 进程组
  • 进程控制信息
    • 当前进程状态
    • 优先级
    • 代码执行入口
    • 程序的磁盘地址
    • 运行统计信息(执行时间、页面调度)
    • 进程间同步和通信
    • 进程的队列指针
    • 进程的的消息队列
  • 拥有的资源和使用情况
    • 虚拟地址空间的状况
    • 打开文件
  • CPU现场信息
    • 寄存器值(栈指针、通用寄存器、程序计数器、程序状态字)
    • 指向该进程的页表指针

进程状态

进程三种基本状态:

  • 运行态:占用CPU,并在CPU上运行
  • 就绪态:已经具备运行条件,由于没有空闲CPU暂时不能运行
  • 等待态(阻塞态、睡眠态):因为等待某一个事件而不能运行

三状态进程模型的状态之间的转化如下:

其他几种状态:

  • 创建态:已完成创建工作,比如PID、PCB的创建,但是没有执行该进程,可能是因为资源有限等原因
  • 终止态:进程执行结束之后进入该状态,可以完成一些数据统计工作并进行资源的回收
  • 挂起态:用于调节系统负载(比如操作系统中的进程数量太多时),与等待态不同,将进程的内存空间回收同时将其内容送到磁盘上

五状态进程模型的转化如下图:

七状态进程模型的转化如下图:

Linux进程状态转换如下图:

fork是创建了一个进程,进程变成就绪态,通过调度进程占用CPU,可能是等待某个事件,进程进入了睡眠态,当这个事件发生时,就又回到了就绪态,如果正在运行的进程时间片到了,进程就重新进入就绪队列,可能在运行过程中还会等其他的条件,就进入了另一种睡眠。 有深度睡眠浅度睡眠两种睡眠状态,浅度睡眠在睡眠过程中会接收信号,而深度睡眠的进程是不接收信号的。然后,正在运行的进程可能因为调试,设定了断点,所以处于暂停状态,然后又接着执行又进入就绪态,正在执行的进程如果结束,就进入终止态,把终止态称之为僵尸态

进程队列

操作系统中有很多的进程,每个进程的转态不同,为了方便管理这些进程,会根据进程的状态建立进程队列。进程队列的每一个元素就是PCB,伴随着进程的状态的改变,PCB从一个队列进入到另一个队列。

  • 就绪队列可以有多个
  • 等待态因为等待的原因不同,可以有多个队列

五状态进程队列模型

进程控制

进程控制原语主要分为以下几个:

  • 进程创建原语
  • 进程撤销原语
  • 阻塞原语
  • 唤醒原语
  • 挂起原语
  • 激活原语
  • 改变进程优先级

以上操作都是原子操作

进程创建原语

  1. 给每一个新的进程分配一个标识ID以及进程控制块
  2. 为进程分配它所需要的地址空间
  3. 初始化进程控制块,设置相应的内容的默认值
  4. 把它插入到相应的队列当中,设置相应的队列指针

进程撤销原语

  1. 回收进程占用的资源
    • 关闭打开文件、断开网络连接、回收分配内存
  2. 撤销该进程的PCB

进程阻塞

当被等待的时间未发生时,由进程自己执行阻塞原语,使进程由运行态变成阻塞态

UNIX中几个进程控制操作

  • fork是创建新的进程,它的创建过程是通过复制调用进程本身来创建,那么调用进程我们称之为父进程,也就是通过复制父进程,来创建子进程
  • exec是一个系列的系统调用,通过用一段新的程序来覆盖原来的地址空间,也就是子进程用一些新的程序代码,把父进程拷贝过来的内容给它覆盖掉
  • wait实际上是一个初级的一个同步操作,它能使得一个进程等待另外一个进程的结束
  • exit是用来终止一个进程的运行

UNIX中fork

  • 子进程分配一个空闲的进程描述符(PCB)
  • 分配了一个唯一的标识 pid
  • 分配子进程分配地址空间,以一次一页的方式把父进程的地址空间内容完全地拷贝给子进程
  • 从父进程那里继承各种共享资源,比如:打开的文件啊、当前工作目录等等
  • 子进程的状态设置为就绪态,并且把它插入到了就绪队列
  • 为子进程返回一个值0
  • 为父进程返回一个值,是子进程的pid

上述第三部中以一次一页的方式来复制父进程的地址空间,但是其实父进程的内存地址空间对于子进程来说是无意义的,因为子进程会接着执行 exec来把父进程拷贝过来的这些地址空间给覆盖掉,所以这个拷贝的过程花费了很多无用的时间和空间。在Linux中采用Copy-on-Write技术(写时复制技术)来避免这个问题,在使用写时复制技术时,只需要父进程把地址空间的指针传递给子进程,再把地址空间设置为只读,那么当子进程要往地址空间里写数据时。这个信号被操作系统接受,然后呢操作系统会为子进程单独再开辟一块空间,把相应的内容放进去,这样节省了之前复制父进程地址空间的时间,加快了fork的实现速度。