【王道·操作系统】第二章 进程管理

一、进程的基本概念

1.1 进程的概念

  • 程序:静态的,存放在磁盘里的可执行文件,是一系列的指令集合
  • 进程Process:动态的,是程序的一次执行过程

1.2 进程的组成

  • 数据结构PCB(process control block),即进程控制块
    1. PID(进程ID,process ID:os为进程分配的唯一的、不重复的号,是进程存在的唯一标志)
    2. UID(进程所属用户:基本进程描述信息,可以让os区分各个进程)
    3. 分配的资源(内存、I/O设备、使用那些文件等:用于实现os对资源的管理)
    4. 运行情况(CPU运行时间、磁盘使用情况、网络流量使用情况:用于实现os对系统进程的控制、调度)
  • 一个进程实体(进程映像)由PCB、程序段、数据段组成,进程实体反应了进程在某一时刻的状态
  • 进程是动态的,进程实体(进程映像)是静态的
  • 进程是进程实体的运行过程,是系统进行资源分配和调度(os让进程在CPU上运行)的独立单位
    在这里插入图片描述

1.3 进程的特征

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

1.4 进程的状态

  • 三种基本状态:
    1. 运行态Runing:占有CPU,并在CPU上运行
    2. 就绪态Ready:已具备运行条件,但没有空闲CPU暂时不能运行
    3. 阻塞态/等待态Waiting/Blcoked:因等待某一事件而暂时不能运行
  • 其他两种状态:
    1. 创建态/新建态New:进程正在被创建,os为进程分配资源、初始化PCB
    2. 终止态/结束态Terminated:进程正从系统中撤销,回收进程拥有的资源、撤销PCB
  • 进程PCB中有变量state表示当前状态
    在这里插入图片描述

1.5 进程的组织方式

  • 链式方式:
    1. 按照进程状态将PCB分为多个队列:执行指针、就绪队列指针、阻塞队列指针(等待打印机的阻塞队列、等待磁盘的阻塞队列…)
    2. 操作系统持有指向各队列的指针
  • 索引方式:
    1. 根据进程状态不同,建立几张索引表:执行指针、就绪表指针、阻塞表指针
    2. 操作系统持有指向各索引表的指针

二、进程的行为

进程控制会导致进程状态的转换,无论哪个进程控制原语,要做的无非三类事情:

  • 更新PCB中的信息
    1. 所有的进程控制原语一定都会修改进程状态标志
    2. 剥夺当前运行进程的CPU使用权必然需要保存其运行环境
    3. 某进程开始运行前必然要恢复期运行环境
  • 将PCB插入合适的队列
  • 分配/回收咨源

2.1 进程控制的基本概念

  • 进程控制的主要功能:对系统中的所有进程实施有效的管理,它具有创建新进程、撤销已有进程、实现进程转换等功能
  • 如何实现进程控制?原语
    1. 原语的执行具有原子性,即执行过程一气呵成、期间不允许被中断
    2. 通过“关中断指令”和“开中断指令”这两个特权指令实现其原子性

2.2 进程控制相关的原语

2.2.1 进程的创建

在这里插入图片描述

2.2.2 进程的终止

在这里插入图片描述

2.2.3 进程的阻塞与唤醒

在这里插入图片描述

2.2.4 进程的切换

在这里插入图片描述

2.3 进程通信IPC

  • 进程间通信Inter-Process Communication,IPC:两个进程之间产生数据交互
  • 进程通信需要操作系统支持:进程是分配系统资源的单位(包括内存地址空间),因此各进程拥有的内存地址空间相互独立
    在这里插入图片描述

2.3.1 共享存储

  • 基于数据结构的共享:速度慢、限制多,是一种低级通信方式
  • 基于存储区的共享:操作系统在内存中划出一块共享存储区,数据的形式、存放位置都由通信进程控制
    1. 速度快,是一种高级通信方式
    2. 各个进程对共享空间的访问是互斥的,各个进程可使用操作系统内核提供的同步互斥工具(如P、V操作)

2.3.2 消息传递

进程间的数据交换以格式化的消息Message为单位,进程通过操作系统提供的“发送消息/接受消息”两个原语进行数据交换

在这里插入图片描述

  • 直接通信方式,即点名道姓的消息传递:发送原语send(进程Q,msg)、接受原语receive(进程P,&msg)
  • 间接(信箱)通信方式,以“信箱”作为中间实体进行消息传递:发送原语send(信箱A,msg)、接受原语receive(信箱A,&msg)

2.3.3 管道通信

  • “管道”是系统调用生成的一个特殊的共享文件(pipe文件),就是在内存中开辟一个大小固定的内存缓冲区
  • 数据是单向流动,且先进先出FIFO
    1. 管道只能采用半双工通信,某一时间段只能实现单向传输
    2. 若需实现双向同时通信,则需要设置两个管道
    3. 各进程要互斥地访问管道(os实现)
    4. 当管道写满时,写进程将阻塞,直到读进程将管道中的数据取走,即可唤醒写进程
    5. 当管道读空时,读进程将阻塞,直到写进程往管道中写入数据,即可唤醒读进程
  • 管道中数据读出就彻底消失,当多个进程读同一个管道时,产生错乱:
    1. 408:一个管道允许多个写进程,一个读进程
    2. linux:允许有多个写进程,多个读进程,但系统会让各个进程轮流从管道中读数据

