进程管理

473 阅读12分钟

导语:进程管理是操作系统的核心。本文的重点是理解和掌握进程的概念以及与线程的区别联系。

进程的概念

  • 进程是对一个计算任务的抽象和封装,使得每个计算任务更好地实现隔离性,资源共享和同步的需求。
  • 进程是计算机程序在处理器上执行时所发生的活动,即进程是程序的一次执行活动。

为了更好地说明进程的概念,下面从进程的结构和行为两个方面进行说明

进程的结构

进程是程序执行的一个实例。一个程序的执行不仅仅依赖于该程序本身,而且还需要一个运行环境,即上下文,而进程就是描述这个上下文的一个概念。每一个程序运行在一个进程上下文中。 进程上下文包括五个部分:

  • 用户程序 (私有 , 将被执行的程序,这部分通常是只读的,不可更改)
  • 用户数据 (私有 , 包括程序数据,用户堆栈区和可修改的程序)
  • 系统栈
  • 进程控制块(PCB) (私有, 操作系统控制进程所需要的各种数据信息)
  • 共享区域

用户数据是与用户程序相关联的数据单元。
比如程序中的全局变量和 static 变量等存储在用户数据区,
而局部变量,过程调用的参数,返回值,返回地址等通常存储在用户栈区,
程序运行过程中动态分配的内存存储在用户堆区。
系统栈用于用户程序调用系统调用时,保存调用参数,返回值和返回地址
共享区域用于一个进程与其他进程共享代码,数据和内存

进程的状态

进程作为一个活动体,具有从产生到消亡的生命周期。在生命周期中经历了各个阶段,每个阶段称为进程的一个状态。

五状态模型

img5.imgtn.bdimg.com/it/u=136627…
五状态模型把进程的生命周期划分为五个状态,其中 运行态, 阻塞态 , 就绪态 是三个基本状态。

  1. 运行态(Running): 进程正在被处理器执行的状态。如果计算机只有一个处理器,那么一次最多只有一个进程处于运行状态。
  2. 阻塞态(Blocked): 一个进程正在等待某个事件发生的状态,这个事件可能是 I/O设备就绪等。
  3. 就绪态(Ready): 一个作业已经具有被调度执行的条件,正在排队等待调度的状态。显然,调度只能发生在那些处于就绪的进程中。
    note:在多道程序设计环境下,可能又多个进程都处于就绪态,那么调度程序需要按照某个调度策略,比如按照进程的优先级,选择一个优先级最高的就绪进程投入运行。

此外,为了描述进程的创建和消亡,还需要认定 "新建" 和 "退出" 状态。

  1. 新建: 当一个进程刚被创建时,操作系统还没有将它加入到可执行进程组。处于新建状态的进程,通常进程控制块已经创建,但是其程序和数据还没有被加载到内存中。
  2. 退出: 一个进程因为某种原因从可执行进程组中被释放出来的状态。注意,退出状态不等于进程被操作系统删除的状态。退出仅仅意味着进程不再被执行,但是与进程相关的表和其他信息仍然在操作系统中,这给辅助程序或支持程序提供了提取所需要信息的时间,一旦这些程序提取了所需信息,操作系统就不再保留与该程序相关的数据。

实现原理

五状态模型在操作系统中是通过进程队列来实现的。
为了管理系统中的多个进程,设计了两个队列:

  • 就绪队列
  • 阻塞队列

操作系统只能在就绪队列中选择需要调度的进程。当进程阻塞时,将被加入到阻塞队列,当某事件发生时,操作系统在阻塞队列中寻找正在等待该事件的进程,如果能找到,将其从阻塞队列加入就绪队列,等待调度。
由于阻塞队列中的进程所等待的事件可能有所不同,为了提高效率,通常可以设计多个阻塞队列,每个阻塞队列等待一个事件。这样当一个事件发生时,等待这个事件的阻塞队列中所有进程都将被加入到就绪队列。

七状态模型

img1.imgtn.bdimg.com/it/u=189500…

  1. 挂起状态(Suspended)
    在五状态模型中,当一个进程被允许进入,该进程就被完全载入内存,所有进程必须驻留在内存中。由于 I/O活动的计算速度比处理器的运算速度慢很多,因此所有进程都处于阻塞状态的情况比较常见。这种情况下,由于这些进程占用了大量内存,使得新进程的进入请求无法得到满足。

为了解决这个问题,很多操作系统采取了 "交换策略" , 即当内存中没有处于就绪的进程时,操作系统就把处于阻塞态的进程从内存中换出到磁盘中,并加入到 "挂起队列"
当一个进程被交换到磁盘中,我们把它的状态称为 "挂起" 状态。使用挂起状态可以区分进程是在内存中还是在磁盘中。挂起状态与原来的就绪和阻塞状态一经组合,就出现了四种可能的状态:

  • 就绪态 : 进程在内存中并可以执行
  • 阻塞态 : 进程在内存中并等待一个事件
  • 阻塞挂起 : 进程在外存中并等待一个事件
  • 就绪挂起 : 进程在外存中, 但是只要被载入内存就可以被调度执行

进程调度策略

进程调度也称为 处理器调度,是指为了满足特定系统目标(如响应时间,处理器效率),操作系统把进程分派到一个或者多个处理器中执行的过程。
进程调度是操作系统的核心功能之一 , 通过调度算法来实现。

FCFS策略

