2.进程与线程

2. 进程与线程

2.1 进程与线程

2.1.1 进程的概念和特征

  1. 进程的概念
    程序 :是 静态的 ,就是存放在磁盘里的可执行文件,就是一系列的指令集合.
    进程(Process) :是 动态的 ,是程序的一次执行过程.
    同一个程序多次执行会对应多个进程.

    当进程被创建时,操作系统会为该进程分配一个 唯一的,不重复的 PID(Process ID,进程ID) .

    操作系统要记录PID,进程所属用户ID(UID),还要记录给进程分配了哪些资源,进程的运行情况,这些信息都被保存在一个数据结构 PCB(Process Control Block) 中,即 进程控制块 .

    PCB是进程存在的唯一标志.

  2. 进程的特征

    1. 动态性
      进程是程序的一次执行过程,是动态地产生,变化和消亡的
    2. 并发性
      内存中有多个进程实体,各进程可并发执行
    3. 独立性
      进程是能独立运行,独立获得资源,独立接受调度的基本单位
    4. 异步性
      个进程按各自独立的,不可预知的速度向前推进,操作系统要提供"进程同步机制"来解决异步问题
    5. 结构性
      每个进程都会配置一个PCB.结构上看,进程由程序段,数据段,PCB组成

2.1.2 进程的状态与转换

  1. 运行态
    进程正在处理机上运行,在单处理机中,每个时刻只有一个进程处于运行态.
  2. 就绪态
    进程获得了除处理机外的一切所需资源,一旦得到处理机,便可立即执行.
    系统中处于就绪态的进程可能有多个,通常将它们排成一个队列,称为就绪队列.
  3. 阻塞态
    进程正在等待某一时间而暂停运行,如等待某资源为可用或等待输入/输出完成.即使处理机空闲,该进程也不能运行.
    系统通常将处于阻塞态的进程也排成一个队列,甚至根据阻塞原因的不同,设置多个阻塞队列.
  4. 创建态
    进程正在被创建,尚未转到就绪态.
  5. 终止态
    进程正从系统中消失,可能是进程正常结束或其它原因退出运行.

在这里插入图片描述
进程PCB中,会有一个变量state来表示进程当前状态.

2.1.3 进程的组成

一个 进程实体(进程映像)PCB,程序段,数据段 组成.
进程动态 的, 进程实体(进程映像)静态 的.

  1. 进程控制块
    在这里插入图片描述

  2. 程序段
    程序的代码(指令序列)

  3. 数据段
    运行过程中产生的各种数据(如程序中定义的变量)

PCB是给操作系统用的.
程序段,进程段是给进程自己用的.

进程 是进程实体的 运行过程 ,是系统进行 资源分配调度 的一个独立单位.

2.1.4 进程控制

进程控制的主要功能是对系统中的所有进程实施有效的管理,它具有创建新进程,撤销已有进程,实现进程状态转换等功能.

在操作系统中,一般把进程控制用的程序段称为 原语 ,原语的特点是执行期间不允许中断,它是一个不可分割的基本单位.

原语 的执行具有 原子性 ,即执行过程只能一气呵成,期间 不允许被中断.

可以用" 关中断 指令"和" 开中断 指令",这两个 特权指令 实现 原子性 .

正常情况,CPU每执行完一条指令都会例行检查是否有中断信号需要处理,如果有,则暂停运行当前这段程序,转而执行相应的中断处理程序.
CPU执行了 关中断指令 之后,就不再例行检查中断信号,直到执行 开中断指令 之后才会恢复检查.
这样,关中断,开中断之间的这些指令序列就是不可被中断的,这就实现了"原子性".

  1. 进程的创建
    在这里插入图片描述

  2. 进程的终止
    在这里插入图片描述

  3. 进程的阻塞和唤醒
    在这里插入图片描述

  4. 进程的切换
    在这里插入图片描述

CPU中会设置很多" 寄存器 ",用来存放程序运行过程中所需的某些数据.
PSW:程序状态字寄存器.
PC:程序计数器,存放下一条指令的地址.
IR:指令寄存器,存放当前正在执行的指令.
通用寄存器:其它一些必要信息.

2.1.5 进程的通信

进程间通信(Inter-Process Communication,IPC)是指两个进程之间产生数据交互.
进程是分配系统资源的单位(包括内存地址空间),因此各进程拥有的内存地址空间相互独立.

  1. 共享存储
    在通信的进程之间存在一块可直接访问的共享空间,通过对这片共享空间进行写/读操作实现进程之间的信息交换.
    共享存储又分为两种:

    • 基于数据结构的共享:低级方式
    • 基于存储区的共享:高级方式
  2. 消息传递
    进程间的数据交换以格式化信息为单位.进程通过操作系统提供的"发送消息/接受消息"两个原语进行数据交换.

    • 直接通信方式
      发送进程直接把消息发送给接受进程,并将它挂在接收进程的消息缓冲对列上接收进程从消息缓冲队列中取得消息.
    • 间接通信方式
      发送进程把消息发送到某个中间实体,接受进程从中间实体取得消息.这种中间实体一般称为 信箱 .
  3. 管道通信
    "管道"是一个特殊的共享文件,又名pipe文件.其实是在内存中开辟一个大小固定的内存缓冲区.
    在这里插入图片描述

    管道只能采用 半双工通信 ,某一时间段内只能实现单向的传输.如果要实现 双向同时通信 ,则需要设置两个管道.