三、线程

3.1 线程的概念

  • 线程是一个基本的CPU执行单位,也是程序执行流的最小单位
    1. 进程内的各线程之间可以并发,进一步提升了系统的并发度
    2. 进程只作为除CPU之外的系统资源的分配单元
  • 引入线程带来的变化:
    在这里插入图片描述

3.2 线程的属性

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

3.3 线程的实现方式

在这里插入图片描述

3.3.1 用户级线程User-Level Thread,ULT

  • 早期只进程进程不支持线程,由线程库实现
  • 管理者:用户级线程由应用程序通过线程库实现,所有的线程管理工作都由应用程序负责 (包括线程切换)
  • 状态:用户级线程中,线程切换可以在用户态下即可完成,无需操作系统干预
  • 在用户看来,是有多个线程;但在操作系统内核看来,并意识不到线程的存在,即“用户级线程”就是“从用户视角看能看到的线程
  • 优点:用户级线程的切换在用户空间即可完成,不需要切换到核心态,线程管理的系统开销小效率高
  • 缺点:当一个用户级线程被阻塞后,整个进程都会被阻塞,并发度不高;多个线程不可在多核处理机上并行运行

3.3.2 内核级线程Kernel-Level Thread, KL:内核支持的线程

  • 由操作系统支持的线程
  • 管理者:内核级线程的管理工作由操作系统内核完成
  • 状态:线程调度、切换等工作都由内核负责,因此内核级线程的切换必然需要在核心态下才能完成
  • 操作系统会为每个内核级线程建立相应的TCB (Thread ControlBlock,线程控制块),通过TCB对线程进行管理,即“内核级线程”就是“从操作系统内核视角看能看到的线程”
  • 优点:当一个线程被阻塞后,别的线程还可以继续执行,并发能力强。多线程可在多核处理机租并行执行
  • 缺点:一个用户进程会占用多个内核级线程线程切换由操作系统内核完成,需要切换到核心态,因此线程管理的成本高,开销大

3.4 多线程模型

  • 操作系统只“看得见”内核级线程,因此只有内核级线程才是处理机分配的单位
  • 只有所有的内核级线程中正在运行的代码逻辑都阻塞时,该进程才会阻塞
  • 在支持内核级线程的系统中,根据用户级线程和内核级线程的映射关系,划分为几种多线程模型:
    在这里插入图片描述

3.4.1 一对一模型

  • 一对一模型一个用户级线程映射到一个内核级线程,每个用户进程有与用户级线程同数量的内核级线程
  • 优点:当一个线程被阻塞后,别的线程还可以继续执行,并发能力强。多线程可在多核处理机上并行执行。
  • 缺点:一个用户进程会占用多个内核级线程线程切换由操作系统内核完成,需要切换到核心态封困此线程管理的成本高,开销大

3.4.2 多对一模型

  • 多对一模型:多个用户级线程映射到一个内核级线程,且一个进程只被分配一个内核级线程
  • 优点:用户级线程的切换在用户空间即可完成,不需要切换到核心态,线程管理的系统开销小,效率高
  • 缺点:当一个用户级线程被阻塞后,整个进程都会被阻塞,并发度不高。多个线程不可在多核处理机上并行运行

3.4.3 多对多模型

  • 多对多模型:n用户及线程映射到m 个内核级线程(n >= m),每个用户进程对应 m 个内核级线程
  • 克服了多对一模型并发度不高的缺点 (一个阻塞全体阻塞),又克服了一对一模型中一个用户进程占用太多内核级线程,开销太大的缺点

3.5 线程的状态与转换

  • 运行态 → 阻塞态:等待某事件
  • 阻塞态 → 就绪态:等待的事件发生
  • 运行态 → 就绪态:时间用完
  • 就绪态 → 运行态:被调度程序选中
    在这里插入图片描述

3.6 线程的组织与控制

在这里插入图片描述

四、调度

4.1 处理机调度

在这里插入图片描述

  • 调度:确定某种规则来决定处理这些任务的顺序
  • 作业:一个具体的任务;用户向系统提交一个作业(用户让os启动一个程序,来处理一个具体的任务)
  • 三个层次:
    1. 高级调度(作业调度):按一定的原则从外存的作业后备队列中挑选一个作业调入内存,并创建进程。每个作业只调入一次,调出一次,作业调入时会建立PCB,调出时才撤销PCB
    2. 低级调度(进程调度/处理机调度):按照某种策略从就绪队列中选取一个进程,将处理机分配给它。进程调度是操作系统中最基本的一种调度,频率很高
    3. 中级调度:按照某种策略决定将哪个处于挂起状态(暂时调到外存等待的进程状态,被挂起的进程PCB会被组指出挂起队列)的进程重新调入内存,一个进程可能会被多次调出、调入内存,因此中级调度发生的频率比高级调度更高
  • 挂起状态(挂起态,suspend):就绪挂起、阻塞挂起
  • 七状态模型
    在这里插入图片描述

