操作系统学习笔记第2章 (竟成)

目录

第 2 章 进程管理

2.1 进程与线程

2.1.1 进程的概念和特征

2.1.2 进程的状态与转移

2.1.3 进程组织

2.1.4 进程控制

2.1.5 线程概念和多线程模型

2.1.6 进程和线程的对比

2.1.7 进程通信

2.1.8 习题精编

2.1.9 真题演练

2.2 处理器调度与上下文切换

2.2.1 调度的基本概念

2.2.2 调度的时机

2.2.3 调度的方式

2.2.4 调度算法的目标

2.2.6 CPU 调度算法

2.2.7 甘特图

2.2.8 多处理机调度

2.2.9 习题精编

2.2.10 真题演练

 2.3 进程同步

2.3.1 进程同步的基本概念

2.3.2 临界资源与临界区

2.3.3 同步与互斥

2.3.4 同步机制设计准则

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

2.3.6 信号量

2.3.7 经典同步问题

2.3.8 管程

2.3.9 习题精编

2.3.10 真题演练

2.4 死锁

2.4.1 死锁的概念

2.4.2 死锁的处理方法

2.4.3 死锁预防

2.4.4 死锁避免

2.4.5 死锁检测

2.4.6 死锁解除

2.4.7 习题精编

2.4.8 真题演练

2.5 章末总结

2.5.1 本章知识点提炼

2.5.2 思考题解析


第 2 章 进程管理

【考纲内容】

1.进程与线程:
(1) 进程 / 线程的基本概念;
(2) 进程 / 线程的状态与转换;
(3) 线程的实现:内核支持的线程;线程库支持的线程;
(4) 进程与线程的组织与控制;
(5) 进程间通信:共享内存;消息传递;管道;信号

2.CPU 调度与上下文切换:
(1) 调度的基本概念;
(2) 调度的目标;
(3) 调度的实现:调度器 / 调度程序(scheduler);调度的时机与调度方式(抢占式 / 非抢占式);闲逛进程;内核级线程与用户级线程调度;
(4) CPU 调度算法;
(5) 多处理机调度;
(6) 上下文及其切换机制

3.同步与互斥:
(1) 同步与互斥的基本概念;
(2) 同步与互斥的基本方法:软件实现方法;硬件实现方法;
(3) 锁;
(4) 信号量;
(5) 条件变量;
(6) 经典同步问题:生产者 - 消费者问题;读者 - 写者问题;哲学家进餐问题

4.死锁:
(1) 死锁的概念;
(2) 死锁预防;
(3) 死锁避免;
(4) 死锁检测和解除

【考情统计】

年份

单选题数

综合题数

总分值

考点

2009

1

1

9

典型调度算法、死锁原因、P/V 操作

2010

4

0

8

进程控制、典型调度算法、信号量机制、饥饿问题

2011

4

1

16

典型调度算法、线程概念和多线程模型、银行家算法、同步机制、P/V 操作

2012

4

0

8

进程概念与特征、线程概念和多线程模型、调度基本概念、进程概念、线程概念、线程实现、线程组织、银行家算法

2013

2

1

11

典型调度算法、银行家算法、P/V 操作

2014

4

1

16

进程状态与转移、典型调度算法、进程通信、死锁、P/V 操作

2015

2

1

13

进程状态与转换、进程控制、死锁避免、死锁检测、P/V 操作

2016

5

1

16

调度的基本概念、典型调度算法、死锁原因、硬件同步、互斥机制、管程机制、饥饿问题

2017

2

1

12

调度的方式、典型调度算法、调度算法、P/V 操作

2018

6

0

12

进程的状态与转移、调度的方式、典型调度算法、状态转换、互斥机制、银行家算法、管程机制、同步机制

2019

4

1

16

线程概念和多线程模型、典型调度算法、死锁原因、银行家算法、死锁预防、死锁解除、P/V 操作

2020

4

1

15

进程概念与特征、调度算法的目标、典型调度算法、银行家算法、互斥机制、临界资源、P/V 操作

2021

4

1

15

进程状态、进程组织、典型调度算法、调度的方式、调度时机、互斥机制、临界资源

2022

3

1

14

进程组织、典型调度算法、银行家算法、进程状态

2023

2

2

13

进程状态、典型调度算法、硬件同步

2024

3

1

14

进程终止、进程切换、线程、P/V 操作综合题

【考点解读】

        本章是选择题和综合题考查的重点章节。进程与线程一节中,历年考查的形式为概念相关的选择题。一般考查 1 道选择题,与其他章节内容结合考查时可能命制 2 道选择题。处理器调度与上下文切换一节中,着重考查调度相关的概念选择题和典型调度算法相关的计算选择题,一般考查 1 道选择题。2016 年的 408 试卷比较特殊,针对调度的概念考查了一次综合题,请考生注意。
        进程同步是十分重要的一节,408 统考几乎每年都会考查 1 道 7 分左右的综合应用题,此外还会以客观题的形式考查临界资源与临界区、同步与互斥等基本概念。如果考生充分掌握信号量机制的应用,得分并不困难。死锁一节中,平均每年考查 1 道选择题,其中对银行家算法的考查尤为侧重,值得考生重点关注。另外,进程间信号通信机制、多处理机调度是 2025 考研 408 大纲中新增考点。

【复习建议】

关于本章考生应:

1.理解进程的概念与特征、线程的概念、多线程模型。

2.重点掌握进程的状态转移,进程的控制相关内容。

3.了解进程的组织,其中 PCB 相关内容需要理解。

4.理解线程和进程之间的相同点和不同点。

5.了解进程通信的四种方式,尤其注意管道、共享内存和消息传递机制。

6.了解调度的机制、时机、方式和实现,这有助于考生对调度有一个全面的了解。

7.重点掌握调度算法的目标。

8.重点掌握典型调度算法。本章列举的七种调度算法中,除了多级队列(注意不是多级反馈队列)以外,都应该熟练掌握。

9.甘特图是分析调度的工具,建议熟练运用于涉及调度的计算中。

10.理解进程同步、临界区、临界资源的基本概念。

11.重点掌握同步机制的四个设计准则,它们为后续知识作了重要铺垫。

12.了解实现临界区互斥的基本方法,重点关注其中的 Peterson 算法。

13.重点掌握信号量的类型及其应用,可以应用信号量实现进程的同步、互斥和前驱关系。

14.重点掌握生产者 - 消费者问题、哲学家进餐问题、读者 - 写者问题等经典同步问题,力求熟练使用 P/V 操作解决复杂的多进程同步问题。

15.了解管程的定义、组成和基本特性。

16.理解死锁的定义和原因,重点掌握死锁产生的四个必要条件。

17.了解资源问题和资源分配图。

18.理解死锁预防、死锁检测和解除的概念和方法。

19.重点掌握死锁避免的概念和方法,重点关注其中的安全状态和银行家算法。

2.1 进程与线程

        本节的目标是对进程的概念、组织、控制、通信问题以及线程的概念、线程实现方式有一个全面的认识。进程和线程是操作系统的基本概念,也是考试重点考查的内容。尽管本节考查的知识范围广,但难度适中,对于进程和线程的理解到位后,很多知识点记忆负担不大。考生在学习本节内容时,可以先尝试对进程和线程建立起一个总体的认识,通过本节的各个例子和提示加深理解,尝试回答下列问题(答案见本章末总结)。
        (1) 什么是进程,有什么特点?是否可以举出一个实际的例子?
        (2) 一个进程由哪些部分组成,为什么要提出进程这一概念?
        当考生可以回答上述问题时,说明已经对本节知识点有了一定的理解,接下来需要针对各个知识点进行系统性学习,当能回答以下问题时,说明已经系统地掌握了本节(题目可以在各个小节中找到答案)。
        (1) 进程在其生命周期中,有哪些状态?这些状态间哪些状态是可以转移的,什么场合下可以转移?
        (2) 进程控制有哪些原语?以进程创建为例,能否说出进程创建原语发生在哪些场合,具体执行过程是什么?
        (3) PCB 中有哪些内容,为什么需要这些内容?
        (4) 进程通信有哪些方式?

2.1.1 进程的概念和特征

1. 进程的概念

        自诞生以来,操作系统就需要运行各种各样的程序。为了管理这些程序的运行,操作系统提出了进程(Process)这一概念。进程(Process)是计算机中一个执行的程序实例,每个进程对应一个运行中的程序。是操作系统资源分配和调度的独立单位,是操作系统中最基础与最重要的概念之一。有了进程这一概念,应用程序在运行时仿佛独占了 CPU,而不用考虑何时需要将 CPU 让给其他程序;进程的管理等任务则交给操作系统。
        一个进程包括了以下内容:程序段(Program Code)、数据段和进程控制块。其中,程序段和数据段是一组指令、数据和对其组织描述的总和;操作系统通过进程控制块(参见本节进程控制块)描述程序的 “状态”。
        程序段与数据段存储在磁盘上,是静态的;进程是指将这些程序与数据从磁盘加载到内存后的执行过程,是动态的,多个进程可以对应于同一段程序代码。
        【举例】“int a = 1;” 包含了指令与数据,而我们写程序时有序地将代码组织起来,就是在对其组织进行描述。Windows 操作系统中一个 exe 文件就是一个程序,执行该程序就会创建一个对应进程,执行多次该文件会创建出多个对应进程,所以进程是动态的,是一次执行过程。
这里列举更多角度下对进程的定义:
        (1) 进程是程序的一次执行过程 。
        (2) 进程是一个程序及其数据在处理器上顺序执行时所发生的活动 。
        (3) 进程是具有独立功能的程序在一个数据集合上运行的过程,它是系统进行资源分配和调度的独立单位 。
        (4) 进程是正在计算机上执行的程序实例 。
        (5) 进程是能分配给处理器并由处理器执行的实体 。
        (6) 进程是由一组执行的指令、一个当前状态和一组相关的系统资源表征的活动单元 。
        (7) 进程由程序段、数据段、进程控制块三部分组成。
        【举例】在现实世界中,我们准备好了各种的食材(数据)并编写了一个料理配方(每个步骤的操作 + 将操作合理地组织),这就对应了一个程序。按照料理配方开始料理食材的过程,就对应了一个进程。显然,我们可以使用一个料理配方对两批食材同时处理,这就相当于开启了两个进程;在料理一批食材的过程中,安排多个人同时进行料理,这就相当于开启了多个线程。

2. 进程控制块

        进程控制块(Process Control Block,PCB),是操作系统为每个进程配备的一个记录型数据结构。PCB 上包含了操作系统管理进程所需要的信息,用于管理操作系统中存在的所有进程,是进程的组成部分之一。进程与线程的组织一节会详细介绍 PCB 的主要内容、功能。
        【提示】考生第一次接触到 PCB 这个概念时,可以类比学生档案。一个班级(对应操作系统)会有一份记录所有学生信息的表格(对应 PCB),每个学生有一个独一无二的学号(对应进程标识符)以及其他重要信息,如是否报到(对应进程状态)等。操作系统用 PCB 来记录和管理所有进程,就像班级管理者利用学生档案来记录和管理每一位学生。

3. 进程的特点

        与程序相比,进程主要有以下 4 个特点。理解进程的并发性、独立性和异步性时,可以和批处理系统中作业的顺序执行进行对比学习。

(1) 动态性

        进程的实质是程序在多道系统中的一次执行(Execution),从创建到消亡,是动态的、具有生命周期的。与此相对,程序只是一串二进制位,静态存储于硬盘等介质之中,并不具有动态性。

(2) 并发性

        引入进程概念后,操作系统中可以有多个进程并发执行。当多个进程并发执行时,会有以下 3 个特性:

(a) 间断性

        并发的进程会有间断运行的特点。当多个进程协同完成同一任务时,需要进行同步等操作,进而导致进程的暂停。例如,两个进程 Pa​、Pb​ 协作处理一组数据。Pa​ 需要对 Pb​ 处理后的数据进行进一步处理,那么 Pa​ 在得到 Pb​ 数据前,会陷入阻塞态。Pa​ 进程的执行过程可以描述为:执行 —— 阻塞 —— 就绪 —— 执行。

(b) 失去封闭性

        当系统中存在多个进程并发执行时,这些并发进程将会共享系统中的各类资源。任意进程都可能改变某个资源的状态,从而影响到其他进程。失去封闭性意味着进程的执行结果与执行速度有关,使得进程的执行有不可再现性。失去封闭性导致了不可再现性。

(c) 不可再现性

        并发执行的进程,在相同的初始环境和条件下,可能得到不同的结果。这是因为进程失去了封闭性。例如,进程 Pa​ 和 Pb​ 共享一个整数型共享变量 M,Pb​ 的工作是对 M 进行三次加一操作,Pa​ 的工作是读出 M 的值,则 Pb​ 进程运行的快慢会直接导致 Pa​ 读出的结果不同。
        【提示】重温并发(Concurrent)和并行(Parallel)两个概念。并发是多个进程在同一时间段内运行,在同一时刻仅有一个进程处于执行态;并行则是同一时刻多个进程处于执行态。在单处理器多道程序系统中,进程可以并发执行;在多处理器系统中,进程不但可以并发执行,还能并行执行。

(3) 独立性

        进程是操作系统调度和资源分配的最小单位,即一个进程能够独立运行。各个进程获得的资源独立于其他进程。

(4) 异步性

        因为异步性,进程的执行、暂停等变得不可预知,进程以不可预知的状态向前推进。
        【提示】进程的并发性导致了进程的异步性。通过使用多种同步手段(参见本章同步与互斥),可以保证在异步性的前提下得到确定(可再现)的结果。

2.1.2 进程的状态与转移

1. 进程的五种状态

        由于进程的并发特征,进程在操作系统中呈现出间断运行的规律,因此进程在其生命周期中会经历不同的状态。408 考试大纲中要求掌握进程的五种状态:创建状态、就绪状态、执行状态、阻塞状态、终止状态,如图 2.1 所示。其中,就绪状态、执行状态、阻塞状态三个状态称为基本状态,因为在一个进程的生命周期中,这三种基本状态可以多次切换,而创建状态和终止状态在整个周期中只能有一次。所以在讨论进程的状态转移问题时,常常用三状态模型一词对其描述。简洁起见,后文使用创建态、就绪态、执行态、阻塞态、终止态指代上述五种状态。

(1) 创建(New)态

        当进程第一次被创建时所处的状态。在此状态下,进程等待着由此状态转换为 “就绪态”。操作系统决定进程由创建态转换为就绪态时,一般会考虑系统资源的占用情况。若当前系统资源紧张,系统会推迟处于创建态的进程转换为就绪态。

(2) 就绪(Ready)态

        当一个进程获得了其他所有资源,等待处理器的分配时,其状态为就绪态。在此状态下,进程排队等待系统为其分配处理器资源。就绪态是很常见的状态,处理器作为 “珍贵” 的资源,很难同时分配给所有就绪态的进程。
        【提示】尽管计算机可以有多个处理器,但是依然难以满足所有进程对处理器资源的请求。在一个单处理器的计算机中,任意时刻最多只能有一个进程处于执行态,其他进程只能处于其他状态。由于处于就绪态的进程很多,这些进程一般被放在就绪队列中等待调度。后续的处理器调度章节中,会详细讨论将处理器分配给进程的各种调度算法。

(3) 执行(Running)态

        当一个进程被分配处理器并开始执行时,就进入了执行态。进程中的程序代码得以在处理器上运行。
        【提示】内核态下,进程能同时访问内核和用户地址、不受限制地访问硬件,执行特权指令;用户态下,进程不能访问内核的指令和数据。这种模式的设计保证了安全性。

(4) 阻塞(Block)态

        阻塞态是指进程在执行过程中因为发生某些事件而导致无法继续执行的状态。将进程转换为阻塞态是因为:操作系统不希望进程因为等待未到来的数据或条件而导致处理器的空闲,系统效率下降。
        【提示】导致进程进入阻塞态的事件很多,可能是进程发生了 I/O 操作,等待关键数据从磁盘读入内存;可能是进程申请访问临界资源但该临界资源被其他进程占有(详见本章进程同步一节)。

(5) 终止(Terminated)态

        当进程结束时,就会进入终止态。进入终止态的原因可能是进程正常地结束,也可能是被用户强制结束(例如 Linux 中向进程发送 KILL 信号,或 Windows 下在任务管理器中结束进程)。该状态下,操作系统开始回收该进程占有的资源。
        【拓展】① 终止态中有一个术语:僵尸进程(Zombie Process)。用来描述那些已经处于终止态但却未被从系统进程表中删除的进程。Unix 系统中,有父进程的子进程可能会发生这种情况。僵尸进程会占用进程号,对系统的危害是:可能导致系统中没有可用的进程号而无法产生新进程。② 父进程已经结束运行,但它的子进程还在运行,将这些子进程称为孤儿进程。孤儿进程会被进程号为 1 的 init 进程所收养,并由 init 进程完成其状态收集等善后工作。孤儿进程对系统没有危害。
进程的五种状态特点对比如表 2.1 所示。

2. 进程的状态转换

进程可能出现的状态转换及对应的原因如表 2.2 所示。表 2.2 中进程状态转换说明如下:
(1) 创建态→就绪态:进程创建成功后,首先会被设置为就绪态,插入就绪队列中。
(2) 就绪态→执行态:处于就绪态的进程被调度后,转换为执行态并分配处理器,运行。
(3) 执行态→就绪态:处于执行态的进程在其运行过程中,由于分配给它的处理器时间片用完而让出处处理器,变为就绪态。
        【提示】还有很多情况会导致这种状态转换。在抢占式操作系统中,当有优先级更高的进程变为就绪态时,当前正在使用处理器的进程会让出处处理器并由执行态变为就绪态。
(4) 执行态→阻塞态:当处于执行态进程请求某资源且必须等待时,会(主动)转换为阻塞态。
(5) 阻塞态→就绪态:当处于阻塞态的进程得到之前请求的全部资源后,会被唤醒(被动)并转换为就绪态。
(6) 执行态→终止态:当处于执行态的进程运行完毕、发生错误、被用户或操作系统强制终止时,会转换为终止态。
        【提示】阻塞态无法直接转换为执行态,这取决于调度算法的设计。调度算法调度的对象就是就绪队列中的进程,故一个进程结束阻塞态后,会先转换为就绪态。

2.1.3 进程组织

        进程是操作系统资源分配的最小单位,在未引入线程概念的操作系统中,同样是调度的最小单位。一个进程包含了代码段、数据段以及进程控制块 PCB。

1. 进程控制块 PCB

① PCB 的功能概述

PCB 主要有以下五个功能。
        (1) 作为独立运行基本单位的标志:当一个程序(含数据)配置了 PCB 后,就表示该程序能够在支持多道程序的系统上独立运行。
        (2) 实现进程的间断运行:在多道程序环境下,进程的运行是断断续续的。当一个进程由执行态转换为其他状态时,必须要保留该进程运行时的处理器现场信息,用于之后的处理器现场恢复。如果没有 PCB 来保存进程停止运行时的现场信息,那么该进程便失去了间断运行的能力。
        (3) 提供进程管理的重要信息:例如,当调度程序希望某个进程运行时,会通过 PCB 中的对应指针,找到该进程存储在内 / 外存上的程序和数据。PCB 中还会记录进程打开文件、拥有资源等多种信息,用以实现进程管理的多种功能。
        (4) 提供进程调度所需要的信息:PCB 上存储了进程的状态,用于帮助调度程序合理地完成调度。有些调度算法还需获知进程的等待时间等信息,这些信息往往也存储于 PCB 中。
        (5) 实现与其他进程的同步与通信:操作系统为实现进程间的同步与通信,在 PCB 中也设置了相应的数据结构。例如,采用信号量机制实现进程同步时,信号量就是位于 PCB 中。

② PCB 中的内容

        不同的操作系统内核对于 PCB 这一数据结构的实现不尽相同,PCB 中内容如表 2.3 所示,总体分为四类:
        (1) 标识符:标识符是唯一标识,以数字或数字加字母的形式存储在 PCB 中。Linux 中,每个进程都有一个称为 PID(Process ID)的进程标识符。


        (2) 处理器状态信息:处理器状态信息又称为处理器上下文,记录进程在执行态下关键寄存器的信息,用于进程切换后恢复处理器上的重要信息。涉及的寄存器主要有:①通用寄存器;②程序计数器 (Program Counter, PC);③程序状态字 (Program State Word, PSW);④用户栈指针。
        【提示】PCB 中存储的寄存器数量和种类都不是固定的,取决于处理器的设计。PCB 存储的寄存器信息用于上下文切换,故 PCB 也被称为切换帧 (Switch Frame),就好像在进程停止运行前对处理器上的各种重要信息做了一个 “快照”,当操作系统想要重新让暂停的进程继续运行时,只需要把之前保存的信息恢复到处理器即可。
        (3) 处理器调度信息:该类信息的存储取决于操作系统使用何种调度方法来将处理器分配给进程,该类信息一般包括:
(a) 进程调度状态:记录执行态、就绪态、阻塞态等。
(b) 进程调度优先级:在多个进程就绪时,用于决定哪个进程先获得处理器资源。
(c) 调度的相关信息:这取决于调度算法。例如进程已使用的处理器时间、进程等待处理器的时间等,这些信息用于实现操作系统的调度算法。
(d) 事件:指进程由执行态转换为阻塞态的原因。记录进程的阻塞原因,可以帮助操作系统更好地设计各个进程的优先级。
        (4) 进程控制信息:用于进程控制所必须的信息,包括:
(a) 代码段和数据段的地址:在 PCB 中通过指针的方式标明其在内存或外存中的位置。
(b) 进程同步和通信机制:用来实现进程同步的信号量、消息队列指针等。
(c) 资源清单:记录操作系统分配给该进程的全部资源,用于资源分配和回收等。
(d) 链接指针:PCB 所在队列中下一个进程的 PCB 首地址。操作系统为了能对 PCB 加以有效管理,一般按照进程所处的状态将各 PCB 存储于不同的队列(如就绪队列、若干阻塞队列)中,这些队列往往以链表的方式实现,这也是 PCB 中存储下一个 PCB 首地址的原因。

2.1.4 进程控制

        本小节我们讨论操作系统如何管理进程,包括创建和结束进程、转换进程等。计算机在进程的控制方面,往往使用原语 (Primitive) 完成。原语,也有教材称作原子操作 (Atomic Operation),是指由若干条指令组成用于完成某个行为的过程。原语是不可中断的,即一个原语中的指令要么全部被执行,要么全部不执行。在进程同步一章中,还会学习 P/V 原语。原语保证了使用者能够得到确定的结果。
        【提示】请考生思考两个问题。第一,为什么需要把多个指令设计成一个原语,例如 GetAndSet 指令能不能由 get 指令加上 set 指令代替?假设一个设备一次只能由一个进程使用,操作系统使用 flag 变量标记是否有进程使用该设备,0 代表空闲,1 代表忙碌。一个进程使用该设备需要进行两步操作:①获得 flag 值,如果 flag==0 则进行下一步,否则等待;②将 flag 值置为 1 并使用该设备。此时 PA​,PB​ 均想要使用该设备,如果上述两个步骤不是原语操作,则可能发生这样的执行序列:PA​ 成功执行①;PB​ 成功执行①;PA​ 成功执行②并开始使用设备;PB​ 成功执行②并开始使用设备。这样就发生了错误,因为两个进程都认为自己可以使用设备。如果上述两个步骤是原语操作,就不会出现这种错误。
        第二,为什么在进程控制的过程中要使用原语?因为进程创建、进程唤醒等一系列控制操作均包含了多个步骤,要确保这些步骤全部进行或全部不进行才能得到正确的结果。假设需要进行进程唤醒操作,该操作至少分为两个步骤:①将待唤醒进程的状态设置为就绪态;②将该进程放入就绪队列中。倘若①和②不是原语操作,在执行完①后,发生中断,则此时一个就绪态的进程却被放在了阻塞队列中,这样就发生了错误。

1. 进程的创建

① 进程创建的场合

进程创建的场合主要有以下四类:
(1) 用户登录时。
(2) 高级调度发生时:即作业由外存等待队列被调度到内存中,进行初始化的场合。
(3) 系统响应用户程序提出的请求时:此时操作系统会创建对应的服务进程。情况 1、2、3 均是操作系统内核为用户创建进程。
(4) 现有进程创建新进程时:用户进程可以通过创建新进程来提高程序并发度。例如用户希望同时从键盘接受输入、处理数据、将数据转为统计图显示在屏幕上,就可以在数据处理进程之外新建键盘输入进程和统计图展示进程。

② 进程创建原语的执行过程

(1) 为在创建中的新进程分配一个进程标识符(PID),作为进程的唯一标识;为进程分配空白 PCB,用于记录进程的各类资源与信息。
(2) 为该新进程分配所需资源。包括各类的物理资源和逻辑资源,如内存、文件、I/O 设备和处理器时间等。
(3) 初始化 PCB 上的内容。包括进程标识符、父进程标识符、处理器状态信息和处理器控制信息。
(4) 若上述过程顺利,将该新进程状态设置为就绪态,放入就绪队列,等待调度;若不顺利,分配资源不足时,则创建该新进程的父进程置为阻塞态,新进程仍处于创建状态。
        【提示】各种操作系统在进程创建原语的设计上不尽相同,以 Linux 为例,除了 PID 为 0 的进程以外,其余的进程都是由 PID 为 0 的进程创建而来的,而这个 “0” 进程是内核进程,相比于其他进程的数据结构都是动态分配的,它的数据结构是静态的。Linux 提供了 fork、vfork 和 clone 三种函数用于进程创建,这里简单了解即可。

2. 进程的终止

进程是一个动态的执行过程,这意味着一个进程必然会迎来它的结束。

① 进程终止的场合

(1) 进程运行完毕,正常退出。
        【举例】Linux 系统中的 exit 系统调用,就是用于向操作系统汇报当前进程的工作已经完成,可以终止并回收资源。C 语言中,main 函数执行 return 0 后也会运行完毕,正常退出。
(2) 进程运行中发生出现无法恢复的异常(如整数除以零、访问越界等)而退出。
(3) 被其他进程或操作系统杀死。
【举例】Linux 系统中,得知其他进程的 PID 后,就可以使用 kill 系统调用可以向这一进程发送信号来强制结束对应的进程。

② 终止原语的执行过程

(1) 根据进程的进程号(PID),找到进程对应的 PCB,获取进程的状态。
(2) 修改该进程的状态,置为终止态。
(3) 若该进程运行期间调用了进程创建指令,创建了子进程, 则要把其子进程也进行终止。
(4) 将分配给该进程的所有资源收回:①如果该进程有父进程,则将资源归还给其父进程,这个进程会成为僵尸进程,其 PCB 会被保留;②如果没有父进程,则将资源归还给操作系统。
(5) 将被终止进程的 PCB 从所在队列中移除;若该进程原先处于执行态,则应再从就绪队列中调度一个进程执行。

3. 进程的阻塞和唤醒

        当进程请求资源失败(资源不足、资源未到达等)或等待某个事件发生时,该进程无法继续执行,便会执行阻塞原语,让出处理器资源,变为阻塞态。当进程请求的资源到达或等待的事件发生时,相关进程可调用唤醒原语,通知之前因等待而变为阻塞态的进程,使其变为就绪态。
        【提示】进程阻塞的过程是主动的,由进程自己调用阻塞原语来自 我阻塞(因此进程只能从运行态转为阻塞态,不能由就绪态转为阻塞态);而进程唤醒的过程是被动的,由其他进程调用唤醒原语来唤醒自己。此外,正常情况下,阻塞原语和唤醒原语一定是成对出现的,这样才能保证进程不会被无限期阻塞。

① 进程阻塞发生在以下几个场合

(1) 进程向系统请求临界资源(详见进程同步一节)失败。
        【举例】考虑一台计算机拥有 3 台打印机资源且均已经被分配给其他进程,此刻进程 PA​ 请求使用 1 台打印机,则 PA​ 会调用阻塞原语。等其他进程释放了打印机资源,才会唤醒 PA​ 继续执行。
(2) 进程等待某种操作的完成:进程在执行过程中,一些操作有着严格的先后顺序。
        【举例】在进程启动了 I/O 设备希望读取数据后,该进程必须等待 I/O 设备将数据读取完毕,才能进行之后的操作。这个等待期间,进程会触发阻塞原语进入阻塞态,等待 I/O 操作完成后,再由中断处理程序将进程唤醒。
(3) 新数据尚未到达。
        【举例】考虑进程 PA​、PB​ 相互协作的情况,如果 PA​ 需要 PB​ 的相关数据才能继续运行,则 PA​ 在等待数据时会进入阻塞态,直到 PB​ 向 PA​ 提供数据并将其唤醒。
(4) 等待新任务的到达:当前进程无任务需要处理,但等待着下一个任务的到来,就可以在等待的过程中将自己阻塞。
        【举例】例如网络操作系统中的某些系统进程,在完成任务后,会自我阻塞并等待新任务到来。

② 阻塞原语的执行过程

(1) 根据进程的 PID,找到进程对应的 PCB,获取进程的状态。
(2) 若该进程此时为执行态,则将其置为阻塞态,将其 PCB 插入到阻塞队列中。
(3) 进行上下文切换,从就绪队列中选择一个进程,将其转换为运行态并为其分配处理器,使其可以运行。

③ 进程唤醒发生在以下几个场合:
(1) 进程向系统请求临界资源失败后,使用该临界资源的进程释放了该资源。
(2) 进程等待的操作已经发生。
(3) 进程等待的数据到达。
(4) 进程获得了一个新的任务。
【提示】进程唤醒发生的场合与阻塞发生的场合相对应,记住阻塞发生的场合,自然能想到进程唤醒发生的场合。
④ 唤醒原语的执行过程:
(1) 找到阻塞队列中对应进程的 PCB。
(2) 更改该进程状态为就绪态,将该进程的 PCB 放入就绪队列中。

4. 进程切换

        进程切换,指操作系统内核挂起当前运行的进程,调度就绪进程占用处理器运行。进程切换的过程中,通过 PCB 保存处理器状态信息。
        【提示】因为进程切换需操作系统内核的支持,所以进程切换必然发生在内核态而非用户态。
        为什么需要进程切换?操作系统对计算机的各类资源进行管理,而这些资源又是有限的。为了满足每个进程使用资源的请求,操作系统需要进行处理器调度,即决定哪些资源在哪些时间里归哪些进程使用,而进程切换,就是实现处理器调度的手段之一。
① 上下文切换(Context Switch)
        上下文切换,指保存和恢复处理器上的信息,狭义上,可以认为是保存当前使用处理器进程的相关信息,并将待调度进程的处理器信息恢复到处理器上。“上下文” 一词,可以理解为进程的工作环境。考虑进程 PA​ 要进行进程切换,必须把当前时刻处理器上的重要信息(如寄存器信息)记录,就好像是一张快照。无论之后的其他进程怎样设置处理器上的数据,在 PA​ 重新占有处理器时,操作系统把之前的上下文恢复,从 PA​ 的角度而言,就好像没有任何进程使用过处理器一样。
        【提示】因考纲中使用 “上下文切换” 这一说法,故下文统一使用该种说法。
        【举例】有两个进程 PA​、PB​。PA​ 正在占用处理器运行,处理器中的寄存器里存有它的数据,比如 PC 寄存器存储了该进程执行的代码地址、CR3 寄存器存储了该进程的页表基地址。此时操作系统决定暂停 PA​,让进程 PB​ 使用处理器,之后有机会再让 PA​ 继续运行。那么,当 PA​ 再一次使用处理器时,如何保证处理器中寄存器的内容不变呢?倘若不在 PA​ 放弃处理器时保留该瞬间的一些信息,其他进程使用处理器时就会把这些信息搞得一团糟。可以想象,当 PA​ 重新占有处理器时,曾经处理器中的信息已经改变,那么这个进程就不能继续正常运行了。
② 上下文切换的时机
        上下文切换发生在操作系统从执行态进程处获得控制权的任意时刻。中断、陷阱、系统调用,这些事件都会导致控制权移交给操作系统。上下文切换是进程调度的实现手段之一,是进程调度的一个环节,需结合进程调度的相关内容进一步理解。在 2.2.2 调度的时机这一小节,会讨论调度可以发生的场合和不可以发生的场合,这些场合同样适用于分析上下文切换。
③ 上下文切换过程:
(1) 保存处理器的上下文,例如上文提到的一些重要寄存器。这些信息存储在进程对应的 PCB 中。
(2) 修改进程状态,并将该进程的 PCB 放入对应的队列。
(3) 调度程序选择另一个进程执行。
(4) 读取待调度进程的 PCB,修改其运行状态为执行态。
(5) 更新内存管理的数据结构。
【提示】这一步涉及地址转换知识,请学习内存管理一章后再做了解。
(6) 恢复待调度程序的上下文环境。
(7) 根据程序计数器 (PC) 指向的位置找到下一行执行的代码,恢复该进程。
④ 模式切换:
        模式切换指处理器在不同模式间转换的过程,如果当前操作系统拥有内核态和用户态两个状态,那么模式切换即指从内核态转换为用户态或用户态转换为内核态的过程。
模式切换发生在中断处理的过程中。当处理器检查中断信号并发现存在未处理的中断时,需要执行对应的中断处理程序。具体过程为:将 PC 置为中断处理程序的起始地址,从用户态转换为内核态来获得执行特权指令的权限。这一过程中,需要将①中断处理可能改变的信息;②恢复程序运行需要用到的信息保存到被中断程序的 PCB 中。
        【提示】上下文切换不是模式切换。模式切换确实涉及上下文环境的保存,但和进程切换是不相同的,模式切换可以不改变正处于执行态进程的状态,这种情况下,保存和恢复上下文环境的开销更小。

2.1.5 线程概念和多线程模型

1. 线程的定义

        线程(Threads),是比进程更小的概念,也称轻量级进程(Light Weight Process, LWP),是将进程进一步细分的单位。在多道程序操作系统中,引入了进程的概念,用于解决单处理器环境中程序的并发执行问题。为了进一步提高程序的并发执行程度,又引入了线程的概念,一个进程可以同时拥有多个线程,借助多个线程实现程序的并发执行(而不必借助进程),事实也证明线程的引入确实能改善操作系统的性能,所以多数现代操作系统都引入并实现了线程这一概念。
        【提示】后文会提到用户级线程和内核级线程,一般教材中提到的轻量级进程特指通过内核实现的线程,注意区分。
        未引入线程概念时,进程有两个特点:是资源所有权和调度 / 执行的单位。前者指一个进程拥有对资源的控制和所有权,包括内存、I/O 通道、I/O 设备等;后者指进程是一个可以被操作系统调度的独立实体。线程可以理解为在调度 / 执行这一属性上对进程进一步细分。
        【提示】在引入线程前,进程是操作系统调度的最小单位;在引入线程且是由操作系统内核支持的线程后,线程是操作系统调度的最小单位。同时,进程仍然是操作系统分配资源的最小单位,线程的引入并未改变进程资源所有权这一特点。

2. 引入线程的优势

(1) 进一步提高并发性

        线程可以进一步细化进程中的各种任务,提高任务的并发性。在单处理机系统中,线程模型可以分离同一个进程内的 CPU 计算和 I/O 访问,让它们并发进行。在多处理机系统中,引入线程模型前,无论有多少处理机,一个进程都只能占用一个处理机;引入线程模型后,每个线程都能占用一个处理机,这使得多处理机系统的优势得以充分发挥。
        【提示】线程和进程在很多概念和设计理念上,是极其相似的。例如进程的提出,就是想让多个作业能够同时在操作系统中运行,防止一些作业阻塞时浪费处理器资源的情况,而线程则是让一个作业的多种活动进一步细分,例如一个作业需要请求外存数据以及进行大量的计算,在请求资源且未得到回复时,另一个线程仍然可以进行计算,无需因为等待结果的返回而阻塞。

(2) 共享地址空间与可用数据

        进程间由于相互的 “隔离”,无法直接共享数据,这就带来了进程间通信的问题。而线程模型的引入,使得并行实体拥有了共享地址空间、数据的能力,换言之,线程之间可以直接共享数据而不需要经由操作系统内核,在速度上有很大优势。

(3) 线程更轻量级

        线程无论是创建、切换还是撤销,都远快于进程。在很多操作系统中,一个线程的创建较进程的创建快 10 - 100 倍。在短时间需要反复开启或撤销大量线程时,线程的轻量级是十分有优势的。

(4) 提高交互性

        线程还能提高图形界面程序的交互性,避免程序 “卡死”。例如,一个运行中的文字编辑器就可以是一个由多线程构成的进程。假设这个进程只有两个功能:接收用户的输入以及将当前的文件存储到外存。若该进程未引入线程概念,当用户命令其存储文件时,进程就会进行 I/O 请求直到 I/O 响应,在此期间对于用户的任何操作,编辑器均不会做出响应。如果该进程引入线程概念并开启两个线程(分别对应文件存储工作和与用户交互工作),那么在一个线程请求并等待响应时,另一个线程还能接受用户请求,从而使用户得到更好的体验。

3. 线程的组织

        一个操作系统中的多个进程之间具有很大的独立性,而进程中的多个线程则不同,它们拥有完全相同的地址空间,这意味着它们可以共享全局数据。一个多线程的进程模型如图 2.2 所示。每个线程访问的地址空间,都由其所在进程拥有。同一进程的不同线程间甚至可以修改对方的堆栈上的数据,这种情况下,多个线程间是没有保护的。但是,“没有保护” 不是线程的缺点,而是使得线程间能够共享数据的优点。同一个进程的多个线程是属于同一用户的,该用户创建多个线程的目的是让其彼此更好地配合完成作业,因此用户在编程时就会避免多个线程之间可能出现的互相干扰行为,使这些线程间不会因为 “没有保护” 而相互干扰。

(1) 线程的三个基本状态

        和传统的进程相似,线程也有三个基本状态:执行态、阻塞态、就绪态。线程状态之间的转换和进程状态之间的转换是一样的,可参考图 2.1。


(a) 执行态:在该状态下,线程获得处理器并运行。
(b) 就绪态:表示线程可以被调度执行,被分配处理器就可以立刻执行工作。
(c) 阻塞态:指线程在执行过程中,因为某些事件而阻塞并等待。
【举例】线程申请了 I/O 操作并等待结果的返回,则它进入阻塞态。

(2) 线程控制块 TCB

        和进程类似的,每个线程也具有一个对应的线程控制块(Thread Control Block, TCB),用于控制和管理线程的状态、保存线程的信息。线程控制块中通常有以下几类信息:
(a) 线程标识符(TID):每个线程的唯一标识符。
(b) 寄存器信息:用来记录包括通用寄存器、程序计数器、状态寄存器等关键寄存器的信息。
(c) 线程执行状态:用于记录线程的执行状态。
(d) 优先级:用于线程调度。
(e) 线程专有存储区:用于线程切换时保存现场信息。
(f) 信号屏蔽:每个线程拥有各自的信号屏蔽字。
(g) 堆栈指针:TCB 中分别设置了两个指向堆栈的指针,分别指向用户自己的堆栈和核心栈。每个线程都有自己的堆栈,在线程运行过程中,会进行过程调用(或称函数调用),就可以把过程调用相关的局部变量以及调用完成之后的返回地址存储在堆栈中。
        【提示】核心栈是线程运行在内核态时使用的。
        【举例】有三个过程(函数) FA​、FB​、FC​:FA​ 调用了 FB​,FB​ 调用了 FC​,在 FC​ 执行时,FA​、FB​、FC​ 三个过程的变量和返回地址都会存储在堆栈上。

4. 线程的实现

        如图 2.3 所示,根据操作系统的不同,线程的实现方式也不完全相同,主要分为两类:内核级线程(Kernel Supported Threads, KST)和用户级线程(User - Level Threads, ULT)。


【提示】不同文献对线程实现方式的英文称呼不尽相同,有的文献还会使用 Kernel - Level Thread 来表示内核级线程。

(1) 用户级线程

        线程的管理由应用程序实现,在用户空间下完成。操作系统内核感知不到线程的存在,在该种方式下,调度的最小单位依然是进程,线程的调度则由其所在进程实现。

(2) 内核级线程

        线程和进程一样,均需要操作系统内核的支持,其创建、阻塞、撤销和切换都是在内核空间下实现的,内核通过 TCB 来对线程进行感知和控制。在该种方式下,线程是调度的最小单位。

5. 用户级线程

① 用户级线程的优点

(1) 可以在不支持线程的操作系统中实现线程。具体实现上,主要通过用户空间的线程库实现,应用程序使用线程库进行多线程设计,进程通过调用函数开启多线程。
(2) 可以允许进程自主定制调度算法。操作系统只负责把处理器与其他资源分配给进程,由进程继续将这些资源分配给该进程创建的线程,线程的调度与资源分配都由进程自主完成,可以更好地安排各个线程的工作,也使得线程具有更好的扩展性。
(3) 线程的切换完全在本进程中完成而没有内核的参与,所以这种实现方式的效率更高。
【提示】所有线程相关的数据结构都在进程的用户地址空间,所以线程的切换不需要转换成内核态,这就避免了两次状态转换带来的开销。

② 用户级线程的缺点

(1) 一旦某个线程被阻塞,该线程所属的整个进程都会被阻塞。
(2) 如果只使用用户级线程,一个多线程程序不能利用多处理器技术。因为操作系统只会为一个进程分配一个处理器,所以一个进程中只能有一个线程处于执行态。
【提示】对于这两个缺点,也有对应的解决方法,例如将多线程程序转换为多进程程序、使用 jacketing 技术克服阻塞等,此处了解即可。

6. 内核级线程

① 内核级线程的优点

(1) 在多处理系统中,内核可以调度一个进程内的多个线程到多个处理器上运行。
(2) 当一个线程被阻塞,内核可以调度该进程中的另一个线程到处理器上运行。
(3) 操作系统内核自身可以使用多线程。

② 内核级线程的缺点

(1) 线程切换时,需要内核介入。模式切换带来了额外开销。
【提示】《操作系统精髓与设计原理》中提到,在 Null Fork 和 Signal - Wait 两个测试中,用户级线程的性能优于内核级线程,内核级线程的性能优于进程,且两两之间都有一个数量级以上的性能差距。

7. 用户级线程和内核级线程的组合方式

        有些操作系统把内核级线程和用户级线程两种方式组合,比如使用内核级线程,然后将用户级线程与内核级线程进行多路复用。采用这种方法时,编程人员可以决定有多少个内核级线程与多少个用户级线程对应。内核只识别内核级线程并对其进行调度,而这些内核级线程会被多个用户级线程多路复用。用户级线程和内核级线程之间有以下三种关系,考生可以结合图 2.3 中的 (c) 理解:

① 一对一模型

        一个用户级线程映射到一个内核级线程,相比于多对一模型,具有更好的并发,当一个线程被阻塞时,其余线程能够继续执行。缺点是太多的内核级线程会增加开销,影响程序性能。

② 多对一模型

        将多个用户级线程映射到一个内核级线程。这一模型的优点在于效率较高,线程切换在用户态就能完成;缺点在于如果一个用户级线程发生了阻塞,整个进程都将被阻塞;此外,由于同一进程的多个用户级线程共同映射到一个内核级线程上,这导致用户级线程无法在多处理机系统上并行运行。
【提示】在一对一模型的实现下,同一进程的不同线程间切换时,需要涉及内核状态转换;在多对一模型实现下,同一进程的不同线程间切换时,不需要涉及内核状态转换。

③ 多对多模型

        多对多模型。将 x 个用户级线程映射到 y 个内核级线程,其中 x > y。这种模型可以认为是上述两类模型的折中,避免了上述两个模型的缺点:编程人员可以创建任意多个用户级线程,这些线程也能在多核系统上并发运行。
【提示】若 x = y,则该模型退化为一对一模型;若 x < y,则该模型退化为一对一模型且有若干内核级线程的浪费。

2.1.6 进程和线程的对比

进程和线程有着很多类似的特征,传统进程相当于一个单线程进程,进程和线程的对比如表 2.4 所示。

表 2.4  线程和进程的对比

对比项

进程

线程

调度

不再是调度的最小单位(引入内核线程后)

是调度的最小单位

拥有资源

进程都是资源分配的基本单位

线程只拥有少量资源

并发性

多个进程间可以并发执行

一个进程的多个线程也能并发执行

独立性

只共享全局变量

只有少数资源不能共享,例如线程的栈区

系统开销

通信、进程切换开销大

通信、切换开销小

多处理机

单个进程只能运行在一个处理机上

多线程进程可以充分利用多处理机

其他

进程间相互不影响

用户级线程的阻塞会影响整个进程

        (1) 调度的最小单位:在未实现线程的传统操作系统中,进程是处理器调度的最小单位;在实现了线程的操作系统中,线程是处理器调度的最小单位。
        【提示】上述线程特指内核级线程,用户级线程并不是处理器调度的最小单位。详见上文线程的实现。
        (2) 并发性:在未实现线程的传统操作系统中,只有进程之间可以并发执行;在实现了线程的操作系统中,一个进程中的所有线程和不同进程中的不同线程均可以并发执行,从而提高了操作系统的并发性、资源利用率和系统吞吐量。
        【举例】未引入线程概念时,一个音乐播放进程只能播放音乐;引入线程后,该进程可以开启多个线程,分别实现音乐播放、显示歌词等任务,实现多个任务的并发执行。
        (3) 拥有资源:进程可以拥有资源且是资源分配的最小单位。线程的资源依赖于创建它的进程,每个线程除了一些最核心的资源是私有的,包括线程控制块 TCB、少量寄存器(程序计数器)和栈区外,剩下的资源全部依赖于进程,并与该进程的其他线程共享。
        (4) 独立性:线程间的独立性要比进程间的独立性低得多,多个线程可以共享进程的内存地址空间和资源;进程之间除了共享全局变量外,每个进程都拥有独立的地址空间和不同资源。
        【提示】进程的高独立性是为了防止进程与进程间的有意或无意的破坏,而一个进程的多个线程因为同属于一个所有者,不会相互破坏。降低独立性有利于提高线程间的通讯效率以及进一步提高并发性,同一进程的线程间可以便捷地实现通信,而进程间通信往往需要操作系统内核的支持。
        (5) 系统开销:线程的创建、撤销以及切换的代价远小于进程。此外,因为线程的独立性低,线程间的同步、通信的实现代价也小于进程。
        【举例】Solaris2 OS 中,线程的创建比进程的创建快 30 倍,切换比进程快 5 倍。

2.1.7 进程通信

        进程与进程在运行过程中需要相互协作和交换信息,例如:父进程安排子进程处理一批数据并将结果返回给自己。这就涉及一个重要的问题:如何进行通信?进程通信问题,称为 IPC(Inter Process Communication)问题。
实现进程通信需要解决三个问题:
(1) 进程通过何种方式把信息传递给其他进程。
(2) 保证多个进程在关键活动上不会重叠。
【举例】多个进程同时请求修改同一块共享内存,但是这块内存需要互斥访问,此时就发生了重叠,需要操作系统考虑并解决谁先访问、谁后访问、如何保证互斥的问题。
(3) 进程按照正确的顺序推进。
【举例】父进程安排子进程处理数据,得到子进程返回的数据后才能进行下一步计算,如何控制父进程和子进程按照用户希望的顺序执行?这也是操作系统需考虑和解决的问题。
        为什么会有上述的问题?进程之间是平等且互相隔离的,进程难以察觉到其他进程的存在,也难以察觉到自己被操作系统调度。当进程的状态转换为执行态以外的状态时,该进程的整个 “世界” 就停止了。所以在一个进程的视角中,自己独占了整个计算机的资源,从开始到结束一直在运行。只有这台计算机上的操作系统如 “上帝” 一样,能够看清楚无数个进程在以什么样的模式被组织与调度。

1. 进程通信机制综述

        通信机制可以进一步细分为低级通信方式与高级通信方式。①低级通信方式:通信效率低;通信对用户不透明,编程人员在使用时需要考虑更多方面的问题(数据的传输、进程间的互斥、同步等)。②高级通信方式:可以高效地实现大规模数据的传输;由操作系统以系统调用形式提供,实现细节、通信过程等问题对编程人员透明,从而减少编程负担。这里主要介绍进程通信的 5 种方法:信号量机制、共享存储机制、消息传递机制、管道通信机制和信号机制。这些进程通信机制的总结如表 2.5 所示。
【提示】本部分内容为宏观上的总结,考生可以在学习完本章进程同步一节后再回顾一遍。

(1) 信号量(Semaphore)机制

        信号量机制,可以有效地解决进程同步和互斥问题,进程通过执行 P/V 操作控制信号量,属于低级通信方式。
        【举例】实际使用时,编程人员会将信号量和实际资源联系在一起。若有 3 个打印机资源,就设置一个值为 3 的信号量。占用打印机时,进程执行 P 操作,使得信号量值减 1;释放打印机资源时,进程执行 V 操作,使得信号量加 1。

(2) 共享存储(Shared Memory)机制

        根据共享公用数据结构还是存储区,可以将该机制细分为以下两种方式。
        (a) 共享数据结构的通信方式:该种实现下,进程间共享同一个数据结构,编程人员需要考虑进程间的同步互斥问题,传输效率低,属于低级通信方式。
        (b) 共享存储区的通信方式:该种实现下,操作系统在内存中开辟一块共享存储区,多个进程可以对该内存区域进行读写,传输效率高,属于高级通信方式。当进程希望对共享存储区进行存取时,会将自己的虚地址空间映射到共享存储区,并向操作系统提出申请。
        【举例】某个网站同时只能有一个用户登录,该网站有一个留言板功能,不同的用户之间想交流时就轮流登录在留言板上写下自己的话。留言板就类似于上文提到的共享存储区。

(3) 消息传递(Message Passing)机制

        上文提到,虽然进程间相互独立难以感知,但是操作系统是可以有效管理所有进程的。进程间可以调用由操作系统实现的通信命令来传递消息。实现消息传递即分别实现 Send 和 Receive 两个原语,用于消息的发送和接收。消息传递系统是高级通信方式,可以细分为以下两类:
        (a) 直接通信方式:发送进程直接将消息发送给接收进程,将信息挂载到接收进程的消息队列上,接收进程从自己的消息队列中获取消息。
        (b) 间接通信方式:操作系统额外创建一片空间用于存储各种消息,类似一个信箱,发送进程和接收进程依靠这个中间实体实现信息的发送和接收。这种方式也被称为信箱通信模式,在计算机网络中被广泛运用。在信箱通信方式下,通过不阻塞发送,阻塞接收的方式实现进程的同步。
        【提示】就同一个信息的复制次数而言,间接通信方式需要两次复制:第一次是将信息从发送者进程的存储区复制至操作系统内核开辟的存储区,第二次是将信息从操作系统内核开辟的存储区复制到接收者进程的存储区;直接通信方式只需一次复制,即操作系统内核直接将消息从发送者进程的存储区复制到接收者进程的存储区;共享存储区通信方式则可以实现 0 复制,亦即发送者可以直接在共享存储区生成信息,生成后无需任何操作就能被接收者读取。
        回忆第一章所述,微内核操作系统将一部分操作系统的功能实现为用户态进程,换言之,很多系统调用都需要交给另一个进程来执行,它们的实现方式就是进程通信。因此,进程通信的效率很大程度上决定了微内核操作系统的效率。最早的微内核操作系统采用间接通信方式,每个系统调用都需要两次复制,因此效率较低;后来的微内核操作系统引入了直接通信方式,将两次复制减为一次,大大提高了运行效率。

(4) 管道(Pipeline)通信机制

        该方式下,使用一种共享文件来实现进程间通信。这种共享文件称为管道(或 pipe 文件),可连接一个读进程和一个写进程。写进程以字符流的形式,将数据送入管道,读进程通过管道将数据读出。操作系统实现管道机制需要提供三种机制:同步机制、互斥机制,和通信进程间确定对方存在的机制。管道具有以下特点:
(a) 同步:读进程和写进程需要相互配合,写进程向管道中写入一定量数据后,会进入阻塞状态,等待读进程将数据读出后将其唤醒;读进程在将管道中数据读完后,也会进入阻塞状态,等待写进程对管道写操作完毕后将其唤醒。
(b) 互斥:当有一个进程在对一个管道读 / 写时,其他想要读 / 写该管道的进程必须等待 。
(c) 管道需要通信双方确定对方的存在,否则会陷入阻塞。考生可以使用 Linux 的 FIFO 管道进行简单实验,创建一个 FIFO 文件并向管道中写入随机内容,在未出现读进程前,写入进程是处于阻塞状态的。
(d) 管道是一个固定大小的缓冲区(如 4KB),在内核中实现,其大小不受磁盘大小影响。
(e) 管道的实质是一个共享文件,读进程将管道视为输出文件,写进程将管道视为输入文件,管道通信可以利用文件系统机制实现。
(f) 读数据操作是一次性的,管道中的数据一旦被读取就会被抛弃。
(g) 管道是半双工通信的,某一时刻,数据只能单向传输,若希望实现双向传输(即全双工),可以使用两个管道,如图 2.4 所示。另外,管道以字符流进行读出和写入,子进程会继承父进程的管道并利用管道与父进程通信。

(5) 信号(Signal)通信机制(2025 考研 408 大纲新增内容)

        信号是一种具有单向事件通知能力的软中断,在 Linux 系统中有非常广泛的使用 。操作系统通过发送指定信号来通知进程某个事件发生,以迫使进程执行信号处理程序。信号处理完毕后,被中断进程将恢复执行 。
信号可以由用户、操作系统内核和进程产生:
① 用户:用户能通过输入特定字符来请求内核产生信号。例如:在 Unix、Linux 和其他类 Unix 操作系统中,用户按下中断键(通常是 Ctrl + C),操作系统会给前台进程组中的所有进程发送中断信号 SIGINT。
② 操作系统内核:当进程运行遇到错误时,操作系统内核检测到错误事件会发送信号给出错进程。例如:当数组下标越界时,操作系统内核会发送段错误 SIGSEGV 信号给进程。
③ 进程:进程之间可以通过系统调用产生信号进行通信。
【提示】在 Linux 系统中总共有 62 个信号:①编号为 1 ~ 31 的信号称为标准信号,这些信号在 Unix 和类 Unix 操作系统中是标准化的,每个信号都有其特定的用途;②编号为 34 ~ 64 的信号称为实时信号,这些信号是 Linux 独有的,用于应用程序之间自定义的进程间通信。

        在 Linux 系统中,32 号和 33 号信号被系统保留,没有赋予标准的名称和用途。
操作系统内核一般会通过如下 3 种方式之一来处理信号:
① 直接忽略:进程可以将信号处理函数设置为 SIG_IGN 来通知内核忽略大部分信号,但信号 SIGKILL 和信号 SIGSTOP 除外,不可忽略。
② 执行内核默认处理函数:对于未注册信号处理函数的信号,进程会执行其默认操作(大多为终止进程或直接忽略信号)。
③ 执行信号处理函数:进程可以通过系统调用(signal 或 sigaction)为特定信号注册处理函数,内核接收到信号时会将信号编号作为参数调用该函数。信号处理函数可以执行任何合法的操作,例如:清理资源、保存状态、通知其他进程等。
【提示】进程间信号通信机制是通过用户注册的信号处理函数来实现的。
常见的信号、信号值及功能如表 2.6 所示:

表 2.6 常见的信号、信号值及功能

信号名称

信号值

信号功能

SIGINT

2

中断信号,通常由用户按下 Ctrl + C 组合键产生,用于中断前台进程的执行

SIGILL

4

用于报告进程执行了 “非法指令”(例如:非法操作码、非法操作数、特权指令等)

SIGKILL

9

强制终止信号,无法被捕获、忽略或阻塞,用于立即终止进程

SIGCHLD

17

在子进程状态(终止、暂停、恢复执行)发生改变时通知父进程

SIGSTOP

19

停止信号,无法被捕获或忽略,用于暂停一个进程的执行

2.1.8 习题精编

1.下列关于进程叙述,错误的说法是( )。
A. 进程是动态的,而程序是静态的
B. 在内存中的程序段就是进程
C. 在进程的生命周期中,存在多种状态
D. 进程可以占有处理机运行,而程序不行

1.【参考答案】 B
【解析】 进程由程序段、数据段、进程控制块 PCB 三部分组成。B 选项认为程序段就是进程,这种观点片面且错误,进程运行需将程序段装入内存,但内存中的程序段并非进程。A 选项强调进程的动态性,C 选项强调进程的状态,D 选项表明进程是处理器调度单位,这些说法均正确。

2.进程在处理器上执行时,错误的说法是( )。
A. 进程是一个动态的过程,终有结束的时刻
B. 多个进程可以并发地在处理机上执行
C. 并行的进程之间都存在着相互依赖和制约的关系
D. 进程的并发执行可能导致程序的结果与进程执行速度有关

2.【参考答案】 C
【解析】 并行的进程之间,可利用信号量等机制实现同步、互斥等关系,但并非所有并行(或并发)进程间都存在相互依赖或制约关系。例如,系统中音乐播放进程和聊天进程运行时可毫无联系。A、B、D 选项是对进程特点的正确叙述。

3.下列关于并发进程特性的陈述,正确的是( )。
A. 进程是一个动态过程,其生命周期是连续的
B. 并发进程执行完毕后,一定能够得到相同的结果
C. 并发进程对共享变量的操作结果与执行速度无关
D. 并发进程的结果具有不可再现性

3.【参考答案】 D
【解析】 A 选项前半句正确,后半句错误,失去封闭性会使并发进程的执行结果与执行速度相关,这也导致并发进程具有不可再现性,所以 B、C 选项错误,D 正确。

4.进程的基本特征不包括( )。
A. 动态性
B. 并发性
C. 共享性
D. 异步性

4.【参考答案】 C
【解析】 进程的基本特征为动态性、并发性、独立性和异步性,C 选项共享性错误,进程间的隔离程度较高。

5.有若干并发进程均将一个共享变量 count 中的值减 1 一次,那么有关 count 中的值说法正确的是( )。
I. 肯定有不正确的结果
II. 肯定有正确的结果
III. 若控制这些并发进程互斥执行 count 减 1 的操作,count 中的值正确
A. I、III
B. II、III
C. III
D. 上述说法都不正确

5.【参考答案】 C
【解析】 只有当每个进程互斥修改 count 时,才能得到确定且正确的结果,所以 III 正确;若不控制进程互斥修改 count,结果是未知的(可能正确也可能错误),I、II 错误,因此选项 C 正确。

6.关于子进程和父进程的说法,下面正确的是( )。
A. 一个父进程可以创建若干个子进程,一个子进程可以从属于若干父进程
B. 父进程被撤销时,其所有的子进程也应该被相应撤销
C. 子进程被撤销时,其所属的父进程也被撤销
D. 一个进程必定有父进程或子进程

6.【参考答案】 B
【解析】 一个父进程可创建若干子进程,一个子进程只从属于一个父进程,A 错误。当父进程被撤销时,操作系统会收回分配给该进程的所有资源,并终止其创建的子进程,B 正确。子进程被撤销时,其父进程不会被撤销,C 错误。在 Unix 系统中,除进程 0 外,并非任意一个进程都一定有父进程或子进程,D 错误。

7.并发进程失去了封闭性是指( )。
A. 多个相对独立的进程以各自的速度推进
B. 并发进程的执行结果与速度无关
C. 并发进程执行时,在同时刻发生的错误
D. 并发进程共享变量,其执行结果与速度有关

7.【参考答案】 D
【解析】 进程的封闭性指进程执行结果只取决于自身,不受外界因素影响,即执行结果确定。当进程失去封闭性后,其执行结果会受速度影响,选项 D 正确。

8.下面的叙述中,错误的是( )。
A. 就绪态、执行态和阻塞态,是进程的三个基本状态
B. 因为并发进程的间断性,进程在其生命周期中,呈现出多种状态
C. 每个进程的创建态、终止态只能有一次
D. 进程申请处理器却未得到满足时,其状态应该是阻塞态

8.【参考答案】 D
【解析】 进程申请处理器资源时,表明该进程已获得除处理器外的全部资源,此时应处于就绪态而非阻塞态,D 选项错误。阻塞态下,进程需等待 I/O 数据返回或所等待事件发生,才会转换为就绪态参与处理器竞争。

9.进程的哪一种状态,是进程的基本状态且可以从另外两个基本状态转换而来( )。
A. 执行态
B. 就绪态
C. 阻塞态
D. 终止态

9.【参考答案】 B
【解析】 进程的三个基本状态是就绪态、阻塞态和执行态,其中就绪态可由阻塞态和执行态转换而来,就绪态不能转换为阻塞态,阻塞态也不能转换为执行态,所以正确答案是 B 选项。

10.在支持多道程序设计的操作系统中,调度程序会调度各个进程并发使用处理器,下列事件中,不一定能引起进程调度的是( )。
A. 处于执行态的进程进行 I/O 操作并等待结果返回
B. 一个新进程被创建并转换为就绪态
C. 处于执行态的进程发生错误
D. 处于执行态的进程时间片耗尽

10.【参考答案】 B
【解析】 当进程被创建并放入就绪队列时,不一定会引起调度,所以选 B。A、C、D 三种情况,均是执行中的进程因某些事件放弃处理器,此时操作系统必须调度下一个进程使用处理器。

11.下列事件中,会引起进程由执行态转换为就绪态的是( )。
A. 时间片耗尽
B. 进程等待某一事件的完成
C. 进程进行 I/O 请求并等待结果
D. 进程运行结束

11.【参考答案】 A
【解析】 B、C 使进程转换为阻塞态,D 使进程变为终止态,时间片耗尽使进程由执行态转换为就绪态,A 正确。

12.下列事件中,会引起进程由执行态转换为就绪态的是( )。
A. 一个新进程被创建并进入就绪队列
B. 进程发生错误
C. 被高优先级进程抢占处理器
D. 进程等待的事件发生

12.【参考答案】 C
【解析】 A 选项(创建新进程)不一定会导致当前运行进程的处理器被抢占,所以 A 选项错误,C 选项正确。进程发生错误会导致进程终止,B 选项错误;进程等待的事件发生时,会从阻塞态转换为就绪态,D 选项错误。

13.下列事件中,不会引起进程转换为阻塞态的是( )。
A. 进程等待某个事件的发生
B. 进程等待 I/O 请求的数据
C. 进程等待处理器使用权
D. 拥有前后驱关系的两个进程,后继进程等待前驱进程的信号

13.【参考答案】 C
【解析】 进程等待处理器使用权时处于就绪状态;等待其他事件和资源时处于阻塞状态。A、B、D 选项均涉及 “等待” 处理器以外的事件或资源,会使进程进入阻塞状态;C 选项进程等待处理器时处于就绪态,不会转换为阻塞态,故选 C。

14.关于进程的状态和状态转换,以下哪一种说法是正确的( )。
A. 进程由创建而产生,由调度而执行,因得不到资源而挂起,以及由撤销而消亡
B. 在具有挂起状态的进程管理中,处于挂起就绪状态的进程会因为申请资源失败而进入挂起阻塞状态
C. 进程在运行期间,不断地从一个状态转换到另一个状态,它可以多次处于就绪状态和执行状态,也可多次处于阻塞状态,但可能排在不同的阻塞队列
D. 正在执行的进程,若时间片用完,会进入阻塞状态

14.【参考答案】 C
【解析】 进程因得不到资源会进入阻塞态而非挂起态,挂起是指操作系统将进程从内存移到辅存,发生在中级调度过程中,A 错误。就绪挂起状态无法转换到阻塞挂起状态,B 错误。时间片用完后,进程通常转换为就绪状态,D 错误。

15.在进程状态切换时,引起内存与辅存之间交换数据的是( )。
A. 运行到就绪
B. 运行到等待
C. 运行到挂起
D. 就绪到运行

15.【参考答案】 C
【解析】 进程的三个基本状态间的转换都发生在内存中,所以 A、B、D 选项均不符合。挂起状态是将内存中的进程移动到辅存中,C 正确。建议考生先学习进程调度的三个层次相关内容,再做此题。

16.进程控制块是进程的重要组成部分,下列内容中,PCB 中不应该包括( )。
A. 进程标识符(PID)
B. 处理器状态信息
C. 进程状态
D. 互斥信号量

16.【参考答案】 D
【解析】 互斥信号量由操作系统管理,并不记录在 PCB 中,所以本题选 D。

17.进程控制块 PCB 中不可能包含的信息是( )。
A. 进程优先级
B. 进程状态
C. 进程执行的代码
D. 进程名

17.【参考答案】 C
【解析】 进程执行的代码属于代码段,和 PCB 一样是进程的组成部分,但不会包含在 PCB 中,所以本题选 C;A、B、D 选项内容均可出现在 PCB 中。

18.进程创建原语中,不需要包含的步骤是( )。
A. 分配空白 PCB
B. 分配处理器资源
C. 初始化 PCB 上内容
D. 分配内存资源

18.【参考答案】 B
【解析】 进程从创建到转换为就绪态的过程中,不包括分配处理器这一步骤,分配处理器资源发生在调度过程中,所以 B 选项错误。

19.下列选项中,导致创建新进程的操作是( )。
I. 用户登陆
II. 高级调度发生时
III. 操作系统响应用户提出的请求
IV. 用户打开了一个浏览器程序
A. 仅 I、IV
B. 仅 II 和 IV
C. 仅 I、II 和 IV
D. 全部

19.【参考答案】 D
【解析】 进程创建的场景可归为四类:用户登录时、高级调度发生时、系统响应用户请求时以及现有进程创建新进程时。I、II、III 显然正确,IV 中用户打开一个浏览器程序,通常也会创建一个或多个进程,所以答案选 D。

20.进程创建过程必需的内容是( )。
I. 建立进程控制块
II. 为进程分配 CPU
III. 为进程分配内存
IV. 将进程链入就绪队列
A. 仅 I、III
B. 仅 I
C. I、II、III
D. I、III、IV

20.【参考答案】 A
【解析】 II 不是进程创建必需的,分配处理器的行为发生在调度过程中(就绪态转换为执行态的过程)。IV 也不是进程创建必需的,在多数操作系统中,新进程创建后,若系统资源充足且就绪队列未满,通常会被链入就绪队列等待 CPU 调度;若资源不足或就绪队列已满,新进程可能会处于挂起态或阻塞态,直至资源可用或就绪队列有空位。I、III 是进程创建必需的。

21.下列关于用户进程被创建后的陈述,错误的是( )。
A. 一定会先进入就绪队列
B. 进程在其生命周期中,可能不会经历阻塞态
C. 当时间片耗尽后,进程转换为终止态
D. 操作系统会回收处于终止态进程的资源

21.【参考答案】 C
【解析】 一般情况下,时间片耗尽时,进程大多会转换为就绪态,只有当时间片耗尽且进程恰好运行结束时,才可能转换为终止态,C 错误。

22.一个进程在运行中,释放了一台磁带机资源和一台打印机资源,这个行为可能会导致( )。
A. 某进程由就绪态转换为执行态
B. 自身状态由执行态转换为就绪态
C. 某进程由阻塞态转换为就绪态
D. 系统中所有等待打印机资源的进程被唤醒

22.【参考答案】 C
【解析】 进程释放资源,可能会唤醒之前因请求该资源而阻塞的进程,使其由阻塞态转换为就绪态,A 错误,C 正确。释放打印机和磁带资源与释放处理器并转换为就绪态并无关联,B 错误。因为只释放了一台打印机资源,最多唤醒一个等待该资源的进程,D 错误。

23.进程被唤醒时一定会发生( )。
A. 该进程立即占用处理器运行
B. 进程的 PCB 移动到就绪队列之首
C. 优先级改变
D. 进程变为就绪态

23.【参考答案】 D
【解析】 进程被唤醒时,状态会转换为就绪态,且其 PCB 会进入就绪队列,D 正确。A 发生在就绪态转换为执行态的过程中。B、C 不是必然发生的,取决于操作系统的具体实现。

24.下列关于线程的叙述中,正确的是( )。
A. 一个进程只有一个线程
B. 线程之间的通信需要依靠操作系统内核实现
C. 线程只拥有少量私有资源,所以不能独立被调度
D. 属于不同进程的线程,它们的地址空间相互独立

24.【参考答案】 D
【解析】 进程可以包含多个线程,A 错误;同一进程内的多个线程共享进程的用户地址空间,无需依赖内核实现线程间通信,B 错误;C 选项前半句正确,后半句错误,线程是调度的最小单位。

25.下面的叙述中,正确的是( )。
A. 线程的切换,一定会引起进程的切换
B. 线程的切换,不会引起进程的切换
C. 用户级线程的切换,需要操作系统内核支持
D. 内核级线程的切换,需要操作系统内核支持

25.【参考答案】 D
【解析】 同一进程内的线程切换不会引起进程的切换,不同进程内的线程切换会引起进程切换,所以 A、B 均不正确。用户级线程的切换由进程完成,操作系统内核感知不到用户级线程的存在,C 错误。内核级线程的切换需要操作系统内核的支持,D 正确。

26.下面的叙述中,正确的是( )。
A. 一个进程一定包含多个线程
B. 线程是将进程进一步细分的单位,可以脱离进程独立运行
C. 引入线程可以进一步提升进程的并行性
D. 线程间相互 “隔离”,无法直接共享数据

26.【参考答案】 C
【解析】 一个进程可以只包含一个线程,A 错误。线程是对进程的进一步细分,但线程不是资源分配的基本单位,需依靠进程的资源,不能脱离进程独立运行,B 错误。同一进程的多个线程之间能够共享进程的大部分资源,D 错误。

27.下面的叙述中,正确的是( )。
A. 同一进程的多个线程可以并发执行,不同进程的多个线程只能串行执行
B. 同一进程的多个线程只能串行执行,不同进程的多个线程可以并发执行
C. 多线程程序设计上,可以将程序的 I/O 部分和计算部分拆分成两个线程,以发挥线程优势
D. 同一进程的线程间通信代价很低,但线程创建操作的代价和进程创建相似

27.【参考答案】 C
【解析】 同一进程和不同进程的多个线程均可并发执行,A、B 错误。线程创建、销毁的代价低于进程,D 错误。

28.同一进程的不同线程之间,不能共享的是( )。
A. 进程的代码段
B. 进程的全局变量
C. 进程的用户地址空间
D. 进程中各个线程的栈指针

28.【参考答案】 D
【解析】 同一进程内的多个线程共享进程的代码段、全局变量和用户地址空间。线程的栈指针是私有的,存放在线程控制块 TCB 中。

29.下列关于多对一线程模型的论述中,正确的叙述是( )。
A. 指将多个内核级线程映射到一个用户级线程
B. 当一个线程被阻塞时,同一进程内的多个线程均会被阻塞
C. 多处理器操作系统中,同一进程的多个线程可以并行执行
D. 操作系统内核可以感知用户线程的存在

29.【参考答案】 B
【解析】 多对一模型是指将多个用户级线程映射到一个内核级线程,操作系统无法感知多个用户级线程的存在,A、D 错误。由于多个用户级线程只对应一个内核级线程,一个线程的阻塞会导致整个进程的阻塞,B 正确。即使在多处理器操作系统中,多对一线程模型下多个线程也无法并发执行,任意时刻最多只能有一个用户级线程运行,C 错误。

30.针对用户级线程和内核级线程的特点,错误的是( )。
A. 用户级线程实现简单,可以在所有操作系统上实现
B. 用户级线程的调度由进程管理和控制
C. 即使实现了用户级线程,内核调度的对象依然是进程
D. 内核级线程的切换由操作系统内核负责,较用户级线程切换开销更低

30.【参考答案】 D
【解析】 内核级线程的实现依赖操作系统支持,而用户级线程切换在所属进程内完成,无需内核参与,所以内核级线程开销高于用户级线程,D 选项错误。

31.关于内核级线程优缺点的描述中,错误的是( )。
A. 相较于进程,线程切换的系统开销更小
B. 需要操作系统内核支持
C. 线程阻塞会导致该进程的其他线程一同阻塞
D. 在多处理器系统上,同一进程的多个线程可以并行执行

31.【参考答案】 C
【解析】 若一个进程由多个内核级线程构成,其中一个线程阻塞不会致使整个进程阻塞,C 选项正确。

32.关于用户级线程优缺点的描述中,错误的是( )。
A. 操作系统的调度单位依旧是进程
B. 线程间切换代价小
C. 不需要操作系统内核支持
D. 在多处理器系统上,同一进程的多个线程可以实现并行执行

32.【参考答案】 D
【解析】 用户级线程由进程管理,操作系统无法感知,调度单位仍是进程,A 选项正确。相较于进程,线程切换代价小,且用户级线程无需内核支持,切换代价较内核级线程更小,B、C 正确。用户级线程的不足在于多个线程无法并行执行,D 错误。

33.以下描述中,哪个不是多线程系统的特长( )。
A. 利用线程并行地执行矩阵乘法运算
B. Web 服务器利用线程请求 HTTP 服务
C. 键盘驱动程序为每一个正在运行的应用配备一个线程,用来响应相应的键盘输入
D. 基于 GUI 的 debugger 用不同线程处理用户的输入、计算、跟踪等操作

33.【参考答案】 C
【解析】 系统中通常只有一个键盘,且用户键盘输入速度较慢,用一个线程响应键盘输入即可。其余选项可借助线程优势提高效率。

34.用户级线程的优点不包括( )。
A. 线程切换不需要内核态(或系统态)特权
B. 支持不同的应用程序采用不同的调度算法
C. 在不同操作系统上不经修改就可直接运行
D. 同一个进程内的多个线程可以同时调度至多个处理器执行

34.【参考答案】 D
【解析】 用户级线程切换由用户空间的线程库完成,与操作系统内核无关,无需内核态特权,A 正确。用户级线程中,应用程序可自行决定线程调度方式,B 正确。只要有线程库,用户级线程可在任意操作系统运行,无需修改代码,C 正确。用户级线程对操作系统不可见,操作系统以进程为单位调度,这些线程不能在多个 CPU 上同时运行,D 错误。

35.下面的说法中,正确的是( )。
A. 内核级线程和用户级线程的切换需要内核介入
B. 进程是调度的单位,线程是资源分配的单位
C. 进程总是资源分配的基本单位,线程只拥有少量资源
D. 进程总是调度和资源分配的最小单位,线程无法脱离进程运行

35.【参考答案】 C
【解析】 用户级线程切换无需操作系统内核介入,A 错误。进程是资源分配基本单位,引入线程后,线程是调度最小单位,B 错误、C 正确。线程可以是调度最小单位,D 选项前半句错误。

36.下列关于进程和线程的叙述,正确的是( )。
A. 线程间的地址空间互相隔离
B. 线程间可以共享用户地址空间
C. 线程是一部分程序段,多个线程构成一个进程
D. 线程间通信主要利用管道、共享存储、消息传递等机制

36.【参考答案】 B
【解析】 同一进程内的线程共享进程的用户地址空间,A 错误。线程是进程的进一步细分单位,并非简单对代码段的划分,C 错误。D 选项提及的方法主要用于进程间通信,因线程共享进程地址空间,线程间通信更简便,同一进程下的多个线程共享该进程地址空间,B 正确。

37.进程之间通信的机制不包括( )。
A. 共享存储区
B. 管道
C. 消息传递
D. 访问对方进程地址空间

37.【参考答案】 D
【解析】 进程的地址空间相互隔离,进程不能直接访问其他进程的地址空间,D 错误。

38.两个进程协作完成一个任务,不能实现数据传递的手段是( )。
A. 程序段的全局变量
B. 共享数据结构
C. 共享存储区
D. 管道机制

38.【参考答案】 A
【解析】 进程地址空间相互隔离,其他进程无法直接访问某进程的全局变量,A 正确。B、C、D 是进程的通信方式。

39.管道通信是以( )进行写入和读出的。
A. 消息为单位
B. 自然字符流
C. 文件
D. 报文

39.【参考答案】 B
【解析】 管道是特殊文件,读写进程以字符流形式进行读写操作,B 正确。

40.下列关于管道 (Pipe) 通信的叙述中,正确的是( )。
A. 一个管道可实现双向数据传输
B. 管道的容量仅受磁盘容量大小限制
C. 进程对管道进行读操作和写操作都可以被阻塞
D. 一个管道只能有一个读进程或一个写进程对其操作

40.【参考答案】 C
【解析】 管道是半双工的,同一时刻数据只能单向流动,A 错误。管道是固定大小的缓冲区(如 4KB) ,在内核中实现,其大小不受磁盘大小影响,B 错误。管道空时读进程阻塞,满时写进程阻塞,C 正确。一个管道可有多个读进程和写进程,但不能同时进行写操作,D 错误。

41.下面关于进程互斥的论述中不正确的是( )。
A. 信号量是一种进程互斥技术
B. 管程是一种进程互斥技术
C. 消息机制可用于实现进程互斥
D. 消息机制不能支持进程间的互斥

41.【参考答案】 D
【解析】 设置值为 1 的信号量可实现进程互斥,A 正确。管程中每次只允许一个进程进入,实现进程互斥,B 正确。消息缓冲通信中,消息队列是临界资源,通过执行 P、V 操作实现进程互斥,C 正确,D 错误。

2.1.9 真题演练

42.【2010】下列选项中,导致创建新进程的操作是( )。
I. 用户登录成功
II. 设备分配
III. 启动程序执行
A. 仅 I 和 II
B. 仅 II 和 III
C. 仅 I 和 III
D. I、II 和 III

42.【参考答案】 C
【解析】 引发新进程创建的情况主要有:用户登录、高级调度发生、系统响应用户程序请求、现有进程派生。用户登录成功后,操作系统会为其创建进程,I 正确。设备分配无需创建新进程,设置对应数据结构即可,II 错误。启动程序执行会导致新进程创建,III 正确。

43.【2011】在支持多线程的系统中,进程 P 创建的若干个线程不能共享的是( )。
A. 进程 P 的代码段
B. 进程 P 中打开的文件
C. 进程 P 的全局变量
D. 进程中某线程的栈指针

43.【参考答案】 D
【解析】 线程拥有保证自身独立运行的必要资源,线程控制块 TCB 中记录专属堆栈指针,用于保存局部变量和返回地址,对其他线程透明、不可共享,D 正确。

44.【2012】下列关于进程和线程的叙述中,正确的是( )。
A. 不管系统是否支持线程,进程都是资源分配的基本单位
B. 线程是资源分配的基本单位,进程是调度的基本单位
C. 系统级线程和用户级线程的切换都需要内核的支持
D. 同一进程中的各个线程拥有各自不同的地址空间

44.【参考答案】 A
【解析】 无论是否引入线程,进程都是资源分配基本单位,线程拥有少量资源,可访问进程全部资源,A 正确。引入线程前,进程是独立调度基本单位;引入系统级线程后,线程是独立调度基本单位,B 错误。操作系统内核无法感知用户级线程,只能支持内核级线程切换,C 错误。进程有独立地址空间,同一进程的线程共享一个地址空间,D 错误。

45.【2014】一个进程的读磁盘操作完成后,操作系统针对该进程必做的是( )。
A. 修改进程状态为就绪态
B. 降低进程优先级
C. 给进程分配用户内存空间
D. 增加进程时间片大小

45.【参考答案】 A
【解析】 进程向操作系统请求读磁盘后进入阻塞态,I/O 操作完成后需唤醒进程,将其状态转为就绪态。注意题目 “必做” 一词,进程读磁盘操作完成后可做之事众多,如提高进程优先级,但这并非必须。

46.【2014】下列关于管道(Pipe)通信的叙述中,正确的是( )。
A. 一个管道可实现双向数据传输
B. 管道的容量仅受磁盘容量大小限制
C. 进程对管道进行读操作和写操作都可能被阻塞
D. 一个管道只能有一个读进程或一个写进程对其操作

46.【参考答案】 C
【解析】 管道半双工,实现双向传输需两个管道,A 错误。管道是特殊文件,容量一般为内存一页,不受磁盘容量限制,B 错误。管道可多个读、写进程操作,但同一时刻只能一个进程操作,满时写进程阻塞,空时读进程阻塞,C 正确。

47.【2015】下列选项中,会导致进程从执行态变为就绪态的事件是( )。
A. 执行 P(wait)操作
B. 申请内存失败
C. 启动 I/O 设备
D. 被高优先级进程抢占

47.【参考答案】 D
【解析】 执行 P 操作后只会继续运行或被阻塞,不会转为就绪态,A 不符合。申请内存失败进程进入阻塞态,直到有可用内存,B 不符合。启动 I/O 设备后进程自动进入阻塞态直到 I/O 任务完成,C 不符合。被高优先级进程抢占后,进程仍具备运行条件,由运行态转为就绪态,D 符合。

48.【2018】下列选项中,可能导致当前进程 P 阻塞的事件是( )。
I. 进程 P 申请临界资源
II. 进程 P 从磁盘读数据
III. 系统将 CPU 分配给高优先权的进程
A. 仅 I
B. 仅 II
C. 仅 I、II
D. I、II、III

48.【参考答案】 C
【解析】 进程 P 申请临界资源,资源充足则继续执行,不足则进入阻塞态,I 可能。进程 P 从磁盘读数据属 I/O 操作,数据到达前进程 P 处于阻塞态,II 可能。系统将 CPU 分配给高优先级进程,进程 P 进入就绪态,III 错误。

49.【2019】下列关于线程的描述中,错误的是( )。
A. 内核级线程的调度由操作系统完成
B. 操作系统为每个用户级线程建立一个线程控制块
C. 用户级线程间的切换比内核级线程间的切换效率高
D. 用户级线程可以在不支持内核级线程的操作系统上实现

49.【参考答案】 B
【解析】 用户级线程的管理在用户空间完成,操作系统内核感知不到其存在,仅调度进程;内核级线程管理在内核空间,需内核支持,A 正确。操作系统无法感知用户级线程,由进程创建并管理相应线程控制块,B 错误。用户级线程切换在用户空间进行,无需内核介入,比内核级线程切换效率高,C 正确。用户级线程管理依赖进程,引入线程库即可,无需内核支持,D 正确。

50.【2019】下列选项中,可能将进程唤醒的事件是( )。
I. I/O 结束
II. 某进程退出临界区
III. 当前进程的时间片用完
A. 仅 I
B. 仅 III
C. 仅 I、II
D. I、II、III

50.【参考答案】 C
【解析】 本题考查进程状态转换的触发事件。I/O 结束,若此前有进程因申请 I/O 操作受阻而阻塞,CPU 会唤醒该进程,使其由阻塞态转为就绪态,I 符合;某进程退出临界区,若有进程等待临界区资源,则会被唤醒,II 符合;当前进程时间片用完,转换为就绪态,不存在唤醒事件,III 不符合。所以选 C。

51.【2020】下列关于父进程与子进程的叙述中,错误的是 ( )。
A. 父进程与子进程可以并发执行
B. 父进程与子进程共享虚拟地址空间
C. 父进程与子进程有不同的进程控制块
D. 父进程与子进程不能同时使用同一临界资源

51.【参考答案】 B
【解析】 A 选项正确,如父进程创建子进程处理部分数据,父子进程可并发执行。父进程与子进程仅共享代码段和部分资源,虚拟地址空间各自独立,不共享,B 错误。PCB 是每个进程独有的数据结构,父子进程不同,拥有不同的进程控制块,C 正确。判断父子进程相关性质时,可将子进程视作普通进程,若答案对普通进程成立,对子进程也成立,据此可判断 A、C、D 正确。

52.【2021】下列操作中,操作系统在创建新进程时,必须完成的是 ( )。
I. 申请空白的进程控制块
II. 初始化进程控制块
III. 设置进程状态为执行态
A. 仅 I
B. 仅 I、II
C. 仅 I、III
D. 仅 II、III

52.【参考答案】 B
【解析】 进程创建过程:①为进程分配进程标识符(PID)和空白 PCB;②分配所需资源;③初始化 PCB 内容;④若顺利,设进程状态为就绪态,放入就绪队列等待调度;若资源不足,设为阻塞态。申请空白进程控制块对应①,初始化进程控制块对应②、③,I、II 正确。进程创建时状态为就绪态或阻塞态,非执行态,III 错误,故选 B。

2.2 处理器调度与上下文切换

        本节主要围绕着一个核心展开:调度。上下文切换是进程调度的实现方式,在上一节的进程控制小节已经进行过详细讲述,如有遗忘可以重温。考生在学习本节知识的过程中,请先尝试回答下列问题:
(1) 什么是处理器调度?
(2) 为什么要进行处理器调度?

        本节前半部分(调度的概念、时机、方式实现、调度算法的目标)重点在概念的理解与记忆,后半部分(典型调度算法、甘特图)重点在理解概念的基础上运用调度算法分析实际问题。对于重点调度算法,书中设置了多道例题和详细的解析。

请考生在学习过程中尝试回答下列问题(题目可以在各个小节中找到答案):
(1) 什么是抢占式调度算法,什么又是非抢占式调度算法?
(2) 调度算法有没有最优解决策略,在设计调度算法时,设计者看重哪些要素和指标?
(3) 有哪些典型的调度算法?针对这些调度算法,可不可以说出它们各自的优缺点?

2.2.1 调度的基本概念

        宏观上讲,调度的发生意味着竞争的产生,资源不足以让所有需求者同时使用,就必须决定使用的先后次序,这个决定的过程,就是一种调度,可以理解成一种资源分配的行为。在计算机的世界中,也存在着 “珍贵” 的资源,处理器就是其中一种,操作系统需要为处理器实现调度。

1.处理器调度
        在多道程序设计系统中,允许多个进程或线程的存在,那么不可避免地会遇到多个进程或线程同时竞争处理器的情况。例如,计算机只有一个处理器,且有两个或更多进程处于就绪态,竞争就出现了,此时只能选择一个进程上处理器运行。在操作系统中,完成上述选择工作的部分是 “调度程序(scheduler)”,调度程序使用的选择算法称为 “调度算法(scheduling algorithm)”。类似的,线程级别的调度和进程级别的调度有着很大的相似性,许多适用于进程调度的处理方法也同样适用于线程调度。
        【提示】请考生区别 “进程调度” 和 “上下文切换”。调度是一种资源分配的行为,包括了调度程序的决策和上下文切换;上下文切换是调度的实现手段,执行调度程序的决策。

2.三种调度层次
        在多道程序系统中,一个作业从被提交并加入等待队列开始,到运行结束这一过程,可能会发生三种层次的调度,分别是作业调度、内存调度和进程调度,根据调度发生的层次,依次称为高级调度,中级调度和低级调度,如图 2.5 所示。
(1) 高级调度(High Level Scheduling)
        高级调度,又称长程调度或作业调度,调度的对象是外存中等待的作业,行为是将作业由外存调度到内存中。当作业调入内存后,操作系统会为该作业创建进程,分配所需资源,执行进程初始化的一系列操作。高级调度能够限制同时运行的进程数量,避免低级调度开销过大;也能根据系统当前的 CPU 和 IO 利用率选取合适的计算密集型或 I/O 密集型作业,以免资源竞争过于激烈或某项资源利用率过低。
        从调度对象可以知道,这种调度主要是针对批处理系统的,分时操作系统和实时操作系统不涉及该层次调度。一个作业只会有一次调入和一次调出,两次的时间差就是作业执行完毕所花费的时间,所以这种调度的频率是最低的。

(2) 中级调度(Intermediate Scheduling)
        中级调度,又称内存调度,该调度的对象是暂时不能运行的进程,行为是将目标进程的相关数据在内存和外存间移动。在现代操作系统中,这种调度实际上是换页功能的一部分。如果当前运行中的进程已经占用了大量内存(系统缺页率过高),那么中级调度就会将部分进程切换为对应的挂起状态。挂起状态的进程不参与低级调度,同时页面置换机制也会倾向于将这些进程使用的内存页面换入磁盘中,这就缓解了内存紧张的问题,进而提高内存利用率和系统吞吐量。中级调度还会在适当的时机激活此前挂起的进程,使其重新参与调度。这种调度一般发生在系统内存资源不足的情况下,所以发生频率要根据计算机内存资源多少和当前内存中进程所占资源多少两个因素共同确定。
        【拓展】中级调度中涉及进程的就绪挂起态和阻塞挂起态,考生结合图 2.6 了解即可。当进程从内存被调入外存时,如果调入前进程状态为就绪态,则调入后进程状态转换为就绪挂起态;如果调入前进程状态为阻塞态,则调入后进程状态转换为阻塞挂起态。我们将引入了挂起状态的模型称为七状态模型。

(3) 低级调度(Low Level Scheduling)
        低级调度,又称进程调度,调度的对象是进程(或内核级线程),行为是决定将处理器资源先分配给哪个进程。根据选用的进程调度算法,调度程序会决定就绪队列中的哪个进程应获得处理器资源,并由分派程序将处理器分配给被选中的进程。进程调度是最基本的一种调度,是发生频率最高的调度,多道批处理、分时和实时操作系统中,都要实现进程调度。在典型调度算法一节,我们会重点讨论这一级别的调度。

三级调度的总结对比如表 2.7 所示。

表 2.7 三级调度对比

调度层次

调度目标

发生频率

进程状态的改变

高级调度

将作业由外存调度到内存中,为其创建进程

作业→创建态→就绪态

中级调度

将进程数据暂存在辅存中(或恢复到内存中)

就绪态→就绪挂起态;阻塞态→阻塞挂起态

低级调度

调度进程轮流使用处理器

就绪态→执行态

2.2.2 调度的时机

调度发生于以下几种情况:
(1) 创建一个新进程之后。当创建完进程后,调度程序需要决定是运行当前进程还是运行其父进程,两个进程都是处于就绪态的,可以调度其中任意一个进程。
(2) 进程退出后。在一个进程退出后,其占有的资源会被释放,可以立即供其他进程使用,所以需要执行调度。
(3) 进程时间片用尽。当前进程放弃处理器,调度程序从就绪队列中选择进程进行调度。
(4) 可抢占式系统中进程进入就绪队列。
        【举例】可抢占优先级调度算法中,有优先级更高的进程进入就绪队列时,会发生调度,由优先级最高的进程占有处理器;可抢占式短进程优先调度算法中,有进程进入就绪队列且该进程要求的运行时间少于目前占有处理器进程的剩余运行时间时,会发生调度。
(5) 阻塞发生时。当一个进程在执行的过程中发生了让其阻塞的情况,例如 I/O 操作、信号量阻塞等等。为了不浪费资源,可以进行调度,选择其他的就绪进程运行。
        【提示】当阻塞发生时,阻塞的原因对调度是有指导意义的。考虑PA​、PB​两个进程。PA​因为尝试访问临界区而被阻塞,此时PB​正在临界区内执行,那么调度程序可以选择先调度PB​,从而更快 “唤醒”PA​来继续执行任务。
(6) I/O 中断发生时。当 I/O 中断发生时,需要进行调度。

43.【2021】下列事件中,可引起进程调度程序执行的是( )。
I. 中断处理结束
II. 进程阻塞
III. 进程执行结束
IV. 进程的时间片用完
A. 仅 I、III
B. 仅 II、IV
C. 仅 III、IV
D. I、II、III、IV

43.【参考答案】D
【解析】I 正确,例如时钟中断发生时,若当前进程时间片用尽,则会发生进程调度;II 正确,例如此刻运行的进程进行了 I/O 操作,在数据到来之前,该进程会主动阻塞,操作系统会进行进程调度;III 正确,当一个进程运行结束后,需要调度其他进程占有处理器运行,如果没有其他就绪进程,则会调度闲逛进程;IV 正确,是进程调度的时机之一;所以正确答案是 D。

3.下面的情况中,进程调度可能发生的时机有( )。
I. 正在执行的进程时间片用完
II. 正在执行的进程提出 I/O 请求后进入等待状态
III. 有新的用户登录进入系统
IV. 等待硬盘读取数据的进程获得了所需的数据
A. I
B. I、II、III、IV
C. I、II、IV
D. I、III、IV

3.答案:B

解析:

Ⅰ:正在执行的进程时间片用完,会被剥夺 CPU 使用权,调度程序会选择其他进程运行,此时会发生进程调度。

Ⅱ:正在执行的进程提出 I/O 请求后进入等待状态,CPU 空闲,需要调度其他进程来使用 CPU ,会发生进程调度。

Ⅲ:有新的用户登录进入系统,新进程进入就绪队列,可能会触发调度程序选择更合适的进程运行,会发生进程调度。

Ⅳ:等待硬盘读取数据的进程获得了所需数据,从等待状态变为就绪状态,就绪队列发生变化,可能会引发进程调度。

        【举例】进程PA​因为申请 I/O 操作而被阻塞。当 I/O 设备执行完毕PA​安排的工作,就可以选择唤醒阻塞的PA​,让其继续执行。当然,也可以不进行调度,让当前占据处理器的进程继续执行。

        大多数情况下,调度和进程切换都会立即执行;但下列情况下并非如此,需要等待对应事件结束后才能进行调度和进程切换。
(1) 中断处理过程中。中断发生后,需要将待恢复数据(PC、PSW、通用寄存器等)保存在被中断进程的核心栈中,如果此时发生调度或上下文切换,会导致中断无法恢复。
(2) 原语执行过程中。原语的执行具有不可中断的特性。

2.2.3 调度的方式

        调度的方式分为非抢占式调度和抢占式调度,主要区别在于操作系统能否 “违背” 一个进程的意愿,主动打断其运行并将处理器资源分配给其他进程。

1.非抢占式调度
在非抢占式调度中,操作系统选择一个进程并让其运行,直到其发生阻塞或者完成任务释放资源为止。在这种调度策略下,进程自愿进入等待状态或终止时发生调度。
(1) 调度发生的时机:
        (a) 进程运行完毕,放弃处理器的使用权。
        (b) 进程发生某种事件而无法继续运行,放弃处理器使用权。
        (c) 进程发生阻塞需要等待继续运行的条件,放弃处理器使用权。
(2) 非抢占式调度的优点:
        (a) 调度算法设计更简单,调度成本更低(例如,没有执行态与就绪态之间的转换开销)。
        (b) 高吞吐的调度策略。
        【提示】调度行为本身,是一个 “白白” 消耗系统资源的行为,因为调度程序占用处理器资源这一行为对作业的完成是没有任何贡献的。抢占式调度会触发更多次的调度,因而系统整体的吞吐量会变低。

2.抢占式调度
        抢占式调度算法中,系统可以根据某种调度的原则,暂停一个进程的执行并将处理器资源分给另一个进程。抢占式调度算法使得系统可以实现实时交互,但是更复杂的实现会带来更大的系统开销。
(1) 抢占遵循的常用原则:
        (a) 优先级:优先级高的进程可以抢占优先级低的进程,这个抢占行为可以发生在进程调度的时机上。例如,当有新进程被加入就绪队列中,如果它拥有比当前运行进程更高的优先级,则可以抢占。
        (b) 短进程优先:请求处理器时间短的进程,可以抢占请求时间长的进程,同样的,这个抢占行为发生在可以进程调度的时机。
        【提示】这种情况下,可以认为进程要求时间的长短决定了进程优先级的高低。
        (c) 时间片轮转原则:每个进程依次占用处理器资源,一个进程耗尽当前分配的时间片后,其他就绪进程可以在进程调度发生时进行抢占,被抢占的进程如果未运行完毕,则会转换为就绪态,进入就绪队列,等待下一次使用处理器资源。
(2) 抢占式调度的优点:
        (a) 抢占式调度方法,可以防止一个进程长时间独占处理器的恶意行为。
        (b) 与非抢占式调度相比,处理器利用率更高。
        (c) 抢占式调度的等待时间和响应时间更短,用户体验好,有利于实时系统和分时系统。
        (d) 每次中断后都需要考虑调度,这使得操作系统更加灵活(对比非抢占式系统需要严格执行到一个程序的结束或阻塞)。
        (e) 操作系统确保所有正在运行的进程的处理器使用率相似,改善了平均响应时间。

2.2.4 调度算法的目标

        调度算法没有最优解。不同情况下,人们根据需要选择不同的调度算法。用户看重的指标不一样,因而对调度算法优化的方向也不同。这里针对几种操作系统介绍其应用环境和典型的调度算法。

1.操作系统的使用场景

        (1) 批处理系统:常用于商业领域,用来处理存货清单等周期性作业。这类系统无需与用户交互,目标是尽快完成作业,因此对调度算法要求平均周转时间短、系统吞吐率高、处理器利用率高。在这种情况下,非抢占式调度策略和不把响应时间作为首要考虑目标的抢占式调度算法都可考虑。这些调度算法降低调度频率和每次执行调度算法的开销,从而提升 CPU 利用率和吞吐量。

        (2) 分时系统:常用于个人电脑等需频繁与用户交互的领域。用户希望快速得到响应,因此应优先考虑响应时间短的调度算法。

        (3) 实时系统:如股票交易系统、航天器自动控制系统等。此类系统为保证任务在规定时间内完成,需要特殊设计的调度算法,且抢占式调度算法居多。

2.调度算法的指标
        以下指标从不同角度评价调度算法,不同调度算法围绕其中一个或多个指标设计。部分指标关注调度算法性能,可量化;部分难以量化。

(1) 资源利用率:为提高计算机资源利用率,应让各种资源处于忙碌状态。处理器资源重要,其利用率计算如公式 2.1:

(2) 周转时间:指作业(进程)从提交到完成消耗的时间,包括等待高级调度、在就绪队列等待、运行和 I/O 耗时。周转时间短,用户能更快得到结果,操作系统希望平均周转时间最短,体现更高效率。

(a) 周转时间T,作业从提交到完成的时间,公式 2.2,tfinish​表示作业完成时间,tarrive​表示作业到达时间。

(b) 平均周转时间Tˉ,公式 2.3,其中n表示作业总数,Ti​表示作业i的周转时间。

(c) 带权周转时间W,作业周转时间T与实际运行时间ts​之比,公式 2.4​。

(d) 平均带权周转时间Wˉ,综合考虑每个作业带权周转时间,公式 2.5,Ti​表示进程i的带权周转时间,ti​表示操作系统为进程i提供的服务时间。

【提示】带 “平均” 的指标描述调度算法整体性能,其他指标描述特定进程性能。典型调度算法小节会用 FCFS 和 SPF 算法举例计算。

(3) 吞吐量:指单位时间内完成的作业数,受运行作业长度影响,短作业多则系统吞吐率高。

(4) 响应时间:用户提交请求开始,直到系统首次对作业做出响应所花费的时间。分时操作系统中是重要衡量指标,低响应时间给用户更好体验。

(5) 等待时间:进程在队列中等待资源的时间,等待时间 = 周转时间 - 运行时间。对用户和操作系统而言,平均等待时间长都不利,反映调度算法设计欠佳。

难以量化的指标则包括:

(1) 公平性:相似进程应获相似服务,如获得合理处理器时间,避免进程长时间得不到处理器资源(饥饿)。需注意 “相似” 是相对的,有些调度算法为进程定义不同优先级,先调度高优先级进程。

(2) 平衡性:指处理器密集型作业和 I/O 密集型作业的调度平衡,保证计算机各资源尽可能忙碌。

        【提示】特殊用途操作系统还需考虑特殊指标,如移动设备能耗、实时系统实时性等。

        综上,好的调度算法需考虑操作系统应用场景、用户偏好、系统运行效率等。典型调度算法一节将讨论不同算法的目标、优缺点和适用场景。

2.2.5 调度的实现

1.调度的主要任务
进程调度需完成三点任务:

(1) 保存处理器现场信息。上下文切换后其他进程会使用这些资源,需提前将处理器上的程序计数器、通用寄存器等寄存器内容保存在进程控制块中。

(2) 按进程调度算法确定下一个分配处理器的进程,将其状态改为执行态,准备分配处理器资源。

(3) 将处理器资源分配给进程,根据进程控制块信息恢复其运行环境,使其能从上一次中断的断点继续运行。

2.进程调度机制


为实现上述任务,进程调度机制应包含以下三个部分:

(1) 排队器:进程调度系统中,根据进程不同状态维护多个进程队列,如就绪队列、阻塞队列。调度时通过这些队列快速选择下一个分配处理器资源的进程。排队器在进程状态转为就绪态时,将其插入就绪队列。

(2) 分派器:根据进程调度程序选择的进程,从就绪队列取出并分配处理器资源。

(3) 上下文切换器:分配处理器给新进程时进行上下文切换。操作系统先保存当前使用处理器进程的上下文到进程控制块,装入分派程序上下文保证其运行,再取出下一个要分配处理器的进程,从分派程序上下文转换到该进程上下文,使其开始运行。

3.闲逛进程(Idle Process)
        闲逛进程无明确工作,用于解决调度特殊情况。就绪队列无其他进程可调度时,系统调用闲逛进程使用处理器,运行中检查中断是否发生。闲逛进程优先级最低,有其他进程进入就绪队列就会引发处理器调度。
【提示】Unix 操作系统下 PID 为 0 的进程就是闲逛进程。

4.两种线程的调度

(1) 用户级线程调度:此方式下,由各个进程管理其包含的线程,操作系统内核感知不到线程存在,调度仍以进程为单位。进程获得处理器后,利用进程内调度程序调度各个用户级线程运行。

(2) 内核级线程调度:该方式下,由操作系统管理各个线程并调度,需内核参与,增加额外开销。好处是线程阻塞不会导致同属一个进程的其他线程阻塞,且能利用多处理器技术。

2.2.6 CPU 调度算法

        处理器调度层次有三个层次的调度,以下介绍典型 CPU 调度算法,即三个层次调度中调度算法的具体实现。部分算法适用于多种层次调度,如先来先服务调度算法。

1.先来先服务调度算法(First - come First - serve, FCFS)
        FCFS 是最简单朴素的算法,也叫先进先出算法(First In First Out, FIFO),适用于作业调度和进程调度。作业调度时,系统按作业放入外存队列顺序依次调入内存执行;进程调度时,从就绪队列选最先进入的进程,转为执行态并分配处理器资源,直到下一个调度时机。该算法虽不会成为操作系统主要调度算法,但一些复杂调度算法(如优先级队列调度算法)包含其思想。

(1) 优点:逻辑简单。

(2) 缺点:①效率差;②无法实现人机交互;③未考虑进程间差异,如进程紧急程度;④更偏向处理器密集型进程和长进程。

        【提示】FCFS 算法偏向长进程和处理器密集型进程原因:长进程排队并使用处理器直到作业完成或阻塞,使用处理器时间长;I/O 密集型进程遇 I/O 操作会阻塞重新排队,处理器密集型进程阻塞少,排队次数少,等待时间也会少。

        【例 2.1】有P1​,P2​,P3​,P4​,P5​五个进程,其到达时间和执行时间如表 2.8 所示。在单处理器且使用 FCFS 算法的情况下,分析该调度算法的各项指标。分析系统吞吐率时,每个进程对应一个作业,进程完成意味着作业完成。

2.短进程(作业)优先调度算法
        短进程(作业)优先调度算法,适用于作业调度和进程调度。进程调度时称短进程优先调度算法(Shortest Process First, SPF),依据就绪队列中进程预估的处理器使用时间,每次调度选剩余处理器使用时间最短的进程;作业调度时称短作业优先调度算法(Shortest Job First, SJF) ,按外存队列中作业要求的执行时间调度,选预估剩余处理器使用时间最短的作业。

(1) 优点:相较于 FCFS,性能有所提升,SPF 的平均等待时间和平均周转时间最优。

        【提示】实际上 SPF/SJF 是较为理想的调度算法,但现实中难以实现,主要原因有二:一是进程难以准确预估运行所需时间;二是程序可能谎报运行时间恶意竞争处理器使用权。不过该算法可作为评测其他调度算法优劣的标杆。

(2) 缺点:①算法需进程(作业)预估运行时间,可能出现估算不准或进程 “谎报” 时间的问题;②该算法偏向短进程,若等待调度过程中不断有短进程创建,长进程可能被无限期搁置,产生饥饿现象;③算法仅依据进程(作业)耗时长短分配处理器,未考虑进程间的差异性。

根据是否可抢占,短进程调度算法分为:

(1) 非抢占式短进程优先调度算法:调度时,选择当前就绪队列中要求处理器时间最少的进程分配处理器,该进程运行期间不会被抢占,直至主动放弃处理器。

(2) 抢占式短进程优先调度算法:调度时,选择当前就绪队列中要求处理器时间最少的进程分配处理器。若该进程运行时,就绪队列出现要求时间更短的进程,此进程会被抢占处理器资源,当前运行进程状态转为就绪态。

【提示】部分教材对上述两种情况做更细致区分,SPF 专指非抢占式短进程优先算法,抢占式算法称最短剩余时间优先调度算法(Shortest Remaining Time, SRT),因为抢占式算法比较的是进程剩余执行时间。分析题目时,要留意调度算法名称及是否可抢占的描述。

【例 2.2】有P1​,P2​,P3​,P4​,P5​五个进程,其到达时间和执行时间如表 2.8 所示。在单处理器且使用 SPF(非抢占式)算法的情况下,分析该调度算法的各项指标。

【例 2.3】有P1​,P2​,P3​,P4​,P5​五个进程,其到达时间和执行时间如表 2.8 所示。在单处理器且使用 SPF(抢占式)调度算法的情况下,试求这 5 个进程的平均周转时间。

        计算过程中,可以利用甘特图确定各个进程的结束时间(Tfinish​),各个进程的到达时间(Tarrive​)是已知的,利用T=tfinish​−tarrive​计算出各个进程的周转时间;各个进程执行时间(Ts​)是已知的,继续使用W=ts​T​计算出各个进程的带权周转时间;最后按题目要求算出这五个进程的平均带权周转时间即可。计算完毕后,考生可以思考这样一个问题,短进程优先调度算法的两种形式(可抢占式、不可抢占式)的性能哪种更好,为什么是这样?

        关于甘特图的知识,可以参见甘特图小节。在分析进程调度算法时,主要考虑处理器个数和调度算法特征。通过上面的例题,请考生注意区分短进程优先调度算法中,可抢占和不可抢占的情况。关于调度相关的知识,难点主要在于计算,考生可以继续利用表 2.8 数据和其他调度算法去计算 2.2.3 小节中调度算法的各个指标。
3. 优先级调度算法(Priority - scheduling Algorithm, PSA )
        优先级调度算法,可用于作业调度、进程调度。前两种调度算法未考虑进程间差异性,PSA 则为解决该问题而提出。此算法下,作业(进程)优先级由外界赋予,系统依此进行调度。

        【举例】对于紧急进程,可设更高优先级,下次进程调度时,系统优先为其分配处理器资源。作业调度时也如此,系统从外存队列挑选优先级最高作业,装入内存,创建进程分配资源,设进程状态为就绪态,放入就绪队列。

根据是否可抢占,优先级调度算法分为以下两种:

        (1) 非抢占式优先级调度算法:每次分配处理器的进程会持续运行至释放处理器资源,如程序运行完毕或因某些事件退出等,此时调度程序选等待队列中优先级最高程序分配时间片。

        (2) 抢占式优先级调度算法:每次选最高优先级进程分配处理器资源,若进程运行中就绪队列出现更高优先级进程,该进程会抢占处理器资源,当前运行进程状态由执行态变为就绪态。

在调度程序运行过程中,根据优先级是否动态变化,分为静态优先级和动态优先级。

(1) 静态优先级:指各进程优先级在调度程序运行初始就确定,运行期间不变。设计优先级一般依据以下几点:

        (a) 系统进程优先级通常高于用户进程优先级。

        (b) I/O 密集型进程优先级高于处理器密集型进程。

        (c) 对资源要求少的进程优先级高于对资源要求多的进程。

        (d) 用户自定义优先级。

(2) 动态优先级:指调度程序运行中,各进程优先级动态变化。下面将提到的高响应比优先调度算法,就通过考虑进程等待时间实现动态优先级。

4.高响应比优先调度算法(Highest Response Ratio Next, HRRN )
        高响应比优先调度算法,适用于作业调度、进程调度。该算法综合考虑进程(作业)等待时间和运行时间来调度。根据公式 2.6 确定进程(作业)优先级。可看出,因等待时间变化,优先级也动态变化,进程等待时间增加,优先级变高,更易被调度。不难发现,两进程要求时间相同时,优先级由等待时间决定,符合 FCFS 算法;两进程等待时间相同时,短进程优先级更高,类似 SJF,有较好平均等待时间和平均周转时间,且不会出现饥饿现象,长进程等待足够时间,优先级会高于短进程。

5.时间片轮转(Round Robin, RR )调度算法

        RR 算法将处理器使用时间划分成一个个时间片,公平地分给每一个等待处理器资源的就绪进程,时钟中断发生时,系统会修改当前进程在时间片内的剩余时间,当一个进程分配的时间片耗尽后,其会重新进入就绪队列等待下一次分配时间片。该算法可以适用于分时操作系统中的进程调度。系统会将所有就绪进程按照 FCFS 算法来维护就绪队列,每个时间片结束后操作系统都会结束当前进程,并调度下一个就绪程序运行。在这种策略下,每个进程都能公平地使用处理器资源,也保证了进程的响应时间。

①RR 算法的调度时机:

(1) 一个时间片耗尽,在时钟中断发生时,调度程序调度下一个就绪进程上处理器运行,将当前进程放回到就绪队列末尾。

(2) 一个时间片未耗尽,但进程已完成工作,或因阻塞等原因放弃处理器资源,调度程序会选择下一个就绪程序进行进程调度。

②时间片大小的确定:
        RR 算法涉及对于时间片长短的选择,这对于 RR 算法的性能有着巨大的影响。当时间片很小时,操作系统需要频繁地进行进程调度,大大增加系统开销,使整体性能变差。当时间片足够大,使得每个进程只用一个时间片就可以完成任务时,该算法就退化成了 FCFS 算法。影响时间片大小的主要因素包括响应时间、系统开销和进程数量等,设计一个合理的时间片大小应该考虑各个进程的实际情况,使得每个进程在一个时间片内,至少可以完成一次交互。

6.多级队列调度算法
        多级队列调度算法,可以适用于进程调度。该算法将就绪队列设置为多个,根据就绪进程的特点和类型,将其分配在不同的就绪队列中。针对每个就绪队列,可以采用不同的调度算法。例如,每个队列选择上述调度算法的一种,这种调度算法,不但可以凸显出各种调度算法的优势,也能在一定程度上弥补这些算法的缺点。在多处理器系统中,可以利用该调度算法,为每个处理器资源指定一个就绪队列,这样既可以根据进程类型和紧急程度等因素为进程分配更合理的就绪队列,还有利于需要多组线程或进程协作完成任务的情况,即将这些线程或进程分配到不同的就绪队列中,使得它们可以同时获得处理器资源。

7.多级反馈队列(Multilevel Feedback Queue)调度算法
多级反馈队列调度算法,可以适用于进程调度,这种调度算法能够较好地满足各类进程对于处理器资源的需求,是公认较好的一种调度算法。

①算法实现过程:

(1) 该算法将就绪队列分成多个,为这些队列依次分配递减等级的优先级。第一个就绪队列拥有最高的优先级,第二个就绪队列的优先级次于第一个就绪队列,以此类推。在每个就绪队列中,根据该队列的优先级,划分大小不同的时间片,高优先级的队列中,时间片小,低优先级的队列中,时间片大。
        【提示】有时间片并不意味着是时间片轮转算法,因为进程在运行完一个时间片的时间后,会被放入下一级就绪队列而不是当前队列中。关于时间片大小的确定,一般来说,第一级队列的时间片大小能够满足大多数进程人机交互所需要的时间即可,后续队列的时间片可以按照每级增加一倍的规律增长。

(2) 当一个作业的进程被创建并分配资源后,先将其加入第一个队列的末尾,依据 FCFS 算法等待分配时间片。如果该进程被调度执行后在一个时间片内未完成其任务,将会被降级到下一个队列,重新进行上述过程,直到进程被降级到最低优先级的队列中。此时进程不会继续降级,将在该就绪队列中遵循时间片轮转调度算法等待调度。

(3) 按照队列的优先级进行调度。等级高的就绪队列中的进程会被优先调度,对于第 i 级队列,只有当 1 到 i - 1 队列中不存在就绪进程,才可以调度该队列中进程。如果当前使用处理器资源的进程来自第 i 级队列,而此时第一级队列中进入了新的就绪进程,那么会立刻进行抢占式进程调度,并将此时正在运行的程序放回 i 级队列队尾。

②多级反馈队列调度算法如何满足各类用户:

(1) 终端型用户:多为交互型作业,所需时间较少,大多数在第一级队列中就能快速完成,而第一级队列具有最高的优先级,这样使得任务的周转时间短。

(2) 短批处理作业用户:作业长度稍长,在前几级队列中就可以完成,周转时间较短。

(3) 长批处理作业用户:作业长度长,但是也会在前几级队列中等待并获得时间片,执行部分程序,不会长时间得不到执行。

8.调度算法对比

                                                        表2.13 调度算法对比2

调度算法

特点

先来先服务

优点:逻辑简单
缺点:完全不考虑进程特点
利于:长进程、处理器密集型进程
不利于:短进程、I/O 密集型进程

短进程优先

缺点:进程预计运行时间难以计算
优点:拥有最优的平均等待时间和平均周转时间
利于:短进程
不利于:长进程

优先级

通过设置进程优先级以区分进程特点
利于:高优先级进程
不利于:低优先级进程

高响应比优先

优先级动态变化,利于短进程但可以消除饥饿
利于:短进程
不利于:长进程

时间片轮转

公平的将处理器分配给每个进程

多级反馈队列

能较好满足各类进程对处理器的需求
长进程、I/O 密集型进程随着运行会逐步下沉到低优先级队列

2.2.7 甘特图

        甘特图(Gantt chart),由亨利・劳伦斯・甘特(Henry Laurence Gantt)提出,用于显示和管理项目的进度。本节我们学习甘特图以辅助解题,甘特图在分析进程调度、多进程并发执行等场景时,有着直观清晰的优点。
在设计一个甘特图时,要通过进程列表(并发执行的进程)和时间刻度表示出特定行为(输入、运行等)的顺序与持续时间。一个典型的甘特图中,横轴表示时间,一个块表示一个单位的时间;纵轴表示进程,可以清晰地看到某个进程开始执行和结束的时间;线条的不同花纹表示不同的任务,这些任务的执行需要计算机硬件的支持。分析场景时,可以将描述中资源、进程、任务三个要素抽取出来,对应甘特图的三个维度。
        【提示】下面提到的 “单资源” 是指同类型的资源只有一个。例如现有一个处理器和一个输出设备,即是 “单资源” 的情况,在单资源情况下,使用每种资源时都要保证不能有重叠。
甘特图有如下三种场景:

①场景 A:多进程单任务单资源

        (1) 资源:一个处理器,运行先来先服务调度算法(FCFS)。

        (2) 进程:有三个进程 {A,B,C},分别在 0 时刻、5 时刻和 6 时刻进入就绪队列。

        (3) 任务:每个进程均预计使用处理器运行 4 个时间单位。

        (4) 甘特图:如2.13所示。

②场景 B:多进程多任务单资源

        (1) 资源:一个处理器,运行先来先服务调度算法(FCFS);一个输出设备。

        (2) 进程:有两个进程 {A,B},分别在 0 时刻、3 时刻进入就绪队列。

        (3) 任务:每个进程均预计使用处理器运行 4 个单位时间,之后使用输出器 2 个单位时间进行输出。

        (4) 甘特图:如图 2.14 所示。

③场景 C:多进程多任务多资源

        (1) 资源:两个处理器,均运行先来先服务调度算法(FCFS);一个输出设备。

        (2) 进程:有两个进程 {A,B},分别在 0 时刻、3 时刻进入就绪队列。

        (3) 任务:每个进程均预计使用处理器运行 4 个单位时间,之后使用输出器 2 个单位时间进行输出。

        (4) 甘特图:如图 2.15 所示。

【例 2.4】某单 CPU 系统中有输入和输出设备各 1 台,现有 3 个并发执行的作业,每个作业的输入、计算和输出时间如下表所示,则执行完 3 个作业需要的时间最少是 ( )。

A. 15ms        B. 17ms        C. 22ms        D. 27ms

解:选 B。甘特图如图 2.16、2.17、2.18 所示,多种执行方式均可以在 17ms 内执行完毕。

2.2.8 多处理机调度

        【提示】此部分内容是 2025 考研 408 大纲中新增的内容,以概念的了解记忆为主。其实真的不难,耐心看一遍,应该就能理解。

        多处理机系统 MPS(MultiProcessor System)指由多个处理机(CPU)通过高速总线或通信线路等互连技术,共享输入 / 输出设备、主存储器等资源,在统一的操作系统控制下,协同工作的计算机系统。

        多处理系统 MPS 具有增加系统吞吐量、节省成本(达到相同的处理能力,n 个处理机比 n 台独立计算机更节省成本)、提高系统可靠性(某个 CPU 故障,系统依然可以正常运行)等优点。

多处理机系统 MPS 的类型

(1) 根据系统中处理机的结构和功能,将多处理系统 MPS 分为对称和非对称 2 种类型:

        ①对称多处理机系统 SMP(Symmetric MultiProcessor System):系统中所用 CPU 在硬件结构和功能上都是相同的,各 CPU 之间地位平等,不存在主从之分。目前绝大部分的 MPS 属于 SMPS。

        ②非对称多处理机系统 ASMP(Asymmetric MultiProcessor System):在系统中存在多种硬件结构和功能上不同的 CPU,CPU 之间存在主从之分。

(2) 根据系统中各处理机之间是否相互独立,将多处理系统 MPS 分为紧密耦合和松弛耦合 2 种类型:

        ①紧密耦合 MPS:由操作系统集中统一管理系统中的资源,多处理机通过高速总线互连。

        ②松弛耦合 MPS:各处理机(或计算机)之间相对独立,通过通信线路互连。

2.多处理机系统的进程调度
        多处理机系统中的进程调度与系统结构相关。对于对称多处理机系统,因为 CPU 都是相同的,进程被调到任一处理机上均可。对于非对称多处理机系统,要选择合适的 CPU 运行进程。

3.多处理机系统的进程分配方式

(1) 对称多处理机系统(SMP)中的进程分配方式
        在 SMP 系统中,将所有处理机组织成一个处理机池,选择处理机池中的处理机分配给进程,有以下 2 种分配方式:

        ①静态分配:一个进程在其生命周期内被固定分配到同一个处理机上运行。这种方式的优点是系统开销小,缺点是各处理机之间可能会忙闲不均。

        ②动态分配:一个进程在其生命周期内被随机分配到处于空闲的处理机上运行。这种方式解决了各处理机间忙闲不均的问题。并且,对于紧密耦合 MPS 不会增加开销,而对于松弛耦合 MPS 会增加开销。

(2) 非对称多处理机系统中的进程分配方式
非对称 MPS 大多采用主从式结构,由主机负责进程调度,从机负责运行用户程序。

4.多处理机系统的进程 / 线程调度方式

        (1) 自调度方式:处理机在空闲时,从公共的就绪队列(整个系统中只设置一个)中选择进程 / 线程来运行。可沿用单处理机环境中的调度算法(例如:先来先服务 FCFS)。其缺点是存在系统瓶颈、效率低、线程切换频繁等问题。

        (2) 成组调度方式:将同属于一个进程的一组线程同时分配到一组处理机上运行。为了解决自调度方式中线程切换频繁的问题而提出的,其调度性能比自调度方式更好。成组调度中处理机时间的分配有以下 2 种方式:

        ①给所有进程(应用程序)平均分配处理机时间:对于含有 n 个处理机和 m 个进程(应用程序)的系统,每个进程最多有 n 个线程,则分配给每个进程去占用 n 个处理机的时间最多为m1​。

        ②给所有线程平均分配处理机时间,比按进程分配更高效。

(3) 专用处理机分配方式:进程在其生命周期内被分配一组专用的处理机。

(4) 动态调度:进程的线程数量在运行期间可以动态变化。操作系统负责给作业分配处理机,作业再负责将得到的处理机分配给对应的任务线程。缺点是调度开销大。

2.2.9 习题精编

1.某单处理器系统支持多道程序设计,若此刻有多个就绪态进程,则下列叙述中错误的是( )
A. 进程调度的目标是让进程轮流使用处理器
B. 当一个进程运行结束后,会调度下一个就绪进程运行
C. 上下文切换是进程调度的实现手段
D. 处于临界区的进程在退出临界区前,无法被调度

1.【参考答案】D
【解析】当一个临界区内有进程,则其他想要进入该临界区的进程会被阻塞,不进入该临界区的进程依然可以被调度;一个进程退出临界区前,可以发生调度,只是调度对象不能是要进入该临界区的进程,故 D 选项错误。

2.下列各级调度中,发生频率最高的是( )。
A. 低级调度
B. 中级调度
C. 高级调度
D. 三者发生频率相近

2.【参考答案】A
【解析】低级调度,又称进程调度,该调度的对象是进程(或内核级线程),行为是决定将处理器资源分配给哪个进程。根据选用的进程调度算法,调度程序会决定就绪队列中的哪个进程应获得处理器资源,并由分派程序将处理器分配给被选中的进程。进程调度是最基本的一种调度,是发生频率最高的调度,所以 A 选项正确。

3.下列场合中,一定会发生调度的时机有( )。
I. 新进程被创建时
II. 进程运行完毕
III. 可抢占式系统中高优先级进程进入就绪队列
IV. 时间片耗尽
V. 进程运行中发生错误
A. I、II、III 和 IV
B. II、III、IV 和 V
C. I、III、IV 和 V
D. 全部都是

3.【参考答案】B
【解析】一个新进程被创建,可以是调度的时机,但并不是一定,I 错误。II、III、IV、V 四种情况,都是运行的进程主动或被动放弃处理器,需要调度下一个就绪进程。

4.在非剥夺调度方式下,必定会引起进程调度的情况是( )。
A. 一个新进程被创建
B. 一个进程从执行态进入阻塞态
C. 一个进程从阻塞态进入就绪态
D. 一个进程从就绪态进入阻塞态

4.【参考答案】B
【解析】题目条件要求在非剥夺调度方式下,所以 A、C 不正确;倘如在剥夺调度方式下,A、C 选项陈述的时机是有可能发生调度的。一个进程阻塞后,必须要调度进程,所以 B 选项正确;D 选项的情况不会发生,就绪态进程无法直接进入阻塞态。

5.下列调度算法中,一定是抢占式调度的是( )。
A. 时间片轮转
B. 先来先服务
C. 优先级
D. 短进程优先

5.【参考答案】A
【解析】先来先服务一定是非抢占式,时间片轮转一定是抢占式,优先级和短进程优先既可以是非抢占的,也可以是抢占的。

6.下列调度算法中,既可以设计成抢占式调度,也可以设计成非抢占式调度的是( )。
A. 短进程优先
B. 优先级
C. 高响应比优先
D. A、B、C 均可

6.【参考答案】D
【解析】A、B、C 均可设计成抢占式和非抢占式调度算法,高响应比优先和短进程优先都可以理解成优先级的一种实现方式。考生可以思考一下各种调度算法设计成可抢占式和非抢占式的区别,再翻看知识点解析检查。

7.以下哪些指标是调度算法设计时可以考虑的( )。
I. 公平性
II. 资源利用率
III. 互斥性
IV. 平均周转时间
A. I、II
B. I、II、IV
C. I、III、IV
D. 全部都是

7.【参考答案】B
【解析】互斥性不是调度算法考虑的,III 排除;调度算法的目标是更好地选取下一个运行的进程,一般考虑公平性、资源利用率、平衡性、周转时间、系统吞吐率、响应时间和等待时间等因素,所以选择包含 I、II、IV 的 B 选项。

8.下列关于时间片轮转调度算法的叙述,错误的是( )。
A. 考虑了调度的公平性
B. 能够及时对多个用户做出响应
C. 系统的平均周转时间是最优的
D. 每次时间片耗尽时,会进行调度

8.【参考答案】C
【解析】系统平均周转时间最优的调度算法是短进程优先调度算法。A、B、D 三个选项都是时间片轮转调度算法的特点。

9.下面关于调度算法的叙述中,不正确的是( )。
A. 可以提高处理器利用率
B. 可以提高设备利用率
C. 进程调度发生地越频繁,系统吞吐量越高
D. 调度算法需要对多个指标进行折中

9.【参考答案】C
【解析】进程调度程序本身也是需要占用处理器资源的,如果调度程序运行得太过频繁,反而不利于增加系统吞吐量,可以理解为调度程序抢占了本该属于进程运行的计算机资源,所以 C 选项错误。

10.一个单处理器单道操作系统上,有 4 个作业,到达时间和执行时间如下表所示,调度算法选用先来先服务。求系统平均周转时间( )。

A. 2
B. 3.5
C. 4.5
D. 8

10.【参考答案】C
【解析】平均周转时间为每个进程周转时间的平均值。先求出各个进程的周转时间,再求平均值即可。本题答案为(2+4+5+7)/4=4.5。

11.关于进程优先级的论述中,错误的是( )。
A. 系统进程的优先级一般高于用户进程
B. I/O 密集型进程优先级高于处理器密集型进程
C. 对资源要求少的进程优先级一般高于对资源要求多的进程
D. 优先级可以由用户自定义,但是只能是静态的

11.【参考答案】D
【解析】优先级分为静态优先级和动态优先级,用户可以自定义各个进程的静态优先级,也可以定义动态优先级的计算公式:例如高响应比优先调度算法,就是一种动态优先级调度算法,响应比计算公式可以由用户定义,故 D 选项错误。A、B、C 选项都是确定优先级时常用的原则。

12.一个好的 CPU 调度算法应当可以( )。
A. 降低系统吞吐率
B. 提高系统 CPU 利用率
C. 提高进程周转时间
D. 提高进程等待时间

12.【参考答案】B
【解析】好的 CPU 调度算法可以提高 CPU 的利用率,B 正确。CPU 利用率提高,相同时间会完成更多的任务,系统吞吐量增加,进程周转时间降低,等待时间降低,A、C、D 错误。

13.下列选项中,不应该提高进程优先级的场合是( )。
A. 进程发生 “饥饿” 现象
B. 用户需要一个进程在规定时间内完成
C. 进程等待的 I/O 操作完成,进入就绪队列
D. 进程时间片耗尽

13.【参考答案】D
【解析】进程发生饥饿时,应当增加其优先级,使其有机会使用处理器;当用户需要一个进程在规定时间完成时,也应该给予其高优先级以让该进程可以立即运行;I/O 操作完成后,可以适当提高进程优先级,A、B、C 均是提高优先级的场合。当时间片耗尽后,显然不应该提高其优先级,否则该进程可能再次使用处理器,导致其他进程无法得到处理器资源。一般来说,D 选项的情况下应该降低优先级。

14.某服务器系统中,一个低优先级进程被提交后等待了 6 年尚未被运行,这一现象在操作系统的 CPU 调度中被称为(?),(?)调度算法可以避免这一问题。以上问题的正确选项是( )。
I. 同步
II. 饥饿
III. 并行
IV. 死锁
I. 静态优先级
II. 最短剩余时间优先
III. 短进程优先
IV. 高响应比优先
A. I、IV
B. II、I
C. III、IV
D. II、IV

14.【参考答案】D
【解析】进程长时间得不到处理器资源被称为饥饿,高响应比优先调度算法考虑了进程的等待时间,故一个进程经过长时间的等待,优先级一定会变得很高,从而被分配处理器资源。

15.高响应比优先的进程调度算法综合考虑了进程的等待时间和计算时间,响应比的定义是( )。
A. 进程周转时间与等待时间之比
B. 进程周转时间与计算时间之比
C. 进程等待时间与计算时间之比
D. 进程计算时间与等待时间之比

15.【参考答案】B
【解析】响应比=1+S/T(S:等待时间;T:运行时间)。

16.某系统内存中,共有五个进程,该系统的就绪队列、阻塞队列中最多能同时存在的 PCB 数量分别是( )。
A. 5 个、5 个
B. 5 个、4 个
C. 4 个、5 个
D. 4 个、1 个

16.【参考答案】C
【解析】就绪队列中最多能有 4 个就绪进程,且另一个进程一定在运行态。阻塞队列中可以有 5 个进程。

17.下列调度算法中,有利于处理器密集型进程,不利于 I/O 密集型进程的是( )。
A. 先来先服务
B. 时间片轮转
C. 多级反馈队列
D. 优先级

17.【参考答案】A
【解析】先来先服务算法利于处理器密集型不利于 I/O 密集型,因为处理器密集型进程可以长时间占用处理器,I/O 密集型进程一旦进行 I/O 操作,就需要重新进入就绪队列排队,所以选择 A 选项。

18.下列关于调度算法的叙述中,错误的是( )。
A. 优先级调度算法偏向于用户的紧急作业
B. 短进程优先调度算法不利于长作业
C. 时间片轮转算法利于人机交互
D. 多级反馈队列调度算法偏向长作业

18.【参考答案】D
【解析】用户的紧急作业会被赋予更高的优先级,A 选项正确;短进程优先调度算法利于短进程,长进程则可能长时间得不到处理器,B 选项正确;时间片轮转算法利于人机交互,因为响应时间很短,C 选项正确。多级反馈队列调度算法可以很好的满足各种作业,D 选项错误。

19.现在有三个同时到达的作业J1​,J2​和J3​,它们的执行时间分别是T1​,T2​,T3​,且T1​<T2​<T3​。系统按单道方式运行且采用短作业优先调度算法,则平均周转时间是( )。
A. T1​+T2​+T3​
B. (T1​+T2​+T3​)/3
C. (T1​+2T2​+3T3​)/3
D. (3T1​+2T2​+T3​)/3

19.【参考答案】D
【解析】执行顺序是J1​−J2​−J3​。J1​的周转时间是T1​,J2​的周转时间是T1​+T2​,J3​的周转时间是T1​+T2​+T3​。平均周转时间计算得到 D 选项。

20.某单处理器单道操作系统中,使用优先级调度算法,有三个进程在 0 时刻到达,则平均周转时间为( )。

A. t1​+t2​+t3​
B. (t1​+t2​+t3​)/3
C. (3t1​+2t2​+t3​)/3
D. (t1​+2t2​+3t3​)/3

20.【参考答案】D
【解析】执行顺序是作业 3 - 作业 2 - 作业 1。作业 3 的响应时间是t3​,作业二的响应时间是t3​+t2​,作业三的响应时间是t3​+t2​+t1​。平均周转时间计算得到 D 选项。

21.时间片轮转调度算法中,时间片的大小会影响算法效率。当时间片过小时,算法效率( );当时间片过大时,该算法转换为( )调度算法。
A. 不变;先来先服务
B. 下降;优先级
C. 提高;高响应比优先
D. 其他

21.【参考答案】B、A
【解析】若时间片过小,则调度程序运行频率会很高,因此占用大量的处理器资源,导致效率下降。若时间片大到每一个进程都能在一个时间片内运行完毕,则会变成先来先服务调度算法。

22.下列各个调度算法中,能够较好满足各类进程的是( )。
A. 多级反馈队列调度算法
B. 短进程优先调度算法
C. 最短剩余时间优先调度算法
D. 先来先服务调度算法

22.【参考答案】A
【解析】B、C 调度算法对长进程不利,D 调度算法对 I/O 密集型进程不利,多级反馈队列调度算法可以较好地满足各类进程。

23.某单处理器单道操作系统中,0 时刻 5 个批处理作业同时到达。分别使用短作业优先调度算法和先来先服务调度算法(按 3 - 1 - 2 - 5 - 4 顺序调度)时,平均周转时间分别为( )。

A. 8.4、12.4
B. 8.4、12.6
C. 8.6、12.4
D. 8.6、12.6

23.【参考答案】B
【解析】使用短作业优先调度算法时,平均周转时间计算表达式为(5×1+4×2+3×4+2×5+1×7)/5=42/5=8.4。使用先来先服务调度算法时,平均周转时间计算表达式为(5×4+4×7+3×1+2×5+1×2)/5=63/5=12.6。

24.下列关于时间片轮转算法的叙述中,不正确的是( )。
A. 在时间片轮转算法中,系统将 CPU 的处理时间划分成一个个时间片
B. 就绪队列中的各个进程轮流在 CPU 上运行,每次运行一个时间片
C. 时间片结束时,运行进程自动让出 CPU 并进入阻塞队列
D. 如果时间片长度很小,则调度程序抢占 CPU 的次数频繁,增加了系统开销

24.【参考答案】C
【解析】时间片耗尽后,进程变为就绪态,进入就绪队列,C 选项错误。

25.多级反馈(Feed Back)进程调度算法不具备的特性是( )。
A. 资源利用率高
B. 响应速度快
C. 系统开销小
D. 并行度高

25.【参考答案】C
【解析】多级反馈队列中的各级队列可以使用不同的调度算法,从而较好地满足各个进程。C 选项不正确,因为多级反馈队列调度算法相较其他算法,实现上较为复杂,开销也会变大。

26.以下进程使用最短剩余时间调度算法进行调度,进程周转时间之和为( )。

A. 25
B. 26
C. 27
D. 28

26.【参考答案】C
【解析】注意此算法是可抢占算法,周转时间总和为4+1+8+12+2=27。
P1​:0 时刻到达。0 - 1 时刻运行,1 时刻被抢占,2 - 4 时刻运行,周转时间为 4。
P2​:1 时刻到达。1 - 2 时刻运行,周转时间为 1。
P3​:2 时刻到达。6 - 10 时刻运行,周转时间为 8。
P4​:3 时刻到达。10 - 15 时刻运行,周转时间为 12。
P5​:4 时刻到达。4 - 6 时刻运行,周转时间为 2。

27.设有 4 个作业同时到达,若采用最短作业优先调度算法,则作业的平均周转时间为( )。

A. 1.5h
B. 10.5h
C. 8.75h
D. 10.25h

27.【参考答案】C
【解析】执行顺序是作业 1 - 作业 4 - 作业 2 - 作业 3。不涉及抢占的调度算法较为简单,确定执行顺序后,每个进程的周转时间就等于运行时间 + 该进程之前的所有进程运行时间之和。平均周转时间为(4×2+3×3+2×5+1×8)/4=35/4=8.75。

28.在单道程序环境中,有 4 个作业 A、B、C、D,它们的提交时间与预计运行时间如下表。如果按短作业优先算法调度,它们的运行顺序为( )。

A. A - B - C - D
B. B - C - D - A
C. B - A - D - C
D. A - B - D - C

28.【参考答案】D
【解析】A 作业最先到达且就绪队列中无其他作业,故 A 作业先运行。当 A 作业运行结束后,B、C、D 均到达,再按照执行时间决定顺序为 B - D - C。

29.某单处理器单道操作系统中,使用多级反馈队列调度算法,队列 1、2、3 优先级递减,具体见下表。则这 4 个作业的周转时间之和为( )。

A. 25
B. 27
C. 28
D. 29

29.【参考答案】A
【解析】多级反馈队列调度算法涉及抢占和进程在各级队列间的移动。
周转时间之和为10+1+2+12=25。
J1​:0 时刻到达。0 - 2 时刻运行,2 时刻进入 2 级队列,7 - 10 时刻运行,周转时间为 10。
J2​:2 时刻到达。2 - 3 时刻运行,周转时间为 1。
J3​:3 时刻到达。3 - 5 时刻运行,周转时间为 2。
J4​:4 时刻到达。5 - 7 时刻运行,7 时刻进入 2 级队列;10 - 14 时刻运行,14 时刻进入 3 级队列;14 - 16 时刻运行。周转时间为 12。

30.一个动态优先级调度算法(优先数大的优先级低,优先级相同时选序号小的进行调度),根据等待时间和运行时间对优先数进行动态变化,算法如下:
① 处于就绪队列中的进程的优先数p根据等待时间t(单位秒)进行变化,p=p−t;
② 处于运行状态的进程的优先数p根据运行时间t(单位秒)进行变化,p=p+2×t;
③ 优先数p每隔 1 秒重新计算;
④ 采用抢占式调度策略。
根据下表给出的 5 个进程的到达时间和执行时间,回答下面的问题。(时间单位:秒)

(1) 画出 5 个进程执行的顺序图。
(2) 根据以上的调度算法,分别计算出每个进程的周转时间和响应时间。

2.2.10 真题演练

31.【2009】下列进程调度算法中,综合考虑进程等待时间和执行时间的是( )。
A. 时间片轮转调度算法
B. 短进程优先调度算法
C. 先来先服务调度算法
D. 高响应比优先调度算法

31.【参考答案】D
【解析】时间片轮转调度算法将处理机时间划分成固定长度的时间片,考虑了进程的执行时间而未考虑等待时间,A 不符合;短进程优先调度算法仅考虑了进程执行时间,B 不符合;先来先服务调度算法仅考虑了调度的公平性,C 不符合;高响应比调度算法通过等待时间和执行时间动态调整进程的优先级,D 符合。

32.【2010】下列选项中,降低进程优先级的合理时机是( )。
A. 进程的时间片用完
B. 进程刚完成 I/O,进入就绪队列
C. 进程长期处于就绪队列中
D. 进程从就绪态转为运行态

32.【参考答案】A
【解析】在进程的时间片用完后,进程由运行态变为就绪态,重新等待分配处理机,此时降低其优先级也可保证其他就绪进程使用处理机,A 选项正确;进程完成 I/O 后进入就绪队列等待使用处理机,应该提高其优先级,B 选项错误;进程长期处于就绪队列中说明一直未得到处理机使用权,应该提高其优先级以防止发生饥饿现象,C 选项错误;在实行抢占式调度算法的机器上,在进程刚从就绪态转化为运行态时,若降低优先级,可能会导致进程刚进入运行态就被抢占,降低了处理器效率,D 选项错误。
降低进程优先级的合理时机还有:在进程运行结束后应该降低该进程优先级,否则,刚运行结束的进程因为高优先级,会再次抢占处理器。

33.【2011】下列选项中,满足短任务优先且不会发生饥饿现象的调度算法是( )。
A. 先来先服务
B. 高响应比优先
C. 时间片轮转
D. 非抢占式短任务优先

33.【参考答案】B
【解析】A 和 C 没有考虑任务的长短,错误。D 考虑了任务的长短,但短进程优先有导致饥饿现象的风险,倘若就绪队列中一直有短任务进程进入,则长任务进程会一直等待。高响应比调度算法通过等待时间和执行时间动态调整进程的优先级:优先级等待时间要求服务时间要求服务时间。短任务计算优先级时因为分母更小,会比长任务优先,但长进程只要等待的时间足够长,一定会比短任务优先级高,故不会发生饥饿。

34.【2012】一个多道批处理系统中仅有P1​和P2​两个作业,P2​比P1​晚 5ms 到达,它们的计算和 I/O 操作顺序如下:
P1​:计算 60ms,I/O 80ms,计算 20ms
P2​:计算 120ms,I/O 40ms,计算 40ms
若不考虑调度和切换时间,则完成两个作业需要的时间最少是( )。
A. 240ms
B. 260ms
C. 340ms
D. 360ms

34.【参考答案】B
【解析】P1​先于P2​到达,则P1​先执行;因为是多道批处理系统,P2​到达后可以和P1​并发执行。使用甘特图分析,结果如下图所示。


 

35.【2013】某系统正在执行三个进程P1​、P2​和P3​,各进程的计算(CPU)时间和 I/O 时间比例如下表所示。为提高系统资源利用率,合理的进程优先级设置应为( )。

A. P1​>P2​>P3​
B. P3​>P2​>P1​
C. P2​>P1​=P3​
D. P1​>P2​=P3​

35.【参考答案】B
【解析】解题时需要思考如何提高系统资源利用率 — 尽可能保证处理器和 I/O 设备并行工作。
优先执行 I/O 时间占比高的进程P3​,在P3​阻塞时,其他进程可以占有处理器,从而实现系统资源的并发。如果考生感觉这样理解不直观,可以思考如果只存在P1​和P3​进程,优先级应该如何设置。显然,先执行P3​可以实现更高的系统资源利用率。优先级设计原则总结如表 2.14 所示。

表 2.14 优先级设计原则

优先级设计

                                                        设计原则

静态优先级

1. 系统进程高于用户进程
2. I/O 密集型高于处理器密集型
3. 资源要求低的进程高于资源要求高的进程
4. 用户自定义需求:如要求某任务准时完成,会设置高优先级

动态优先级

高响应比优先调度算法:等待时间越长,运行时间越短,优先级越高
多级反馈队列调度算法:进入就绪队列次数越多,优先级越低

36.【2014】下列调度算法中,不可能导致饥饿现象的是( )。
A. 时间片轮转
B. 静态优先级调度
C. 非抢占式短作业优先
D. 抢占式短作业优先

36.【参考答案】A
【解析】抢占式和非抢占式短作业优先调度算法中,若不断有短进程进入就绪队列,长进程就可能会发生饥饿现象。静态优先级调度中,如果不断有高优先级进程进入就绪队列,低优先级进程可能会发生饥饿。时间片轮转算法保证了各个进程公平使用处理机,不会发生饥饿。

37.【2016】某单 CPU 系统中有输入和输出设备各 1 台,现有 3 个并发执行的作业,每个作业的输入、计算和输出时间均分别为 2ms、3ms 和 4ms,且都按输入、计算和输出的顺序执行,则执行完 3 个作业需要的时间最少是( )。
A. 15ms
B. 17ms
C. 22ms
D. 27ms

37.【参考答案】B
【解析】利用甘特图解题,无论作业的执行顺序如何,都能在 17ms 执行完毕,如图 2.19 所示。

38.【2017】假设 4 个作业到达系统的时刻和运行时间如下表所示。系统在t=2时开始作业调度。若分别采用先来先服务和短作业优先调度算法,则选中的作业分别是( )。

A. J2​、J3​
B. J1​、J4​
C. J2​、J4​
D. J1​、J3

​38.【参考答案】D
【解析】根据题目可知,进行作业调度时,可以调度的作业有J1​、J2​、J3​。采用先来先服务算法时,会选取J1​,因为J1​最先到达;采用最短作业优先调度算法时,选取J3​,因为J3​的运行时间最短。

39.【2018】某系统采用基于优先权的非抢占式进程调度策略,完成一次进程调度和进程切换的系统时间开销为 1μs。在T时刻就绪队列中有 3 个进程P1​、P2​和P3​。其在就绪队列中的等待时间、需要的 CPU 时间和优先权见下表。若优先权值大的进程优先获得 CPU,从T时刻起系统开始进程调度,则系统的平均周转时间为( )。

A. 54μs
B. 73μs
C. 74μs
D. 75μs

39.【参考答案】D
【解析】①P2​的优先权最高,所以会先调度P2​,花费 1μs,切换成进程P2​;②2μs 到 25μs,执行进程P2​;③第 26μs,根据优先权,切换到进程P3​;④第 27μs 到 62μs,执行进程P3​;⑤第 63μs,切换到进程P1​;⑥第 64μs 到 75μs,执行进程P1​;
进程P1​的周转时间=30μs+75μs=105μs;进程P2​的周转时间=15μs+25μs=40μs;进程P3​的周转时间=18μs+62μs=80μs。系统的平均周转时间=(105μs+40μs+80μs)/3=75μs。

40.【2019】系统采用二级反馈队列调度算法进行进程调度。就绪队列Q1​采用时间片轮转调度算法,时间片为 10ms;就绪队列Q2​采用短进程优先调度算法;系统优先调度Q1​队列中的进程,当Q1​为空时系统才会调度Q2​中的进程;新创建的进程首先进入Q1​;Q1​中的进程执行一个时间片后,若未结束,则转入Q2​。若当前Q1​、Q2​为空,系统依次创建进程P1​、P2​后即开始进程调度。P1​、P2​需要的 CPU 时间分别为 30ms 和 20ms,则进程P1​、P2​在系统中的平均等待时间为( )。
A. 25ms
B. 20ms
C. 15ms
D. 10ms

40.【参考答案】C
【解析】系统依次创建进程P1​、P2​后,二者依次进入到队列Q1​,按照时间片轮转调度算法,两个进程先后被分配一个时间片 (10ms) 时间运行。根据二级反馈队列调度算法的性质,在各自的时间片用完后,两个进程先后进入Q2​队列。根据Q2​队列采用的短进程优先调度算法,此时需要考虑两个进程的剩余执行时间 (P1​剩余 20ms,P2​剩余 10ms) 并选择P2​进行调度。P2​运行结束后,调度P1​。具体时间节点为:①1ms 到 10ms 执行P1​;②11ms 到 20ms 执行P2​;③21ms 到 30ms 执行P2​;④31ms 到 50ms 执行P1​。P1​等待时间为 20ms (11ms 到 30ms);P2​等待时间为 10ms (1ms 到 10ms)。平均等待时间为(20ms+10ms)/2=15ms,故选 C。

41.【2020】下列与进程调度有关的因素中,在设计多级反馈队列调度算法时需要考虑的是( )。
I. 就绪队列的数量
II. 就绪队列的优先级
III. 各就绪队列的调度算法
IV. 进程在就绪队列间的迁移条件
A. 仅 I、II
B. 仅 III、IV
C. 仅 II、III、IV
D. I、II、III、IV

41.【参考答案】D
【解析】本题实际在考查多级反馈队列调度算法的特点。首先该调度算法需要定义多个就绪队列,所以 I 需要考虑;第二,当多个就绪队列均有进程排队时,需要确定先调度哪个队列的进程,也就是确定各个队列优先级,所以 II 需要考虑;第三,各个就绪队列的调度算法可以是不同的,所以要为每个就绪队列确定一个调度算法,III 需要考虑;第四,多级反馈队列调度算法需要一个进程可以在不同队列间迁移,这能体现出 “反馈” 这一特点,如果不迁移就成了多级队列调度算法,所以 IV 需要考虑。

42.【2021】下列内核的数据结构程序中,分时系统实现时间片轮转调度需要使用的是( )。
I. 进程控制块
II. 时钟中断处理程序
III. 进程就绪队列
IV. 进程阻塞队列
A. 仅 II、III
B. 仅 I、IV
C. 仅 I、II、III
D. 仅 I、II、IV

42.【参考答案】C
【解析】进程控制块是描述进程状态和特性的数据结构,记录了进程占用 CPU 的时间,所以 I 需要使用;使用时间片轮转调度算法时,在每次时钟中断时,时钟中断处理程序会判断当前运行进程时间片是否耗尽,如果耗尽会进行进程调度,所以 II 需要;当一个进程的时间片耗尽且仍未完成,则该进程进入进程就绪队列,所以 III 需要;时间片轮转调度算法不涉及阻塞状态,所以 IV 不需要,正确答案选 C。

43.【2021】下列事件中,可引起进程调度程序执行的是( )。
I. 中断处理结束
II. 进程阻塞
III. 进程执行结束
IV. 进程的时间片用完
A. 仅 I、III
B. 仅 II、IV
C. 仅 III、IV
D. I、II、III、IV

43.【参考答案】D
【解析】I 正确,例如时钟中断发生时,若当前进程时间片用尽,则会发生进程调度;II 正确,例如此刻运行的进程进行了 I/O 操作,在数据到来之前,该进程会主动阻塞,操作系统会进行进程调度;III 正确,当一个进程运行结束后,需要调度其他进程占有处理器运行,如果没有其他就绪进程,则会调度闲逛进程;IV 正确,是进程调度的时机之一;所以正确答案是 D。

44.【2016】某进程调度程序采用基于优先数(priority)的调度策略,即选择优先数最小的进程运行,进程创建时由用户指定一个 nice 作为静态优先数。为了动态调整优先数,引入运行时间 cpuTime 和等待时间 waitTime,初值均为 0。进程处于执行态时,cpuTime 定时加 1,且 waitTime 置 0;进程处于就绪态时,cpuTime 置 0,waitTime 定时加 1。请回答下列问题。
(1) 若调度程序只将 nice 的值作为进程的优先数,即 priority = nice,则可能会出现饥饿现象,为什么?
(2) 使用 nice、cpuTime 和 waitTime 设计一种动态优先数计算方法,以避免产生饥饿现象,并说明 waitTime 的作用。

44.【参考答案】
(1) 根据第一问的描述,如果调度程序只将 nice 值作为进程的优先数,则本调度算法为静态优先级调度算法。如果有进程的优先级过低,而就绪队列中不断有高优先级进程进入,则该低优先级进程会一直无法使用处理机,从而发生饥饿现象。
(2) 解决饥饿现象的手段之一,就是引入动态优先数,通过动态调整进程的优先数来使得每个进程都可以在有限时间内使用处理机。当设计动态优先数时,不难想到可以类比高响应比优先调度算法,保证设计出来的动态优先数有三个特点:两个进程如果其他指标均相同,由 nice 决定优先数;低优先数进程通过长时间等待优先数会减小;进程使用处理机运行后优先数会变大。最终设计出的计算公式为:
                                        priority=(β×cpuTime)÷(1+nice+α×waitTime)
其中,常数β>0,常数α>0。公式中的除数需要加 1,从而避免除数为 0。α和β用于调整waitTime和cpuTime的影响程度。waitTime用来提高那些长时间等待处理器资源的进程的优先级,防止其饥饿。

 2.3 进程同步

        本节的目标是对临界资源与临界区、同步与互斥关系、软硬件同步机制和信号量机制有一个全面的认识。临界资源和临界区是进程同步最基本的概念,它们引出了进程之间的同步和互斥关系,而信号量及其应用则是本节的重点和难点。考生在学习本节内容的过程中,请先尝试回答下列问题:
(1) 什么是进程同步?为什么要进行进程同步?
(2) 什么是信号量?信号量有哪些种类?

        本节的前半部分(临界资源与临界区、同步与互斥、同步机制设计准则、实现临界区互斥的基本方法)重在掌握进程同步的基本概念,后半部分(信号量、经典同步问题、管程)重在对概念理解的基础上应用信号量机制来实现多进程的同步和互斥问题。

请考生在学习过程中尝试回答下列问题(题目可以在各个小节中找到答案):
(1) 什么是临界资源?有哪些资源属于临界资源?
(2) 如何设计同步机制?设计同步机制需要遵循哪些原则?
(3) 有哪些方法可以实现临界区的互斥?它们各有什么样的优缺点?
(4) 如何使用信号量机制和 P/V 操作来解决多进程同步问题?

2.3.1 进程同步的基本概念

        现代操作系统大多实现了 “进程管理” 的功能,通过对进程的管理,可以保障多个程序并发执行,合理向前推进。一个系统中多道程序异步并发,一方面可以有效地提高资源利用率,大大增加系统的吞吐量,另一方面也使得系统更加复杂。进程同步的任务是采取有效措施来管理多个进程的运行,避免进程不受控制地争夺系统资源而导致混乱局面的出现,以消除进程运行结果中的不确定性和不可再现性。

                为了确保多个进程有序执行,有必要在多道程序系统中引入进程同步机制。本节详细介绍了单处理器系统中的进程同步机制:软 / 硬件同步机制、信号量机制、管程机制等,这些机制用于保证程序执行结果的可再现性。

2.3.2 临界资源与临界区

        由并发进程共享的硬件资源(如打印机)和软件资源(如文本文件)都有一个共同的特点,即在任意时间段内只允许一个进程访问。换句话说,由于这些资源被共享,进程必须实现对它们的互斥访问。

        【举例】P1​、P2​两个进程在系统中并发运行,它们需使用同一台打印机。操作系统先调度进程P1​上处理机运行,当P1​在使用打印机的过程中,用完了分配给P1​的时间片,而此时操作系统又调度P2​上处理机运行,进程P2​也使用了打印机,最终P1​、P2​的打印内容混在了一起。从这个例子可以看出,操作系统需要对临界资源实现互斥访问。

1.临界资源
        同一时刻只能由一个进程使用的资源称为临界资源。打印机、磁带机、绘图仪等物理设备属于临界资源,由不同进程共享的消息队列、变量、数据、文件等软件资源也属于临界资源。

2.临界区
        程序中访问临界资源的那一部分代码称为临界区。进程的运行是异步并发的,为了避免与推进顺序有关的错误,进程必须互斥访问临界区,来获得临界资源的使用权。换句话说,当一个进程进入临界区使用临界资源时,其他请求进程必须在临界区外等待;只有当占据临界资源的进程离开临界区时,才允许其他的进程进入临界区,这是进程之间的互斥关系。

        为了避免不同进程同时访问临界区,可以使用软件方法,也可以在系统中引入特殊的同步机制来协调这些进程。为此,每个程序的代码可以分为两部分:一部分是涉及临界资源的临界区,另一部分是与临界资源无关的非临界区。

        为了保证临界资源的互斥使用,每个进程在进入其临界区之前,必须检查它希望访问的临界资源是否空闲。只有当临界资源没有被访问时,该进程才能够进入临界区,并设置标志以表明临界资源正在被访问;如果临界资源当前正在被其他进程访问,则该进程不能进入临界区。

        为了实现这一目的,临界区前面必须有一段代码用于对临界资源的检查,这段代码称为进入区。同样地,临界区之后必须有一个退出区,表明进程已经释放了相应的临界资源。代码中的剩余部分则称为剩余区。一个试图访问临界区的程序可以归纳如下:

while (1) {
    进入区; // “上锁”,用于检查是否可以进入临界区,若可进入,
             // 则通过设置标志等手段通知其他进程,防止其他进程进入临界区
    临界区; // 用于访问临界资源的代码段
    退出区; // “解锁”,用于解除正在访问临界资源的标志
    剩余区; // 其余部分代码
}

2.3.3 同步与互斥

        进程同步机制的主要任务是协调几个相互协作的进程的执行顺序,使并发执行的进程能够共享系统资源,并按照一定的规则相互制约,保证程序按序执行。在多道程序环境下,并发执行的进程通过共享系统资源或相互合作以完成某项任务,它们之间可能存在互斥或同步关系。

1.互斥关系
        互斥关系,又称进程的间接相互制约关系(程序 - 资源 - 程序模式)。当几个进程并发执行时,由于需要互斥访问系统中的临界资源,这些进程之间就形成了一种相互制约的关系。为了保证进程能顺利运行,系统必须实现对临界资源的统一分配,也就是说,用户必须在使用这些资源之前向操作系统提出申请,而不能直接使用。

2.同步关系
        同步关系,又称进程的直接相互制约关系(程序 - 程序模式)。同步关系通俗来讲就是几个进程为了完成一项任务而规定的执行顺序问题,是进程的执行先后问题。有些程序为了完成某项任务,创建了两个或多个进程,这些进程的相互合作产生了同步关系。

        【举例】小明从盘子里取水果吃,小明的妈妈往盘子里放水果,通过共享同一个缓冲区(盘子)交换数据。只有在盘子为空时,小明的妈妈才能往盘子放水果;只有在盘子里有水果时,小明才能从盘子取水果吃,这就形成了同步关系(同一时刻只能有一个人能对盘子进行操作,则形成了互斥关系)。

        在多道程序环境中,由于互斥和同步这两种制约关系的存在,进程在执行过程中,是否能获得处理器以及以何种速度向前推进,并不由进程本身控制,这也就是进程的异步性。这可能导致进程以不正确的顺序访问共享资源,也可能造成进程每次的运行结果不一致。这些错误往往与时间有关,因此称为 “时间相关的错误”。为了防止此类错误,必须协调进程的执行顺序,确保它们按顺序执行。

2.3.4 同步机制设计准则

无论如何实现进程之间同步与互斥的同步机制,都应遵循以下四条设计准则:

1.空闲让进
        如果一个进程请求进入一个临界区,而此时没有其他进程在该临界区内,应立即允许该进程进入临界区,以便有效使用临界资源。

        【举例】银行里放有自动取款机的小房间可以看作临界资源(假设同一时间只允许一人进入),该房间无人使用时即允许客户进入,这被称为 “空闲让进”。
2. 忙则等待
        如果已经有一个进程进入临界区,正在访问临界资源,那么其他希望进入临界区的进程应在临界区外等待,确保进程互斥访问临界资源。

        【举例】如果取款机的门是锁着的,而且有人正在使用,你就得在取款机外面排队等候,这就是 “忙则等待”。
3. 有限等待
        对于请求访问临界资源的进程,必须保证它能在有限时间内进入对应的临界区。否则,进程长期得不到所需的资源,造成 “无限等待” 的局面,即使最终得到临界资源,也失去了意义。

        【举例】银行必须使得客户在有限的等待时间里,进入取款机获取服务,这就是 “有限等待”。
4. 让权等待
        若一个进程无法进入临界区,应立即释放处理器,避免出现 “忙等”,以提高处理器利用率。

        【举例】如果你进入了 ATM 机,发现自己忘了带银行卡,就要马上出来,供其他人使用,这就是 “让权等待”。上述例子可以说明进程的同步和互斥在生活中的许多地方都有原型。

        【提示】这四条设计准则的严格程度并不相同。若违反 “忙则等待”,则本该保持互斥访问的资源失去了互斥性,同一时间有多个进程访问临界资源,这会导致出现严重错误。若违反 “空闲让进” 或者 “有限等待”,则有可能会导致某些进程无法向前推进。违反 “让权等待” 造成的后果是最轻的:程序本身不会发生错误,只会导致处理机资源被浪费。实践中,有不少实现只满足了前三条设计准则,并不满足让权等待。这些 “忙等” 的实现一般称为 “自旋锁(Spinlock )”,自旋指的就是处理机 “空转”。如果开发者能够确保临界区很小,访问时间很短,那么这些实现也是可以接受的。

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

1.软件同步机制
        由于进程调度的不确定性,用软件实现临界区互斥是很难的。本节先考虑两个进程的互斥。我们先介绍几种常见的错误实现,然后介绍正确实现中比较简单的一种(即 Peterson 算法)。

①几种错误实现
        常见的错误想法主要有两类,一是设置共享变量 turn 记录进入临界区的权限,从而让进程轮流进入临界区的单标志法;二是用整型数组 flag 进行标记(双标志法),进程检查其余进程是否希望进入临界区,若没有其他进程希望进入临界区,则该进程能进入临界区。但实际上二者都违背了上面的设计准则。

(1)单标志法
        考虑在程序中设置一个共享的整型变量标志 turn,表示程序进入临界区的权限,可以取值为 0 或 1。当 turn 取值为 0 时,进程P0​可以进入临界区;取值为 1 时,进程P1​可以进入临界区。当进程P0​退出临界区时,将 turn 置为 1(将进入临界区的权限转让给进程P1​);当进程P1​退出临界区时,将 turn 置为 0(将进入临界区的权限转让给进程P0​),算法可描述如下:

// 进程(P0)
while(TRUE) {
    // 若turn等于本进程编号,准入
    while(turn!= 0);
    临界区;
    turn = 1; // 准许对方进入临界区
    剩余区;
}
// 进程(P1)
while(TRUE) {
    // 若turn等于本进程编号,准入
    while(turn!= 1);
    临界区;
    turn = 0; // 准许进程进入临界区
    剩余区;
}

        算法中的while(turn!= 0);值得解释一下,这一表示方法在后文中也会反复出现。这是一个 while 循环,其循环体为空语句;其含义就是反复检查循环条件,直到条件被打破为止。这就是所谓的 “忙等”。

        该算法能保证进程P0​、P1​交替进入临界区,但也只适用于进程交替进入临界区的情况。考虑这样一种情况:进程P1​在使用完临界资源后,将标志 turn 置为 0(将进入临界区的权限转让给进程P0​),但是P0​不再使用临界资源,因此 turn 一直保持为 0,使得P1​即便希望进入临界区,也无法得到权限。其问题在于强制要求进程轮流进入临界区,但这样通常不符合实际需求,因此,该算法违背了 “空闲让进” 准则。

(2)双标志先检查法
        双标志先检查法将共享变量 turn 改为整型数组 flag [2],进程Pi​在进入临界区前,将 flag [i] 置为 TRUE,表示其正在使用临界资源,退出后将 flag [i] 置为 FALSE,表示其已经退出临界区。双标志先检查法的算法描述如下:

while(TRUE) {
    while(flag[j]); // (1) 检查对方进程是否在使用临界资源
    flag[i] = TRUE; // (2) 表示本进程已进入临界区
    临界区;
    flag[i] = FALSE; // 表示本进程已退出临界区
    剩余区;
}

        考虑这样一种情况:进程P0​和P1​同时要求进入临界区,P0​执行语句 (1),发现 flag [1] 为 FALSE,退出了 while 循环准备执行语句 (2);此时刚好时间片用完调度到了P1​,由于P0​还未执行语句 (2),flag [0]=FALSE,因此P1​恰好能够退出循环继续执行,将 flag [1] 置为 TRUE 进入临界区。再调度时,P0​不会重新检查 flag [1],只会将 flag [0] 置为 TRUE 并进入临界区。如此一来,两个进程就能同时访问临界区,可见双标志先检查法没有确保临界区的互斥访问。

        双标志先检查法相对于单标志法,满足了空闲让进的准则,但是不能保证进程对临界资源的互斥使用,违背了 “忙则等待” 准则。

(3)双标志后检查法
        相对于双标志先检查法,双标志后检查法调整了赋值语句flag[i] = TRUE和 while 所在的标志检测语句的顺序,先设置标志,再进行准入检测,避免了两个进程同时进入临界区的情况。双标志后检查法的算法描述如下:

while(TRUE) {
    flag[i] = TRUE;
    while(flag[j]);
    临界区;
    flag[i] = FALSE;
    剩余区;
}

        双标志后检查法能保证不同进程不能同时进入临界区,但是违背了 “有限等待” 准则。考虑P0​置 flag [0] 为 TRUE 后发生调度,进程P1​置 flag [1] 为 TRUE 后无法进入临界区,调度到P0​后同样无法进入临界区。那么两个进程均无法跳出随后的 while 循环,均无法获得进入临界区的权限,陷入无限等待。
② 正确实现:Peterson 算法
        Peterson 算法将单标志法和双标志后检查法结合起来,既设置 flag 标志,用于表明进程是否希望进入临界区,又设置共享变量 turn,用于规定进程进入临界区的顺序。在进入临界区前,进程Pi​既检查另一个进程Pj​的标志 flag [j],又检查共享变量 turn。Peterson 算法可描述如下:

while(TRUE) {
    flag[i] = TRUE; // flag[i]置为TRUE,表示本进程希望进入临界区
    turn = j; // 将turn置为对方编号(礼让)
    while(flag[j] && turn == j); 
    // 只要flag[j]为FALSE(进程j无意进入临界区)或turn为i(轮到进程i)就可进入临界区
    临界区;
    flag[i] = FALSE;
    剩余区;
}

        Peterson 算法满足互斥条件:假设进程Pi​和进程Pj​都通过了进入区,而没有到达剩余区,由于 flag [i] = flag [j] = TRUE,turn = i 且 turn = j,i 和 j 不相等,两个进程无法同时进入临界区,实现了对临界资源的互斥使用。
        Peterson 算法满足空闲让进条件:假设进程Pj​不打算进入临界区,那么 flag [j] = FALSE,进程Pi​能直接跳出 while (flag [j] && turn == j) 语句的循环过程,不会造成单标志法的 “有空不让进” 的局面。又假设进程Pi​、Pj​都希望进入临界区,那么 turn 就发挥作用了,轮到哪个进程,哪个进程就能进入临界区(先申请的进程先进人,因为双方进程相互礼让,后申请的进程将 turn 覆盖为先申请进程的编号)。
        Peterson 算法满足有限等待条件:假设进程Pj​在临界区内,而进程Pi​因执行 while 循环而等待,turn 已经被进程Pi​设置为 j,而在Pj​退出临界区后,会把 flag [j] 设置为 FALSE。所以在Pj​退出临界区后,Pi​必然能进入临界区。临界区代码的执行时间一般是有限的,故 Peterson 算法满足有限等待。
        Peterson 算法的缺点在于只能处理两个进程之间的临界区问题,但是实际问题通常不止两个并发进程。此外,Peterson 算法同样不满足让权等待(因为进程即使进入不了临界区,也会一直运行 while 循环占用处理器资源)。因此,仅凭软件机制仍有不足之处。
四种软件同步机制方法的对比如表 2.15 所示:

                                        表 2.15 四种软件同步机制方法对比

方法

简述

缺点

单标志法

引入 turn,循环检查直到 turn = 本进程编号

只能交替进入,违背 “空闲让进”

双标志先检查法

引入 flag [2],先检查对方是否进入临界区

会同时进入,违背 “忙则等待”

双标志后检查法

引入 flag [2],先设置自身 flag,再检查

同时希望进入时违背 “有限等待”

Peterson 算法

引入 turn 和 flag [2],进入临界区前检查两次

违背 “让权等待”,不适用于多进程

2. 硬件同步机制

        现代计算机有特殊的硬件指令,可以检测和设置一个字(Word)的内容(Test And Set 指令),也可以交换两个字的内容(Swap 指令),这些指令是解决临界区问题的基本工具。
(1) 关中断方法
        尽管在单核环境中,多个进程不能并行执行,但调度器仍可以安排多个进程在处理器上交错执行,这仍会导致临界区问题。因此,关闭中断,暂停进程调度,让进程独占处理机,就可以解决临界区问题。
        关中断方法的一般流程如下:

while(TRUE) {
    关中断;
    临界区;
    开中断;
    剩余区;
}

        单处理机系统中,关中断后就只能执行当前进程,也就能避免多个进程进入临界区,满足了 “忙则等待”。临界区执行结束后,中断重新打开,其他进程就能被调度;其他进程若想要访问临界资源,同样可以关闭中断并进入临界区,满足了 “空闲让进”。如果操作系统的调度算法能保证每个进程都能在有限时间内调度上处理机,那么也能满足 “有限等待”。因此,关中断方法大体上实现了临界区互斥。然而,关中断的方法有很多缺点:

        滥用关中断权会产生严重的后果,不应将这一权限交给用户进程。

        并发环境中关中断会限制程序交替运行,导致 CPU 和 I/O 不能并行,影响系统执行效率。

        对多处理器环境不适用。即使同时关闭了所有核心的中断,也不能阻塞其他核心上正在执行的进程。
(2) 硬件指令方法
        在处理临界区问题时,可以把标志(Flag)看作一把锁,“锁开” 对应进入,“锁关” 对应等待,初始状态为 “锁开”。每个希望进入临界区的进程必须先测试锁的开与关:若 “锁关”,则进程必须等待;若 “锁开”,则进程必须立即对其锁定,防止其他进程进入临界区。为了防止几个进程同时测试一把锁的开与关,测试和上锁的操作必须是连续的原子操作,不能单独进行。
① TSL(TestAndSetLock,测试并上锁)指令
        TSL 指令是一条特殊的机器指令,其特殊之处在于用一条指令连贯地执行了多个操作。这条指令可以等价于下面的函数:

bool TSL(bool *lock) { // lock就是想要获取的锁
    bool old = *lock;
    *lock = TRUE; //上锁
    return old;
}

        lock 变量的值为 FALSE 时对应未上锁,lock 变量的值为 TRUE 则对应上锁,TSL 函数的返回值与 lock 变量被检测时的值相同。该指令是原子操作,其在执行过程中不会被打断。假如进程希望进入临界区,应测试 lock 变量的值是否为 FALSE,若为 FALSE(检测到原先锁是开的)则立刻上锁,线程就能进入临界区。若 lock 变量的值为 TRUE(检测到锁是关的),则该进程需要等待持有锁的线程解锁。

while(TRUE) {
    ……
    while(TSL(&lock));
    临界区;
    lock = FALSE;
    剩余区;
}

        当 lock 变量的值为 FALSE 时,TSL 函数才会返回 FALSE,进程可以离开 while 循环体,并进入临界区,同时将 lock 变量的值设为 TRUE。如果有其他进程申请进入临界区,TSL 函数的返回值为 TRUE,该进程需自旋等待。只有当进入临界区的进程退出时,将 lock 变量的值设为 FALSE(开锁),其他进程才能进入临界区。
        TestAndSetLock 指令满足空闲让进:当临界区空闲,lock 变量的值一定为 FALSE,TSL 函数也会返回 FALSE,由于 TSL 是硬件原子指令,其执行由硬件实现,一气呵成,不会有多个进程同时访问 lock 变量,必然只存在一个进程可以顺利离开 while 循环体。因为被拦在临界区外的进程会不断地测试 while 语句,故该方法是 “忙等” 的。此外,由于 lock 内存地址是共享的,相对于关中断方法,TestAndSetLock 指令可以在多核处理器上使用。
② Swap 指令
        该指令又称 exchange 指令,用于交换地址 a 和 b 中的内容。Swap 等价于下面的函数:

void Swap(bool *a, bool *b) {
    bool temp = *a;
    *a = *b;
    *b = temp;
}

        对于每个临界资源,Swap 指令为其设置一个全局布尔变量 lock,其初值为 FALSE;在每个进程中还会设置一个局部布尔变量 key,其初值为 TRUE。利用 Swap 指令实现进程互斥的一般流程如下:

while(TRUE) {
    key = TRUE;
    while (key != FALSE) {
        Swap(&lock, &key);
    }
    临界区;
    lock = FALSE;
    剩余区;
}

        只有全局变量 lock 的值被初始化为 FALSE,而 n 个并发进程中的局部变量 key 的初始值都被初始化为 TRUE,故对应的 n + 1 个地址中只有 1 个的值为 FALSE。只有第一个将 lock 地址中的变量 Swap 到其局部变量 key 的进程才能离开 while 循环体,并进入临界区。此后,其余进程都会被排斥在临界区外。只有当有进程退出临界区,lock 才会重置为 FALSE,允许新的进程进入临界区。Swap 指令也满足空闲让进。
实现临界区互斥的硬件方法的优点:
        (a) 可以保证共享变量的完整性和正确性,且简单有效。
        (b) 可以适用于以共享内存方法通信的多处理器环境,并支持运行任意数量的进程。
        (c) 可用于复杂的多临界区并发问题,可以为每个临界区定义相应的变量。
实现临界区互斥的硬件方法的缺点:
        (a) 不满足 “让权等待”,会发生 “忙等”,不能有效利用处理器时间。
        (b) 可能导致饥饿(不能保证 “有限等待”):硬件指令方法中并没有指定下一个访问临界区的是哪一个进程,而是交给各个进程 “竞争”:哪个进程加锁成功,哪个进程就能进入临界区。而进程只有在处理器上运行时才有可能加锁成功。若有些进程在每一次分配 CPU 时都碰巧得不到调度,那么就有可能一直无法加锁,导致饥饿。换言之,硬件指令方法无法保证等待时间的上界。
        (c) 受调度算法影响。调度算法的公平性会极大影响硬件指令方法的效果。若调度算法 “偏心” 某些进程(比如说设置了高优先级),其他进程得不到处理机也就无法加锁,这就使得上一条中的情况更为严重。更有甚者,若某个低优先级进程持有了高优先级进程正在等待的临界资源,操作系统又只调度高优先级进程上处理机,那么就会引发死锁。
        (d) 难以应用于复杂的多进程同步问题。
关中断方法和硬件指令方法的对比如下表所示:

方法

简述

缺点

关中断方法

进入临界区前关中断,退出再开中断

只能运行在内核态,且会降低效率

TSL 指令、Swap 指令

硬件保证测试和解锁为原子操作

违背让权等待,可能导致死锁、饥饿

2.3.6 信号量

        上一小节介绍了进程互斥的四种软件实现方式(单标志法、双标志先检查法、双标志后检查法、Peterson 算法)和三种硬件实现方式(关中断方法、硬件指令方法、Swap 指令),上述解决方案都无法满足 “让权等待” 准则。因此,荷兰计算机科学家 Dijkstra 在 1965 年提出了 “信号量” 这一机制。

        信号量(Semaphore)是一种解决多道程序系统中的同步和互斥问题的工具,用于为一组合作进程提供可靠的支持,其基本原理是在几个进程之间使用简单的信号来实现同步在这个过程中,一个进程可以被阻塞在某个特定位置,直到它收到一个特殊的信号,方可继续运行。

        在操作系统中,信号量是一种关联一类临界资源的数据结构,信号量的不同值表示一个临界资源的不同状态。进程在信号量上进行合法操作,以便相互交换控制信息,协调其执行顺序,互斥使用临界资源。自提出以来,信号量机制不断地发展与改进。通过使用信号量机制,编程人员可以实现进程间的同步与互斥,也可以使多个进程按照预设的前后驱关系向前推进。

        【提示】考生在阅读和编写代码的同时,要注意理解信号量的含义,一个信号量对应一种资源,信号量的值等于该资源的剩余数量。若信号量的值小于 0,说明此时有进程在等待这种资源,信号量的值的绝对值就是等待该资源的进程数;若信号量的值等于 0,说明该资源恰好被分配完。P (S) 表示申请一个资源 S,若资源不够则阻塞等待;V (S) 表示释放一个资源 S,若有进程在等待该资源,则唤醒一个进程。

1. 信号量的类型

(1) 整型信号量

        整型信号量把信号量定义为一个用于表示资源数目的整数 S,对于 S 的访问(加减),只能通过原语实现。可以把整型信号量机制看作是对 S 定义了三种操作,能且仅能通过以下操作对信号量进行访问:
(a) 将 S 初始化为任意非负整数,S 指代的是操作系统中某资源的数量,初始化时需要符合资源的实际情况。
(b) 可以通过 P 原语实现 S 值的减 1,如果此时 S ≤ 0,操作系统无法为其分配资源,该进程就会陷入 while 循环,直到 S > 0,方可结束等待。

void P(int S) {
    while (S <= 0); // 不符合条件则陷入“忙等”
    S--;
}

(c) 可以通过 V 原语实现 S 数值的加 1。

void V(int S) {
    S++;
}

        P 原语,又称 wait 原语,用于实现对 S 值的减 1,减 1 相当于申请并获取一个资源,资源不足则开始等待;V 原语,又称 signal 原语,用于实现对 S 值的加 1,加 1 相当于使用结束并释放一个资源。P/V 符号是 Dijkstra 在大多数论文中的表述,同时考虑到书写简洁,本教材统一使用 P/V 符号来表示对信号量的原子操作。

(2) 记录型信号量

        整型信号量中,如果 S ≤ 0 时,P 操作会陷入 “忙等” 状态,这有悖于同步机制设计的 “让权等待” 准则。为了实现 “让权等待” 准则,记录型信号量在整型信号量的基础上,增加了一个被阻塞进程的队列,用于链接所有等待相应资源的进程。

struct semaphore {
    int s; // 整型信号量
    queueType blocked_process_queue; // 用于记录被阻塞进程的队列
};

【提示】互斥信号量等价于取值只能为 0、1 的记录型信号量。

        可以把记录型信号量机制看作是对上述 semaphore 结构体定义了三种操作,能且仅能通过以下操作对信号量进行访问:
① 初始化 semaphore 结构体。
② 可以通过 P 原语实现对 S.s 的减一,如果 S.s < 0,则阻塞该进程,并将其放入 S 的阻塞队列。

void P(semaphore S) {
    S.s--;
    if (S.s < 0)
        阻塞该进程并将其放入阻塞队列中;

}

③ 可以通过 V 原语实现 S.s 的加一,如果 S.s ≤ 0,则从阻塞队列中唤醒一个进程。

void V(semaphore S) {
    S.s++;
    if (S.s <= 0)
        从阻塞队列中选择并唤醒一个进程;
}

        【提示】尽管记录型信号量相比于整型信号量有诸多优点,但实际考试中按照整型信号量进行声明和定义即可。以下代码给出了信号量命名的示例。

semaphore printer = 8; // 声明一个记录系统中打印机资源的信号量
semaphore mutex = 1; // 声明一个互斥信号量

2. 信号量的应用

(1) 利用信号量实现进程互斥

        本节使用信号量来实现多个进程的互斥。一般而言,用于实现进程互斥的信号量命名为 mutex(源自互斥的英文 MUTual EXclusion)。当多个进程需要互斥访问某一临界资源时,每个进程在访问临界区前需对互斥量 mutex 执行 P 操作,在退出临界区后需对互斥量 mutex 执行 V 操作。

利用信号量实现进程互斥的伪代码实现如下(以两个进程为例):

semaphore mutex = 1; //互斥量的初始值为1
Process_A {
    while(ture) {
        P(mutex); //加锁
        访问临界区;
        V(mutex); //解锁
        剩余区;
    }
}
Process_B {
    while(ture) {
        P(mutex); //加锁
        访问临界区;
        V(mutex); //解锁
        剩余区;
    }
}

【提示】在 P/V 操作中,要避免引入不需要在临界区内执行的指令,避免其他进程不必要的等待,从而提高效率。

(2) 利用信号量实现进程同步

        本节使用信号量来实现两个进程PA​和PB​的同步关系。这里所谓的同步关系其实指的是PA​和PB​的前后关系,亦即PA​执行结束后执行PB​。实践中确实经常有这种需求,例如PA​用于拉取数据,PB​用于对数据进行处理,那么PB​就必须在PA​之后执行。

利用信号量实现进程同步的伪代码实现如下:

semaphore S = 0; // 同步信号量的初始值为0
Process_A {
    修改文件;
    V(S); // 表示修改完毕
}
Process_B {
    P(S); // 判断是否修改完成
    修改文件;
}

同步与互斥的关系总结:

        ·同步是协作关系:多个协作进程在工作中相互等待或通信的制约关系。

        ·互斥是竞争关系:当一个进程进入临界区访问临界资源时,另一进程必须等待,当访问临界资源的进程退出临界区后,另一个进程才被允许访问该临界资源。

        ·进程互斥是进程同步的一种特殊情况,互斥的目的也是让多个进程协调推进。

        ·互斥信号量的初始值一般为 1。

(3) 利用信号量实现前驱关系

        本节使用信号量来实现多个进程间的前驱关系。假设有 6 个进程,进程与进程之间存在着如图 2.20 所示的前驱关系。图中两个节点间的箭头表示两个节点存在直接前后驱关系,例如S1​→S2​表示S2​需要在S1​进程之后运行。

在实现前驱关系时,可以分两步解决问题:
(1) 列出题目中给出节点的所有直接前后驱关系。图 2.20 中,共有 7 个直接前后驱关系(不难发现就是图中 “→” 的数量),分别是 {S1​→S2​,S1​→S3​,S2​→S4​,S2​→S5​,S3​→S6​,S4​→S6​,S5​→S6​}。
(2) 为上述每一个直接前后驱关系设置一个信号量,并初始化为 0。在编写伪代码时,把每个进程分成以下三个部分:
(a) 检查前驱进程是否运行完毕(使用 P 操作)。
(b) 执行本进程的任务。
(c) 告知直接后继进程,本进程已运行完毕(使用 V 操作)。

根据上述的解题步骤,实现图 2.20 中前驱关系的伪代码如下:

//声明控制各个直接前后驱关系的信号量
//s后面两个数字代表本信号量控制的前后进程号
semaphore s12 = s13 = s24 = s25 = s36 = s46 = s56 = 0;
S1 (){
   ...;
    V(s12);
    V(s13);
}
S2 (){
    P(s12);   //检查前驱进程是否运行完毕(使用P操作)
   ...;     //本进程工作,对应上述2(b)步骤
    V(s24);
    V(s25);  //告知直接后继进程已经运行完毕(使用V操作)
}
S3 (){
    P(s13);
   ...;
    V(s36);
}
S4 (){
    P(s24);
   ...;
    V(s46);
}
S5 (){
    P(s25);
   ...;
    V(s56);
}
S6 (){
    P(s36);
    P(s46);
    P(s56);
   ...;
}

2.3.7 经典同步问题

        在多道程序环境下,进程同步问题非常重要。有很多经典的进程同步问题,其中最具代表性的是 “生产者 - 消费者问题”、“哲学家进餐问题” 和 “读者 - 写者问题”。

1. 生产者 - 消费者问题

        生产者 - 消费者问题是对合作进程中内部关系的抽象化。例如,当输入进程和计算进程相互合作时,输入进程属于生产者(输入要计算的变量值),计算进程可以归为消费者;对于计算和打印进程,计算进程可以归为生产者,而打印进程则属于消费者。因此,生产者 - 消费者问题是一种最具实用性和代表性的同步问题。

        生产者 - 消费者问题可以描述为一组生产者和一群消费者一起工作,通过一个大小为 n 的有限缓冲区进行生产和消费。一个缓冲区可以容纳 n 个产品,其中生产者负责投放产品,消费者负责消费产品。

        由于生产者和消费者共享的缓冲区的大小为 n,缓冲区最多可以容纳 n 个产品。因此,生产者和消费者进程在操作过程中受到以下两个约束:
(1) 如果缓冲区已满,生产者不能继续生产,否则会使产品溢出。
(2) 如果缓冲区为空,消费者不能继续消费,否则会导致从空缓冲区拿取产品。

如图 2.21 所示,为避免产品溢出或从空缓冲区拿取产品,应满足以下同步规则:
(1) 如果缓冲区已满,生产者生产结束后应当被阻塞,等待消费者取出产品、缓冲区有空位后将其唤醒。
(2) 如果缓冲区空,那么消费者试图拿取产品时也必须被阻塞,等待生产者把产品放进去后将其唤醒。

        为了实现上述进程的同步关系,可以设置两个信号量:empty 表示当前空缓冲区的数量,初始值为 n;full 表示当前非空缓冲区的数量,初始值为 0。此外,缓冲区是临界资源,需设置互斥信号量 mutex,实现生产者和消费者进程对缓冲区的互斥访问。

生产者 - 消费者问题的程序实现如下:

semaphore mutex = 1, empty = n, full = 0;
void producer() {
    while(TRUE) {
        生产一个新的产品;
        P(empty);
        P(mutex);
        将产品放入空闲缓冲区;
        V(mutex);
        V(full);
    }
}
void consumer() {
    while(TRUE) {
        P(full);
        P(mutex);
        从缓冲区取出一个产品;
        V(mutex);
        V(empty);
        消费该产品;
    }
}

        信号量 mutex 保证同一时刻只能有一个进程对缓冲区进行操作(放入或取走产品)。需要注意的是 P 操作的顺序很重要,P (empty/full) 必须在 P (mutex) 之前,不能颠倒。否则假设消费者首先访问临界区,会被阻塞,但此时生产者希望往缓冲区放东西时,无法进入临界区,就形成了死锁。V 操作的顺序则允许颠倒。

2. 哲学家进餐问题

        哲学家进餐问题是进程同步的一个典型问题。问题描述:如图 2.22 所示,有五个哲学家(图中表示为圆圈),他们共用一张圆桌,围坐在五把椅子上,桌上有五个碗和五只筷子(筷子表示为线条),他们的行为模式是思考和进餐交替进行。通常情况下,哲学家会思考。当哲学家饥饿的时候,他会尝试拿取离自己最近的左右两根筷子,只有当他手上有两根筷子的时候,才能吃饭。饭后,他放下筷子,继续思考。

        在哲学家进餐问题中,临界资源是筷子。值得注意的是,该问题和生产者 - 消费者问题的区别在于由于位置的不同,每根筷子都不相同。必须为每根筷子分别定义一个初始值为 1 的互斥信号量 chopstick [i],其代码如下所示:

semaphore chopstick[5] = {1, 1, 1, 1, 1};

其中第 i 位哲学家的活动可描述如下:

while(TRUE) {
    P(chopstick[i]);
    P(chopstick[(i+1) % 5]);
   ...
    吃饭;
   ...
    V(chopstick[i]);
    V(chopstick[(i+1) % 5]);
   ...
    思考;
   ...
}

        上述方法保证了筷子的互斥使用,但仍可能导致死锁发生。如果五个哲学家同时拿起他们左边的筷子,就会出现循环等待的情况,导致死锁发生。常见的几种解决方案如下:
        (1) 最多允许四名哲学家同时进食(破坏了死锁产生的 “循环等待” 条件)。
        (2) 只有在哲学家的左右两边都有筷子的情况下才被允许就餐(破坏了死锁产生的 “请求和保持” 条件)。
        (3) 让编号为奇数的哲学家先拿左筷子,再拿右筷子,编号为偶数的哲学家先拿右筷子,再拿左筷子(破坏了死锁产生的 “循环等待” 条件)。

【提示】死锁产生的四个必要条件将在下一节详细阐述。

3. 读者 - 写者问题

        读者 - 写者问题是对数据对象(数据文件或记录)的访问模型,问题描述如下:有一个共享文件 F,允许多个进程(读者)同时读取 F 中包含的信息,但在任何时刻只允许一个进程(写者)写入或修改 F 中的信息。当一个进程正在读取,不允许其他进程写入(但可以读取);当一个进程正在写入,其他进程的任何读写操作均不被允许。

        【提示】若生产者作为一个写进程,消费者作为一个读进程,生产者 - 消费者问题能不能看作是一个特殊的读者 - 写者问题?答案是否定的。生产者与写进程的区别在于,它还会同时读取队列指针,确定在哪里写,以及确定缓冲区是否已满;消费者也不仅仅是一个读进程,其同样涉及队列指针和缓冲区操作。

        读者 - 写者问题有三种典型的算法,即读者优先算法、写者优先算法和读写公平算法,下面分别进行讨论。

① 读者优先

        在该算法中,读进程具有优先权,其访问临界区时,只要有一个读进程在读,读进程就能继续获得对临界区的控制权,但可能会导致写进程饥饿。读者优先的算法描述如下:
(1) 当一个读进程正在读取时,写进程必须等待。
(2) 在写进程等待时,随后的读进程仍可以进入临界区读取。
(3) 只有在没有读进程在读取的情况下,写进程才可以写入。

读者优先的程序实现如下:

semaphore rmutex = 1; //定义readcount的互斥信号量,初始值为1
semaphore wmutex = 1; //定义用于写的互斥信号量,初始值为1
int readcount = 0; //用于记录当前读者数量,初始值为0
void reader() { //读进程
    do {
        P(rmutex); //(readcount操作互斥)对rmutex执行P操作,防止他人访问
        if (readcount == 0)
            P(wmutex); //(同步)如果自己是第一个读者,禁止写者写
        readcount++; //读者数量加1
        V(rmutex); //(readcount操作互斥)对rmutex执行V操作,允许他人访问
        进行读操作;
        P(rmutex); //(readcount操作互斥)对rmutex执行P操作,防止他人访问
        readcount--; //读者数量减1
        if (readcount == 0)
            V(wmutex); //(同步)如果自己是最后一个读者,允许写者写
        V(rmutex); //(readcount操作互斥)对rmutex执行V操作,允许他人访问
    } while(TRUE);
}
void writer() { //写进程
    do {
        P(wmutex); //(写互斥)对wmutex执行P操作,防止他人访问文件
        进行写操作;
        V(wmutex); //(写互斥)对wmutex执行V操作,允许他人访问文件
    } while(TRUE);
}

        首先,定义一个初始值为 1 的互斥信号量 wmutex,第一个进入的读者和所有进入的写者在对共享文件 F 进行操作之前,必须先执行 P (wmutex) 操作,在结束对 F 的操作后,必须执行 V (wmutex) 操作。

        此外,定义一个整数型变量 readcount,用于表示当前正在读取 F 的进程数量。读进程在读取 F 之前,必须执行 readcount 加 1 的操作;而在读取 F 之后,必须执行 readcount 减 1 的操作。readcount 变量是一种临界资源,因此定义一个 rmutex 信号量,实现所有的读者进程对 readcount 变量的互斥访问。

② 写者优先

        在该算法中,写进程具有优先权,如果有写进程希望访问临界区,就会禁止新的读进程进入临界区,解决了写进程的饥饿问题。写者优先的算法描述如下:
(1) 当一个读进程在读取时,写进程不能进入临界区。
(2) 当一个写进程表示希望进行写入时,随后的读进程不得进入临界区读取。
(3) 只有当所有的写进程都离开临界区后,读进程才可以进入临界区读取。

写者优先的程序实现如下:

semaphore rmutex = 1; //定义readcount的互斥信号量,初始值为1
semaphore wmutex = 1; //定义writecount的互斥信号量,初始值为1
semaphore read_write = 1; //用于在读取时检测是否有写者的互斥信号量,初始值为1
semaphore write_read = 1; //用于在写入时防止他人进入的互斥信号量,初始值为1
int readcount = 0; //用于记录当前读者数量,初始值为0
int writecount = 0; //用于记录当前写者数量,初始值为0
void reader() { //读进程
    do {
        P(read_write); //(读写互斥)在读取时检测是否有写者,如有写者则等待
        P(rmutex); //(readcount操作互斥)对rmutex执行P操作,防止其他读者访问
        readcount++; //读者数量加1
        if(readcount == 1)
            P(write_read); //(读写互斥)如果自己是第一个读者,禁止写者写
        V(rmutex); //(readcount操作互斥)对rmutex执行V操作,允许其他读者访问
        V(read_write); //(读写互斥)允许其他读者或写者访问文件
        进行读操作;
        P(rmutex); //(readcount操作互斥)对rmutex执行P操作,防止其他读者访问
        if(readcount == 1)
            V(write_read); //(读写互斥)如果自己是最后一个读者,允许他人读写
        readcount--; //读者数量减1
        V(rmutex); //(readcount操作互斥)对rmutex执行V操作,允许其他读者访问
    } while(TRUE);
}
void writer() { //写进程
    do {
        P(wmutex); //(写互斥)对wmutex执行P操作,防止其他写者访问writecount
        writecount++; //写者数量加1
        if(writecount == 1)
            P(read_write); //(读写互斥)如果自己是第一个写者,禁止读者读
        V(wmutex); //(写互斥)对wmutex执行V操作,允许其他写者访问writecount
        P(write_read); //(读写互斥)若没有读者或写者在访问文件,则可写入
        进行写操作;
        V(write_read); //(读写互斥)写操作完成,允许其他读者或写者访问文件
        P(wmutex); //(写互斥)对wmutex执行P操作,防止其他写者访问writecount
        if(writecount == 1)
            V(read_write); //(读写互斥)如果自己是最后一个写者,允许读者读
        writecount--; //写者数量减1
        V(wmutex); //(写互斥)对wmutex执行V操作,允许其他写者访问writecount
    } while(TRUE);
}

        首先定义两个信号量 read_write 和 write_read,初始值均为 1,用于互斥地读和写。定义两个整型变量 readcount 和 writecount,表示当前读取 F 和写入 F 的进程数量。在读者读取 F 之前,readcount 加 1,在完成读取 F 之后,readcount 减 1。同理,在写者写入 F 之前,writecount 加 1,在完成写入 F 之后,writecount 减 1。变量 readcount 和 writecount 是临界资源,因此定义信号量 rmutex 和 wmutex,分别实现读者进程对 readcount 变量的互斥访问和写者进程对 writecount 变量的互斥访问。

③ 读写公平

        读写公平算法不偏袒读者或写者中的任何一方,仅根据读写请求的到达顺序来赋予读写的权利。读写公平的算法描述如下:
(1) 当没有读进程或写进程时,允许读进程或写进程进入。
(2) 当一个读进程试图读取时,若有写者正在等待操作或进行写操作时,应等待其完成写操作后,才可以开始读操作。
(3) 同理,当一个写进程试图写入时,若有读者正在等待或进行读操作时,也需要进行等待。

读写公平的程序实现如下:

semaphore rmutex = 1; //定义readcount的互斥信号量,初始值为1
semaphore wmutex = 1; //定义用于写的互斥信号量,初始值为1
semaphore read_write = 1; //用于在读取时检测是否有写者的互斥信号量,初始值为1
int readcount = 0; //用于记录当前读者数量,初始值为0
void reader() { //读进程
    do {
        P(read_write); //(读写互斥)在读取时检测是否有写者,如有写者则等待
        P(rmutex); //(readcount操作互斥)对rmutex执行P操作,防止他人访问
        if (readcount == 0)
            P(wmutex); //(同步)如果自己是第一个读者,禁止写者写
        readcount++; //读者数量加1
        V(rmutex); //(readcount操作互斥)对rmutex执行V操作,允许他人访问
        V(read_write); //(读写互斥)允许其他读者或写者访问文件
        进行读操作;
        P(rmutex); //(readcount操作互斥)对rmutex执行P操作,防止他人访问
        readcount--; //读者数量减1
        if (readcount == 0)
            V(wmutex); //(同步)如果自己是最后一个读者,允许写者写
        V(rmutex); //(readcount操作互斥)对rmutex执行V操作,允许他人访问
    } while(TRUE);
}
void writer() { //写进程
    do {
        P(read_write); //(读写互斥)检测是否有读者或写者,如有则等待
        P(wmutex); //(写互斥)对wmutex执行P操作,防止他人访问文件
        进行写操作;
        V(wmutex); //(写互斥)对wmutex执行V操作,允许他人访问文件
        V(read_write); //(读写互斥)允许其他读者或写者访问文件
    } while(TRUE);
}

2.3.8 管程

1. 管程的定义

        虽然信号量机制可以解决进程的同步与互斥问题,但每个希望访问临界资源的进程都必须分别定义自己的 P/V 操作,这样会使众多同步操作分散在各个进程中。这不仅使系统难以管理,而且可能会因为误用同步操作,导致代码中存在死锁的概率大大增加,且难以排查。因此引入了新的进程同步工具 —— 管程。

引入管程的目的是:
(1) 集中管理分散于不同进程的临界区。
(2) 防止进程违反同步操作(特别是无意识的情况下)。
(3) 便于用高级语言编写程序、检查程序的正确性。

        由于系统中的各种软硬件资源可以通过数据结构来抽象地描述,即通过少量的信息和对其进行的操作来描述资源的特征,从而忽略其内部结构和实现细节。一个共同的数据结构可以抽象地表示系统中的共享资源,并将对该数据结构进行的特定操作定义为一组函数(有些书中也翻译为过程 Procedure)。

        管程是一个软件模块,由一些公共变量及其描述和所有访问这些变量的函数组成。进程对共享资源的请求、释放和其他操作必须通过同一个管程来实现。当多个进程请求访问同一资源时,会根据资源的情况接受或拒绝,确保每次只有一个进程进入临界区,有效实现进程互斥。

管程的语法描述如下:

Monitor monitor_name { //管程名
    share variable declarations; //共享变量说明
    cond declarations; //条件变量说明
    public: //能被进程调用的函数
        void P1(...) { //对数据结构操作的函数
           ...
        }
        void P2(...) {
           ...
        }
       ...
        void (...) {
           ...
        }
       ...
        { 
            //管程主体
            initialization code; //初始化代码
           ...
    }
}

        管程包含了面向对象的思想,将表达共享资源的数据结构和对其进行操作的过程封装在一个对象中,并封装了同步操作,从而对进程隐藏同步的细节,简化了调用同步功能的接口。用户编写一个并发程序,就像编写一个按顺序执行的程序一样。

如图 2.23 所示,管程由以下四个部分组成:
(1) 管程的名称;
(2) 局部于管程的共享数据结构;
(3) 一组对该数据结构进行操作的函数;
(4) 初始化共享数据结构的语句。

管程是编程语言的一个结构性组件,具有和信号量一样的表达能力。管程具有以下特性:
        (1) 共享性:管程可以被系统中的不同进程以互斥方式访问,是一种共享资源。
        (2) 安全性:管程的函数只能访问属于本管程的局部变量,甚至不能访问其局部变量以外的变量。
        (3) 互斥性:进程对管程的访问是互斥的,同一时间只有一个进程能够访问管程。
        (4) 封装性:管程中的函数只能使用管程中的数据结构,同时,管程中的数据结构都是私有的,也只能在管程中使用,进程可以通过调用管程中的函数来使用临界资源。

2. 条件变量

        管程本身提供了互斥的能力,能够保证同一时间只有一个进程能够访问管程中的数据结构;但只有互斥能力是不够的,还需要其他机制来实现进程之间的前后驱关系。管程选择了条件变量机制来实现这一需求。条件变量(condition variable)是管程内的一种全局数据结构。所谓 “条件”,指的是引起进程阻塞或唤醒的事件或原因。管程使用的每个条件变量都必须在管程中予以描述。条件变量和信号量都属于同步机制,信号量有对应的 P/V 操作,条件变量也有对应的 wait/signal 操作。和记录型信号量一样,每个条件变量都带有一个阻塞队列,用以存储因条件变量对应的条件未满足而阻塞的进程。

条件变量的 wait/signal 操作过程如下:
        (1) x.wait():若调用管程的进程由于条件 x 而需要被阻塞或暂停,则释放管程,挂起调用进程并插入到条件 x 的等待队列中,直到另一个进程向条件变量执行了 x.signal () 操作。
        (2) x.signal():当调用管程的进程注意到条件 x 已经改变,调用对应的 x.singal () 操作。如果存在某个进程由于 x.wait () 操作被挂起,则将其释放;如果没有其他进程被阻塞,则不做任何操作(区别于信号量机制中的 V 操作,V 操作总会将信号量进行加一操作)。

2.3.9 习题精编

1.下列关于并发进程的说法中,错误的是( )。
A. 并发进程是异步的
B. 并发进程可以提高资源的利用率
C. 并发进程可以提高系统的吞吐量
D. 并发进程独立运行,所以它们彼此之间没有任何联系

1.【参考答案】D
【解析】进程的并发性会导致进程的执行、暂停等都变得不可预知,即异步性会导致进程以不可预知的状态向前推进,A 正确;系统中多道程序并发执行,可以有效提高资源利用率,大大增加系统的吞吐量,B 和 C 正确;虽然并发进程独立运行,但是他们之间可能存在某种协作关系,共同完成某一项任务,因此 D 错误。题干要求选择错误的选项,故本题选 D。

2.有两个并发进程P1​和P2​,它们的程序代码如下(其中 x 为P1​,P2​的共享变量):

P1(){
    x = 0;
    y = -1;
    z = x * y;
    print z;
}
P2(){
    x = 3;
    c = x * x;
    print c;
}

可能打印出的 z 和 c 的值有( )。
A. z=0或−3;c=0或 9
B. z=0或 3;c=0或 9
C. z=0或 3 或 1;c=9
D. z=0;c=0或 9

2.【参考答案】A
【解析】

P1() {
    x = 0; //(1)
    y = -1; //(2)
    z = x*y; //(3)
    print z; //(4)
}
P2() {
    x = 3; //(5)
    c = x*x; //(6)
    print c; //(7)
}

        按照 1,2,3,4,5,6,7 的顺序执行,可以得到z=0,c=9的结果,按照 1,5,2,6,3,7,4 的顺序执行,可以得到z=−3,c=9的结果,按照 5,1,6,2,7,3,4 的顺序执行,可以得到z=0,c=0的结果。因此z=0或−3,c=9或0。故本题选 A。

3.下列关于临界区和临界资源的说法中,正确的是( )。
I. 临界资源一次只允许一个进程使用
II. 临界资源是互斥共享资源
III. 临界区是指进程中用于访问临界资源的那段代码
IV. 临界区是指进程中用于实现进程同步、互斥的那段代码
A. I 和 IV
B. I 和 III
C. I、II 和 III
D. I、II 和 IV

3.【参考答案】C
        【解析】本题考查了临界资源和临界区的基本概念,同一时间只能由一个进程使用的资源称为临界资源,临界资源是互斥共享资源,I 和 II 正确;程序中访问临界资源的那一部分代码称为临界区,III 正确,IV 错误,故本题选 C。

4.一个正在访问临界资源的进程突然被操作系统阻塞,那么它( )。
A. 允许其他进程抢占处理器,且允许其他进程进入该进程的临界区
B. 允许其他进程抢占处理器,但不允许其他进程进入该进程的临界区
C. 不允许其他进程抢占处理器,但允许其他进程进入该进程的临界区
D. 不允许其他进程抢占处理器,且不允许其他进程进入该进程的临界区

4.【参考答案】B
        【解析】当进程进入阻塞态,它就需要交出 CPU 的使用权,即允许其他进程竞争 CPU,C 和 D 错误;但是它不会放弃其他已占有的资源,因为进程一旦占有一个资源(即一旦它的资源请求被批准),那么该资源就不能被夺走,直到该进程主动释放它,所以其他进程不能进入该进程的临界区,A 错误,故本题选 B。

5.以下不属于临界资源的是( )。
I. 打印机
II. 非共享数据
III. 磁盘
IV. 共享缓冲区
A. 仅 II
B. I、II 和 IV
C. II 和 III
D. I、III 和 IV

5.【参考答案】C
        【解析】同一时间只能由一个进程使用的资源称为临界资源。打印机同时只能为一个进程打印文件,因此它是临界资源,I 正确;非共享数据无法被共享,不存在多进程访问的情况,因此不是临界资源,II 错误;磁盘可供多个进程同时使用,因此不是临界资源,III 错误;共享缓冲区一次只能由一个进程使用,在不同时间可被不同进程访问,因此它是临界资源,IV 正确,故本题选 C。

6.系统中的变量 A 是临界资源,有 4 个并发进程使用了该变量,那么该系统中与该变量相关的临界区的个数是( )。
A. 1
B. 3
C. 4
D. 5

6.【参考答案】C
        【解析】每个进程的程序代码中访问临界资源的那一部分代码称为临界区,每个进程访问变量 A 的时候,都有自己的临界区,4 个进程访问变量 A,共有 4 个临界区,故本题选 C。

7.一组生产者和一组消费者同时工作,它们通过一个大小为 n 的缓冲区来生产和消费。每个缓冲区可以容纳一件产品,其中生产者负责投放产品,消费者负责消费产品,则该过程中的制约关系有( )。
A. 仅互斥关系
B. 仅同步关系
C. 互斥和同步关系
D. 不存在制约关系

7.【参考答案】C
        【解析】生产者和消费者要互斥访问缓冲区,故两者存在互斥关系,B 和 D 错误;生产者和消费者协作完成生产和消费,消费者负责消费生产者生产的产品,存在一种先生产后消费的同步关系,A 错误,故本题选 C。

8.以下选项中,不属于同步机制应遵循的准则的是( )。
A. 空闲让进
B. 请求并保持
C. 忙则等待
D. 有限等待

8.【参考答案】B
【解析】同步机制设计的四个准则分别是空闲让进、忙则等待、有限等待和让权等待,A、C、D 正确;“请求并保持” 是死锁产生的四个必要条件之一,B 不属于同步机制应遵循的准则,故本题选 B。

9.如果取款机的门是锁着的,而且有人正在使用取款机,那么下一个希望使用取款机的人就要排队等候,这就是所谓的( )。
A. 空闲让进
B. 忙则等待
C. 有限等待
D. 让权等待

9.【参考答案】B
【解析】“忙则等待” 指如果已经有一个进程进入临界区,即正在访问临界资源,那么其他希望进入临界区的进程应在临界区外等待,确保进程以互斥的方式访问临界资源。题干中的取款机属于临界资源,需要互斥使用,有人在使用取款机时,其他人应排队等待,这符合 “忙则等待” 的定义,故本题选 B。

10.两个进程互斥的 Peterson 算法描述如下:

P0() {
    do{
        flag[0] = 1;
        (1);
        while(flag[1]&&(turn == 1))
            skip;
        临界区;
        flag[0] = 0;
        其余代码;
    } while(1)
};
P1() {
    do{
        flag[1] = 1;
        (2);
        while(flag[0]&&(turn == 0))
            skip;
        临界区;
        flag[1] = 0;
        其余代码;
    } while(1);
};
        

其中,(1) 和 (2) 处的代码分别为( )。
A. turn = 0, turn = 0
B. turn = 0, turn = 1
C. turn = 1, turn = 0
D. turn = 1, turn = 1

10.【参考答案】C
【解析】Peterson 算法中,设置 flag 标志表明进程是否请求进入临界区,又设置共享变量 turn 规定进程进入临界区的顺序,当进程P0​想要使用临界区时,首先将自己的 flag [0] 标志设置为 1,然后将 turn 设置为对方编号(进行礼让),故本题选 C。

11.若一个信号量的初始值为 4,经过多次 P/V 操作后,该信号量的值为 - 2,那么等待进入临界区的进程数是( )。
A. 1        B. 2        C. 3        D. 4

11.【参考答案】B
        【解析】如果信号量的值是负值,那么它的绝对值等于等待进入临界区的进程数。当前信号量的值为−2,说明有 2 个进程在临界区外等待,故本题选 B。

12.有 4 个进程共享同一程序段,每次只允许 2 个进程进入该程序段,若使用 P/V 操作来实现同步机制,则信号量 S 的取值范围是( )。
A. 2, 1, 0, -1, -2
B. 4, 3, 2, 1, 0
C. 2, 1, 0, -1
D. 4, 3, 2, 1, 0, -1, -2

12.【参考答案】A
        【解析】每次只允许 2 个进程进入该程序段,所以信号量的最大取值为 2,若有 4 个进程同时希望进入该程序段,则最多有 2 个进程等待进入临界区,则信号量的最小值为−2,因此信号量 S 的可能取值为−2,−1,0,1,2,故本题选 A。

13.对于 3 个并发进程,设互斥信号量为 mutex,并令其初值为 1,若 mutex = -1,则等待进入临界区的进程数为( )。
A. 3
B. 2
C. 1
D. 0

13.【参考答案】C
【解析】若信号量的值小于 0,其绝对值就是等待该资源的进程数,故本题选 C。

14.若某进程执行 V (mutex) 操作会唤醒另一个进程,则 V 操作执行前信号量 mutex 的值( )。
A. 大于 0
B. 小于 0
C. 小于等于 0
D. 大于等于 0

14.【参考答案】B
【解析】如果信号量的值是负值,那么它的绝对值等于等待进入临界区的进程数。进程执行 V (mutex) 操作后会唤醒另一个进程,说明它在执行 V (mutex) 操作前,有进程在临界区外等待,这说明此时的信号量是负值,故本题选 B。题干关注的是执行 V 操作前的互斥信号量的值,而不是执行 V 操作后的互斥信号量的值,考生需要注意。

15.有一个信号量 S,假如若干进程对 S 执行 36 次 P 操作和 26 次 V 操作后,信号量 S 的值变为 0。假设未执行上述 P/V 操作,若干进程对信号量 S 执行 16 次 P 操作和 1 次 V 操作后,在信号量 S 的等待队列中等待的进程数为( )。
A. 2
B. 3
C. 5
D. 7

15.【参考答案】C
        【解析】执行 36 次 P 操作和 26 次 V 操作后,信号量 S 的值为 0,说明信号量 S 的初始值为 10。对初始值为 10 的信号量 S 执行 16 次 P 操作和 1 次 V 操作后,计数信号量 S 的值为10−16+1=−5。如果信号量的值是负值,那么它的绝对值等于等待进入临界区的进程数,因此有 5 个进程在信号量 S 的等待队列中等待,故本题选 C。

16.若有 11 个进程共享同一程序段,每次允许 5 个进程同时进入,若用 P/V 操作来实现互斥机制,则该信号量的最大值是( )。
A. 5
B. 11
C. 6
D. 1

16.【参考答案】A
        【解析】题干中信号量的最大值等于可以进入该程序段的最大进程数,可得信号量的最大值为 5。考生也可以尝试换一种思路,每次允许 5 个进程进入该程序段,也就是进行 5 次 P 操作后,信号量的值变为 0,第 6 个进行 P 操作的进程将被阻塞,这也就可以推导出信号量的最大值为 5,故本题选 A。

17.有两个优先级相同的并发进程P1​和P2​,它们的执行过程如下。假设当前信号量 s1 = 0, s2 = 0,并假设 z = 1,那么进程运行完毕后,x、y 和 z 的值分别是( )。

进程P1 {
    y = 1;
    y = y + 2;
    z = y + 1;
    V(s1);
    P(s2);
    y = x + y + z;
}

进程P2 {
    x = 2;
    x = x + 1;
    P(s1);
    x = x + y;
    z = y + z;
    V(s2);
}

A. 6, 16, 7
B. 4, 11, 3
C. 6, 11, 7
D. 6, 16, 3

17.【参考答案】A
【解析】由于对 s1 和 s2 的 P/V 操作,7、8 一定发生在 1、2、3 之后;4 一定发生在 7、8 之后。故两个进程可以按 1,2,3,5,6,7,8,4 的顺序执行,最终可得x=6,y=16,z=7,故本题选 A。

进程P1 {
    y = 1; //(1)
    y = y + 2; //(2)
    z = y + 1; //(3)
    V(s1);
    P(s2);
    y = x + y + z; //(4)
}
进程P2 {
    x = 2; //(5)
    x = x + 1; //(6)
    P(s1);
    x = x + y; //(7)
    z = y + z; //(8)
    V(s2);
}

18.对计数型信号量 S 执行 V 操作后,下列选项正确的是( )。
A. 当 S.value < 0 时,唤醒一个阻塞队列进程
B. 当 S.value ≤ 0 时,唤醒一个阻塞队列进程
C. 当 S.value ≤ 0 时,唤醒一个就绪队列进程
D. 当 S.value < 0 时,唤醒一个就绪队列进程

18.【参考答案】B
【解析】V 操作不能唤醒就绪队列进程,将就绪进程转为执行态是处理机调度的工作,因此 C 和 D 错误;在对计数型信号量 S 执行 V 操作前,若S.value<0,说明有进程因等待资源而在临界区外等待。对计数型信号量 S 执行 V 操作后,S.value执行了加 1 操作,由于有进程在等待,它会唤醒阻塞队列的队首进程,由于此前S.value执行了加 1 操作,所以S.value⩽0,A 错误,故本题选 B。

19.下列不属于管程的组成部分的是( )。
A. 程序名称
B. 对管程内数据结构进行操作的一组过程
C. 对管程内资源分配给多个进程的方式的说明
D. 对受限于管程的数据结构设置初始值的语句

19.【参考答案】C
【解析】管程由程序的名称、程序本地的共享数据结构、一组对数据结构进行操作的程序和一条初始化程序本地共享数据的语句共四部分组成,A、B、D 正确;对管程内资源分配给多个进程的方式的说明不是管程的组成部分,故本题选 C。

20.下列关于管程的叙述中,错误的是( )。
A. 管程引入了面向对象的思想
B. 管程一次只允许一个进程进入
C. 管程中 signal 操作的作用和信号量机制中的 V 操作完全相同,可以互换
D. 管程是一个基本程序单位,可以被单独编译

20.【参考答案】C
【解析】管程包含了面向对象的思想,将表达共享资源的数据结构和对其进行操作的进程封装在一个对象中,并封装了同步操作,从进程中隐藏了同步的细节,简化了调用同步功能的接口,A 正确;管程一次只允许一个进程进入,这是由于管程的互斥性决定的,B 正确;管程是编程语言的一个结构性组件,具有和信号量一样的表达能力,D 正确;管程中 signal 操作的作用和信号量机制中的 V 操作是不完全一样的,signal 操作前,无进程进入阻塞状态,则 signal 操作什么也不执行,而信号量机制中的 V 操作前,无进程进入阻塞状态,则 V 操作会使信号量执行加 1 操作,而且 signal 操作不能与信号量机制中的 V 操作互换,题干要求选择错误选项,故本题选 C。

21.下列关于管程的说法中,错误的是( )。
A. 允许进入管程的进程数目与临界资源的个数有关
B. 管程内部定义了函数的具体实现,它在外部是不可见的
C. 管程机制可以便于集中管理分散于不同进程的临界区
D. 管程是进程管理的工具,避免了信号量机制中大量且分散的同步操作

21.【参考答案】A
【解析】管程一次只允许一个进程进入,这是由管程的互斥性决定的,A 错误;管程包含了面向对象的思想,将表达共享资源的数据结构和对其进行操作的进程封装在一个对象中,并封装了同步操作,从而对进程隐藏同步的细节,简化了调用同步功能的接口,B 正确;管程机制可以便于集中管理分散于不同进程的临界区,C 正确;管程是进程同步工具,避免了信号量机制中大量且分散的同步操作,D 正确。题干要求选择错误选项,故本题选 A。

22.在 9 个生产者、6 个消费者共享 8 个单元缓冲区的生产者 - 消费者问题中,互斥使用缓冲区信号量的初始值为( )。
A. 1
B. 6
C. 8
D. 9

22.【参考答案】A
【解析】本题的临界资源是整个缓冲区,而不是其中的每个单元。生产者和消费者在每次进行活动前,需要竞争对整个缓冲区的使用权,因此互斥使用缓冲区的信号量的初值为 1,故选 A。

23.生产者 - 消费者问题用于解决( )。
A. 多个并发进程共享一个数据对象的问题
B. 多个进程之间的同步和互斥问题
C. 多个进程共享资源的死锁与饥饿问题
D. 利用信号量实现多个进程并发的问题

23.【参考答案】B
【解析】生产者 - 消费者问题可以描述为一组生产者和一群消费者一起工作,通过一个大小为 n 的有限缓冲区进行生产和消费。每个缓冲区可以容纳一个产品,其中生产者负责投放产品,消费者负责消费产品。缓冲区需互斥使用,生产者先生产,消费者才能消费,因此生产者 - 消费者问题用于解决多进程之间的同步互斥问题,故选 B。

24.某寺庙有小和尚、老和尚若干,有一水缸,由小和尚提水入缸供老和尚饮用。水缸可容 10 桶水,水取自同一井中。水井径窄,每次只能容一个桶取水。水桶总数为 3 个。每次入缸取水仅为 1 桶水,且不可同时进行。试用信号量和 P/V 操作给出有关从缸取水、入水的算法描述。

24.【参考答案】

Semaphore mutex_well = 1; //实现对水井这一互斥资源的互斥访问
Semaphore mutex_vat = 1; //实现对水缸这一互斥资源的互斥访问
Semaphore vat_empty = 10; //水缸剩余能容纳的水的桶数
Semaphore vat_full = 0; //水缸已容纳的水的桶数
Semaphore pail = 3; //空闲的水桶的数量
CoBegin{
    process 小和尚 {
        while(TRUE) {
            P(vat_empty); //(同步)申请向水缸倒水,若水缸已满则等待
            P(pail); //申请使用空闲水桶,如没有空闲水桶则等待
            P(mutex_well); //(互斥)防止他人使用水井
            从井中取水;
            V(mutex_well); //(互斥)允许他人使用水井
            P(mutex_vat); //(互斥)防止他人使用水缸
            将水倒入水缸;
            V(mutex_vat); //(互斥)允许他人使用水缸
            V(vat_full); //(同步)水缸已用容量加1,若有老和尚等喝水则通知
            V(pail); //释放一个空闲水桶,若有和尚在等待使用,则通知
        }
    }
    process 老和尚 {
        while(TRUE){
            P(vat_full); //(同步)申请从水缸取水,若水缸为空,则等待
            P(pail); //申请使用空闲水桶,如没有空闲水桶则等待
            P(mutex_vat); //(互斥)防止他人使用水缸
            从水缸中取水;
            V(mutex_vat); //(互斥)允许他人使用水缸
            V(vat_empty); //(同步)水缸剩余容量加1,若有小和尚打水则通知
            喝水;
            V(pail); //释放一个空闲水桶,若有和尚在等待使用,则通知
        }
    }
}CoEnd

25.某双车道公路中一小段因发生塌方事故,变成了单车道(对向行驶的车辆无法同时通行),如下图所示。为保证车辆顺利通行,必须对经过塌方路段的车辆予以控制。请用信号量描述此控制过程,并说明信号量含义。

25.【参考答案】

int LToR = 0; //正在从左向右行驶的车辆数目
int RToL = 0; //正在从右向左行驶的车辆数目
Semaphore mutex_LToR = 1; //实现对变量LToR的互斥访问
Semaphore mutex_RToL = 1; //实现对变量RToL的互斥访问
Semaphore mutex = 1; //实现对单车道这一互斥资源的互斥访问
CoBegin{
    process 从左向右行驶的车 {
        P(mutex_LToR); //(互斥)防止其他进程访问变量LToR
        if(LToR == 0) //若该车辆是从左向右行驶的第一辆车
            P(mutex); //(互斥)防止从右向左的车辆访问单车道
        LToR++; //从左向右行驶的车辆数目加1
        V(mutex_LToR); //(互斥)允许其他进程访问变量LToR
        通过单车道;
        P(mutex_LToR); //(互斥)防止其他进程访问变量LToR
        LToR--; //从左向右行驶的车辆数目减1
        if(LToR == 0) //若所有从左向右行驶的车辆通行完毕
            V(mutex); //(互斥)允许所有车辆进入单车道
        V(mutex_LToR); //(互斥)允许其他进程访问变量LToR
    }
    process 从右向左行驶的车 {
        P(mutex_RToL); //(互斥)防止其他进程访问变量RToL
        if(RToL == 0) //若该车辆是从右向左行驶的第一辆车
            P(mutex); //(互斥)防止从左向右的车辆访问单车道
        RToL++; //从右向左行驶的车辆数目加1
        V(mutex_RToL); //(互斥)允许其他进程访问变量RToL
        通过单车道;
        P(mutex_RToL); //(互斥)防止其他进程访问变量RToL
        RToL--; //从右向左行驶的车辆数目减1
        if(RToL == 0) //若所有从右向左行驶的车辆通行完毕
            V(mutex); //(互斥)允许所有车辆进入单车道
        V(mutex_RToL); //(互斥)允许其他进程访问变量RToL
    }
}CoEnd

26.银行负责办理业务有 3 个柜台,每个柜台有一名银行职员负责相关业务,有 N 个供用户等待的椅子。如果没有顾客,那么银行职员便休息;当有顾客到来时,唤醒银行职员。每位顾客进入银行后,如果还有空椅子,那么顾客到取号机领取一个号并且坐在椅子上等待,如果顾客进入银行后发现没有空椅子就离开银行。请用信号量和 P、V 操作正确编写银行职员进程和顾客进程并发的程序。

26.【参考答案】

int customer_num = 0; //进入银行的顾客数
Semaphore counter = 3; //可用的柜台数
Semaphore mutex = 1; //实现对互斥资源customer_num的互斥访问
Semaphore ready = 0; //(同步)当有顾客时,职员才可以提供服务
CoBegin {
    process 银行职员 {
        while(TRUE) {
            P(ready); //(同步)有顾客则提供服务,没有顾客则等待
            向顾客提供服务;
        }
    }
    process 顾客 {
        P(mutex); //(互斥)防止其他进程访问互斥资源
        if (customer_num < N) {//若有空椅子供新来的顾客坐下
            customer_num++; //等待的顾客数增1
            V(mutex); //(互斥)允许其他进程访问互斥资源customer_num
            P(counter); //申请占用一个柜台,若没有可用柜台则等待
            V(ready); //(同步)若职员在等待顾客,则通知
            P(mutex); //(互斥)防止其他进程访问互斥资源customer_num
            customer_num--; //等待的顾客数减1
            V(mutex); //(互斥)允许其他进程访问互斥资源customer_num
            接受服务;
            V(counter); //释放一个柜台,若有顾客在等待,则通知
        } 
        else { //若没有空椅子供新来的顾客坐下
            V(mutex); //(互斥)允许其他进程访问互斥资源customer_num
            离开银行;
        }
    }
}CoEnd

27.一组合作进程,执行顺序如图 2.24 所示。请用 P/V 操作实现进程间的同步操作。

27.【参考答案】

//定义用于实现进程前后驱关系的信号量
Semaphore a = b = c = d = e = f = g = h = 0;
CoBegin{
    process P1() {
        完成P1任务;
        V(a); //(后继)实现先P1后P2
        V(b); //(后继)实现先P1后P3
    }
    process P2() {
        P(a); //(前驱)实现先P1后P2
        完成P2任务;
        V(c); //(后继)实现先P2后P4
        V(d); //(后继)实现先P2后P5
    }
    process P3() {
        P(b); //(前驱)实现先P1后P3
        完成P3任务;
        V(e); //(后继)实现先P3后P4
        V(f); //(后继)实现先P3后P5
    }
    process P4() {
        P(c); //(前驱)实现先P2后P4
        P(e); //(前驱)实现先P3后P4
        完成P4任务;
        V(g); //(后继)实现先P4后P6
    }
    process P5() {
        P(d); //(前驱)实现先P2后P5
        P(f); //(前驱)实现先P3后P5
        完成P5任务;
        V(h); //(后继)实现先P5后P6
    }
    process P6 {
        P(g); //(前驱)实现先P4后P6
        P(h); //(前驱)实现先P5后P6
        完成P6任务;
    }
}CoEnd

28.设有 8 个进程M1​,M2​,...,M8​,它们有如图 2.25 所示的优先关系,试用 P/V 操作实现这些进程间的同步。

28.【参考答案】

//定义用于实现进程前后驱关系的信号量
Semaphore S12 = S13 = S14 = S26 = S36 = S47 = S57 = S38 = S78 = 0;
CoBegin{
    process M1() {
        完成M1任务;
        V(S12); //(后继)实现先M1后M2
        V(S13); //(后继)实现先M1后M3
        V(S14); //(后继)实现先M1后M4
    }
    process M2() {
        P(S12); //(前驱)实现先M1后M2
        完成M2任务;
        V(S26); //(后继)实现先M2后M6
    }
    process M3() {
        P(S13); //(前驱)实现先M1后M3
        完成M3任务;
        V(S36); //(后继)实现先M3后M6
        V(S38); //(后继)实现先M3后M8
    }
    process M4() {
        P(S14); //(前驱)实现先M1后M4
        完成M4任务;
        V(S47); //(后继)实现先M4后M7
    }
    process M5() {
        完成M5任务;
        V(S57); //(后继)实现先M5后M7
    }
    process M6() {
        P(S26); //(前驱)实现先M2后M6
        P(S36); //(前驱)实现先M3后M6
        完成M6任务;
    }
    process M7() {
        P(S47); //(前驱)实现先M4后M7
        P(S57); //(前驱)实现先M5后M7
        完成M7任务;
        V(S78); //(后继)实现先M7后M8
    }
    process M8() {
        P(S38); //(前驱)实现先M3后M8
        P(S78); //(前驱)实现先M7后M8
        完成M8任务;
    }
} CoEnd

29.某工厂有两个生产车间和一个装配车间,两个生产车间分别生产 A 和 B 两种零件,装配车间的任务是把 A 和 B 两种零件组装成产品。两个生产车间每生产一个零件后都要分别把他们送到装配车间的货架F1​,F2​上。F1​存放零件 A,F2​存放零件 B,F1​和F2​的容量均可以存放 10 个零件。装配工人每次从货架上取一个 A 零件和一个 B 零件后组装成产品。请用 P/V 操作进行正确管理。

29.【参考答案】

Semaphore empty_F1 = 10; //货架F1的剩余容量
Semaphore empty_F2 = 10; //货架F2的剩余容量
Semaphore full_A = 0; //货架F1已存放的零件A的数量
Semaphore full_B = 0; //货架F2已存放的零件B的数量
Semaphore mutex_F1 = 1; //实现对货架F1这一互斥资源的互斥访问
Semaphore mutex_F2 = 1; //实现对货架F2这一互斥资源的互斥访问
CoBegin{
    process 车间A {
        while(TRUE){
            生产一个零件A;
            P(empty_F1); //(同步)申请货架F1的一个空位,若已满则等待
            P(mutex_F1); //(互斥)防止他人使用货架F1
            将零件A放到货架F1上;
            V(mutex_F1); //(互斥)允许他人使用货架F1
            V(full_A); //(同步)新增一个零件A,若装配车间在等待则通知
        }
    }
    process 车间B {
        while(TRUE){
            生产一个零件B;
            P(empty_F2); //(同步)申请货架F2的一个空位,若已满则等待
            P(mutex_F2); //(互斥)防止他人使用货架F2
            将零件B放到货架F2上;
            V(mutex_F2); //(互斥)允许他人使用货架F2
            V(full_B); //(同步)新增一个零件B,若装配车间在等待则通知
        }
    }
    process 装配车间 {
        while(TRUE){
            P(full_A); //(同步)申请从货架F1取一个零件A,若货架为空则等待
            P(mutex_F1); //(互斥)防止他人使用货架F1
            从货架F1上取一个零件A;
            V(mutex_F1); //(互斥)允许他人使用货架F1
            V(empty_F1); //(同步)释放一个货架F1的空位,若车间A在等待则通知
            P(full_B); //(同步)申请从货架F2取一个零件B,若货架为空则等待
            P(mutex_F2); //(互斥)防止他人使用货架F2
            从货架F2上取一个零件B;
            V(mutex_F2); //(互斥)允许他人使用货架F2
            V(empty_F2); //(同步)释放一个货架F2的空位,若车间B在等待则通知
            将取得的零件A和零件B组装成产品;
        }
    }
}CoEnd

30.三个进程P0​、P1​、P2​互斥使用一个仅包含一个单元的缓冲区。P0​每次用 produce () 生成一个正整数,并用 put () 送入缓冲区。对于缓冲区中的每个数据,P1​用 get1 () 取出一次并用 compute1 () 计算其平方值,P2​用 get2 () 取出一次并用 compute2 () 计算其立方值。请用信号量机制实现进程P0​、P1​、P2​之间的同步与互斥关系,并说明所定义信号量的含义,要求用伪代码描述。

30.【参考答案】


semaphore mutex = 1; //实现对缓冲区这一互斥资源的互斥访问
semaphore square = 0; //(同步)P0生成正整数后,P1才可取出、求平方
semaphore cube = 0; //(同步)P0生成正整数后,P2才可取出、求立方
semaphore empty = 1; //(同步)缓冲区需要放入才能取,取走才能放
CoBegin{
    process P0 {
        P(empty); //(同步)申请向缓冲区放整数,若缓冲区满则等待produce();
        生成一个正整数;
        P(mutex); //(互斥)防止其他进程使用缓冲区
        put(); //将该正整数送入缓冲区
        V(mutex); //(互斥)允许其他进程使用缓冲区
        V(square); //(同步)整数已放入缓冲区,若进程P1在等待则通知
    }
    process P1 {
        P(square); //(同步)申请从缓冲区取整数,若缓冲区为空等待
        P(mutex); //(互斥)防止其他进程使用缓冲区
        get1(); //从缓冲区取出正整数
        V(mutex); //(互斥)允许其他进程使用缓冲区
        compute1(); //计算该正整数的平方值
        V(empty); //(同步)释放一个缓冲区的空位,若进程P0在等待则通知
        V(cube); //(同步)整数已放入缓冲区,若进程P2在等待则通知
    }
    process P2 {
        P(cube); //(同步)申请从缓冲区取整数,若缓冲区为空等待
        P(mutex); //(互斥)防止其他进程使用缓冲区
        get2(); //从缓冲区取出正整数
        V(mutex); //(互斥)允许其他进程使用缓冲区
        compute2(); //计算该正整数的立方值
        V(empty); //(同步)释放一个缓冲区的空位,若进程P0在等待则通知
    }
}CoEnd

31.桌上有一只盘子,最多可容纳两个水果,每次只能放入或取出一个水果。爸爸专向盘中放苹果,妈妈专向盘中放橘子;两个儿子专等吃盘中的橘子,两个女儿专等吃盘子中的苹果。请用 P/V 操作来实现爸爸、妈妈、儿子、女儿之间的同步与互斥关系。

31.【参考答案】

Semaphore empty = 2; //盘子的剩余容量
Semaphore apple = 0; //实现女儿和爸爸之间取放苹果的同步关系
Semaphore orange = 0; //实现儿子和妈妈之间取放橘子的同步关系
Semaphore mutex = 1; //实现对盘子这一互斥资源的互斥访问
CoBegin{
    process father(){
        while(TRUE){
            P(empty); //(同步)申请向盘子放水果,若盘子已满则等待
            P(mutex); //(互斥)防止他人向盘子取水果、放水果
            向盘子中放一个苹果;
            V(mutex); //(互斥)允许他人向盘子取水果、放水果
            V(apple); //(同步)新增一个苹果,若有女儿等吃苹果则通知
        }
    }
    process mother(){
        while(TRUE){
            P(empty); //(同步)申请向盘子放水果,若盘子已满则等待
            P(mutex); //(互斥)防止他人向盘子取水果、放水果
            向盘子中放一个橘子;
            V(mutex); //(互斥)允许他人向盘子取水果、放水果
            V(orange); //(同步)新增一个橘子,若有儿子等吃橘子则通知
        }
    }
    process son(){
        while(TRUE){
            P(orange); //(同步)申请从盘子取橘子,若没有橘子则等待
            P(mutex); //(互斥)防止他人向盘子取水果、放水果
            从盘子中取走一个橘子;
            V(mutex); //(互斥)允许他人向盘子取水果、放水果
            吃橘子;
            V(empty); //(同步)盘子新增一个空位,若有人等待放水果则通知
        }
    }
    process daughter(){
        while(TRUE){
            P(apple); //(同步)申请从盘子取苹果,若没有苹果则等待
            P(mutex); //(互斥)防止他人向盘子取水果、放水果
            从盘子中取走一个苹果;
            V(mutex); //(互斥)允许他人向盘子取水果、放水果
            吃苹果;
            V(empty); //(同步)盘子新增一个空位,若有人等待放水果则通知
        }
    }
}CoEnd

32.设有P1​、P2​、P3​三个进程共享某一资源 F,P1​对 F 只读不写,P2​对 F 只写不读,P3​对 F 先读后写。当一个进程写 F 时,其他进程对 F 不能进行读写,但多个进程同时读 F 是允许的。试用 P/V 操作正确实现P1​、P2​、P3​的同步互斥。要求:
(1) 正常运行时不产生死锁。
(2) 使用 F 的并发度要高。

32.【参考答案】

int reader_count = 0; //读进程的数量
Semaphore mutex_variable = 1; //实现对变量reader_count的互斥访问
Semaphore mutex_file = 1; //实现没有读进程时,写进程才可写文件
CoBegin{
    process P1() {
        while(TRUE){
            P(mutex_variable); //(互斥)防止其他进程访问变量reader_count
            if(reader_count == 0) //若自己是第一个进入的读进程
                P(mutex_file); //(互斥)读进程读文件时,写进程不能进入
            reader_count++; //读进程数加1
            V(mutex_variable); //(互斥)允许其他进程访问变量reader_count
            读文件;
            P(mutex_variable); //(互斥)防止其他进程访问变量reader_count
            reader_count--; //读进程数减1
            if(reader_count == 0) //若自己是最后一个读进程
                V(mutex_file); //(互斥)读进程读取完毕,允许写进程写文件
            V(mutex_variable); //(互斥)允许其他进程访问变量reader_count
        }
    }
    process P2() {
        while(TRUE){
            P(mutex_file); //(互斥)写进程写文件与其他读写操作互斥
            写文件;
            V(mutex_file); //(互斥)写文件完毕,其他进程可以访问文件
        }
    }
    process P3() {
        while(TRUE){
            P(mutex_variable); //(互斥)防止其他进程访问变量reader_count
            if(reader_count == 0) //若自己是第一个进入的读进程
                P(mutex_file); //(互斥)读进程读文件时,写进程不能进入
            reader_count++; //读进程数加1
            V(mutex_variable); //(互斥)允许其他进程访问变量reader_count
            读文件;
            P(mutex_variable); //(互斥)防止其他进程访问变量reader_count
            reader_count--; //读进程数减1
            if(reader_count == 0) //若自己是最后一个读进程
                V(mutex_file); //(互斥)读进程读取完毕,允许写进程写文件
            V(mutex_variable); //(互斥)允许其他进程访问变量reader_count
            //P3是先当读者再当写者
            //故上方代码与P1的实现相同,下方代码与P2的实现相同
            P(mutex_file); //(互斥)写进程写文件与其他读写操作互斥
            写文件;
            V(mutex_file); //(互斥)写文件完毕,其他进程可以访问文件
        }
    }
}CoEnd

33.分析下面信号量,解决哲学家进餐问题的同步算法是否满足同步机制的准则。若不满足,说明为什么,并给出满足同步机制准则的同步算法。

Semaphore fork[0] = fork[1] = fork[2] = fork[3] = fork[4] = 1;
CoBegin{
    Pi{
        //第i个哲学家的生活过程
        While(TRUE){
            Think for while;
            P(fork[i]);
            P(fork[(i + 1) mod 5]);
            Eat for while;
            V(fork[i]);
            V(fork[(i + 1) mod 5]);
        }
    }
}CoEnd

33.【参考答案】
本题不满足同步机制的准则,因为如果每个哲学家都只拿到一把叉子,则会发生死锁,会出现无限等待的情况,未满足 “有限等待” 的同步机制准则。

//定义对叉子互斥使用的信号量
//定义互斥信号量,防止拿起第一只叉子时,第二只叉子被其他哲学家拿走
Semaphore fork[0] = fork[1] = fork[2] = fork[3] = fork[4] = 1;
Semaphore mutex = 1;
CoBegin{
    Pi{
        //第i个哲学家的生活过程
        While(TRUE){
            Think for while; //思考问题
            P(mutex); //(互斥)防止其他哲学家拿叉子
            P(fork[i]); //申请拿取左边的叉子,若正被使用则等待
            P(fork[(i + 1) mod 5]); //申请拿取右边的叉子,若正被使用则等待
            V(mutex); //(互斥)允许其他哲学家拿叉子
            Eat for while; //进餐
            V(fork[i]); //左边的叉子使用完毕,放下供其他哲学家使用
            V(fork[(i + 1) mod 5]); //右边的叉子使用完毕,放下供他人使用
        }
    }
}CoEnd

34.哲学家甲请哲学家乙、丙、丁到某处讨论问题,约定全体到齐后开始讨论;在讨论的间隙四位哲学家进餐,每人进餐时都需使用刀、叉各一把,餐桌上的布置如下图所示。请用信号量及 P/V 操作说明这四位哲学家的同步、互斥过程。

34.【参考答案】

Semaphore knife1 = 1; //实现对第一把刀的互斥访问
Semaphore knife2 = 1; //实现对第二把刀的互斥访问
Semaphore fork1 = 1; //实现对第一把叉的互斥访问
Semaphore fork2 = 1; //实现对第二把叉的互斥访问
Semaphore a = b = c = d = 0; //实现哲学家到齐才可开始讨论的同步关系
CoBegin{
    process Pa() {//哲学家甲
        while(TRUE){
            V(a);
            V(a);
            V(a); //哲学家甲落座,若有其他哲学家等待甲则通知
            P(b); //若哲学家乙未到,则等待
            P(c); //若哲学家丙未到,则等待
            P(d); //若哲学家丁未到,则等待
            讨论问题;
            P(knife1); //(互斥)防止其他哲学家使用刀1
            P(fork1); //(互斥)防止其他哲学家使用叉1
            进餐;
            V(knife1); //(互斥)允许其他哲学家使用刀1
            V(fork1); //(互斥)允许其他哲学家使用叉1
        }
    }
    process Pb() {//哲学家乙
        while(TRUE){
            V(b);
            V(b);
            V(b); //哲学家乙落座,若有其他哲学家等待乙则通知
            P(a); //若哲学家甲未到,则等待
            P(c); //若哲学家丙未到,则等待
            P(d); //若哲学家丁未到,则等待
            讨论问题;
            P(knife2); //(互斥)防止其他哲学家使用刀2
            P(fork1); //(互斥)防止其他哲学家使用叉1
            进餐;
            V(knife2); //(互斥)允许其他哲学家使用刀2
            V(fork1); //(互斥)允许其他哲学家使用叉1
        }
    }
    process Pc() {//哲学家丙
        while(TRUE){
            V(c);
            V(c);
            V(c); //哲学家丙落座,若有其他哲学家等待丙则通知
            P(a); //若哲学家甲未到,则等待
            P(b); //若哲学家乙未到,则等待
            P(d); //若哲学家丁未到,则等待
            讨论问题;
            P(knife2); //(互斥)防止其他哲学家使用刀2
            P(fork2); //(互斥)防止其他哲学家使用叉2
            进餐;
            V(knife2); //(互斥)允许其他哲学家使用刀2
            V(fork2); //(互斥)允许其他哲学家使用叉2
        }
    }
    process Pd() {//哲学家丁
        while(TRUE){
            V(d);
            V(d);
            V(d); //哲学家丁落座,若有其他哲学家等待丁则通知
            P(a); //若哲学家甲未到,则等待
            P(b); //若哲学家乙未到,则等待
            P(c); //若哲学家丙未到,则等待
            讨论问题;
            P(knife1); //(互斥)防止其他哲学家使用刀1
            P(fork2); //(互斥)防止其他哲学家使用叉2
            进餐;
            V(knife1); //(互斥)允许其他哲学家使用刀1
            V(fork2); //(互斥)允许其他哲学家使用叉2
        }
    }
}CoEnd

2.3.10 真题演练

35.【2010】进程P0​和P1​的共享变量定义及其初值为:

boolean flag[2];int turn = 0;
flag[0] = FALSE; flag[1] = FALSE;

若进程P0​和P1​访问临界资源的类 C 伪代码实现如下:

void P0() //进程 P0
{
    while(TRUE)
    {
        flag[0] = TRUE; turn = 1;
        while(flag[1] && (turn == 1));
        临界区;
        flag[0] = FALSE;
    }
}
void P1() //进程 P1
{
    while(TRUE)
    {
        flag[1] = TRUE; turn = 0;
        while(flag[0] && (turn == 0));
        临界区;
        flag[1] = FALSE;
    }
}

则并发执行进程P0​和P1​时产生的情形是( )。
A. 不能保证进程互斥进入临界区,会出现 “饥饿” 现象
B. 不能保证进程互斥进入临界区,不会出现 “饥饿” 现象
C. 能保证进程互斥进入临界区,会出现 “饥饿” 现象
D. 能保证进程互斥进入临界区,不会出现 “饥饿” 现象

35.【参考答案】D
【解析】题干描述的代码是 Peterson 算法的实例,既设置 flag 标志表明进程是否请求进入临界区,又设置共享变量 turn 规定进程进入临界区的顺序。在进入临界区前,进程P0​检查另一个进程P1​的标志 flag [1],又检查共享变量 turn,可以保证两个进程同时希望进入临界区时,只有一个进程能进入临界区,不会出现饥饿现象,故本题选 D。

36.【2010】设与某资源关联的信号量初值为 3,当前值为 1。若 M 表示该资源的可用个数,N 表示等待该资源的进程数,则 M、N 分别是( )。
A. 0、1
B. 1、0
C. 1、2
D. 2、0

36.【参考答案】B
【解析】互斥信号量的值表示该资源当前的可用量。信号量K>0时,K为资源的可用数,信号量K<0时,K的绝对值为等待使用该资源的进程数。题干中,该资源的可用数为 1,显然没有其他进程在等待使用该资源,M=1,N=0,故本题选 B。

37.【2011】有两个并发执行的进程P1​和P2​,共享初值为 1 的变量 x。P1​对 x 加 1,P2​对 x 减 1。加 1 和减 1 操作的指令序列分别如下所示。

//加1操作
load R1, x //取x到寄存器R1中
inc R1
store x, R1 //将R1的内容存入x
//减1操作
load R2, x
dec R2
store x, R2

两个操作完成后,x 的值( )。
A. 可能为 - 1 或 3
B. 只能为 1
C. 可能为 0、1 或 2
D. 可能为 - 1、0、1 或 2

37.【参考答案】C
【解析】将P1​中的语句编号为 1,2,3;P2​中的语句编号为 4,5,6。以 1,2,3,4,5,6 的运行顺序得x=1,以 1,2,4,5,6,3 的运行顺序得x=2,以 4,5,1,2,3,6 的运行顺序得x=0,而x=−1无法以任何运行顺序得到,故本题选 C。

38.【2016】使用 TSL (Test and Set Lock) 指令实现进程互斥的伪代码如下所示。

do {
    while (TSL(&lock));
    critical section;
    lock = FALSE;
} while (TRUE);

下列与该实现机制相关的叙述中,正确的是( )。
A. 退出临界区的进程负责唤醒阻塞态进程
B. 等待进入临界区的进程不会主动放弃 CPU
C. 上述伪代码满足 “让权等待” 的同步准则
D. while (TSL (&lock)) 语句应在关中断状态下执行

38.【参考答案】B
【解析】TSL 指令属于硬件同步机制,标志(Flag)可以被看作是一把锁,“锁开” 对应进入,“锁关” 对应等待。TSL 指令无法实现让权等待,不存在被阻塞的情况,A 和 C 错误;等待进入临界区时,持续执行 while 循环,也就持续占用 CPU,B 正确;若 while 循环在关中断状态下执行,则系统无法响应其他中断源的中断请求,D 错误。故本题选 B。

39.【2016】进程P1​和P2​均包含并发执行的线程,部分伪代码描述如下所示。

// 进程P1
int x = 0;
Thread1() {
    int a;
    a = 1;
    x += 1;
}
Thread2() {
    int a;
    a = 2;
    x += 2;
}
// 进程P2
int x = 0;
Thread3() {
    int a;
    a = x;
    x += 3;
}
Thread4() {
    int b;
    b = x;
    x += 4;
}

下列选项中,需要互斥执行的操作是( )。
A. a=1与a=2
B. a=x与b=x
C. x+=1与x+=2
D. x+=1与x+=3

39.【参考答案】C
【解析】不同线程的 a 不是同一个变量,对 a 的赋值操作无需互斥执行,A 错误;a=x与b=x的执行顺序与 a 和 b 的结果无关,无需互斥执行,但是可以同步执行,B 错误;x+=1与x+=2执行顺序与 x 的结果有关,需互斥执行,C 正确;P1​和P2​中的 x 不是同一个变量,也不需要互斥执行,D 错误。故本题选 C。

40.【2016】下列关于管程的叙述中,错误的是( )。
A. 管程只能用于实现进程的互斥
B. 管程是由编程语言支持的进程同步机制
C. 任何时候只能有一个进程在管程中执行
D. 管程中定义的变量只能被管程内的过程访问

40.【参考答案】A
【解析】管程由编程语言支持,既可以实现进程的互斥,也可以实现进程的同步,A 错误,B 正确;管程具有互斥性,在任何时候,一个管程中只能有一个活动进程,C 正确;管程具有封装性,管程中的过程只能使用管程中的数据结构,同时管程中的数据结构也只能在管程中使用,D 正确。故本题选 A。

41.【2018】属于同一进程的两个线程 thread1 和 thread2 并发执行,共享初值为 0 的全局变量 x。thread1 和 thread2 实现对全局变量 x 加 1 的机器级代码描述如下。

thread1:
mov R1,x  //(x)→R1
inc R1    //(R1)+1→R1
mov x,R1  //(R1)→x

thread2:
mov R2,x  //(x)→R2
inc R2    //(R2)+1→R2
mov x,R2  //(R2)→x

在所有可能的指令执行序列中,使 x 的值为 2 的序列个数是( )。
A. 1
B. 2
C. 3
D. 4

41.【参考答案】B
【解析】全局变量 x 的初值为 0,thread1 和 thread2 对 x 执行的操作都是自增。执行顺序只有 2 种:先执行 thread1,后执行 thread2;或先执行 thread2,后执行 thread1。两种执行顺序执行完毕后,x 的值为 2,故本题选 B。

42.【2018】若 x 是管程内的条件变量,则当进程执行 x.wait () 时所做的工作是( )。
A. 实现对变量 x 的互斥访问
B. 唤醒一个在 x 上阻塞的进程
C. 根据 x 的值判断该进程是否进入阻塞状态
D. 阻塞该进程,并将之插入 x 的阻塞队列中

42.【参考答案】D
【解析】管程由代表共享资源的数据结构以及对该共享数据结构实施操作的一组过程所组成,条件变量是管程内包含的一种全局数据结构。x.wait () 操作与变量 x 的互斥访问无关,A 错误;x.signal () 用于唤醒一个在 x 上阻塞的进程,B 错误;当进程执行 x.wait () 时,将自己插入 x 条件变量的阻塞队列中,并释放管程,直到另一个进程向条件变量执行了 x.signal () 操作,C 错误,D 正确。故本题选 D。

43.【2018】下列同步机制中,可以实现让权等待的是( )。
A. Peterson 方法
B. swap 指令
C. 信号量方法
D. TestAndSet 指令

43.【参考答案】C
【解析】Peterson 算法会发生忙等,无法实现让权等待,A 错误;TestAndSet 指令、swap 指令属于硬件同步机制,无法实现让权等待,B 和 D 错误;记录型信号量用阻塞机制代替 while 循环,实现了让权等待,C 正确。故本题选 C。

44.【2020】下列准则中,实现临界区互斥机制必须遵循的是( )。
I. 两个进程不能同时进入临界区
II. 允许进程访问空闲的临界资源
III. 进程等待进入临界区的时间是有限的
IV. 不能进入临界区的执行态进程立即放弃 CPU
A. 仅 I、IV
B. 仅 II、III
C. 仅 I、II、III
D. 仅 I、III、IV

44.【参考答案】C
【解析】当已有进程进入其临界区时,其他试图进入临界区的进程必须等待,是 “忙则等待” 准则,I 正确;访问临界资源的代码称为临界区,相应临界资源空闲时,应立即允许该请求进程进入临界区,是 “空闲让进” 准则,II 正确;对要求访问临界资源的进程,应保证能在有限时间内进入自己的临界区,是 “有限等待” 准则,III 正确;当进程不能进入自己的临界区时,应释放 CPU,是 “让权等待” 准则,Peterson 算法未实现这一准则,IV 错误,故本题选 C。

45.【2009】三个进程P1​、P2​、P3​互斥使用一个包含N(N>0)个单元的缓冲区。P1​每次用 produce () 生成一个正整数并用 put () 送入缓冲区某一空白单元中;P2​每次用 getodd () 从该缓冲区中取出一个奇数并用 countodd () 统计奇数个数;P3​每次用 geteven () 从该缓冲区中取出一个偶数并用 counteven () 统计偶数个数。请用信号量机制实现这三个进程的同步与互斥活动,并说明所定义信号量的含义(要求用伪代码描述)。

45.【参考答案】
定义同步信号量 odd,控制P1​与P2​之间奇数取用的同步;定义同步信号量 even,控制P1​与P3​之间偶数取用的同步;定义同步信号量 empty,表示空余位置数,有空余位置才可放入正整数;定义互斥信号量 mutex,实现对临界区的互斥访问,程序具体实现如下:

semaphore odd = 0;//定义奇数取用的同步信号量
semaphore even = 0;//定义偶数取用的同步信号量
semaphore empty = N;//定义同步信号量,表示空余位置数
semaphore mutex = 1;//定义对临界区访问的互斥信号量
CoBegin{
    P1(){
        x = produce();//生成一个正整数
        P(empty);//(同步)申请一个空余位置,empty自减
        P(mutex);//(互斥)对临界区执行P操作,防止他人进入
        Put();//将生成的正整数放入缓冲区空单元
        V(mutex);//(互斥)对临界区执行V操作,允许他人进入
        if(x % 2 == 0)
            V(even);//(同步)如果是偶数,even自增,以便等待中的P3使用
        else
            V(odd);//(同步)如果是奇数,odd自增,以便等待中的P2使用
    }
    P2(){
        P(odd);//(同步)P1产生奇数后,P2才可取用
        P(mutex);//(互斥)对临界区执行P操作,防止他人进入
        getodd();//取出一个奇数
        V(mutex);//(互斥)对临界区执行V操作,允许他人进入
        V(empty);//(同步)新增一个空余位置,以便等待中的P1使用
        countodd();//统计奇数个数
    }
    P3(){
        P(even);//(同步)P1产生偶数后,P3才可取用
        P(mutex);//(互斥)对临界区执行P操作,防止他人进入
        geteven();//取出一个偶数
        V(mutex);//(互斥)对临界区执行V操作,允许他人进入
        V(empty);//(同步)新增一个空余位置,以便等待中的P1使用
        counteven();//统计偶数个数
    }
}CoEnd

46.【2011】某银行提供 1 个服务窗口和 10 个供顾客等待的座位。顾客到达银行时,若有空座位,则到取号机上领取一个号,等待叫号。取号机每次仅允许一位顾客使用。当营业员空闲时,通过叫号选取一位顾客,并为其服务。顾客和营业员的活动过程描述如下:

Cobegin{
    process 顾客 i
    {
        从取号机获取一个号;
        等待叫号;
        获取服务;
    }
    process 营业员
    {
        while (TRUE)
        {
            叫号;
            为客户服务;
        }
    }
}Coend

请添加必要的信号量和 P/V(或 wait ()、signal ())操作,实现上述过程中的互斥与同步。要求写出完整的过程,说明信号量的含义并赋初值。

46.【参考答案】
定义同步信号量 call_custom,实现顾客与营业员之间叫号的同步;定义互斥信号量 mutex,实现对取号机这一互斥资源的互斥访问;定义一对信号量 seets_empty 和 seets_full,分别表示空余座位数和已用座位数,有空余座位才可落座;定义互斥信号量 mutex,实现对临界区的互斥访问,程序具体实现如下:

semaphore seets_empty = 10; //座位的空余数量
semaphore seets_full = 0; //座位的已用数量
semaphore mutex = 1; //实现对取号机这一互斥资源的互斥访问
semaphore call_custom = 0; //实现顾客与营业员之间叫号的同步
CoBegin{
    process 顾客 i {
        P(seets_empty); //占用一个空座位
        P(mutex); //(互斥)防止他人使用取号机
        从取号机获取一个号码;
        V(mutex); //(互斥)允许他人使用取号机
        V(seets_full); //(同步)有顾客来,则通知营业员
        P(call_custom); //(同步)等待营业员叫号
        获取服务;
    }
    process 营业员 {
        while(TRUE) {
            P(seets_full); //(同步)没有顾客来,则营业员休息
            叫号;
            V(call_custom); //(同步)通知顾客,已被叫号
            V(seets_empty); //释放一个座位
            为顾客服务;
        }
    }
}CoEnd

47.【2013】某博物馆最多可容纳 500 人同时参观,有一个出入口,该出入口一次仅允许通过一个人。参观者的活动描述如下:

cobegin
    参观者进程i() {
        进门;
        参观;
        出门;
    }
coend

        请添加必要的信号量和 P/V (或 wait ()、signal ()) 操作,以实现上述过程中的互斥与同步。要求写出完整的过程,说明信号量的含义并赋初值。

47.【参考答案】
本题需实现对博物馆出入口的互斥和对博物馆参观总人数的限制,定义互斥信号量 mutex,实现对博物馆出入口这一互斥资源的互斥访问;定义信号量 empty,实现对人数的限制,具体实现如下:

semaphore empty = 500; // 博物馆剩余可容纳人数,初值为500
semaphore mutex = 1; // 实现对博物馆出入口的互斥使用
CoBegin{
    process 参观者进程 i {
        P(empty); //(互斥)占用一个可容纳的位置,如剩余容量不足则等待
        P(mutex); //(互斥)防止他人使用博物馆出入口
        进门;
        V(mutex); //(互斥)允许他人使用博物馆出入口
        参观;
        P(mutex); //(互斥)防止他人使用博物馆出入口
        出门;
        V(mutex); //(互斥)允许他人使用博物馆出入口
        V(empty); //(互斥)释放一个可容纳的位置
    }
}CoEnd

48.【2014】系统中有多个生产者进程和多个消费者进程,共享一个能存放 1000 件产品的环形缓冲区 (初始为空)。当缓冲区未满时,生产者进程可以放入其生产的一件产品,否则等待;当缓冲区未空时,消费者进程可以从缓冲区取走一件产品,否则等待。要求一个消费者进程从缓冲区连续取出 10 件产品后,其他消费者进程才可以取产品。请使用信号量 P/V(wait ()/signal ())操作实现进程间的互斥与同步,要求写出完整的过程,并说明所用信号量的含义和初值。

48.【参考答案】
本题属于生产者 - 消费者问题,定义互斥信号量 mutex_consumer,用于实现在一个消费者进程进入缓冲区,且尚未取出 10 件产品时,其他消费者进程不可以进入缓冲区取产品;定义互斥信号量 mutex_buffer,用于实现对缓冲区的互斥访问,定义信号量 empty 和 full,分别表示缓冲区的空位数和已用数,程序具体实现如下:

semaphore mutex_consumer = 1; //实现消费者进程之间的互斥操作
semaphore mutex_buffer = 1; //实现对缓冲区的互斥访问
semaphore empty = 1000; //缓冲区的空位数
semaphore full = 0; //缓冲区的已用数
CoBegin{
    process producer {
        while(TRUE){
            生产一件产品;
            P(empty); //占用一个缓冲区,如剩余容量不足则等待
            P(mutex_buffer); //(互斥)防止他人访问缓冲区
            把产品放入缓冲区;
            V(mutex_buffer); //(互斥)允许他人访问缓冲区
            V(full); //(同步)缓冲区的已用容量加1
        }
    }
    process consumer {
        while(TRUE){
            P(mutex_consumer); //(互斥)防止其他消费者进程进入缓冲区
            for(int i = 0; i < 10; ++i){
                P(full); //申请使用缓冲区的产品,如没有产品则等待生产者生产
                P(mutex_buffer); //(互斥)防止他人访问缓冲区
                从缓冲区取走一件产品;
                V(mutex_buffer); //(互斥)允许他人访问缓冲区
                V(empty); //释放一个缓冲区
                消费这件产品;
            }
            V(mutex_consumer); //(互斥)允许其他消费者进程进入缓冲区
        }
    }
}CoEnd

49.【2015】有 A、B 两人通过信箱进行辩论,每个人都从自己的信箱中取得对方的问题。将答案和向对方提出的新问题组成一个邮件放入对方的邮箱中。假设 A 的信箱最多放 M 个邮件,B 的信箱最多放 N 个邮件。初始时 A 的信箱中有 x 个邮件 (0<x<M)。B 的信箱中有 y 个邮件 (0<y<N)。辩论者每取出一个邮件,邮件数减 1。A 和 B 两人的操作过程描述如下:

CoBegin
    Process_A {
        while (ture) {
            P(mutex); /* 加锁 */
            访问临界区;
            V(mutex); /* 解锁 */
            剩余区;
        }
    }
    Process_B {
        while (ture) {
            P(mutex); /* 加锁 */
            访问临界区;
            V(mutex); /* 解锁 */
            剩余区;
        }
    }
CoEnd

        当信箱不为空时,辩论者才能从信箱中取邮件,否则需要等待。当信箱不满时,辩论者才能将新邮件放入信箱,否则需要等待。请添加必要的信号量和 P、V(或 wait、signal)操作,以实现上述过程的同步。要求写出完整过程,并说明信号量的含义和初值。

49.【参考答案】
        本题属于生产者 - 消费者问题,A 和 B 既是生产者,同时也是消费者。为实现同步访问,定义一对同步信号量 A_full 和 A_empty,分别表示 A 的信箱的已用容量和剩余容量,A_full 的初始值为 x,A_empty 的初始值由 A 的信箱总容量减去 x 计算得出,B 的信箱同理。虽然题干没有要求实现互斥,但 A 的信箱、B 的信箱显然是互斥资源,需要对其实现互斥访问,故定义互斥信号量 A_mutex 和 B_mutex,其初始值均为 1。
        同一时间只能有一人能在信箱中取放邮件,因此对取放邮件这一类操作,前后分别使用 P (mutex) 和 V (mutex) 实现互斥访问。
        对于 A:在 A 从自己的信箱取邮件之前,需要先检查 A 的邮箱是否为空,如为空则需等待,故先执行 P (A_full) 操作;取出操作导致了新增一个空余位置,执行 V (A_empty) 操作让 A_empty 自增,假设 B 在等待从 A 的信箱取走邮件,则在此之后 B 将能够进入 A 的信箱。当 A 准备将新邮件放入 B 的信箱前,需要先检查 B 的邮箱是否有空位,如为满则需等待,故先执行 P (B_empty) 操作;放入操作导致了新增一个已用位置,执行 V (B_full) 操作让 B_full 自增,假设 B 在等待往 A 的信箱放邮件,则在此之后 B 将能够进入 A 的信箱。
        对于 B:同理。程序具体实现如下:

semaphore A_full = x; //A的信箱的已用容量
semaphore A_empty = M-x; //A的信箱的剩余容量
semaphore A_mutex = 1; //实现对A的信箱这一互斥资源的互斥访问
semaphore B_full = y; //B的信箱的已用容量
semaphore B_empty = N-y; //B的信箱的剩余容量
semaphore B_mutex = 1; //实现对B的信箱这一互斥资源的互斥访问
CoBegin{
    A{
        while(TRUE){
            P(A_full); //(同步)申请一个A信箱中的已用位置,A_full--
            P(A_mutex); //(互斥)防止他人访问A的信箱
            从A的信箱中取出一个邮件;
            V(A_mutex); //(互斥)允许他人访问A的信箱
            V(A_empty); //(同步)新增一个A信箱中的空余位置,A_empty++
            回答问题并提出一个新问题; //该操作无需实现互斥
            P(B_empty); //(同步)申请一个B信箱中的空余位置,B_empty--
            P(B_mutex); //(互斥)防止他人访问B的信箱
            将新邮件放入B的信箱;
            V(B_mutex); //(互斥)允许他人访问B的信箱
            V(B_full); //(同步)新增一个B信箱中的已用位置,B_full++
        }
    }
    B{
        while(TRUE){
            P(B_full); //(同步)申请一个B信箱中的已用位置,B_full--
            P(B_mutex); //(互斥)防止他人访问B的信箱
            从B的信箱中取出一个邮件;
            V(B_mutex); //(互斥)允许他人访问B的信箱
            V(B_empty); //(同步)新增一个B信箱中的空余位置,B_empty++
            回答问题并提出一个新问题; //该操作无需实现互斥
            P(A_empty); //(同步)申请一个A信箱中的空余位置,A_empty--
            P(A_mutex); //(互斥)防止他人访问A的信箱
            将新邮件放入A的信箱;
            V(A_mutex); //(互斥)允许他人访问A的信箱
            V(A_full); //(同步)新增一个A信箱中的已用位置,A_full++
        }
    }
}CoEnd

50.【2019】有n(n≥3)位哲学家围坐在一张圆桌边,每位哲学家交替地就餐和思考。在圆桌中心有m(m≥1)个碗,每两位哲学家之间有一根筷子。每位哲学家必须取到一个碗和两侧的筷子后,才能就餐完毕,将碗和筷子放回原位,并继续思考。为使尽可能多的哲学家同时就餐,且防止出现死锁现象,请使用信号量的 P/V 操作 [wait ()、signal () 操作] 描述上述过程中的互斥与同步,并说明所用信号量及初值的含义。

50.【参考答案】
        本题属于哲学家进餐问题,有n个哲学家、n只筷子和m个碗,需实现对碗和筷子的互斥访问。定义互斥信号量 bowl,其初始值为min(m,n−1)。若碗的数量m大于哲学家数量n,则bowl=n−1,确保最多只有n−1个哲学家同时进餐,破坏死锁产生的循环等待条件;若碗的数量m小于哲学家数量n,则bowl=m,防止所有哲学家都拿起一侧的筷子而无限等待另一侧筷子而产生死锁。
        定义互斥信号量 chopsticks [n],设 chopsticks [i] 是哲学家左侧的筷子数,chopsticks [(i + 1) MOD n] 是哲学家右侧的筷子数。注意哲学家进餐的桌子是圆桌,因此需要用到取模操作。每两个哲学家之间只有一只筷子,故对 chopsticks [i] 赋的初值为 1。哲学家进餐需要同时拿到碗和两侧的筷子,如果哲学家没有拿到碗,即使拿到筷子也无法进餐,所以先对 bowl 执行 P 操作,然后对两侧筷子执行 P 操作,方可进餐。哲学家进餐完毕后,分别对碗和两侧筷子执行 V 操作,三个 V 操作可以颠倒。题干中的n位哲学家独立思考、独立就餐,没有协同关系,故无需定义同步信号量。
        程序具体实现如下:

int amount_bowl = m, amount_chop = n - 1;
semaphore bowl = min(amount_bowl, amount_chop);//实现对碗这一互斥资源的互斥访问
semaphore chopsticks[n];//实现对筷子这一互斥资源的互斥访问
for(int i = 0; i <= amount_chop; i++){
    chopsticks[i] = 1;//每两个哲学家之间的筷子数量是一只
}
CoBegin{
    while(TRUE){//第i个哲学家
        进行思考;
        P(bowl);//(互斥)拿起一个碗
        P(chopsticks[(i + 1) MOD n]);//(互斥)拿起右侧筷子
        P(chopsticks[i]);//(互斥)拿起左侧筷子
        开始就餐;
        V(chopsticks[(i + 1) MOD n]);//(互斥)放下右侧筷子
        V(chopsticks[i]);//(互斥)放下左侧筷子
        V(bowl);//(互斥)放下一个碗
    }
}CoEnd

51.【2020】现有 5 个操作 A、B、C、D 和 E,操作 C 必须在 A 和 B 完成后执行,操作 E 必须在 C 和 D 完成后执行,请使用信号量的 wait ()、signal () 操作 (P/V 操作) 描述上述操作之间的同步关系,并说明所用信号量及其初值。

51.【参考答案】
        本题属于利用信号量实现前驱的经典问题,操作 C 必须在 A 和 B 完成后执行,操作 E 必须在 C 和 D 完成后执行,其执行顺序如右图所示。定义同步信号量 A、B、C、D,一开始四个操作都未执行,令它们的初始值为 0。在执行操作 C 前需先检查操作 A 和 B 是否已完成,故在其之前加上 P (A) 和 P (B),只有操作 A 和 B 都完成,才能进行操作 C;与之对应,在完成操作 A 和 B 后分别执行 V (A) 和 V (B) 操作,确保操作的先后顺序。同理,在执行操作 E 前需先检查操作 C 和 D 是否已完成,故在其之前加上 P (C) 和 P (D),只有操作 C 和 D 都完成,才能进行操作 E;与之对应,在完成操作 C 和 D 后分别执行 V (C) 和 V (D) 操作,确保操作的先后顺序。程序具体实现如下:

semaphore A = B = C = D = 0; //定义实现前驱关系的信号量
CoBegin{
    A{
        完成操作A;
        V(A); //(后继)实现先A后C
    }
    B{
        完成操作B;
        V(B); //(后继)实现先B后C
    }
    C{
        P(A); //(前驱)实现先A后C
        P(B); //(前驱)实现先B后C
        完成操作C;
        V(C); //(后继)实现先C后E
    }
    D{
        完成操作D;
        V(D); //(后继)实现先D后E
    }
    E{
        P(C); //(前驱)实现先C后E
        P(D); //(前驱)实现先D后E
        完成操作E;
    }
}CoEnd

52.【2017】某进程中有 3 个并发执行的线程 thread1、thread2 和 thread3,其伪代码如下所示:

//复数的结构类型定义
typedef struct{
    float a;
    float b;
} cnum;
cnum x,y,z; //全局变量//计算两个复数之和
cnum add(cnum p,cnum q){
    cnum s;
    s.a = p.a + q.a
    s.b = p.b + q.b
    return s;
}
thread1{
    cnum w;
    w = add(x,y);
   ...
}
thread2{
    cnum w;
    w = add(y,z);
   ...
}
thread3{
    cnum w;
    w.a = 1;
    w.b = 1;
    z = add(z,w);
    y = add(y,w);
   ...
}

        请添加必要的信号量和 P、V (或 wait ()、signal ()) 操作,要求确保线程互斥访问临界资源,并且最大限度地并发执行。

52..【参考答案】
        本题属于经典互斥问题,显然x、y、z是临界资源,需对其互斥访问,而三个线程中的三个w是相互独立的,不是临界资源。观察发现只有 thread1 涉及对临界资源x的读取,无需定义x的互斥信号量。而 thread1 读y,thread3 读写y,需实现互斥;thread2 读y,thread3 写y,也需实现互斥,故定义两个互斥信号量 mutex_y_13 和 mutex_y_23。对于z:thread2 读z,thread3 读写z,故定义互斥信号量 mutex_z。对上述互斥信号量赋初值 1。
        thread1 读取y时,应防止 thread3 改动y,因此读取前后分别对 mutex_y_13 执行 PV 操作;thread2 读取y和z时,应防止 thread3 改动y和z,因此读取前后分别对 mutex_y_23 和 mutex_z 执行 PV 操作;thread3 改动z时,应防止 thread2 读取z,因此改动z前后分别对 mutex_z 执行 PV 操作;thread3 改动y时,应防止 thread1 和 thread2 读取y,因此改动y前后分别对 mutex_y_13 和 mutex_y_23 执行 PV 操作。题干中的三个线程没有协同关系,故无需定义同步信号量。
        程序具体实现如下:

semaphore mutex_y_13 = 1; //实现thread1与thread3对y的互斥访问
semaphore mutex_y_23 = 1; //实现thread2与thread3对y的互斥访问
semaphore mutex_z = 1; //实现thread2与thread3对z的互斥访问
CoBegin{
    thread1{
        cnum w;
        P(mutex_y_13); //(互斥)防止thread3读写y
        w = add(x, y); //涉及对互斥资源y的读取
        V(mutex_y_13); //(互斥)允许thread3读写y
       ...
    }
    thread2{
        cnum w;
        P(mutex_y_23); //(互斥)防止thread3读写y
        P(mutex_z); //(互斥)防止thread3读写z
        w = add(y, z); //涉及对互斥资源y和z的读取
        V(mutex_z); //(互斥)允许thread3读写z
        V(mutex_y_23); //(互斥)允许thread3读写y
       ...
    }
    thread3{
        cnum w;
        w.a = 1;
        w.b = 1;
        P(mutex_z); //(互斥)防止读写z时thread2读z
        z = add(z, w); //涉及对互斥资源z的读写
        V(mutex_z); //(互斥)读写完毕,允许thread2读z
        P(mutex_y_13); //(互斥)防止读写y时thread1读y
        P(mutex_y_23); //(互斥)防止读写y时thread2读y
        y = add(y, w); //涉及对互斥资源y的读写
        V(mutex_y_13); //(互斥)读写完毕,允许thread1读y
        V(mutex_y_23); //(互斥)读写完毕,允许thread2读y
       ...
    }
} CoEnd

53.【2021】下表给出了整型信号量 S 的 wait () 和 signal () 操作的功能描述,以及采用开 / 关中断指令实现信号量操作的两种方法。回答以下问题:
(1) 为什么在 wait () 和 signal () 操作中对信号量 S 的访问必须互斥执行?
(2) 分别说明方法 1 和方法 2 是否正确。若不正确,请说明理由。
(3) 用户程序能否使用开 / 关中断指令实现临界区互斥?为什么?

53.【参考答案】
(1) 因为信号量S是能被多个进程进行读写操作的共享变量,所以访问必须互斥。
(2) 方法 1 错误。在 wait () 中,如果S≤0,会导致 while 语句陷入死循环。因为关中断后,当前进程无法被中断,其他进程无法修改信号量S的值。
方法 2 正确。方法 2 改进了方法 1 中存在的问题,wait () 操作中的 while 循环可以不断重复开中断和关中断的操作,当信号量S≤0时,进行 wait () 操作的进程无法对S进行修改,而此时进行开中断和关中断的循环可以允许其他正在使用S信号量的进程执行 signal (),从而使得其他进程可以修改信号量S的值,从而避免 while 语句的死循环。
(3) 不能。因为用户程序运行在用户态,而开 / 关中断指令只能在内核态下执行。

2.4 死锁

        本节讨论的主要问题是操作系统对死锁的处理。考生需要先对死锁的定义和原因有一个初步的了解,并由此展开对死锁处理方法的讨论。在学习本节内容的过程中,请先尝试回答下列问题:
(1) 什么是死锁?死锁在生活中有哪些例子?
(2) 为什么会出现死锁?
        本节的前半部分(死锁的概念)重在掌握死锁的定义和原因、资源问题和资源分配图,后半部分(死锁的处理方法)重在对概念理解的基础上实现死锁的预防、避免、检测与解除。
请考生在学习过程中尝试回答下列问题(题目可以在各个小节中找到答案 ):
(1) 死锁、饥饿、死循环有什么区别和联系?
(2) 死锁的产生有哪些必要条件?
(3) 死锁预防、死锁避免、死锁检测和解除分别是如何处理死锁的?
(4) 如何应用银行家算法进行死锁避免?

2.4.1 死锁的概念

1. 死锁的定义

        死锁是n个进程(n≥2)由于竞争资源或彼此通信导致的一种阻塞现象,若无外力作用,这些进程均无法继续向前推进。死锁的发生通常是由于这些进程对系统资源(包括不可抢占资源和可消耗性资源)的争夺。当一组进程全部被阻塞,且在等待系统中的某一个事件,而该事件只能由其中已被阻塞的进程触发时,该组进程就陷入了死锁。死锁是永久性的,因为被阻塞的进程不能主动触发被等待的事件(通常是释放某些被请求的资源),需要借助外力才能打破。
        【举例】如图 2.26 所示,四辆车从四个方向同时到达十字路口。由于前进方向交汇,这四辆车不得不停下来;同时,由于前排车辆阻塞,后续车辆也无法继续前进,于是就产生了 “死锁”。

        上一节介绍了用信号量实现进程同步,可能会存在这样一种情况:n个进程(n≥2)在无限期等待一个只能由其中某个等待进程触发的事件。例如:在下面这个程序中,等待的 “事件” 就是一个 signal () 操作(V 操作)的执行。假设P0​执行了 wait (S),然后P1​执行了 wait (Q)。当P0​执行 wait (Q) 时,它必须等待,直到P1​执行 signal (Q)。同样地,当P1​执行 wait (S) 时,它必须等待,直到P0​执行 signal (S)。由于这些 signal () 操作不能被执行,程序陷入僵局,这种局面称为死锁。

semaphore S = 1;
semaphore Q = 1;
CoBegin{
    P0{
        Wait(S);
        Wait(Q);
       ...
        Signal(S);
        Signal(Q);
    }
    P1{
        Wait(Q);
        Wait(S);
       ...
        Signal(S);
        Signal(Q);
    }
} CoEnd

【拓展】死锁、饥饿、死循环的异同:
(1) 共同点:死锁、饥饿、死循环都是进程无法顺利向前推进的现象。
(2) 不同点:① 死锁是n个进程(n≥2)循环等待对方手里的资源,导致n个进程都被阻塞,均无法向前推进的现象。
                ② 饥饿是进程(可以只有 1 个)由于长期未获得想要的资源,从而无法向前推进的现象。“资源” 可以是 CPU—— 饥饿进程长期处于就绪态,也可以是 I/O 设备 —— 饥饿进程长期处于阻塞态。
                ③ 死循环是进程(可以只有 1 个)在执行过程中,一直进行某个循环,而不跳出的现象。
(3) 死锁和饥饿是由于操作系统对资源的不合理分配引起的,而死循环是由于程序的设计错误引起的。
(4) 对于死锁,若无外力作用,或者不给锁设定过期时间,将永远无法结束;对于饥饿,只要进程得到其需要的资源,可以继续向前推进。

2. 死锁的原因

(1) 竞争有限的系统资源
        死锁源于对不可抢占性资源的竞争。只要此类资源数量不足以满足几个进程的运行需要,就有可能导致运行中的进程发生死锁。
        【提示】假设系统中有m(m≥1)个进程,每个进程都需要k(k≥1)个相同类型的临界资源,那么确保系统不出现死锁的最小资源数是(k−1)m+1。考虑极端情况:假设每个进程被分配了(k−1)个资源,若此时系统资源耗尽,将会发生死锁。但只需要一个额外资源,就可以帮助其中某个进程完成任务,并释放其占有的资源,以便其他进程运行所需。
(2) 进程推进顺序不当
        当系统中有不可抢占的临界资源R1​和R2​,由相互竞争的进程P1​和P2​共享。如果按P1​请求R2​、P2​请求R1​的顺序分配资源,那么当进程继续请求资源时,进程P1​因请求资源R1​而进入阻塞状态,进程P2​因请求资源R2​而进入阻塞状态,导致P1​和P2​陷入了死锁。上述分析表明,当进程共享有限的资源时,进程运行过程中请求和释放资源的不正确顺序会导致死锁发生。
【提示】某种角度而言,对有限不可抢占资源的无序竞争是死锁发生的 “内因”,进程推进顺序不当是死锁发生的 “外因”,内因与外因一同导致了死锁的发生。

3. 死锁产生的必要条件

        如图 2.27 所示,死锁的产生必须同时满足以下四个必要条件,只要其中一个条件不满足,死锁就不会发生。

        ·互斥条件:进程要求对其分配的资源独占使用,即一个资源在一定时间内只能被一个进程占有,如果其他进程请求该资源,只能等到占有该资源的进程用完后释放。

        ·请求和保持条件:进程已经占有至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时进程需阻塞,且不会释放已占有的资源。

        ·不可抢占条件:一旦一个进程占有一个资源(即一旦它的资源请求被批准),那么该资源就不能被强行抢占,只能由该进程主动释放。

        ·循环等待条件:系统发生死锁时,一定存在一条进程请求资源的循环等待链。链上的每个进程都在等待使用相邻进程所占有的资源,形成循环等待。也就是说,存在一个等待资源的进程序列{P0​,P1​,P2​,⋯,PN​},其中P1​在等待P2​占有的资源,P2​在等待P3​占有的资源,⋯,PN​在等待P1​占有的资源。

        【提示】上述四个必要条件并不是相互孤立的。例如,循环等待条件包含了前三个条件。同样需要注意,这些条件是必要条件,满足这些条件不一定发生死锁,但不满足这些条件一定发生不了死锁。

2.4.2 死锁的处理方法

        一般来说,有四种处理死锁的方法:死锁预防和死锁避免,即系统不允许出现死锁;死锁检测和死锁解除,即当检测到死锁时,通过中止一个进程或抢占一些资源来处理死锁。
        (1) 死锁预防是静态的死锁解决方法,在系统运行前就限定了进程的运行规则,破坏了死锁产生的必要条件中的至少一个。
        (2) 死锁避免是一种运行时的检查方法,在启动进程和请求资源时会通过某种算法判断是否可能导致死锁;如果可能,就将其拒绝。
        (3) 死锁检测不需要事先采取任何限制性措施,并允许死锁的出现。检测机制可以在适当的时候检测到死锁的发生,然后采取适当的行动,使进程脱离死锁。
        (4) 死锁解除需要中止进程或抢占资源。如果既不防止也不检测死锁,那么当死锁发生时,系统将逐渐变慢,因为越来越多的进程被阻塞,且当前系统的可用资源减少,进程等待资源分配的时间增加。
        【拓展】除了以上策略以外,还有一种策略称为 “鸵鸟策略”,指操作系统不对死锁进行任何防范(类似于鸵鸟在遇到危险时将头埋在地里,装作看不见)。假如某类死锁的发生频率非常之低,例如一年左右才发生一次,那么允许它们发生并在必要时重启就可以了。这一策略的优点在于性能损失非常小,缺点在于大部分情况下无法使用。

2.4.3 死锁预防

        死锁预防是一种静态的死锁解决方法。在产生死锁的四个必要条件均满足时,系统才可能进入死锁状态,因此可以通过破坏四个必要条件中的一个或多个来防止死锁的发生。

1. 破坏互斥条件

        多个进程共享临界资源必须保证互斥访问;反过来说,如果只有一个进程访问临界资源就没必要保证互斥访问。对于某些特殊资源,可以为其建立一个专门的进程,用来代理其他进程对这种资源的访问。其他进程想要访问这种资源,就必须通过进程通信的方式告知代理进程。访问这类资源的进程只有代理进程一个,自然不会产生死锁。例如,SPOOLing 技术就能将打印机改为逻辑上的共享设备,达到死锁预防的效果。这一方式的缺点在于条件严苛,适用范围很窄,只有少数资源(如打印机)适合这一方式;且对每种资源都建立一个进程开销很大。

2. 破坏请求和保持条件

        为了预防死锁的发生,可以禁止进程在持有一个或多个资源的同时等待其他资源。此时有以下两种方案:
        (1) 要求所有进程一次性请求所有资源,要么全部被批准,要么全部不批准;运行结束之后释放全部资源。这种方案在大型机中有所使用。这一方案的缺点之一在于提前分配可能会导致系统资源的浪费:有些资源可能只在运行初期或者运行快结束时才会用到,甚至根本用不到,却都会被请求者长期占用。缺点之二在于可能会导致饥饿现象:个别资源长期被某些进程占用,使得等待这些资源的进程迟迟无法开始运行,陷入饥饿。此外,如果资源的竞争程度较高,可能会发生 “活锁” 现象,即多个进程同时开始申请,却因为资源被同期的其他进程持有,所有进程都陷入申请 —— 失败 —— 释放的循环。此时各个进程都没有阻塞,却都难以进入临界区,导致一段时间内没有进程能正常推进。
        (2) 要求进程在申请资源前暂时释放当前占有的所有资源。这种方案减缓了饥饿问题,提高了设备的利用率。

3. 破坏不可抢占条件

        抢占式的进程资源分配可以防止死锁的发生。一种方法是,如果进程在请求新资源时被迫等待,那么该进程须释放其他所有资源,重新请求旧资源和新资源。另一种方法是,当一个资源被请求且不可用时,系统会寻找目前拥有这些资源、且正在等待其他资源的阻塞进程。如果找到了这样的进程,那么相应进程的一些资源可能会被抢占,并被添加到该进程正在等待的资源列表中。这两种方法均适用于状态容易保存和恢复的资源,如寄存器和内存;但一般不适用于多数 I/O 设备,如打印机和磁带刻录机,抢占会导致进程前一阶段的工作失效。

4. 破坏循环等待条件

        可以对所有资源进行编号,并要求进程只能按资源编号严格递增或递减的顺序请求资源。假设每个资源都有编号,而且进程必须按顺序提出请求;也就是说,如果一个资源的编号小于进程已获得的任何资源的编号,则进程不能请求该资源。在这种机制下,死锁无法发生。这种策略的优点在于实现简单,资源利用率高;缺点在于必须对每种资源编号,限制了新类型设备的增加。
        考虑上一节的哲学家进餐问题。假设每根筷子都有编号,哲学家要先拿起较小编号的筷子,再拿起较大编号的筷子。假设哲学家 5 拿起 4 号筷子,哲学家 4 拿起 3 号筷子,哲学家 3 拿起 2 号筷子,哲学家 2 拿起 1 号筷子。哲学家 5 饿了,如果没有这个假设,他会拿起 5 号筷子,从而导致死锁。然而,如果顺序规则生效,他必须先拿起 1 号筷子,而 1 号筷子正被使用,因此该哲学家进程阻塞。哲学家 5 拿起 5 号筷子进餐,然后放下两根筷子,哲学家 4 可以拿起 4 号筷子进餐,以此类推。最终,每个哲学家都能顺利进餐。

2.4.4 死锁避免

1. 死锁避免的概念

        死锁避免和死锁预防之间的区别是有点微妙的:死锁预防涉及改变规则,破坏死锁发生的必要条件,使进程无法提出任何可能导致死锁的请求,这会产生不合理的因素,并且需要每个进程提供更多信息,导致设备利用率低;死锁避免是另一种策略,即每当进程请求一个资源时,操作系统都要判断此次分配是否会带来死锁风险。只有在不会导致死锁的情况下,操作系统才会为其分配资源。
        死锁避免是一种动态的死锁解决方法,其核心思路是:对于每次资源申请,系统都要实时判断其是否有死锁风险,如果存在风险,就拒绝该申请,以避免死锁发生。因此,死锁避免的关键是定义系统安全状态,并给出判断系统是否处于安全状态的算法(银行家算法 )。
        【举例】如果要保证居民用电安全、预防火灾,直接禁止居民用电是不现实的,让居民只能在室外用电也不可行。但是可以采取措施监控电流负载(保险丝)、检测烟雾浓度(烟雾报警器)的方法,如果发现火灾隐患,就切断电路或者发出警报,从而避免火灾的发生。
死锁避免相比死锁预防,施加了更精确的约束,并带来了更好的性能。死锁主要是由不安全的进程推进顺序引起的,可以将系统的状态分为安全状态和不安全状态。只要系统能一直保持在安全状态,就可以避免死锁发生。

2. 安全状态

        安全状态指系统能够分配所有进程所要求的所有资源(达到其要求的最大值)而不会进入死锁的状态。换言之,系统中存在至少一个安全的进程序列{P1​,P2​,P3​,⋯,PN​},如果系统按照这个序列调度进程执行,就能避免资源不足的情况发生。如果不存在一个安全的资源分配序列,则此时系统处于不安全状态,有可能会发生死锁。
        【提示】如图 2.28 所示,所有的安全状态都是无死锁的,但不是所有的不安全状态都会导致死锁,系统在进入不安全状态后有可能会进入死锁状态。

3. 银行家算法

        死锁避免最具代表性的算法是 Dijkstra 的银行家算法,该算法是根据银行系统贷款现金发放设计的,操作系统是银行,系统资源是资金,进程相当于向银行申请贷款的客户,操作系统分配资源时要保证不会进入不安全状态,以避免死锁。
        【举例】客户在银行申请贷款的总金额是有限的,每个客户申请时都要声明他的最大资金需求量,在满足其贷款用途后,及时归还资金。银行在客户申请的贷款总金额不超过自己拥有的钱款总金额时,尽可能地满足客户的需要。
        银行家算法的实现:每次有新进程进入系统时,进程必须提前声明运行周期中可能需要的每一类资源的最大单位数目,该单位数目不得超过系统拥有的资源总数。当一个进程实际请求一组资源时,银行家算法会对请求进行检查,系统必须首先确定是否有足够的资源可以分配给该进程。如果充足,它就会计算将这些资源分配给该进程是否会使系统进入不安全状态。如果该分配安全,则将资源分配给该进程;否则,暂时不批准这一资源请求。
        也就是说,当进程申请使用资源时,银行家算法先通过安全性算法 “试探” 分配给该进程资源后,系统是否处于安全状态,若不安全,则回退到 “试探” 前的状态,不予分配,并让该进程继续等待。
(1) 银行家算法的数据结构
        假定系统中有n个并发进程{P1​,P2​,⋯,Pn​}和m类资源{R1​,R2​,R3​,⋯,Rm​}。在银行家算法中需要定义如下四个数据结构,以描述系统中的可用资源数量、所有进程对资源的最大需求、系统中的资源分配以及所有进程还需要多少资源。
        (a) 系统可用资源向量 Available [m]:表示系统中每种资源的可用数量,该值随着资源分配与回收情况的变动而动态变化。如果 Available [j] = K,则说明在这个时刻,系统中有K个资源Rj​可用。
        (b) 最大需求矩阵 Max [n][m]:表示系统中n个进程中每个进程对m类资源的最多可能请求数量。如果 Max [i][j] = K,则说明进程Pi​在运行过程中需要资源Rj​的最大数量是K个。
        (c) 分配矩阵 Allocation [n][m]:表示当前每个进程占有的各类资源数量。如果 Allocation [i][j] = K,则说明进程Pi​被分配了K个资源Rj​。
        (d) 需求矩阵 Need [n][m]:表示进程完成任务还需要每种资源的数量。如果 Need [i][j] = K,则说明进程Pi​还需要K个资源Rj​来完成任务。
在银行家算法中存在以下恒等式:
                                        Need[i][j]=Max[i][j]−Allocation[i][j]
(2) 银行家算法的思想
        设 Requesti​为进程Pi​的请求向量,如果 Requesti​[j] = K,则说明进程Pi​需要K个Rj​类型的资源。当Pi​请求一个资源时,系统进行以下检查:
        (a) 如果 Requesti​ ≤ Need [i][j],则进入步骤 (b);否则请求失败,因为进程所需要的资源数量已经超过了它所申报的最大需求数量;
        (b) 如果 Requesti​ ≤ Available [j],则进入步骤 (c);否则说明系统还没有足够的资源储备,进程Pi​必须等待;
        (c) 系统试图为进程Pi​分配资源,并更新以下数据结构中的值:
                                        Available[j]=Available[j]−Requesti​[j]
                                        Allocation[i][j]=Allocation[i][j]+Requesti​[j]
                                        Need[i][j]=Need[i][j]−Requesti​[j]
        (d) 系统通过安全性算法检查为该进程分配资源后,是否仍处于安全状态。如果系统是安全的,资源就被正式分配给进程Pi​;否则,终止分配,系统回退到原来的资源分配状态,并让进程Pi​继续等待。
(3) 安全性算法
安全性算法可描述如下:
        (a) 定义两个向量:工作向量 Work [m] 表示系统中可用于维持进程运行的各类资源数量,在安全性算法执行前,令 Work = Available,Finish [n] 表示系统中是否有足够的资源分配给该进程,以使其运行完毕。初始化 Finish [i] = FALSE,当有足够的资源分配给进程Pi​时,令 Finish [i] = TRUE;
        (b) 在进程集合中找到一个进程,使其满足以下条件:
                                        Finish[i]==FALSE && Need[i][j]≤Work[j]
如果能找到,则执行步骤 (c),否则执行步骤 (d);
        (c) 当进程Pi​被分配资源后,它可以顺利运行完毕,并释放其占有的资源,供其他进程使用。所以应执行:
                                        (1) Work [j]=Work [j]+Allocation [i][j];
                                        (2) Finish [i]=TRUE;
                                        (3) 转到步骤 (b)。
(d) 如果 Finish [i]==TRUE 对所有进程都满足,说明系统处于安全状态;否则,系统处于不安全状态。
(4) 银行家算法实例
        本小节使用上述银行家算法来解决具体问题。假设此时的系统中,有{P0​,P1​,P2​,P3​,P4​}五个进程以及{A,B,C}三类资源,其中 A、B、C 三类资源的数量分别为 10、5、7,在T0​时刻为每个进程分配的资源如表 2.16 所示。

        表 2.16 给出了最大需求矩阵 (Max)、分配矩阵 (Allocation)、系统可用资源向量 (Available) 三个数据结构,可以通过公式 2.10 计算出需求矩阵 (Need)。

(5) 判断目前系统的安全性
        使用安全性算法判断如表 2.16 所示时刻时,系统处于安全状态。
① 设置对应数据结构
        (a) 工作向量 Work:初始时,其值为系统可用资源向量 Available = (3,3,2)。
        (b) 完成标记 Finish:为每个进程设置一个,用于标记系统中的资源是否可以满足该进程的运行需要。
        【提示】Finish 标记用于判断某进程是否已经被分配资源,其初始值为 FALSE。在一个安全的系统中,安全性算法运行完毕后每个进程的 Finish 标记都为 TRUE。
② 寻找符合条件的进程
        (a) 当前进程的 Finish 标记为 FALSE,选择P0​进程;
        【提示】判断 Finish 标记时,默认按照进程号从小到大遍历,即对于 Finish 标记同为 FALSE 的 0 号进程 (P0​) 和 1 号进程 (P1​),优先选择P0​进入算法的下一步。
        (b) 由于第 (a) 步所选进程P0​的需求向量 (NeedP0​) 大于工作向量 (Work),不满足条件,重新进行第 (a) 步操作并选择P1​进程;
        【提示】选择P0​后,P0​的需求向量为 (7,4,3),由于NeedP0​(7,4,3)>Work(3,3,2),不符合分配条件。重新第 (a) 步操作并选取P1​,得到对应需求向量 (1,2,2),由于NeedP1​(1,2,2)<Work(3,3,2),可以进行本次分配。
        (c) 无同时符合第 (a) 步、第 (b) 步两个条件的进程,直接进入步骤④。
③ 更新需求矩阵 Need,释放占有资源
        (a) 当满足某个进程所需的资源后,将该进程的 Finish 标记设为 TRUE,更新后的需求矩阵如表 2.17 中的 Need 列所示;
        (b) 将该进程已占有的资源AllocationPi​归还给系统,更新工作向量Worknew​=Workold​+Allocation,继续步骤②。
        【提示】P1​运行所需资源满足后,工作向量Worknew​=(3,3,2)+(2,0,0)=(5,3,2),分配矩阵如表 2.17 中的 Allocation 列所示。
④ 判断所有进程的 Finish 标记是否均为 TRUE,Finish 标记均为 TRUE 则说明系统处于安全状态,否则说明系统处于不安全状态。
        通过反复进行步骤 2 和 3,可以验证系统处于安全状态。考生可以动手演算,本题的一个安全的序列是{P1​,P3​,P4​,P2​,P0​}。
        【提示】上述安全性算法是银行家算法的重要组成部分(但不是全部)。不难看出,该算法可用于计算操作系统中的某一时刻是否为安全状态,其对象是 “静态” 的系统。
        【提示】考生可以任意构造 Requesti​来模拟银行家算法的执行过程。由于很少有进程能在运行前就确定其所需资源的最大值,而且进程数也不是固定的,往往在不断地变化,甚至原本可用的资源也可能忽然不可用了(例如磁带机坏了),因此以银行家算法为代表的死锁避免方法也有一定的局限性。

2.4.5 死锁检测

        虽然死锁预防和死锁避免都能很好地解决死锁问题,但死锁预防对资源使用的限制过于严格,造成系统资源利用率大幅下降,而死锁避免也有诸多不足,如分配资源时有一些保守的限制,以及要求尽可能保持系统中的资源和进程数量不变。
        因此,很多系统没有死锁预防机制,而使用死锁检测机制,该机制只是在操作系统中设置了死锁检测程序。死锁检测程序通常处于睡眠状态,只有被定期唤醒,或在检测到系统性能下降时被唤醒,以检查系统中是否存在死锁进程。死锁检测机制能够尽量满足进程的资源请求,提高系统的并发性。死锁检测算法主要是通过化简资源分配图来检测资源分配过程中是否存在循环等待事件。

1. 资源分配图

        通过使用资源分配图来检测资源分配过程中是否存在循环等待事件。资源分配图有以下四个部分组成:
(1) 一组资源类别,用{R1​,R2​,R3​,⋯,RN​}表示,如图 2.29 上的方形节点所示。资源节点内的圆点表示该资源的具体实例(例如,两个点可以代表两台打印机 )。
(2) 一组进程,用{P1​,P2​,P3​,⋯,PN​}表示。如图 2.29 上的圆形节点所示。
(3) 分配边,一组从Rj​到Pi​的有向弧,表明资源Rj​已经被分配给进程Pi​,并且进程Pi​正占有资源Rj​。
(4) 请求边,一组从Pi​到Rj​的有向弧,表明进程Pi​已经请求资源Rj​,目前正在等待该资源空闲。
        【提示】当资源请求被批准时,请求边可以通过反转弧的方向,从而转换为分配边。请求边指向类别框,而分配边来自框内的特定实例。
        如果资源分配图不包含环路,则系统中不会发生死锁;如果资源分配图包含了环路,且每种资源只有一个实例,那么系统中存在一个死锁;如果资源分配图包含了环路,且每种资源有一个以上的实例,则说明系统中可能出现了死锁。

2. 资源分配图算法

        如果每一种资源只包含该资源的单一实例,则可以通过检查资源分配图中的环路来判断系统是否进入了死锁状态。在这种情况下,不安全的状态可以通过在资源分配图中增加请求边来识别和避免,请求边用虚线表示,它从一个进程指向它将来可能请求的资源。在系统批准资源请求前,必须将所有的请求边添加到资源分配图中。当一个进程提出的请求被批准后,请求边Pi​ -> Rj​被转换为分配边。同样地,当一个资源被释放时,分配边又恢复成一个请求边。当系统检测到系统进入死锁状态时,可以采用死锁解除的策略使相应的进程恢复正常。

        【举例】图 2.30 (b) 中,当进程P1​请求资源R2​时,会出现一个环路,所以该请求不能批准。

        如图 2.31 所示,用简化资源分配图的方法来检测操作系统是否处于死锁状态,过程如下:
        (1) 找到一个进程节点Pi​,其请求边在图中都能立即得到满足;
        (2) 如果存在这样一个Pi​,则删除所有与Pi​相连的边并转到步骤 (1);否则结束简化;
        (3) 如图 2.31 所示,如果所有进程节点在简化后都成为孤立节点,则称该图是完全可简化的;反之,如果该图不能通过任何方法完全简化,则称该图是不可完全简化的。死锁定理表明,系统处于死锁状态,当且仅当资源分配图是不可完全简化的。

2.4.6 死锁解除

        在进行死锁检测后,应当使用一定的方法将陷入死锁进程恢复正常,死锁解除的常见策略如下:
        (1) 系统重启:这是最简单的死锁解除方法,但它的成本最高,因为在重启时,包括死锁进程和非死锁进程在内的所有进程所做的工作都会被浪费。
        (2) 终止所有死锁进程:这是最直接的方法,也是操作系统经常使用的方法,但它的成本也比较高。
        (3) 逐一终止死锁进程,直到死锁不再存在:可以终止资源分配图环中的一个进程打破环路,也可以终止环外的一个进程以满足某个环上进程的需要。这是稍微温和的方法,但是每当一个进程被终止时,都是用死锁检测算法来确定死锁是否依然存在,仍会导致成本增加。
        (4) 抢占资源:操作系统可以临时将某个进程的资源直接转移给其他进程,死锁打破后再送回。
        (5) 退回检查点:如果系统设计者知道死锁可能会发生,就可以为系统提供检查点机制;如果系统提供了设置检查点和重启检查点的机制,可将死锁进程退回到上一个检查点,并在该检查点重启这些进程。从检查点恢复后,原来的死锁可能发生,也可能不发生。

2.4.7 习题精编

1.下列属于操作系统中死锁的表现的是( )。
A. 一个进程被无限期地推迟运行
B. 进程已获得的资源不会被剥夺
C. 若干进程因竞争资源而无限等待其他进程释放其占有的资源
D. 进程数量超过了资源总数

1.【参考答案】C
        【解析】死锁是n个进程 (n≥2) 由于竞争资源或彼此通信造成的永久阻塞,若无外力作用,这些进程都无法继续推进。当一组进程全都被阻塞,且在等待系统中一个只能由该组中某一被阻塞进程才能触发的事件时,该组进程就会陷入死锁状态,A、B、D 描述不准确,故本题选 C。

2.两个进程并发执行,下列说法正确的是( )。

int x, y, z, t, u;
P1(){
    while(1){
        x = 1;
        y = 0;
        if x >= 1 then
            y = x + y;
            z = y + x + 2;
        }
    }

P2(){
    while(1){
        x = 0;
        t = 1;
        if x <= 1 then
            t = t + 12;
            u = t + x;
        }
    }

A. 程序能正确运行,结果唯一
B. 程序不能正确运行,可能发生饥饿现象
C. 程序不能正确运行,结果不确定
D. 程序不能正确运行,可能产生死锁

2.【参考答案】C
【解析】

int x, y, z, t, u;
P1() {
    while(1) {
        x = 1;    //(1)
        y = 0;    //(2)
        if x >= 1 then
            y = x + y; //(3)
            z = y + x + 2; //(4)
    }
}
P2() {
    while(1) {
        x = 0;    //(5)
        t = 1;    //(6)
        if x <= 1 then
            t = t + 12; //(7)
            u = t + x;  //(8)
    }
}

        按照 1,2,3,4,5,6,7,8 的执行顺序,可以得到x=0,y=1,z=4,t=13,u=13的结果;按照 1,5,2,6,3,7,4,8 的执行顺序,可以得到x=0,y=0,z=2,t=13,u=13的结果,答案不唯一,故本题选 C。

3. 某系统中有 4 个并发进程,每个进程需要 4 个相同类型的资源,使得该系统必然不会产生死锁的最小资源数目是( )。
A. 12
B. 13
C. 14
D. 16

3.【参考答案】B
        【解析】极端法:假设每个进程已获得3个同类资源,则只要有可用的资源,总能有一个进程可以获得第4个,然后顺利执行,而不会产生死锁。在极端情况下,12个资源被分给4个进程,每个进程获得3个,是有可能产生死锁的,而再增加一个资源,就必然不会产生死锁,此时的资源数量是13。故本题选 B。

4. 某系统中有 13 台磁带机,被X个进程使用,每个进程最多请求 4 台,则系统中必然不会产生死锁的最大X的值为( )。
A. 2
B. 3
C. 4
D. 5

4.【参考答案】C
        【解析】极端法:假设每个进程已获得3台磁带机,则只要有可用的磁带机,总能有一个进程可以获得第4台,然后顺利执行,而不会产生死锁。当x=4时,发生死锁的极限资源数是4×3=12,所以13个资源则一定不会发生死锁,当x=5时,发生死锁的极限资源数是5×3=15,则需要16个资源才一定不会发生死锁。故本题选 C。

5. 若系统中有 5 台打印机,有多个进程均需要使用 2 台,规定每个进程一次仅允许申请 1 台,则至多允许( )个进程参与竞争,而不会发生死锁。
A. 5
B. 2
C. 3
D. 4

5.【参考答案】D
        【解析】极端法:假设每个进程已获1台打印机,则只要有可用的打印机,总能有一个进程可获得第2台,然后顺利执行。在极端情况下,4台打印机被分给K个进程,每个进程有1台打印机,此时K为4,选 D。

6. 设m为互斥资源A的数量,n为系统中的并发进程数。当n个进程共享m个资源A时,每个进程对资源A的最大需求量为w,则下列情况可能会出现死锁的是( )。
A. m=3,n=3,w=2
B. m=2,n=2,w=1
C. m=4,n=3,w=2
D. m=5,n=2,w=3

6.【参考答案】A
        【解析】极端法:求出每个进程都发生死锁需要的最大资源数,在此基础上,再增加一个资源,则死锁一定不会发生。当n个进程共享m个互斥资源时,每个进程的最大需求是w。则当m≥n×(w−1)+1时,系统一定不发生死锁,显然 B、C、D 对,A 错。

7. 下列情况可能导致死锁的是( )。
I. 有进程一直得不到处理机
II. 进程推进速度过快
III. 进程占有的资源不允许被剥夺,且出现了循环等待现象
IV. 进程并发执行
A. II 和 III
B. III 和 IV
C. 仅 III
D. I、II、III 和 IV

7.【参考答案】B
        【解析】有进程一直得不到处理机属于饥饿现象,I 错误;进程推进速度过快与死锁没有必然关系,II 错误;不可剥夺条件、循环等待条件均属于死锁的必要条件,可能会导致死锁,III 正确;虽然进程并发执行与死锁没有必然关系,但根据死锁的定义,若没有进程并发执行,就不会出现死锁。IV 正确。故本题选 B。

8. 系统中产生死锁的可能原因是( )。
I. 独占资源的分配策略不当
II. 系统资源不足
III. 进程陷入了死循环
IV. 进程的推进顺序不当
A. I 和 IV
B. I 和 III
C. II、III 和 IV
D. I、III 和 IV

8.【参考答案】A
        【解析】死锁的原因有两个,一是竞争有限的系统资源,二是进程推进顺序不当。显然只有 I 和 IV 正确,选 A。

9. 下列关于资源分配图的说法中,不正确的是( )。
A. 资源结点内的圆点表示该资源的实例
B. 各个进程有向边包括进程指向资源类的申请边和资源类指向进程的分配边两类
C. 圆圈结点表示资源类
D. 资源分配图是一个有向图,用于表示某时刻系统资源与进程之间的状态

9.【参考答案】C
        【解析】根据资源分配图的定义,资源结点内的圆点表示该资源的实例,A 对;各个进程有向边包括进程指向资源类的申请边和资源类指向进程的分配边两类,B 对;圆圈结点表示的是进程,C 错;资源分配图是一个有向图,用于表示某时刻系统资源与进程之间的状态,D 对。

10. 解除死锁可以采用的方法有( )。
I. 撤销所有死锁进程
II. 死锁进程退回到上一个检查点
III. 重启系统
IV. 抢夺非死锁进程的资源
A. I、II 和 III
B. I、II 和 IV
C. I 和 IV
D. I、II、III 和 IV

10.【参考答案】A
        【解析】系统重启、撤销所有死锁进程、逐一撤销死锁进程直到死锁解除、退回检查点是解除死锁四个常用方法,因此 I、II、III 正确;抢夺非死锁进程的资源,有可能造成原本正常运行的进程死锁,通常不采用此方法,而是剥夺其它死锁进程的资源,IV 错误,选 D。

11. 下列方法中可以解除死锁的是( )。
A. 资源分配图
B. 撤销进程
C. 一次性请求所有运行所需的资源
D. 银行家算法

11.【参考答案】B
        【解析】资源分配图是死锁检测的工具,A 错误;撤销进程是死锁解除的方法之一,B 正确;一次性请求所有运行所需的资源破坏了死锁产生的请求和保持条件,是死锁预防的方法之一,C 错误;银行家算法是死锁避免的典型方法,D 错误,故本题选 B。

12. 对所有资源进行编号,并要求进程按严格增加 (或减少) 的顺序来请求资源,这是死锁预防的一种方式,破坏了死锁四个必要条件中的( )。
A. 互斥条件
B. 占有并请求条件
C. 不可抢占条件
D. 循环等待条件

12.【参考答案】D
        【解析】循环等待条件指系统发生死锁时,一定存在一条进程请求资源的循环等待链。题干中对所有资源进行编号,并要求进程只按严格增加 (或减少) 的顺序来请求资源,破坏了死锁产生的循环等待条件,选 D。

13. 死锁预防是保证系统不进入死锁状态的一种静态策略,它通过破坏死锁产生的四个必要条件中的一个或多个来预防死锁,下列破坏了死锁产生的 “请求和保持条件” 的是( )。
A. 银行家算法
B. 一次性分配策略
C. 剥夺资源法
D. 有序资源分配法

13.【参考答案】B
        【解析】请求和保持条件指进程已经占用至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占用,此时进程请求阻塞,且不会释放已占用的资源。为所有进程一次性分配其所需的资源破坏了死锁产生的请求和保持条件,选 B。

14. 下列死锁的解决方法中,不属于死锁预防的是( )。
I. 银行家算法
II. 有序资源分配法
III. 一次性分配策略
IV. 对资源分配图进行化简
A. II 和 III
B. I 和 IV
C. 仅 I
D. II、III 和 IV

14.【参考答案】B
        【解析】银行家算法通过对安全状态的判断来决定是否为进程分配资源,I 属于死锁避免的策略;有序资源分配法对所有资源进行编号,并要求进程按严格增加 (或减少) 的顺序来请求资源,破坏了死锁产生的循环等待条件,II 属于死锁预防的策略;一次性分配策略为所有进程一次性分配其所需的资源,破坏了死锁产生的请求和保持条件,III 属于死锁预防的策略;对资源分配图进行化简用于检测资源分配过程中是否有循环等待事件,IV 属于死锁检测的策略,选 B。

15. 死锁的四个必要条件中,绝大多数情况下可以被破坏的有( )。
I. 循环等待条件
II. 互斥条件
III. 请求和保持条件
IV. 不可剥夺条件
A. I、III 和 IV
B. I 和 IV
C. III 和 IV
D. I、II、III 和 IV

15.【参考答案】A
        【解析】SPOOLing 技术可以把独占设备在逻辑上改造成共享设备。但是并非所有的资源都可以改造成可共享使用的资源。且为系统安全,很多地方还必须保护这种互斥性。因此,绝大多数情况下都无法破坏互斥条件。绝大多数情况下,死锁的四个必要条件中,除了互斥条件外,其余都可以被破坏。互斥条件指进程要求对其分配的资源独占使用,即一个资源在一定时间内只能被一个进程占用,如果其他进程请求该资源,只能等到占用该资源的进程用完后释放。系统中资源互斥使用的特性是很有必要的,一旦被破坏,往往会使进程的运行发生错误,所以互斥条件不能被破坏,选 A。

16. 一个进程在获得资源后,只能在资源使用完后主动释放,这是死锁产生的必要条件之一,下列选项中,可以破坏该条件的是( )。
A. 对资源分配图进行化简
B. 一次性请求所有运行所需的资源
C. 资源剥夺法
D. 有序资源分配法

16.【参考答案】C
        【解析】一个进程在获得资源后,只能在使用完资源后自由释放,这属于死锁的必要条件中的不可剥夺条件,可以通过剥夺资源法来破坏,所以本题选 C。

17. 以下关于进程死锁的表述,错误的是( )。
A. 如果每个进程只能同时申请或拥有一个资源,则死锁就不会发生
B. 如果所有资源多个进程都可以无冲突共享访问,则死锁就不会发生
C. 如果所有进程的执行严格区分优先级,则死锁就不会发生
D. 如果进程资源请求之间不存在循环等待,则死锁就不会发生

17.【参考答案】C
        【解析】死锁产生的四个必要条件是互斥条件、请求并保持条件、不可剥夺条件和循环等待条件。A 选项破坏了请求并保持条件,B 选项破坏了互斥条件,D 选项破坏了循环等待条件。对于 A、B、D 选项,死锁均不会发生,对进程区分优先级不能防止死锁发生,选 C。

18. 死锁避免实现的依据是( )。
A. 破坏死锁的必要条件之一
B. 撤销死锁进程
C. 防止系统进入不安全状态
D. 资源分配图

18.【参考答案】C
        【解析】死锁避免是一种动态的死锁解决方法,其核心思路是:对于每次资源申请,系统都要实时判断其是否有死锁风险,如果存在隐患,那么就拒绝此次申请,以避免死锁。因此死锁避免的关键是定义系统安全状态,以及给出判断系统是否处于安全状态的算法,选 C。

19. 关于死锁与安全状态的关系,下列说法正确的是( )。
I. 安全状态一定不发生死锁
II. 不安全状态有可能成为死锁状态
III. 不安全状态一定会变成死锁状态
IV. 不是所有的安全状态都是无死锁的
A. I 和 II
B. I 和 III
C. II 和 IV
D. I、II 和 IV

19.【参考答案】A
        【解析】所有的安全状态都是无死锁的,但不是所有的不安全状态都会导致死锁,I 对,IV 错;不安全状态说明系统有发生死锁的可能,并不意味着死锁必然发生,II 对,III 错,选 A。

20. 系统中有 10 个资源供给 3 个进程P0​、P1​和P2​使用,且它们的最大资源需求量分别是 10 个、3 个和 6 个。假定在时刻t,P0​、P1​和P2​分别拥有 4 个、1 个和 4 个资源。以下正确表述了系统的状态的是( )。
A. 系统处于安全状态
B. 系统处于不是安全状态
C. 系统处于不确定状态
D. 这是一个不可能状态

20.【参考答案】B
        【解析】P0​、P1​和P2​当前的资源需求分别为6个、2个和2个,系统当前的可用资源数为10−4−1−4=1个,不满足任一进程的运行需要,系统处于不安全状态,选 B。

21. 假设具有 4 个进程的进程集合P={P0​,P1​,P2​,P3​},系统中有三类资源A、B、C,某时刻的进程和资源状态如下表所示。请问当x、y、z取下列哪些值时,系统处于安全状态( )。

21.【参考答案】A
【解析】在用银行家算法求解问题的过程中,首先需要计算出需求矩阵:

进程

Need

A

B

C

P0​

0

2

2

P1​

0

0

1

P2​

0

1

2

P3​

0

1

0

        根据需求矩阵和 I、II、III 和 IV 的可用资源数求解安全序列,I 和 IV 没有对应安全序列,故系统未处于安全状态,I 和 IV 错误;能找到 II 的一条安全序列<P1​,P3​,P2​,P0​>和 III 的一条安全序列<P1​,P3​,P2​,P0​>,II 和 III 对,选 A。

22.假设系统有 4 个进程,A、B、C三类资源。某时刻的进程和资源状态如下表所示,下列说法中正确的是( )。

A. 系统不安全
B. 该时刻,系统安全,存在安全序列<P0​,P1​,P2​,P3​>
C. 该时刻,系统安全,存在安全序列<P2​,P3​,P0​,P1​>
D. 该时刻,系统安全,存在安全序列<P2​,P0​,P1​,P3​>

22.【参考答案】D
【解析】在银行家算法计算过程中,首先需要计算出需求矩阵:

进程

Need

A

B

C

P0​

1

1

2

P1​

2

0

0

P2​

0

0

1

P3​

3

1

0

        根据安全性算法可求得安全序列<P2​,P0​,P1​,P3​>,系统处于安全状态,A 错,D 对;无法得出 B 和 C 的安全序列,B,C 错,选 D。

23.系统某一时刻的进程及资源分配情况如下:

系统剩余资源数量=(3,3,2)。根据银行家算法,下列选项正确的是( )。
A. 系统处于不安全状态
B. 系统处于安全状态,可能的安全序列为<P2​,P0​,P4​,P1​,P3​>
C. 系统处于安全状态,可能的安全序列为<P1​,P3​,P0​,P2​,P4​>
D. 系统处于安全状态,可能的安全序列为<P4​,P0​,P1​,P3​,P2​>

23.【参考答案】C
        【解析】当前系统剩余资源数量为(3,3,2),满足P1​和P3​的资源需求。分配给P1​,P1​释放资源后,系统的剩余资源数量为(5,4,2),满足P3​的资源需求,分配给P3​,P3​释放资源后,系统剩余资源数量为(7,5,3),满足P0​的资源需求,分配给P0​,P0​释放资源后,系统剩余资源数量为(7,6,3),满足P2​的资源需求,分配给P2​,P2​释放资源后,系统剩余资源数量为(10,6,5),满足P4​的资源需求,分配给P4​,运行结束,系统没有进入不安全状态,可能的安全序列为<P1​,P3​,P0​,P2​,P4​>,选 C。计算Need矩阵:

24.系统某一时刻的进程数及资源分配情况如下:

系统剩余资源数量=(1,5,2,0)。根据银行家算法,下列选项正确的是 ( )。
A. 系统处于不安全状态
B. 系统处于安全状态,可能的安全序列为<P1​,P3​,P4​,P2​,P5​>
C. 系统处于安全状态,可能的安全序列为<P4​,P1​,P3​,P2​,P5​>
D. 系统处于安全状态,可能的安全序列为<P3​,P5​,P4​,P2​,P1​>

24.【参考答案】C
【解析】计算Need矩阵:

        系统当前剩余的资源数量为(1,5,2,0),满足P4​进程的资源需求。分配给P4​,P4​释放资源后,系统剩余的资源数量为(1,11,5,2),满足P1​、P2​、P3​、P5​的资源需求,可能的安全序列为<P4​,P1​,P3​,P2​,P5​>,选 C。

25. 三个进程A、B、C对某类资源的需求量分别是7个、8个和3个,且目前三个进程已分别得了3个、3个和2个。为保证系统的安全,该系统目前剩余的资源至少是 ( ) 。
A. 1 个
B. 2 个
C. 5 个
D. 10 个

25.【参考答案】B
        【解析】进程的当前需求量 = 资源最大需求 - 已分配资源。进程A当前需要4个资源,进程B当前需要5个资源,进程C当前需要1个资源。当该系统剩余的资源数量为2时,存在安全序列<C,A,B>,可以保证系统安全。当该系统剩余的资源数量为1时,不存在安全序列,无法保证系统安全,选 B。

26. 在避免死锁的银行家算法中,操作系统不必记录的信息是 ( ) 。
A. 系统目前可用资源的数量
B. 每个进程已经获得资源的数量
C. 每个进程已经释放资源的数量
D. 每个进程总共需要资源的数量

26.【参考答案】C
        【解析】银行家算法涉及系统可用资源向量、最大需求矩阵、分配矩阵和需求矩阵,A、B、D 对;每个进程已经释放资源的数量不属于银行家算法记录的信息,C 错。

27. 设有12个同类资源可供四个进程共享,目前剩余资源数为2。现资源分配情况如下:

进程

已占用资源数

最大需求数

本次申请数

P1​

2

4

2

P2​

3

6

3

P3​

4

7

3

P4​

1

4

3

为使系统不致死锁,应先满足 ( ) 的请求。
A. P1​
B. P2​
C. P3​
D. P4​

27.【参考答案】A
        【解析】P1​当前需要的资源数量为2,P2​当前需要的资源数量为3,P3​当前需要的资源数量为3,P4​当前需要的资源数量为3,为保证系统不死锁,应先满足P1​的资源请求,选 A。

28. 死锁检测检查的是 ( ) 。
A. 资源有向图
B. 资源无向图
C. 资源前驱图
D. 资源搜索树

28.【参考答案】A
        【解析】死锁检测算法主要是通过化简资源分配图来检测资源分配过程中是否有循环等待事件存在。资源分配图是有向图,不是无向图,A 对,B 错;资源前驱图和资源搜索树与题干无关,C 和 D 错。选 A。

29. 关于资源分配图,下列说法正确的有 ( ) 。
I. 资源分配图出现了环路,说明一定会产生死锁
II. 资源分配图没有环路,说明一定不会产生死锁
III. 资源分配图没有环路,也可能产生死锁
IV. 每个进程结点至少有一条请求边,说明一定会产生死锁
A. I、II 和 IV
B. II
C. I、III 和 IV
D. I 和 II

29.【参考答案】B
        【解析】资源分配图出现了环路,不一定会产生死锁,I 错误;资源分配图没有环路,无法产生循环等待,不满足死锁产生的必要条件,一定不会产生死锁,II 正确,III 错误;每个进程结点至少有一条请求边与死锁产生没有必然关系,IV 错误,选 II 正确,III 错误;每个进程结点至少有一条请求边与死锁产生没有必然关系,IV 错误,选 B。

30. 死锁定理可以用来 ( ) 。
A. 预防死锁
B. 避免死锁
C. 检测死锁
D. 解除死锁

30.【参考答案】C
        【解析】当且仅当当前状态的进程资源分配图是不可完全简化的,该充分条件称为死锁定理,死锁定理用于死锁检测,选 C。

31. 下列关于死锁避免和死锁检测的说法中,不正确的是 ( ) 。
A. 死锁避免会同一时间请求所有资源,而死锁检测不会
B. 死锁避免需要进程运行所需资源总量信息,而死锁检测不需要
C. 死锁避免不会给可能导致死锁的进程分配资源,而死锁检测会
D. 死锁避免会去评估分配资源的风险,而死锁检测不会

31.【参考答案】A
        【解析】同一时间请求所有资源破坏了死锁产生的请求和保持条件,属于死锁预防的策略,A 错误;死锁避免通过对资源分配的风险评估来决定是否为进程分配资源,需要收集进程运行所需资源总量信息,而死锁检测允许死锁的发生,会给可能导致死锁的进程分配资源,B、C、D 对。

32. 下列关于死锁的说法中,不正确的是 ( ) 。
A. 不安全状态不一定就会死锁
B. 产生死锁的根本原因是系统资源分配不足和进程推进顺序非法
C. 资源的有序分配策略可以破坏死锁的请求和保持条件
D. 采用资源剥夺法可以恢复死锁,还可以通过撤销进程恢复死锁

32.【参考答案】C
        【解析】所有的安全状态都是无死锁的,但不是所有不安全状态都会导致死锁,A 对;死锁产生的根本原因是竞争有限的系统资源和进程推进顺序不当,B 对;资源的有序分配策略破坏了死锁产生的循环等待条件,C 错;资源剥夺法和撤销进程是死锁恢复的方法,D 对。

2.4.8 真题演练

33.【2009】某计算机系统中有8台打印机,由K个进程竞争使用,每个进程最多需要3台打印机。该系统可能会发生死锁的K的最小值是 ( ) 。
A. 2
B. 3
C. 4
D. 5

33.【参考答案】C
        【解析】极端法:假设每个进程已获得 2 台打印机,则只要有可用的打印机,总能有一个进程可以获得第 3 台,然后顺利执行。在极端情况下,8 台打印机被分给 K 个进程,每个进程有 2 台打印机,此时 K 为 4,故本题选 C。

34.【2011】某时刻进程的资源使用情况如下表所示

此时的安全序列是 ( ) 。
A. P1​, P2​, P3​, P4​
B. P1​, P3​, P2​, P4​
C. P1​, P4​, P3​, P2​
D. 不存在

34.【参考答案】D
        【解析】根据银行家算法解题。假设剩余资源分配给P1​,P1​执行完后,可用资源数为(2,2,1),只能满足P4​,A 和 B 错;假设剩余资源分配给P4​,P4​执行完后,可用资源数为(2,2,2),无法满足任何进程的需求,C 错;对于题干中的资源使用情况,无法求得安全序列,选 D。

35.【2012】假设5个进程P0​、P1​、P2​、P3​、P4​共享三类资源R1​、R2​、R3​,这些资源总数分别为18,6,22。T0​时刻的资源分配情况如下表所示,此时存在的一个安全序列是 ( ) 。

进程

已分配资源

资源最大需求

R1​

R2​

R3​

R1​

R2​

R3​

P0​

3

2

3

5

5

10

P1​

4

0

3

5

3

6

P2​

4

0

5

4

0

11

P3​

2

0

4

4

2

5

P4​

3

1

4

4

2

4

A. P0​, P2​, P4​, P1​, P3​
B. P1​, P0​, P3​, P4​, P2​
C. P2​, P1​, P0​, P3​, P4​
D. P3​, P4​, P2​, P1​, P0​

35.【参考答案】D
【解析】求出需求矩阵 Need:

进程

Need

R1​

R2​

R3​

P0​

2

3

7

P1​

1

3

3

P2​

0

0

6

P3​

2

2

1

P4​

1

1

0

求出系统可用资源向量 Available:

Available

R1​

R2​

R3​

2

3

3

通过对 Need 和 Available 的计算可以发现,进程P0​、P2​无法满足运行需求,A 和 C 错;假设先分配资源给P1​,P1​运行完成后Available=(6,3,6),无法供P0​继续运行,B 错误;假设先分配资源给P3​,P3​运行完成后Available=(4,3,7),可满足其他任何进程的需求,选 D。

36.【2013】下列关于银行家算法的叙述中,正确的是 ( ) 。
A. 银行家算法可以预防死锁
B. 当系统处于安全状态时,系统中一定无死锁进程
C. 当系统处于不安全状态时,系统中一定会出现死锁进程
D. 银行家算法破坏了死锁必要条件中的 “请求和保持” 条件

36.【参考答案】B
        【解析】银行家算法可以避免死锁,而不是预防死锁,A 错;所有的安全状态都是无死锁的,但不是所有不安全状态都会导致死锁,B 对,C 错;“破坏请求和保持条件” 是死锁预防的方法,不是银行家算法的方法,D 错。选 B。

37.【2014】某系统有n台互斥使用的同类设备,三个并发进程分别需要3、4、5台设备,可确保系统不发生死锁的设备数n最小为 ( ) 。
A. 9
B. 10
C. 11
D. 12

37.【参考答案】B
        【解析】极端法:假设三个并发进程均只差 1 台设备即可运行,即分别获得 2 台、3 台、4 台设备,共计 9 台设备时,可能发生死锁。而再增加 1 台设备分配给任意进程,即可确保系统不发生死锁,选 B。

38.【2015】若系统 S1 采用死锁避免方法,S2 采用死锁检测方法。下列叙述中,正确的是 ( )
I. S1 会限制用户申请资源的顺序,而 S2 不会
II. S1 需要进程运行所需资源总量信息,而 S2 不需要
III.S1 不会给可能导致死锁的进程分配资源,而 S2 会
A. 仅 I、II
B. 仅 II、III
C. 仅 I、III
D. I、II、III

38.【参考答案】B
        【解析】顺序资源分配法通过限制用户申请资源的顺序来实现死锁预防,而不是死锁避免或死锁检测,I 错误;银行家算法属于死锁避免方法,需要进程运行所需资源总量信息,II 正确;死锁避免方法不会给可能导致死锁的进程分配资源,而死锁检测方法会给可能导致死锁的进程分配资源,III 正确,选 B。

39.【2016】系统中有3个不同的临界资源R1​、R2​和R3​,被4个进程P1​、P2​、P3​及P4​共享。各进程对资源的需求为:P1​申请R1​和R2​,P2​申请R2​和R3​,P3​申请R1​和R3​,P4​申请R2​。若系统出现死锁,则处于死锁状态的进程数至少是 ( ) 。
A. 1
B. 2
C. 3
D. 4

39.【参考答案】C
        【解析】假设把R1​分配给P1​,R2​分配给P2​,R3​分配给P3​,此时,P1​使用了R1​,又申请使用R2​,而R2​正被P2​使用,P2​又申请使用R3​,而R3​正被P3​使用,P3​又申请使用R1​,R1​正被P1​使用,系统发生了死锁。利用资源分配图枚举各种情况可以得出,两个进程在本题条件下无法发生死锁(无法构成循环等待),处于死锁状态的进程数至少是 3 个,故本题选 C。

40.【2018】假设系统中有4个同类资源,进程P1​、P2​和P3​需要的资源数分别为4、3和1,P1​、P2​和P3​已申请到的资源数分别为2、1和0,则执行安全性检测算法的结果是 ( ) 。
A. 不存在安全序列,系统处于不安全状态
B. 存在多个安全序列,系统处于安全状态
C. 存在唯一安全序列P3​,P1​,P2​,系统处于安全状态
D. 存在唯一安全序列P3​,P2​,P1​,系统处于安全状态

40.【参考答案】A
        【解析】系统中有 4 个同类资源,已分配 3 个资源,剩余 1 个可用资源,而P1​、P2​和P3​还需要的资源数分别为 2、2、1,只能将剩余的 1 个资源分配给P3​,P3​执行完成后将其释放,系统中剩余可用资源仍然只有 1 个,无法满足P1​和P2​的运行需要,因此不存在安全序列,选 A。

41.【2019】下列关于死锁的叙述中,正确的是 ( ) 。
I. 可以通过剥夺进程资源解除死锁
II. 死锁的预防方法能确保系统不发生死锁
III. 银行家算法可以判断系统是否处于死锁状态
IV. 当系统出现死锁时,必然有两个或两个以上的进程处于阻塞态
A. 仅 II、III
B. 仅 I、II、IV
C. 仅 I、II、III
D. 仅 I、III、IV

41.【参考答案】B
        【解析】剥夺进程资源属于死锁解除的方法,I 对;死锁预防通过破坏死锁必要条件,防止死锁的发生,II 对;银行家算法属于死锁避免的方法,可以判断当前是否处于安全状态,但不能判断系统是否处于死锁状态,III 错;单个进程处于阻塞态无法形成死锁,不可能满足循环等待条件,因此当系统出现死锁时,必有两个及以上的进程处于阻塞态,IV 对,选 B。

42.【2020】某系统中有A、B两类资源各6个,t时刻资源分配及需求情况如下表所示。

进程

A已分配数量

B已分配数量

A需求总量

B需求总量

P1​

2

3

4

4

P2​

2

1

3

1

P3​

1

2

3

4

t时刻安全性检测结果是 ( ) 。
A. 存在安全序列P1​,P2​,P3​
B. 存在安全序列P2​,P1​,P3​
C. 存在安全序列P2​,P3​,P1​
D. 不存在安全序列

42.【参考答案】B
【解析】计算 Need 矩阵:

        A、B 资源共有 6 个,A 已分配 5 个,B 已分配 6 个,Available=(1,0),只能满足P2​的运行需求,A 错;P2​运行完毕并释放资源后,Available=(3,1),只能满足P1​的运行需求,P1​运行完毕并释放资源后,Available=(5,4),满足P3​的运行需求,得到唯一安全序列:P2​,P1​,P3​,C、D 错,选 B。

43.【2021】若系统中有n(n≥2)个进程,每个进程均需要使用某类临界资源2个,则系统不会发生死锁所需的该类资源总数至少是 ( ) 。
A. 2
B. n
C. n+1
D. 2n

43.【参考答案】C
        【解析】考虑极端情况,当临界资源数为n时,每个进程都拥有一个临界资源并等待另一个资源,会发生死锁。当临界资源数为n+1时,则n个进程中至少有一个进程可以获得 2 个临界资源,顺利运行完后释放自己的临界资源,使得其他进程也能顺利运行,不会产生死锁,系统不会发生死锁所需的该类资源总数至少是n+1,选 C。

2.5 章末总结

2.5.1 本章知识点提炼

        本章内容较多,主要包括了进程与线程、处理器调度与上下文切换、进程同步和死锁四节内容,这里进行简单地回顾。
        进程与线程一节,首先引入并介绍了操作系统中重要概念:进程。进程区别于程序,是一个动态的概念,用以描述一个程序在内存中运行的整个过程,包括各种细节和特点。
        本节中的各个小节,都在围绕着进程的各个特点讲解(线程同理),进程具有动态性、并发性、独立性和异步性四个特性,有 5 种状态(其中 3 种为基本状态),还可以扩展到 7 种状态。这些状态构成了进程的整个生命周期,进程会在这些状态间转换。进程由代码段、数据段和 PCB 构成,PCB 使得进程成为独立运行的单位,PCB 中存储了进程管理与调度的重要信息,主要包含四类:各类标识符、处理器状态信息、处理器调度信息和进程控制信息。
        一个进程从创建到结束,以及中间经历的各种状态转换,称为进程的控制,一般都有它的包现,包括了进程的创建、进程的终止、进程的阻塞与唤醒以及进程切换。每一个原语都有它的包含步骤、执行场合。线程的提出是对进程的进一步细分,进一步提高并发性和效率。线程的组织与控制和进程类似,可以类比学习。线程部分一个重要知识点是线程的两种实现:用户级线程和内核级线程。请注意它们各自的优缺点,以及两者的组合实现。进程通信小节,简单介绍了四种通信方式:信号量、共享存储机制、消息传递机制和管道。
        处理机调度与上下文切换一节,围绕着调度这一行为展开。调度的基本概念,讨论了为什么需要调度:简单来说就是计算机资源的相对有限。调度的时机,列举了一些调度的发生场合。调度的方式,讨论了抢占式调度和非抢占式调度的概念。如何判断调度算法是否满足了用户的需求呢?在调度算法的目标小节,讨论了在各种应用场景下,用户偏向哪些指标。在明确了调度的各种概念后,本章介绍了 7 种调度算法,这些调度算法非常重要,考试对于本节知识点的考查一般都会结合这些调度算法,除了多级队列调度算法以外,其他调度算法都需要熟练掌握,确保对具体题目可以正确分析调度过程和一些重要指标。
        进程同步一节,首先引入了临界资源和临界区的概念,临界资源一次只允许一个进程使用,需实现互斥访问,而临界区是进程中访问临界资源的那一部分代码。同步和互斥是进程之间相互协作和相互竞争资源的两种制约方式,确保进程能够有序且正确地运行,其中空闲让进、忙则等待、有限等待、让权等待是所有同步机制应遵循的准则。对于临界区互斥的软件同步机制,单标志法违背了 “空闲让进” 准则,双标志先检查法违背了 “忙则等待” 准则,双标志后检查法违背了 “有限等待” 准则,Peterson 算法违背了 “让权等待” 准则。对于临界区互斥的硬件同步机制,关中断法、TSL 指令、Swap 指令均不满足 “让权等待” 准则,可能会导致死锁或饥饿,难以应用于复杂的多进程同步问题。
        信号量是解决进程同步和互斥问题的更为有效的工具,信号量机制通过 P/V 操作对信号量进行处理,它是 408 统考的综合应用题的高频考点。在使用信号量机制解决同步和互斥问题时,首先要仔细分析问题,确定进程间的同步协作关系和临界资源的互斥使用关系,为每个同步和互斥关系定义相应的信号量,并确定其初始值。接下来需要确定什么进程在什么时候对什么信号量进行了 P/V 操作:互斥信号量的 P/V 操作通常在同一进程中成对出现;而同步信号量的 P/V 操作一般在两个同步进程之间交替进行,即一个进程执行 P 操作,另一个进程执行 V 操作。在编写完程序的临界区后,需反复推敲不同进程之间所有可能的执行顺序,确保不会出现所有进程都被阻塞而无法向前推进的情况。
        死锁一节,讨论的主要问题是操作系统对死锁的处理。该小节首先阐述了死锁的定义和原因,它的产生必须同时满足四个必要条件。死锁预防采取破坏四个必要条件中的一个或多个来防止死锁的发生;死锁避免以银行家算法为代表,对于请求资源分配的进程,它通过一系列计算来判断分配后系统是否处于安全状态,来决定资源是否分配给相应进程;但死锁预防和死锁避免有约束严格、灵活性不足等缺点,因此许多系统允许死锁的发生,并通过检测算法进行检测,并使用一定的方法将陷入死锁的进程恢复正常。

2.5.2 思考题解析

1.什么是进程,有什么特点?是否可以举出一个实际的例子?
        进程是程序的一次执行过程,是正在计算机上运行的程序实例。进程是一个动态的过程,和一个静态的程序相对应。进程主要有 4 个特性:动态性、并发性、独立性和异步性。生活中处处都有进程的相关例子。例如我们使用的个人电脑(PC)上,从开机开始就存在着多个进程。点击微信的启动图标,一个微信进程就开始运行,点击退出选项,微信进程就消亡并由操作系统回收之前为其分配的各种资源,这体现了进程的动态性。微信可以支持多开,即登陆多个账号,用户可以分别使用两个账号进行聊天,这种情况下就是开启了两个微信进程,体现了进程的并发性。如果该 PC 只有一个处理器,那么当用户用第一个账号聊天时,第二个账号对应的进程就会处于非执行状态,这体现了进程的间断性。因为第一个微信进程可能对一个文件进行写入,则第二个微信进程在读取相同文件时,会受到第一个进程是否在写文件这一事件的影响,这体现了进程的失去封闭性。当别人发来一条微信,如果此时微信进程恰好在运行,它就会立即提示消息,而如果不巧此时微信进程正在阻塞,那么微信进程提示消息的行为就会延后,这体现出了进程的不可再现性(正常接收消息或延迟接收消息是不可再现的)。操作系统中,进程是资源分配的基本单位,一个进程是能够独立运行的,这体现出了进程的独立性。最后,因为多个微信进程的并发执行,用户也不能保证一个微信进程何时才能提示消息,这体现了进程的异步性。
        在系统地学习过进程相关知识以后,考生可以有意识地从生活中挖掘进程相关的现象并尝试做出解释。同时也希望考生可以意识到:虽然书中对进程的讲解主要在于概念层面,但操作系统是一门实践性很强的学科。学会了进程的基础概念后,考生有能力去研究不同的操作系统不同的底层实现。

2.一个进程由哪些部分组成,为什么要提出进程这一概念?
        一个进程由代码段、数据段和进程控制块组成。代码段和数据段对应程序,而进程控制块则记录了一个进程运行过程中的各种信息。回忆 PCB 中记录的信息:处理器状态信息记录了进程某个时刻处理器上寄存器的内容;处理器调度信息记录了进程当前状态等一系列信息,因此我们称进程是一个动态的概念,是对进程在内存中运行过程的刻画。
        为什么需要进程这一概念?在学习了进程的相关概念以及和程序的区别后,考生应该意识到程序一词难以刻画出程序在运行过程中的种种特点。因此,设计者如果希望各个作业可以在多道程序环境下并发执行,就必须对这些能够并发的作业进行形式化的描述和管理,制定有效的规则。例如,进程的状态是对进程生命周期的描述,为了清晰地分析系统中进程的各种状态,设计者提出了进程的五状态模型,并根据这些理论使用代码具体实现了某些操作系统。

3.什么是处理器调度?
        在计算机科学中,调度指在计算机上分配工作所需资源的方法。这里所指的资源包括虚拟资源和硬件资源:虚拟资源可以是线程等,硬件资源可以是处理器等。因此,考生除了在本章学习到了处理器调度相关的知识,还将会在之后学习到更多调度的知识与算法。处理机调度,就是指设置一个专门的进程(调度器)使用合理的调度算法将处理器资源分配给所有申请该资源的进程。
        在本章中,我们还介绍了中级调度,考生可以仿照上文尝试解释什么是中级调度。中级调度是指将进程换入或换出内存的方法,中级调度分配的资源是内存空间,进行中级调度是因为内存中的进程数过多,进程对内存资源存在竞争。

4.为什么要进行处理器调度?
        调度的原因在于竞争:因为处理器资源稀缺,必须决定所有申请处理器资源的进程谁先使用谁后使用。如果操作系统有着充足的处理器资源,能够满足每一个进程,那么也就没有必要考虑处理器调度。
        如果一个计算机中只有一个处理器却不实现处理器调度功能,会出现什么问题?进程会依次执行,在一个进程的执行过程中,即使它因为某些原因发生了阻塞,也会继续占据处理器资源,这让本就 “稀缺” 的处理器资源利用效率更加低下。在实现处理机调度后,当一个进程阻塞时,会让出处理器资源给别的进程使用,这实现了进程的并发执行,提高了资源的利用率,增大了系统吞吐量。

5.什么是进程同步?为什么要进行进程同步?
        进程同步指的是在多道程序环境下,并发执行的进程之间存在的相互协作、相互等待、相互竞争资源的关系。进程同步通过一定的措施来妥善管理多个进程合理地向前推进,并保证程序执行的可再现性。
        为什么要进行进程同步呢?在多道程序环境下,进程与进程之间并不是无联系的,它们之间可能存在某种协作关系:某个进程完成了某项操作后,另一个进程才能在此基础上继续运行。不同的进程也可能对同一个变量进行操作,由于处理机对进程调度顺序的不同,会导致不同进程对同一变量操作顺序的不同,最终影响到执行结果。为了防止多个进程运行时出现混乱的局面,保证每个进程的运行结果是确定的、程序的执行是可再现的,操作系统引入了 “进程同步” 机制。假如系统中只有一个进程,那么进程同步也就失去了意义。

6.什么是信号量?信号量有哪些种类?
        信号量是一种用来解决多道程序系统中的同步和互斥问题的工具,用于为一组合作进程提供可靠的支持。信号量的基本原理是在几个进程之间使用简单的信号来实现同步,在这个过程中,一个进程可以被阻塞在某个特定的位置,直到它收到一个特殊的信号,方可退出阻塞状态。
        408 统考涉及的信号量有整型信号量和记录型信号量两种。整型信号量把信号量定义为一个用于表示资源数目的整数 S;记录型信号量在整型信号量的基础上,增加了一个被阻塞进程的队列,用于链接所有等待相应资源的进程。

7.什么是死锁?死锁在生活中有哪些例子?
        死锁是n个进程(n≥2)由于竞争资源或彼此通信造成的永久阻塞,若无外力作用,这些进程都无法继续推进。在一个十字路口,四辆车从四个方向同时到达十字路口,假设每辆车都没有谦让其他车,就会由于行进方向的交汇而不得不停下来,无法继续前进,这就产生了交通死锁。假设你摔坏了手机,不得不出门购买一台新手机,但是手机摔坏了意味着健康码无法使用,而进入商场购买新手机又意味着你需要出示健康码,若无外力作用,你将无法从商场购买到新手机,这也产生了一个死锁。

8.为什么会出现死锁?
        死锁的出现是因为n个进程(n≥2)争夺有限的资源,或进程的推进顺序不当。进程争夺的 “资源” 可以是不可抢占性资源,也可以是可消耗性资源,系统中可用的不可抢占性资源的数量不足以满足几个进程的运行需要,就有可能导致运行中的进程出现死锁。在进程运行过程中,请求和释放资源的不正确顺序也会导致死锁的发生。
        死锁的发生意味着互斥条件、请求和保持条件、不可剥夺条件和循环等待条件这四个必要条件同时被满足。破坏以上四个必要条件中的任何一个,死锁就一定不可能发生。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值