2.1.6 线程和多线程模型

引入进程的目的是更好地使多道程序并发执行,提高资源利用率和系统吞吐量;而引入线程的目的则是减少程序在并发执行时所付出的时空开销,提高操作系统的并发性能.

线程 是一个基本的CPU执行单元,也是 程序执行流的最小单位 .
引入线程之后,不仅是进程之间可以并发,进程内的个线程之间也可以并发.

在这里插入图片描述

线程的属性:

  1. 线程是处理机调度的单位
  2. 多CPU计算机中,各个线程可占用不同的CPU
  3. 每个线程都有一个线程ID,线程控制块(TCB)
  4. 线程也有就绪,阻塞,运行三种基本状态
  5. 线程几乎不拥有系统资源
  6. 同一进程的不同线程间共享进程的资源
  7. 由于共享内存地址空间,同一进程中的线程间通信甚至无需系统干预
  8. 同一进程中的线程切换,不会引起进程切换
  9. 不同进程中断线程切换,会引起进程切换
  10. 切换进程内的线程,系统开销很小

在这里插入图片描述

  • 用户及线程(User-Level Thread,ULT)
    早期的操作系统只支持进程,不支持线程.当时的"线程"是由线程库实现的.
    在用户级线程中,有关线程管理的所有工作都由应用程序在用户空间中完成,内核意识不到线程的存在.
    用户级线程中,线程切换可以在用户态下即可完成,无需操作系统干预.

  • 内核级线程(Kernel-Level Thread,KLT)
    在内核级线程中,有关线程管理的所有工作都在内核空间中实现的.
    内核级线程的切换必须在核心态下才能完成.
    操作系统会为每个内核级线程建立相应的 线程控制块(Thread Control Block,TCB) .

  • 一对一模型:一个用户级线程映射到一个内核级线程.每个用户进程有与用户级线程同数量的内核级线程.

  • 多对一模型:多个用户级线程映射到一个内核级线程.且一个进程只被分配一个内核级线程.

  • 多对多模型:n个用户级线程映射到m个内核级线程(n>=m).每个用户进程对应m个内核级线程.

2.2 处理机调度

2.2.1 调度的概念

  1. 调度的基本概念
    处理机调度是对处理机进行分配,即从就绪队列中按照一定的算法选择一个进程并将处理机分配给它运行,以实现进程并发地执行.
  2. 调度的层次
    1. 高级调度(作业调度)
      按一定的原则从外存的作业后备队列中挑选一个作业调入内存,并创建进程.每个作业只调入一次,调出一次.作业调入时会建立PCB,调出时会撤销PCB.
      无->创建态->就绪态
    2. 中级调度(内存调度)
      按照某种策略决定将哪个处于挂起状态的进程重新调入内存.
      挂起态->就绪态
    3. 低级调度(进程调度/处理机调度)
      按照某种策略从就绪队列中选取一个进程,将处理机分配给它.进程调度是操作系统中 最基本的一种调度 ,在一般的操作系统中都必须配置进程调度.进程调度的频率很高,一般几时毫秒一次.
      就绪态->运行态
  3. 三级调度的联系

2.2.2 调度的目标

不同的调度算法具有不同的特性,在选择调度算法时,必须考虑算法的特性.为了比较处理机调度调度算法的性能,人们提出了很多评价标准,主要有以下几种.

  1. CPU利用率
    利用率 = 忙碌的时间 总时间 利用率=\frac{忙碌的时间}{总时间} 利用率=总时间忙碌的时间
  2. 系统吞吐量
    系统吞吐量 = 总共完成作业数 总共花费时间 系统吞吐量=\frac{总共完成作业数}{总共花费时间} 系统吞吐量=总共花费时间总共完成作业数
  3. 周转时间
    周转时间是指作业被提交给系统开始,到作业完成为止的这段时间间隔.
    周转时间 = 作业完成时间 − 作业提交时间 周转时间=作业完成时间-作业提交时间 周转时间=作业完成时间作业提交时间
    平均周转时间 = 各作业周转时间之和 作业数 平均周转时间=\frac{各作业周转时间之和}{作业数} 平均周转时间=作业数各作业周转时间之和
    带权周转时间 = 作业周转时间 作业实际运行的时间 带权周转时间=\frac{作业周转时间}{作业实际运行的时间} 带权周转时间=作业实际运行的时间作业周转时间
    平均带权周转时间 = 各作业带权周转时间之和 作业数 平均带权周转时间=\frac{各作业带权周转时间之和}{作业数} 平均带权周转时间=作业数各作业带权周转时间之和
  4. 等待时间
    等待时间,指进程/作业处于等待处理机状态时间之和,等待时间越长,用户满意度越低.
    对于进程来说,等待时间就是指进程建立后等待被服务的时间之和,在等待I/O完成的期间其实进程也在被服务,所以不计入等待时间.
    对于作业来说,不仅要考虑建立进程后的等待时间,还要加上作业在外存后备队列中等待的时间.
  5. 响应时间
    响应时间,指从用户提交请求到首次产生影响所用的时间.