4.2 进程调度的行为

  • 进程调度(低级调度):按照某种算法从就绪队列中选择一个进程为其分配处理机

4.2.1 进程调度的时机

  • 需要进行进程调度与切换的情况:
    1. 进程主动放弃处理机:进程正常终止、运行中发生异常而终止、主动请求阻塞(如等待I/O)
    2. 进程被动放弃处理机:时间片用完、更紧急的事需要处理(如I/O中断)、更高优先级的进程进入就绪队列
  • 不能进行进程调度与切换的情况:
    1. 处理中断
    2. 进程在操作系统内核程序临界区
    3. 原子操作过程中(原语)
  • 进程在操作系统内核程序临界区不能进行调度与切换,如就绪队列.
  • 进程在普通临界区可以进程调度与切换,如打印机
    1. 临界资源:一个时间段内只允许一个进程使用的资源,各进程需要互斥地访问临界资源
    2. 临界区:访问临界资源的代码
    3. 内核程序临界区:一般用来访问某种内核数据结构的,eg:进程的就绪队列

4.2.2 进程调度的方式

  • 非剥夺调度(非抢占)方式:
    1. 只允许进程主动放弃处理机
    2. 实现简单,系统开销小
    3. 无法及时处理紧急任务,适合于早期的批处理系统
  • 剥夺调度(抢占)方式:
    1. 当进程在处理机上执行时,若有一个更重要/紧迫的进程需要使用处理机,则立即暂停该进程,将处理机分配给更重要更紧迫的那个进程
    2. 优化处理更紧急的进程,实现让各进程按时间片轮流执行
    3. 适用于分时操作系统、实时操作系统

4.2.3 进程的切换

  • 狭义进程的调度:从就绪队列中选中一个要运行的进程(可以是刚暂停的进程,也可以是另一个进程)
  • 广义的进程调度:狭义的进程调度(选择进程)、进程切换
  • 进程切换:一个进程让出处理机,另一个进程占用处理机的过程
    1. 保存原运行进程的各数据
    2. 恢复新进程的各数据
  • 进程切换是有代价的,过于频繁的进程调度、切换,必然使整个系统效率降低

4.2.4 调度器/调度程序scheduler

  • 调度程序决定
    1. 运行对象:调度算法
    2. 运行时间:时间片大小
    3. 调度时机:创建新进程、进程退出、运行进程阻塞、I/O中断发送
  • 非抢占式调度策略,只有运行进程阻塞或退出才触发调度程序工作
  • 抢占式调度策略,每个时钟中断或k个时钟中断会触发调度程序工作
  • 闲逛进程idle:没有其他就绪进程时,运行闲逛进程
  • 闲逛进程特性:优先级最低、可以是0地址指令、能耗低

4.3 调度算法

  • 饥饿:某进程/作业长期得不到服务
  • 纯计算型进程:
    • 周转时间 = 完成时间 - 到达时间
    • 带权周转时间 = 周转时间 ÷ 运行时间
    • 等待时间 = 周转时间 - 运行时间
  • 含I/O操作的进程
    • 等待时间 = 周转时间 - 运行时间 - I/O操作时间
  • 早期批处理系统的调度算法(均用于作业/进程调度)
    在这里插入图片描述
  • 交互式系统的调度算法
    在这里插入图片描述

4.3.1 先来先服务FCFS, first come first serve

  • 先来先服务:按照到达的先后顺序调度,事实上是等待时间越久的越先得到服务

4.3.2 最短作业优先SJF,shortest job/process(进程) first

  • 非抢占式的短作业优先调度算法/短进程优先调度算法SPF:每次调度时选择当前已到达且运行时间最短的作业/进程
  • 抢占式的短作业优先调度算法(即最短剩余时间优先算法SRTN):每当有进程加入就绪队列改变时就需要调度,如果新到达的进程剩余时间比当前运行的进程剩余时间更短,则由新进程抢占处理机,当前运行进程重新回到就绪队列;当一个进程完成时也需要调度

4.3.3 最高响应比优先HRRN,highest response ration next

  • 在每次调度时先计算各个作业/进程的响应比,选择响应比最高的作业/进程为其服务
  • 响应比 = (等待时间 + 要求服务时间) ÷ 要求服务时间 ≥ 1
  • 最高响应比优先:非抢占式的调度算法,只有当前运行的进程主动放弃CPU时,才需要进行调度,调度时计算所有就绪进程的响应比,选择响应比最高的进程上处理机
4.3.4 时间片轮转调度算法RP,round-robin
  • 常用于分时操作系统,注重“响应时间”
  • 时间片轮转调度算法:轮流让就绪队列中的进程依次执行一个时间片
    • 若时间片太大,使得每个进程都在一个时间片内完成,则RP退化为先来先服务算法FCFS,并且会增大进程响应时间
    • 若时间片太小,导致进程频繁切换,花费不必要的时间代价(保存、恢复运行环境),导致实际用于进程执行的时间比例减少
    • 设计时间片,让切换进程的开销占比≤1%