FCFS策略也称为先进先出方案。当一个进程就绪后,就被加入就绪队列。当前正在运行的进程停止执行后,选择就绪队列中存在时间最长的进程来运行。FCFS策略采用非抢占方式。因此当一个短进程紧随一个长进程到达时,短进程必须等待较长时间。因此,FCFS策略对于长进程的执行更有利。

时间片轮转

为了缓解FCFS策略对短作业不利的情况,一种简单的办法是采用基于时钟的抢占策略,这类方法中,最简单的是时间片轮转。使用时钟产生周期性中断,当中断发生时,当前正在运行的进程被置于就绪状态,然后基于FCFS策略从就绪队列中选择下一个进程运行。当连续中断的时间间隔被称为时间片。一个有用的思想是时隙最好大于一次典型的交互所需要的时间。

线程

本节主要讲述线程的概念以及进程的区别和联系。

线程概念和引入

线程就是对程序执行流的一个抽象,而进程是这个执行流所依赖的一个上下文。实际上,在一个进程中,可能有一个或者多个线程,每个线程也具有特定的结构。

线程的结构中包括如下信息:

  • 线程控制块 : 与进程控制块类似, 包含了描述线程属性的信息,如线程 ID,线程的栈指针,程序计数器,条件码和通用寄存器的值等,以及线程的执行状态(运行,就绪等)。
  • 线程执行栈 : 保存一个线程执行过程中的活动记录,包括用户栈和内存栈。
  • 线程局部存储(TLS ,Thread Local Storage) : 某些操作系统为线程单独提供的私有空间,用于存储每个线程私有的全局变量,即一个线程内部的各个过程调用都能访问.但其他线程不能访问的变量。

线程与进程的关系

线程概念的引入使得资源分配的单位和调度执行的单位发生了分离。线程的执行离不开进程地址空间,在一个进程地址空间中可以执行一个或多个线程。

  • 传统的 unix : 一个进程中只有一个线程, 或者说一个线程就是一个进程
  • windows , Linux : 可以在一个进程中创建和执行多个线程, 所以这些线程共享进程用户地址空间
  • RS(Clouds) : 一个线程可以从一个进程环境迁移到另一个进程环境
  • TRIX : 结合了 M : 1 和 1 : M

上面的 1 : M 和 M : N 关系意味着一个线程的执行需要用到多个进程地址空间中的代码和数据。或者说 , 线程从一个进程环境移动到另一个进程环境, 跨越了多个进程的边界。特别是在分布式系统中。

进程的并发与死锁

前面我们介绍了进程的概念,属性,行为以及进程调度的各种策略。现在我们来介绍进程的并发,并发控制机制,并发程序设计以及进程并发所引起的死锁现象。

大致介绍

从前面学习过的多道程序设计以及进程的调度策略可知,当多个进程要在同一个处理器上运行时,操作系统通过合理的调度策略和进程切换,使得处理器 "同时" 执行多个进程,造成多个进程并发执行的''假象"。我们把多个进程可以在同一时间段内同时执行的现象称为程序的并发性。对于单处理器而言,这种并发性是 "伪并发" , 实际上是通过进程的交叉执行来实现的。
然后,问题来了:

  • 如果并发的进程的无关的,即每个进程分别在不同的变量集合上操作,那么一个进程的执行与其他进程的执行无关,这种情况下,任意交叉执行的结果都是一样的。
  • 如果并发的进程是交互的,即它们共享某些变量,或者它们的某些操作之间具有特定的顺序关系,那么一个进程的执行可能影响其他进程的执行,使得某些执行是错误的。

我们把这一问题称为交互进程的并发控制问题。一般分为三大类:

  • 进程之间的资源竞争
  • 进程之间通过共享对象合作 : 比如 , 经典的生产者和消费者的问题
  • 进程之间相互通信

并发控制要处理交互,同步以及死锁等问题。下面,我们将逐一介绍这些问题以及解决方法。

进程的互斥

我们首先通过两个例子来引入进程互斥问题。
假设一个飞机订票系统有两个终端,在每个终端上分别运行订票业务进程 P1 和 P2

process P(int i) (i =1,2)
int x;
begin
    read record to x;
    if x>= 1 then x = x - 1;
    write x to record

每个订票业务进程都做完全相同的操作 : 首先把数据库中的机票数额度记录 record 读出到局部变量 x 中,然后 机票数减一 , 得到剩余机票数, 最后把剩余机票写回到数据库中的 record 记录。
两个进程并发进行, 并且通过共享的票务数据库发生交互。如果对数据库的访问顺序不加以控制,就可能得到错误的执行结果。
假定当前只剩余 一张机票,由于 p1 和 p2运行速度不同, 无论其中哪一个订到了票,结果都是可以接受和正确的,
但是如果 p1和 p2 都订到了票, 那么结果一定是错误的。当 p1 和 p2 并发交叠执行时, 如果对他们的操作顺序不加以控制 , 那么就有可能出现错误的结果。
实际上,对共享数据的读/写操作应当是原子的:当一个进程正在对共享数据进行操作时,任何其他进程对该共享数据状态的观察和变更都将引起数据状态的不一致。
比如:在上面的代码中,对共享变量 record 的操作是从 read record to x开始,直到 write x to record 结束, 我们把这样一段代码区成为 临界区(Critical region)

进程互斥的解决

在通过共享数据进行交互的并发进程中,为了防止出现这类不期望的结果,我们通常要求每个进程对临界区中的代码或在数据的操作必须是互斥的,即一个只能允许最多一个进程在临界区内执行。当一个进程进入临界区时,其他企图进入临界区的进程只能等待在临界区外面,只有当该进程退出临界区时,才允许一个等待的进程进入临界区。