2.2.3 调度的实现

  1. 调度程序(调度器)
    在这里插入图片描述

    不支持内核级线程的操作系统,调度程序的处理对象是进程.
    支持内核级线程的操作系统,调度程序的处理对象是内核线程.
    用于调度和分派CPU的组件称为调度程序,它通常由三部分组成.

    1. 排队器
    2. 分派器
    3. 上下文切换器
  2. 调度的时机
    进程调度(低级调度) ,就是按照某种算法从就绪队列中选择一个进程为其分配处理机.
    在这里插入图片描述

  3. 进程调度方式

    1. 非剥夺调度方式 ,又称 非抢占调度方式 .
      即只允许进程主动放弃处理机.在运行过程中即使有更紧迫的任务到达,当前进程依然会继续使用处理机,直到该进程终止或主动要求进入阻塞态.
    2. 剥夺调度方式 ,又称 抢占调度方式 .
      当一个进程正在处理机上执行时,如果有一个更重要或紧迫的进程需要使用处理机,则立即暂停正在执行的进程,将处理机分配给更重要,更紧迫的那个进程.
  4. 闲逛进程
    调度程序在没有其它就绪进程时,运行闲逛进程(idle).
    闲逛进程特性:

    • 优先级最低
    • 可以是0地址指令,占一个完成的指令周期(指令周期末尾例行检查中断)
    • 能耗低

2.2.4 典型的调度算法

Tips: 各种调度算法的学习思路

  1. 算法思想
  2. 算法规则
  3. 用于 作业调度 还是 进程调度
  4. 抢占式 还是 非抢占式
  5. 优点和缺点
  6. 是否会导致饥饿
  1. 先来先服务(FCFS,First Come First Serve)调度算法
    先来先服务调度算法:按照到达的先后顺序调度,事实就是等待时间越久的越优先得到服务.
    在这里插入图片描述

  2. 短作业优先(SJF,Shortest Job First)调度算法
    短进程优先(SPF,Shortest Process First)调度算法
    短作业/进程优先调度算法:每次调度时选择 当前已到达运行时间最短 的作业/进程.
    最短剩余时间优先(SRTN,Shortest Remaining Time Next)算法:每当有进程加入就绪队列改变时就需要调度,如果新到达的进程剩余时间比当前运行的进程剩余时间更短,则由新进程抢占处理机,当前运行进程重新回到就绪队列.
    在这里插入图片描述

    1. 若未特殊说明,"短作业/进程优先算法"默认是抢占式的
    2. 在所有进程都几乎同时到达时 ,采用SPF调度算法的平均等待时间,平均周转时间最少
    3. 若不加入上述前提条件,则SRNT算法的平均等待时间,平均周转时间最少
  3. 高响应比优先(HRRN,Highest Response Ratio Next)调度算法
    响应比 = 等待时间 + 要求服务时间 要求服务时间 响应比=\frac{等待时间+要求服务时间}{要求服务时间} 响应比=要求服务时间等待时间+要求服务时间
    高响应比优先调度算法: 非抢占式 的调度算法,只有当前运行的进程 主动放弃CPU时 ,才需要进行调度,调度时计算所有就绪进程的响应比,选择响应比最高的进程.
    在这里插入图片描述

  4. 时间片轮转(RR,Round-Robin)调度算法
    时间片轮转调度算法:轮流让就绪队列中的进程依次执行一个时间片(每次选择的都是排在就绪队列对头的进程).
    在这里插入图片描述

    如果时间片太大,使得每个进程都可以在一个时间片内就完成,则时间片轮转调度算法退化为先来先服务调度算法,并且会增大进程响应时间,因此时间片不能太大.
    另一方面,进程调度,切换是有代价的,因此如果时间片太小,会导致进程切换过于频繁,系统会花大量的时间来处理进程切换,从而导致实际用于进程执行的时间比例减少,因此时间片不能太小.

  5. 优先级(PS,Priority Scheduling)调度算法
    优先级调度算法:每次调度时选择当前 已到达优先级最高 的进程.当前进程 主动放弃处理机 时发生调度.
    当就绪队列发生改变时,也需要检查是否会发生抢占.
    在这里插入图片描述

  6. 多级反馈队列(MLFQ,Multi-level Feedback Queue)调度算法(融合了前几种算法的优点)
    多级反馈队列调度算法:设置多个不同优先级的就绪队列,第1级队列优先级最高,其余队列依次降低,按优先级赋予各个队列不同大小的时间片,优先级越高则时间片越小,每个队列都采用FCFS算法,当新进程进入内存后,首先放入第1级队列末尾,当轮到该进程执行时,若未能在时间片内完成,调度程序将其转入下一级队列队尾等待调度,以此类推,当进程最后被降到第n级队列后,在第n级队列中便采用时间片轮转方式方式运行,且仅当第1~i-1级对列均为空时,才会调度第i级队列中的进程,若处理机在调度第i级队列时有新进程进入任何一个优先级较高的队列,此时需立即把正在运行的进程放回第i级队列的队尾,而把处理机分配给新到的高优先级进程.
    在这里插入图片描述

    1. 多级队列(MLQ,Multi-level Queue)调度算法
      多级队列调度算法:系统中按进程类型设置多个队列,进程创建成功后插入某个队列,队列之间可采取固定优先级和时间片划分,各队列可采用不同的调度策略.
      在这里插入图片描述