4.3.5 优先级调度算法
  • 优先级调度算法:每次调度时选择当前已到达且优先级最高的进程
    • 非抢占式的优先级调度算法:当前进程主动放弃处理机时发送调度
    • 抢占式优先级调度算法:就绪队列发生改变时也需检查
  • 就绪队列未必只有一个,可以按照不同优先级来组织
  • 优先级可以动态改变:静态优先级、动态优先级
  • 系统进程优先级高于用户进程,前台进程优先级高于后台进程,操作系统更偏好I/O型进程
  • I/O型进程(I/O繁忙型进程)、计算型进程(CPU繁忙型进程)
4.3.6 多级反馈队列调度算法
  • 设置多级就绪队列,各级队列优先级从高到低,时间片从小到大
    • 新进程到达先进入第1级队列,按FCFS分配时间片
    • 时间片用完未结束,则进程进入下一级队列;若已在最下级,则重新放回最下级队列队尾
    • 只有第k级队列为空时,k+1级对头的进程才会分配时间片
  • 优点:
    • 对各类型进程相对公平 (FCFS的优点) :
    • 每个新到达的进程都可以很快就得到响应 (RR的优点)
    • 短进程只用较少的时间就可完成(SPE的优点)
    • 不必实现估计进程的运行时间(避免用户作假)
    • 可灵活地调整对各类进程的偏好程度,比如CPU密集型进程、I/O密集型进程(阻塞的进程重新放回原队列,保证其优先级)
      在这里插入图片描述

4.3.7 多级队列调度算法

  • 系统按进程类型设置多个队列,进程创建成功后插入某个队列
  • 系统进程(如内存管理进程) → 交互式进程(如游戏、打字软件) → 批处理进程(如AI模型训练、视频效果渲染)
    • 固定优先级:高优先级空时低优先级进程才被调度
    • 时间片划分:如50%、40%、10%
  • 调度策略
    • 系统进程队列:优先级调度
    • 交互式进程:RR
    • 批处理队列:FCFS

4.4 调度算法的评价指标

  • CPU利用率:忙碌时间 ÷ 总时间
  • 系统吞吐量:单位时间完成作业的数量, 总归完成了多少道作业 ÷ 总归花了多少时间
  • 周转时间:从作业被提交给系统开始,到完成为止的时间
    1. 包括:作业在外存后备队列上等待作业调度(高级调度)的时间、进程在就绪队列上等待进程调度(低级调度)的时间、进程在CPU上执行的时间、进程等待I/O操作完成的时间
    2. (作业)周转时间 = 作业完成时间 - 作业提交时间
    3. 平均周转时间 = 各作业周转时间之和 ÷ 作业数
    4. 带权周转时间 = 作业周转时间 ÷ 作业实际运行的时间 = (作业完成时间 - 作业提交时间) ÷ 作业实际运行的时间
    5. 带权周转时间必然≥1
    6. 平均带权周转时间 = 各作业带权周转时间之和 ÷ 作业数
  • 等待时间:进程/作业处于等待处理机状态时间之和,等待时间越长用户满意度越低
    1. 对于进程来说,等待时间是进程建立后等待被服务的时间之和,等待I/O完成的期间也是进程在被服务,所以不计入等待时间
    2. 对于作业来说,不仅要考虑建立进程后的等待时间,还要加上作业在外存后备队列中的等待时间
  • 响应时间:从用户提交请求到首次产生响应所用的时间
    在这里插入图片描述

五、进程的行为

5.1 进程同步与互斥

  • 同步(直接制约关系):为完成某种任务而建立的两个或多个进程,这些进程在某些位置上协调它们的工作次序而产生的制约关系
    • 异步性:各并发执行的进程以各自独立的、不可预知的速度向前推进
    • 临界资源:一个时间段内只允许一个进程使用的资源
  • 互斥(间接制约关系):一个进程访问某临界资源时,另一个欲访问的进程必须等待
    • 空闲让进:临界区空闲时,可以允许一个请求进入临界区的进程立即进入临界区
    • 忙则等待:当已有进程进入临界区时,其他试图进入临界区的进程必须等待
    • 有限等待:对请求访问的进程,应保证能在有限时间内进入临界区(保证不会饥饿)
    • 让权等待:当进程不能进入临界区时,应立即释放处理机,防止进程忙等待
  • 临界区(临界段):是进程中访问临界资源的代码;进入区和退出区:是负责实现互斥的代码段
    在这里插入图片描述

5.2 进程互斥的软件实现方法

在这里插入图片描述

5.2.1 单标志法

  • 算法思想:每个进程进入临界区的权限只能被另一个进程赋予,即保证同一时刻最多只允许一个进程访问临界区
  • 问题:违背“空闲让进”原则

5.2.2 双标志先检查

  • 算法思想:设置布尔型数据flag[],数组中各个元素用来标记个进程想进入临界区的意愿;每个进程进入临界区前先检查当前有无别的进程欲访问临界区,若无则把自身对应的标志flag[i]改为true,之后访问临界区
  • 问题:违背“忙则等待”原则
  • 原因:进入区的“检查”和“上锁”不是原子性的

5.2.3 双标志后检查

  • 算法思想:改进双标志先检查法,对进程先“上锁”后“检查”
  • 问题:违背了“空闲让进”和“有限等待”原则,会导致进程长时间无法访问临界资源而产生“饥饿”现象

5.2.4 Peterson算法

  • 算法思想:结合单标志法、双标志法的思想
  • 用软件方法解决了进程互斥问题,遵循了空闲让进、忙则等待、有限等待
  • 问题:违背了“让权等待”
    在这里插入图片描述

5.3 进程互斥的硬件实现方法

在这里插入图片描述

5.3.1 中断屏蔽方法

  • 利用开/关中断指令实现
  • 优点:简单、高校
  • 缺点:不适用于多处理机;只适用于操作系统内核进程,不适用于用户进程

5.3.2 TestAndSet (TS指令/TSL,TestAndSetLock指令)

  • 用硬件实现,执行过程不允许被中断,只能一气呵成;相比软件实现方法,TSL指令将“上锁”与“检查”操作用硬件的方式变成了原子操作
  • 优点:实现简单,无需像软件实现方法那样严格检查是否有逻辑漏洞;适用于多处理机环境
  • 缺点:不满足“让权等待”,暂时无法进入临界区的进程会占用CPU并循环执行TSL(即忙等)

5.3.3 Swap指令(Exchange/XCHG指令)

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

5.4 互斥锁

  • 互斥锁mutex lock:解决临界区最简单的工具,一个进程在进入临界区时获得锁acquire()、在退出临界区时释放锁release()
  • 互斥锁的主要缺点:忙等待
  • 自旋锁spin lock:需要连续循环忙等的互斥锁,如TSL指令、SWAP指令、单标志法
  • 特性:
    • 需忙等,进程时间片用完才下处理机,违反“让权等待”原则
    • 优点:等待期间不用切换进程上下文,多处理器系统中,若上锁的时间短,则等待代价很低
    • 常用于多处理器系统,一个核忙等,其他核照常工作,并快速释放临界区
    • 不太适用于单处理机系统,忙等的过程中不可能解锁
      在这里插入图片描述

六、信号量机制

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

6.1 信号量机制

  • 信号量:一个变量,用一个信号量来表示系统中某种资源的数量
  • 原语:一种特殊的程序段,原语由关中断/开中断实现;用户进程通过使用操作系统提供的一对原语来对信号量进行操作
  • 一对原语:wait(S)原语、signal(S)原语,又称为P、V(荷兰语proberen、verhogen)操作,写为P(S)、V(S)
    在这里插入图片描述

6.1.1 整形信号量

  • 用一个整数型的变量作为信号量,用来表示系统中某种资源的数量
  • 操作:初始化、P操作、V操作
  • 检查“和“”上锁”一气呵成,避免了并发、异步导致的问题
  • 问题:不满足“让权等待”原则,会发生“忙等”

6.1.2 记录型信号量

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

6.2 信号量实现相关操作

P(S) —— 申请一个资源S,如果资源不够就阻塞等待
V(S) —— 释放一个资源S,如果有进程在等待该资源,则唤醒一个进程
在这里插入图片描述

6.2.1 实现进程互斥

  1. 分析并发进程的关键活动,划定临界区
  2. 设置互斥信号量mutex,初值为1
  3. 在进入区P(mutex) —— 申请资源
  4. 在退出区V(mutex) —— 释放资源
  • 对不同的临界资源需要设置不同的互斥信号量
  • P、V操作必须成对出现
  • 初始化信号量机制:semaphore mutex = 1

6.2.2 实现进程同步

  • 进程同步:解决并发执行的异步性,让各并发进程按要求有序地推进
  1. 分析什么地方需要实现“同步关系”,即保证“一前一后”执行的两个操作
  2. 设置同步信号量S,初试为0
  3. 在“前操作”执行V(S)
  4. 在“后操作”执行V(S)

6.2.3 实现进程的前驱关系

每一对前驱关系都是一个进程同步问题:

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

七、经典问题

7.1 生产者消费者问题

  • 生产者消费者问题是一个互斥、同步的综合问题
    在这里插入图片描述
  • PV操作题目分析步骤:
    1. 关系分析:找出题目中描述的各个进程,分析它们之间的同步、互斥关系
      在这里插入图片描述
    2. 整理思路:根据各进程的操作流程确定P、V操作的大致顺序
      在这里插入图片描述
    3. 设置信号量:并根据题目条件确定信号量初值(互斥信号量初值一般为1,同步信号量的初始值要看对应资源的初始值是多少)
      在这里插入图片描述
  • 能否改变相邻P、V操作的顺序
    • 实现互斥的P操作一定要在实现同步的P操作之后(否则会出现死锁)
    • V操作不会导致进程阻塞,因此两个V操作顺序可以交换