2.2.5 进程切换

进程的切换与过程:
狭义的进程调度 指的是从就绪队列中 选中一个要运行的进程 .
进程切换 是指一个进程让出处理机,由另一个进程占用处理机的过程.
广义的进程 包含了 选择一个进程进程切换 两个步骤.
进程切换的过程主要完成了:

  1. 对原来运行进程各种数据的保存
  2. 对新的进程各种数据的恢复

2.3 同步与互斥

2.3.1 同步与互斥的基本概念

同步 亦称 直接制约 关系,它是指为完成某种任务而建立的两个或多个进程,这些进程因为需要在某些位置上 协调 它们的 工作次序 而产生的制约关系.进程间的直接制约关系就是源于它们之间的相互合作.

互斥 亦称 间接制约关系 ,进程互斥指当一个进程访问某临界资源时,另一个想要访问该临界资源的进程必须等待.当前访问临界资源的进程访问结束,释放该资源之后,另一个进程才能去访问临界资源.

临界资源 我们将一次仅允许一个进程使用的资源称为临界资源.
对临界资源的访问,必须互斥地进行,在每个进程中访问临界资源的那段代码称为临界区.为保证临界资源的正确使用,可把临界资源分成4个部分:

  1. 进入区
    在进入区要检查可否进入临界区,若能进入临界区,则应设置正在访问临界区的标志,以阻止其它进程同时进入临界区
  2. 临界区
    进程中访问临界资源的那段代码,又称临界段
  3. 退出区
    将正在访问临界区的标志清除
  4. 剩余区
    代码中的其余部分
do{
   entry section;//进入区
   critical section;//临界区
   exti section;//退出区
   remainder section;//剩余区
}while(true)

注意
临界区 是进程中 访问临界资源 的代码段.
进入区退出区 是负责 实现互斥 的代码段.

为了实现对临界区资源的互斥访问,同时保证系统的整体性能,需要遵循以下原则:

  1. 空闲让进
    临界区空闲时,可以允许一个请求进入临界区的进程立即进入临界区.
  2. 忙则等待
    当已有进程进入临界区时,其它试图进入临界区的进程必须等待.
  3. 有限等待
    对请求访问的进程,应保证能在有限时间内进入临界区(保证不会饥饿).
  4. 让权等待
    当进程不能进入临界区时,应立即释放处理机,防止进程忙等待.