7.2 多生产者-多消费者问题

  1. 关系分析:
    • 互斥关系:对盘子的访问
    • 同步关系1:父亲将苹果放入盘子,女儿才能取
    • 同步关系2:母亲将橘子放入盘子,儿子才能取
    • 同步关系3:盘子为空时,父亲/母亲才能放入水果
  2. 整理思路:
    • 互斥:在临界区前后分别PV
    • 同步:前V后P
  3. 设置信号量:互斥信号量mutex = 1;同步信号量apple、orange为0,plate为1
    在这里插入图片描述
  • 如果缓存区大小为1,则可能不需要设置互斥信号量就可实现(缓冲区大小为1,在任何时刻,apple、orange、plate 三个同步信号量中最多只有一个是1。因此在任何时刻,最多只有一个进程的P操作不会被阻塞,顺利进入临界区)
  • 关键:清理复杂的同步关系
  • 在分析同步问题(一前一后问题)时不能从单个进程行为的角度来分析,要把“一前一后”发生的事看做是两种“事件”的前后关系

7.3 读者-写者问题

  • 问题描述:
    • 允许多个读者可以同时对文件执行读操作
    • 只允许一个写者往文件中写信息
    • 任一写者在完成写操作之前不允许其他读者或写者工作
    • 写者执行写操作前,应让已有的读者和写者全部退出
  • 分析:
    • 两类进程:写进程、读进程
    • 互斥关系:写进程—写进程、写进程—读进程。读进程与读进程不存在互斥问题
  • 读进程优先实现
    在这里插入图片描述
  • 读写公平法实现
    在这里插入图片描述
  • 核心思想:设置了一个计数器 count 用来记录当前正在访问共享文件的读进程数;count 变量需要“一气呵成”,即用互斥信号量;解决“写进程饥饿”问题

7.4 吸烟者问题

  • 问题描述:一个系统有三个抽烟者进程和一个供应者进程。供应者进程无限地提供三种材料(每次将两种材料放桌子上),拥有剩下一种材料的抽烟者卷一根烟抽掉它,并给供应者进程一个信号告诉完成了,供应者就会放另外两种材料再桌上,这个过程一直重复(让三个抽烟者轮流地抽烟)
  • 实质:可生产多种产品的单生产者-多消费者
  • 关系分析:
    • 互斥关系:桌子抽象为容量为1的缓冲区
    • 同步关系(从事件的角度来分析):
      • 桌上有组合一 → 第一个抽烟者取走东西
      • 桌上有组合二 → 第二个抽烟者取走东西
      • 桌上有组合三 → 第三个抽烟者取走东西
      • 发出完成信号 → 供应者将下一个组合放到桌上
        在这里插入图片描述

7.5 哲学家就餐问题

在这里插入图片描述

  • 关键在于解决进程死锁(每个进程都需要同时持有两个临界资源,因此就有“死锁”问题的隐患)
  • 各哲学家拿筷子这件事必须互斥的执行:
    • 保证即使一个哲学家在拿筷子拿到一半时被阻塞,也不会有别的哲学家会继续尝试拿筷子
    • 当前正在吃饭的哲学家放下筷子后,被阻塞的哲学家就可以获得等待的筷子了
  • 思路三:
    在这里插入图片描述

八、管程与死锁

8.1 管程

在这里插入图片描述

  • 信号量机制存在的问题:编写程序困难、易出错
  • 1973年,Brinch Hansen 首次在程序设计语言 (Pascal)中引入了“管程”成分——一种高级同步机制
  • 管程是一种特殊的软件模块,有这些部分组成:
    1. 局部于管程的共享数据结构说明
    2. 对该数据结构进行操作的一组过程
    3. 对局部于管程的共享数据设置初始值的语句
    4. 管程有一个名字
  • 管程的基本特征:
    1. 局部于管程的数据只能被局部于管程的过程所访问
    2. 一个进程只有通过调用管程内的过程才能进入管程访问共享数据
    3. 每次仅允许一个进程在管程内执行某个内部过程
  • 目的:要更方便地实现进程互斥和同步

8.1.1 用管程解决生产者消费者问题

在这里插入图片描述

  1. 管程中定义共享数据(如缓冲区)
  2. 管程中定义用于访问这些共享数据的“入口”——一些函数(如生产者消费者问题中,可以定义一个函数用于将产品放入缓冲区,再定义一个函数用于从缓冲区取出产品)
  3. 只有通过这些特定的“入口”才能访问共享数据
  4. 管程中有很多“入口”,但是每次只能开放其中一“入口”,并且只能让一个进程或线程进入(即可保证一个时间段内最多只会有一个进程在访问缓冲区,这种互斥特性是由编译器负责实现的)
  5. 可在管程中设置条件变量及等待/唤醒操作以解决同步问题。可以让一个进程或线程在条件变量上等待(该进程应先释放管程的使用权,也就是让出“入口”);可以通过唤醒操作将等待在条件变量上的进程或线程唤醒

8.1.2 类似管程的实践——Java 关键字 synchronized

关键字synchronized 描述的一个函数同一时间段内只能被一个线程调用:
在这里插入图片描述

8.2 死锁

在这里插入图片描述

8.2.1 死锁、饥饿、死循环

  • 死锁:各进程互相等待对方手里的资源,导致各进程都阻塞,无法向前推进的现象
  • 饥饿:由于长期得不到想要的资源,某进程无法向前推进的现象。比如:在短进程优先(SPF)算法中,若有源源不断的短进程到来,则长进程将一直得不到处理机,从而发生长进程“饥饿”
  • 死循环:某进程执行过程中一直跳不出某个循环的现象。有时是因为程序逻辑 bug 导致的,有时是程序员故意设计的
    在这里插入图片描述

8.2.2 产生死锁的四个必要条件

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

  1. 互斥条件:对必须互斥使用的资源的争抢才会导致死锁(如哲学家的筷子、打印机设备;像内存、扬声器这样可以同时让多个进程使用的资源是不会导致死锁的)
  2. 不剥夺条件:进程所获得的资源在未使用完之前,不能由其他进程强行夺走,只能主动释放。
  3. 请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源又被其他进程占有,此时请求进程被阻塞,但又对自己已有的资源保持不放。
  4. 循环等待条件:存在一种进程资源的循环等待链,链中的每一个进程已获得的资源同时被下一个进程所请求。
  • 循环等待是死锁的必要不充分条件:发生死锁时一定有循环等待,但是发生循环等待时未必死锁
  • 如果同类资源数大于1,则即使有循环等待,也未必发生死锁。但如果系统中每类资源都只有一个,那循环等待就是死锁的充分必要条件了

8.2.3 发生死锁的时机

对不可剥夺资源的不合理分配,可能导致死锁:

  1. 对系统资源的竞争。各进程对不可剥夺的资源(如打印机)的竞争可能引起死锁,对可剥夺的资源(如CPU)的竞争是不会引起死锁的。
  2. 进程推进顺序非法。请求和释放资源的顺序不当,也同样会导致死锁。例如,并发执行的进程P1、P2 分别申请并占有了资源 R1、R2,之后进程P1又紧接着申请资源R2,而进程P2又申请资源R1,两者会因为申请的资源被对方占有而阻塞,从而发生死锁。
  3. 信号量的使用不当也会造成死锁。如生产者-消费者问题中,如果实现互斥的P操作在实现同步的P操作之前,就有可能导致死锁。(可以把互斥信号量、同步信号量也看做是一种抽象的系统资源)

8.3 死锁的处理策略

8.3.1 静态策略:预防死锁

在这里插入图片描述

破坏互斥条件
  • 互斥条件:只有对必须互斥使用的资源的争抢才会导致死锁
  • 策略:把只能互斥使用的资源改造为允许共享使用,则系统不会进入死锁状态。
  • 缺点:并不是所有的资源都可以改造成可共享使用的资源;并且为了系统安全,很多地方还必须保护这种互斥性
破坏不剥夺条件
  • 不剥夺条件:进程所获得的资源在未使用完之前,不能由其他进程强行夺走,只能主动释放。
  • 方案一:当某个进程请求新的资源得不到满足时,它必须立即释放保持的所有资源,待以后需要时再重新申请(即使某些资源尚未使用完,也需要主动释放,从而破坏了不可剥夺条件)
  • 方案二:当某个进程需要的资源被其他进程所占有的时候,可以由操作系统协助,将想要的资源强行剥夺。这种方式一般需要考虑各进程的优先级(比如:剥夺调度方式,就是将处理机资源强行剥夺给优先级更高的进程使用)
  • 缺点:
    1. 实现起来比较复杂
    2. 释放已获得的资源可能造成前一阶段工作的失效。因此这种方法一般只适用于易保存和恢复状态的资源,如CPU
    3. 反复地申请和释放资源会增加系统开销,降低系统吞吐量
    4. 若采用方案一,意味着只要暂时得不到某个资源,之前获得的那些资源就都需要放弃,以后再重新申请。如果一直发生这样的情况,就会导致进程饥饿。
破坏请求和保持条件
  • 请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源又被其他进程占有,此时请求进程被阻塞,但又对自己已有的资源保持不放
  • 采用静态分配方法,即进程在运行前一次申请完它所需要的全部资源,在它的资源未满足前,不让它投入运行。一旦投入运行后,这些资源就一直归它所有,该进程就不会再请求别的任何资源了
  • 该策略实现简单
  • 缺点:有些资源可能只需要用很短的时间,因此如果进程的整个运行期间都一直保持着所有资源,就会造成严重的资源浪费,资源利用率极低;有可能导致某些进程饥饿