2.3.2 实现临界区互斥的基本方法

  1. 软件实现方法

    1. 单标志法
      两个进程在访问完临界区后会把使用临界区的权限转交给另一个进程.也就是说 每个进程进入临界区的权限只能被另一个进程赋予 .
      在这里插入图片描述

      turn的初值为0,即刚开始只允许0号进程进入临界区.
      若P1先上处理机运行,则会一直卡在进入区,直到P1的时间片用完,发生调度,切换P0上处理机运行.
      P0的进入区不会卡住P0,P0可以正常访问临界区,在P0访问临界区期间即使切换回P1,P1依然会卡在P1的进入区.
      只有P0在退出区将turn改为1后,P1才能进入临界区.
      单标志存在的 主要问题 是违背了 空闲让进 原则.

    2. 双标志先检查法
      设置一个布尔型数组flag[],数组中各个元素用来 **标记各进程想进入临界区的意愿.
      在这里插入图片描述

      双标志先检查法的 主要问题 是违背了 忙则等待 原则.
      原因在于, 进入区 的检查和上锁两个处理不是同时完成的.检查后,上锁前可能发生进程切换.

    3. 双标志后检查法
      双标志先检查法的改版.用先上锁后检查的方法来避免上述问题.
      在这里插入图片描述

    4. Peterson算法
      结合双标志法,单标志法的思想.
      在这里插入图片描述

  2. 硬件实现方法

    1. 中断屏蔽方法
      利用"开/关中断指令"实现(与原语的实现思想相同,即在某进程开始访问临界区到结束访问为止都不允许被中断,也就不能发生进程切换,因此也不可能发生两个进程同时访问临界区的情况).

      关中断;(关中断后即不允许当前进程被中段,也必然不会发生进程切换)
      临界区;
      开中断;(直到当前进程访问完临界区,再执行开中断指令,才有可能有别的进程上处理机并访问临界区)

      优点:简单,高效
      缺点:不适用于多处理机;只适用于操作系统内核进程,不适用于用户进程(因为开/关中断指令只能运行在内核态,这组指令如果能让用户随意使用会很危险)

    2. TestAndSet指令
      简称TS指令,也有地方称为TestAndSetLock指令,或TSL指令.
      TSL指令是 用硬件实现的 ,执行的过程不允许被中断,只能一气呵成.
      在这里插入图片描述

      若刚开始lock是false,则TSL返回的old值为false,while循环条件不满足,直接跳过循环,进入临界区.若刚开始lock是true,则执行TSL后old返回的值为true,while循环条件满足,会一直循环,直到当前访问临界区的进程在退出区进行"解锁".
      相比软件实现方法,TSL指令把"上锁"和"检查"操作用硬件的方式变成了一气呵成的原子操作.
      优点:实现简单,无需像软件实现方法那样严格检查是否会有逻辑漏洞;适用于多处理机环境
      缺点:不满足"让权等待"原则,暂时无法进入临界区的进程会占用CPU并循环执行TSL指令,从而导致"忙等"

    3. Swap指令
      有的地方也叫Exchange指令,或简称XCHG指令.
      Swap指令是 用硬件实现的 ,执行的过程不允许被中断,只能一气呵成.
      在这里插入图片描述

      逻辑上看Swap和TSL并无太大区别,都是先记录下此时临界区是否已经被上锁(记录在old变量上),再将上锁标记lock设置为true,最后检查old,如果old为false则说明之前没有别的进程对临界区上锁,则可跳出循环,进入临界区.
      优点:实现简单,无需像软件实现方法那样严格检查是否会有逻辑漏洞;适用于多处理机环境
      缺点:不满足"让权等待"原则,暂时无法进入临界区的进程会占用CPU并循环执行TSL指令,从而导致"忙等"

2.3.3 互斥锁

解决临界区最简单的工具就是 互斥锁(mutex lock) .一个进程在进入临界区时应获得锁;在退出临界区时释放锁.函数acquire()获得锁,而函数release()释放锁.
每个互斥锁有一个布尔变量available,表示锁是否可用.如果锁是可用的,调用acquire()会成功,且锁不再可用.当一个进程试图获取不可用的锁时,会被阻塞,直到锁被释放.

在这里插入图片描述

acquire()或release()的执行必须是原子操作,因此互斥锁通常采用硬件机制来实现.
互斥锁的 主要缺点是忙等待 ,当有一个进程在临界区中,任何其它进程在进入临界区时必须连续循环调用acquire().当多个进程共享同一个CPU时,就浪费了CPU周期.因此,互斥锁通常用于多处理器系统,一个线程可以在一个处理器上等待,不影响其它线程的执行.

需要连续循环忙等的互斥锁,都可称为 自旋锁(spin lock) ,如TSL指令,Swap指令,单标志法.

特性:

  • 需忙等,进程时间片用完才下处理机,违反"让权等待"
  • 优点:等待期间不用切换进程上下文,多处理器系统中,若上锁的时间短,则等待代价很低
  • 常用于多处理器系统,一个核忙等,其它核照常工作,并快速释放临界区
  • 不太适用单处理机系统,忙等的过程中不可能解锁

在这里插入图片描述

  1. 在双标志先检查法中,进入区的"检查","上锁"操作无法一气呵成,从而导致了两个进程有可能同时进入临界区的问题.
  2. 所有的解决方案都 无法实现"让权等待"

2.3.4 信号量

1965年,荷兰学者Dijkstra提出了一种卓有成效的实现进程互斥,同步的方法 – 信号量机制 .

用户进程可以通过使用操作系统提供的 一对原语 来对 信号量 进行操作,从而很方便地实现了进程互斥,进程同步.

信号量 就是一个变量(可以是一个整数,也可以是更复杂的记录型变量),可以用一个信号量来 表示系统中某种资源的数量 .

原语 是一种特殊的程序段,其 执行只能一气呵成,不可被中断 .原语是由 关中断/开中断指令 实现的.软件解决方案的主要问题是由"进入区的各种操作无法一气呵成",因此如果能把进入区,退出区的操作都用"原语"实现,使这些操作能一起实现,就能避免问题.

一对原语
wait(S) 原语和 signal(S) 原语,可以把原语理解为函数,函数名分别为wait和signal,信号量S使函数调用的一个参数.

wait,signal原语常简称为P,V操作.这对原语可用于实现系统资源的"申请"和"释放".