破坏循环等待条件
  • 循环等待条件:存在一种进程资源的循环等待链,链中的每一个进程已获得的资源同时被下一个进程所请求
  • 采用顺序资源分配法:首先给系统中的资源编号,规定每个进程必须按编号递增的顺序请求资源,同类资源(即编号相同的资源)一次申请完
  • 原理分析:一个进程只有已占有小编号的资源时,才有资格申请更大编号的资源。按此规则,已持有大编号资源的进程不可能逆向地回来申请小编号的资源,从而就不会产生循环等待的现象
  • 缺点:
    1. 不方便增加新的设备,因为可能需要重新分配所有的编号
    2. 进程实际使用资源的顺序可能和编号递增顺序不一致,会导致资源浪费
    3. 必须按规定次序申请资源,用户编程麻烦

8.3.2 动态策略:避免死锁

安全序列与不安全状态
  • 安全序列:若系统按照这种序列分配资源,则每个进程都能顺利完成;只要能找出一个安全序列,系统就是安全状态;安全序列可能有多个
  • 若分配了资源后,系统中找不出任何一个安全序列,系统就进入了不安全状态;之后可能所有进程都无法顺利的执行下去。当然,若有进程提前归还了一些资源,那系统也有可能重新回到安全状态(在分配资源之前总是要考虑到最坏的情况)
  • 如果系统处于安全状态,就一定不会发生死锁;如果系统进入不安全状态,就可能发生死锁(处于不安全状态未必就是发生了死锁,但发生死锁时一定是在不安全状态
  • 在资源分配之前预先判断这次分配是否会导致系统进入不安全状态,以此决定是否答应资源分配请求;即“银行家算法”的核心思想
银行家算法
  • 银行家算法是荷兰学者 Dijkstra 为银行系统设计的,以确保银行在发放现金贷款时,不会发生不能满足所有客户需要的情况;后来该算法被用在操作系统中,用于避免死锁
  • 核心思想:在进程提出资源申请时,先预判此次分配是否会导致系统进入不安全状态。如果会进入不安全状态,就暂时不答应这次请求,让该进程先阻塞等待
    在这里插入图片描述
  • 数据结构:
    • 长度为 m 的一维数组 Available 表示还有多少可用资源
    • n*m 矩阵 Max 表示各进程对资源的最大需求数
    • n*m 矩阵 Allocation 表示已经给各进程分配了多少资源
    • Max – Allocation = Need 矩阵表示各进程最多还需要多少资源
    • 用长度为 m 的一位数组 Request 表示进程此次申请的各种资源数
  • 银行家算法步骤:
    1. 检查此次申请是否超过了之前声明的最大需求数
    2. 检查此时系统剩余的可用资源是否还能满足这次请求
    3. 试探着分配,更改各数据结构
    4. 用安全性算法检查此次分配是否会导致系统进入不安全状态
  • 安全性算法步骤:检查当前的剩余可用资源是否能满足某个进程的最大需求,如果可以,就把该进程加入安全序列,并把该进程持有的资源全部回收;不断重复,看最终能否让所有进程都加入安全序列

8.3.3 检测和解除死锁

在这里插入图片描述

  • 若系统中既不采取预防死锁的措施,也不采取避免死锁的措施,系统就很可能发生死锁
  • 在这种情况下,系统应当提供两个算法:
    1. 死锁检测算法:用于检测系统状态,以确定系统中是否发生了死锁
    2. 死锁解除算法:当认定系统中已经发生了死锁,利用该算法可将系统从死锁状态中解脱出来
死锁的检测
  • 检测死锁:用某种数据结构来保存资源的请求和分配信息;提供一种算法,利用上述信息来检测系统是否已进入死锁状态
    在这里插入图片描述
  • 最终能消除所有边,就称这个图是可完全简化的;此时一定没有发生死锁(相当于能找到一个安全序列)
  • 如果最终不能消除所有边,那么此时就是发生了死锁;最终还连着边的那些进程就是处于死锁状态的进程
  • 死锁定理:如果某时刻系统的资源分配图是不可完全简化的,那么此时系统死锁
  • 检测死锁的算法:
  1. 在资源分配图中,找出既不阻塞又不是孤点的进程 Pi(即找出一条有向边与它相连,且该有向边对应资源的申请数量小于等于系统中已有空闲资源数量),消去它所有的请求边和分配边,使之称为孤立的结点
  2. 进程 Pi 所释放的资源,可以唤醒某些因等待这些资源而阻塞的进程,原来的阻塞进程可能变为非阻塞进程
死锁的解除
  • 解除死锁的主要方法有:
    1. 资源剥夺法:挂起(暂时放到外存上)某些死锁进程,并抢占它的资源,将这些资源分配给其他的死锁进程;同时应防止被挂起的进程长时间得不到资源而饥饿。
    2. 撤销进程法(或称终止进程法):强制撤销部分、甚至全部死锁进程,并剥夺这些进程的资源;优点:实现简单,缺点:所付出的代价可能会很大,因为有些进程可能已经运行了很长时间,已经接近结束了,一旦被终止可谓功亏一篑,以后还得从头再来
    3. 进程回退法:让一个或多个死锁进程回退到足以避免死锁的地步;这就要求系统要记录进程的历史信息,设置还原点
  • 处理进程的选择:进程优先级、已执行多长时间、还要多久能完成、进程已经使用了多少资源、进程是交互式的还是批处理式的
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

啊有礼貌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值