信号量机制

  1. 整型信号量
    用一个 整型变量 作为信号量,用来表示系统中某种资源的数量.
    在这里插入图片描述

    整型信号量的缺陷使存在"忙等"问题.

  2. 记录型信号量
    用记录型数据结构表示信号量.
    在这里插入图片描述

    S.value的初值表示系统中某种资源的数目.
    对信号量S的一次 P操作 意味着进程请求一个单位的该类资源,因此需要执行S.value--,表示资源数减1,当S.value<0时表示该类资源已分配完毕,因此进程应 调用block 原语进行自我阻塞(当前运行进程从运行态进入阻塞态),主动放弃处理机,并插入该类资源的等待队列S.L中.
    该机制 遵循了"让权等待"原则 .不会出现"忙等"现象.
    对信号量的一次 V操作 意味着进程释放一个单位的该类资源,因此需要执行S.value++,表示资源数加1,若加1后仍是S.value<=0,表示仍然有进程在等待该类资源,因此应调用wakeup原语唤醒等待队列中的第一个进程(被唤醒进程从阻塞态进入就绪态).

信号量机制实现进程互斥

  1. 分析并发进程的关键活动,划定临界区
  2. 设置 互斥信号量mutex ,初值为1
    信号量mutex表示进入临界区的名额
  3. 在进入区P(mutex)–申请资源
  4. 在退出区V(mutex)–释放资源

对不同的临界资源需要设置不同的互斥信号量
P,V操作必须成对出现
在这里插入图片描述

信号量机制实现进程同步

  1. 分析什么地方需要实现"同步关系",即必须保证"一前一后"执行的两个操作
  2. 设置同步信号量S,初始为0
  3. 在"前操作"之后执行V(S)
  4. 在"后操作"之后执行P(S)

在这里插入图片描述

若先执行到V(S)操作,则S++后S=1.之后当执行到P(S)操作时,由于S=1,表示有可用资源,会执行S--,S的值变会0,P2进程不会执行block原语,而是继续往下执行代码4.

若先执行到P(S)操作,由于S=0,S--S=-1,表示此时没有可用资源,因此P操作中会执行block原语,主动请求阻塞.之后当执行完代码2,继而执行V(S)操作,S++,使S变回0,由于此时有进程在该信号量对应的阻塞队列中,因此会在V操作中执行wakeup原语,唤醒P2进程.这样P2就可以继续执行代码4.

信号量机制实现前驱关系
进程P1中有代码S1,P2中有代码S2,…,P6中有代码S6.这些代码要求按如下前驱图所示顺序来执行.

在这里插入图片描述

  1. 为每一对前驱关系各设置一个同步信号量
  2. 在"前操作"之后对相应的同步信号量执行V操作
  3. 在"后操作"之前对响应的同步信号量执行P操作

在这里插入图片描述

2.3.5 管程

为什么要引入管程
信号量机制存在的问题:编写程序困难,易出错.
1973年,Brinch Hansen首次在程序设计语言(Pascal)中引入了"管程"成分–一种高级同步机制.

管程的定义和基本特征
管程是一种特殊的软件模块,由以下部分组成:

  1. 局部与管程的 共享数据结构 说明
  2. 对该数据结构进行操作的 一组过程
  3. 对局部与管程的共享数据设置初始值的语句
  4. 管程有一个名字

管程的基本特征

  1. 局部于管程的数据只能被局部于管程的管程所访问
  2. 一个进程只有通过调用管程内的过程才能进入管程访问共享数据
  3. 每次仅允许一个进程在管程内执行某个内部过程

引入管程的目的无非就是要更方便地实现进程互斥和同步

  1. 需要在管程中定义共享数据
  2. 需要在管程中定义用于访问这些共享数据的"入口"
  3. 只有通过这些特定的"入口"才能访问共享数据
  4. 管程中有很多"入口",但是每次只能开放其中一个"入口",并且只能让一个进程或线程进入
  5. 可在管程中设置条件变量及等待/唤醒操作以解决同步问题.可以让一个进程或线程在条件变量上等待(此时,该进程应先释放管程的使用权,也就是让出"入口");可以通过唤醒操作将等待在条件变量上的进程或线程唤醒

2.4 死锁

2.4.1 死锁的概念

死锁: 各进程互相等待对方手里的资源,导致个进程都阻塞,无法向前推进的现象.

饥饿: 由于长期得不到想要的资源,某进程无法向前推进的现象.

2.4.2 死锁产生的必要条件

产生死锁必须同时满足以下四个条件,只要其中任一条件不成立,死锁就不会发生.

  1. 互斥条件
    只有对 必须互斥使用 的资源的抢夺才会导致死锁.
  2. 不可剥夺条件
    进程所获得的资源在未使用完之前, 不能由其它进程强行夺走 ,只能主动释放.
  3. 请求和保持条件
    进程 已经保持了至少一个资源 ,但又提出了新的资源请求,而该资源又被其它进程占有,此时请求进程被阻塞,但又对自己已有的资源保持不放.
  4. 循环等待条件
    存在一种进程 资源的循环等待链 ,链中的每一个进程已获得的资源同时被下一个进程所请求.
    发生死锁时一定有循环等待链,但是发生循环等待时未必死锁,循环等待是死锁的必要不充分条件.
    如果同类资源数大于1,则即使有循环等待,也未必发生死锁.
    但如果系统中每类资源都只有一个,那循环等待就是死锁的充分必要条件.

什么时候会发生死锁

  1. 对系统资源的竞争.
  2. 进程推进顺序非法.
  3. 信号量使用不当.

2.4.2 死锁的处理策略

死锁的处理策略

  1. 预防死锁

    1. 破坏互斥条件
      如果把只能互斥使用的资源改造为允许共享使用,则系统不会进入死锁状态.
      如:SPOOLing技术.
      操作系统可以采用SPOOLing技术把独占设备在逻辑上改造成共享设备.
      缺点:并不是所有资源都可以改造成可共享使用的资源.并且为了系统安全,很多地方还必须保护这种互斥性.因此,很多时候都无法破坏互斥条件.
    2. 破坏不剥夺条件
      方案一:当某个进程请求新的资源得不到满足时,它必须立即释放保持的所有资源,待以后需要时再重新申请.
      方案二:当某个进程需要的资源被其它进程所占有时,可以由操作系统协助,将想要的资源强行剥夺.这种方式一般需要考虑个进程的优先级.
      缺点:
      1. 实现起来比较复杂.
      2. 释放已获得的资源可能造成前一阶段工作的失效.因此这种方法一般只适用于易保存和恢复状态的资源,如CPU.
      3. 反复地申请和释放资源会增加系统开销,降低系统吞吐量.
      4. 方案一,意味着只要暂时得不到某个资源,之前获得的那些资源就都需要放弃,以后再重新申请.如果一直这样,就会导致进程饥饿.
    3. 破坏请求和保持条件
      可采用静态分配方法,即进程再运行前一次申请完它所需要的全部资源,在它的资源未满足前,不让它投入运行,一旦投入运行,这些资源就一直归它所有,该进程不会再请求别的任何资源了.
      缺点:有些资源可能只需要使用很短的时间,因此如果进程的整个运行期间都一直保持这所有资源,就会造成严重的资源浪费,资源利用率极低.可能导致某些进程饥饿.
    4. 破坏循环等待条件
      可采用顺序资源分配法.首先给系统中的资源编号,规定每个进程必须按编号递增的顺序请求资源,同类资源(即编号相同的资源)一次申请完.
      缺点:
      1. 不方便增加新的设备,因为可能需要重新分配所有的编号.
      2. 进程实际使用资源的顺序可能和编号递增顺序不一致,会导致资源浪费.
      3. 必须按规定次序申请资源,用户编程麻烦.
  2. 避免死锁

    安全序列
    如果系统按照一定序列分配资源,则每个进程都能顺利完成.只要能找出一个安全序列,系统就是安全状态.安全序列可能有多个.
    如果分配资源之后,系统中找不出任何一个安全序列,系统就进入了不安全状态.如果有进程提前归还了一些资源,系统也有可能重新进入安全状态.
    如果系统处于安全状态,就一定不会发生死锁.如果系统进入不安全状态,就可能发生死锁.
    因此可以在资源分配之前预先判断这次分配是否会导致系统进入不安全状态,以此决定是否答应资源分配请求.

    银行家算法
    银行家算法时荷兰学者Dijkstra为银行系统设计的,以确保银行在发放现金贷款时,不会发生不能满足所有客户需要的情况.后来该算法被用在操作系统中,用于避免死锁.
    核心思想:在进程提出资源申请时,先预判此次分配是否会导致系统进入不安全状态.如果会进入不安全状态,就暂时不答应这次请求,让该进程先阻塞等待.
    假设 系统中有n个进程,m种资源
    每个进程在运行前先声明对各种资源的最大需求数,则可用一个 n × m n \times m n×m 的矩阵表示所有进程对各种资源的最大需求数.不妨称为 最大需求矩阵Max , M a x [ i , j ] = K Max[i,j]=K Max[i,j]=K 表示进程 P i P_i Pi最多需要K个资源 R j R_j Rj .同理,系统可以用一个 n × m n \times m n×m分配矩阵Allocation 表示对所有进程的资源分配情况.Max-Allocation= Need矩阵 ,表示各进程最多还需要多少各类资源.
    另外,还要用一个长度为m的一维数组 Available 表示当前系统中还有多少可用资源.
    某进程 P i P_i Pi 向系统申请资源,可用一个长度为m的一维数组 Request表示本次申请的各种资源量.
    可用银行家算法预判本次分配是否会导致系统进入不安全状态:

    1. 如果 R e q u e s t i [ j ] ≤ N e e d [ i , j ] ( 0 ≤ j ≤ m ) Request_i[j] \le Need[i,j](0 \le j \le m) Requesti[j]Need[i,j](0jm) 便转向2;否则报错(所需资源数超过它的最大值)
    2. 如果 R e q u e s t i [ j ] ≤ A v a i l a b l e [ j ] ( 0 ≤ j ≤ m ) Request_i[j] \le Available[j](0 \le j \le m) Requesti[j]Available[j](0jm) 便转向3;否则表示尚无足够资源, P i P_i Pi 必须等待
    3. 系统试探着把资源分配给进程 P i P_i Pi ,并修改相应的数据:
      A v a i l a b l e = A v a i l a b l e − R e q u e s t ; Available=Available-Request; Available=AvailableRequest;
      A l l o c a t i o n [ i , j ] = A l l o c a t i o n [ i , j ] + r e q u e s t i [ j ] ; Allocation[i,j]=Allocation[i,j]+request_i[j]; Allocation[i,j]=Allocation[i,j]+requesti[j];
      N e e d [ i , j ] = N e e d [ i , j ] − R e q u e s t i [ j ] Need[i,j]=Need[i,j]-Request_i[j] Need[i,j]=Need[i,j]Requesti[j]
    4. 操作系统执行安全性算法,检查此次资源分配后,系统是否处于安全状态.若安全,才正式分配;否则,恢复相应数据,让进程阻塞等待

    在这里插入图片描述

    1. 检查此次申请是否超过了之前声明的最大需求数.
    2. 检查此时系统剩余的可用资源是否还能满足这次请求.
    3. 试探着分配,更改各数据结构.
    4. 用安全性算法检查此次分配是否会导致系统进入不安全状态.
  3. 死锁的检测和解除

    1. 死锁的检测
      用某种数据结构来保存资源的请求和分配信息.
      在这里插入图片描述

      提供一种算法,利用上述信息来检测系统是否已进入死锁状态.
      如果系统中剩余的可用资源数足够满足进程的需求,那么这个进程暂时是不会阻塞的,可以顺利地执行下去.如果这个进程执行结束了把资源归还系统,就可能使某些正在等待资源的进程被激活,并顺利地执行下去.相应的,这些被激活的进程执行完了之后又会归还一些资源,这样可能又会激活另外一些阻塞的进程.
      在这里插入图片描述

      如果按上述过程分析,最终能消除所有边,就称这个图是可完全简化的.此时一定没有发生死锁(相当于能找到一个安全序列).
      如果最终不能消除所有边,那么此时就是发生了死锁.
      最终还连着边的那些进程就是处于死锁状态的进程.
      检测死锁的算法:

      1. 在资源分配图中,找出既不阻塞又不是孤点的进程Pi(即找出一条有向边与它相连,且该有向边对应资源的申请数量小于等于系统中已有空闲资源数量.如上图,R1没有空闲资源,R2有一个空闲资源.若所有的连接该进程的边均满足上述条件,则这个进程能继续运行直至完成,然后释放它所占有的所有资源.).消去它所有的请求边和分配边,使之称为孤立结点.如上图P1是满足这一条件的进程结点,于是将P1的所有边消去.
        在这里插入图片描述

      2. 进程Pi所释放的资源,可以唤醒某些因等待这些资源而阻塞的进程,原来的阻塞进程可能变为非阻塞进程.如上图,P2就满足这样的条件.根据上述方法,进行简化后,若能消去所有边,则称该图是可完全简化的.
        死锁定理: 如果某时刻系统的资源分配图是 不可完全简化 的,那么此时系统 死锁 .

    2. 死锁的解除

      1. 资源剥夺法
        挂起(暂时放到外存上)某些死锁进程,并抢占它的资源,将这些资源分配给其它的死锁进程.但是应防止被挂起进程长时间得不到资源而饥饿.
      2. 撤销进程法(或终止进程法)
        强制撤销部分,甚至全部死锁进程,并剥夺这些进程的资源.这种方式的优点是实现简单,但所付出的代价可能会很大.因为有些进程可能已经运行了很长时间,已经接近结束了,被终止后还要从头再来.
      3. 进程回退法
        源.若所有的连接该进程的边均满足上述条件,则这个进程能继续运行直至完成,然后释放它所占有的所有资源.).消去它所有的请求边和分配边,使之称为孤立结点.如上图P1是满足这一条件的进程结点,于是将P1的所有边消去.
        [外链图片转存中…(img-k6VAOtvV-1700034049789)]
      4. 进程Pi所释放的资源,可以唤醒某些因等待这些资源而阻塞的进程,原来的阻塞进程可能变为非阻塞进程.如上图,P2就满足这样的条件.根据上述方法,进行简化后,若能消去所有边,则称该图是可完全简化的.
        死锁定理: 如果某时刻系统的资源分配图是 不可完全简化 的,那么此时系统 死锁 .
    3. 死锁的解除

      1. 资源剥夺法
        挂起(暂时放到外存上)某些死锁进程,并抢占它的资源,将这些资源分配给其它的死锁进程.但是应防止被挂起进程长时间得不到资源而饥饿.
      2. 撤销进程法(或终止进程法)
        强制撤销部分,甚至全部死锁进程,并剥夺这些进程的资源.这种方式的优点是实现简单,但所付出的代价可能会很大.因为有些进程可能已经运行了很长时间,已经接近结束了,被终止后还要从头再来.
      3. 进程回退法
        让一个或多个死锁进程回退到足以避免死锁的地步.这就要求系统要记录进程的历史信息,设置还原点.
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值