操作系统学习笔记 第2章

第 2 章 进程与线程

【考纲内容】

(一)进程与线程

        1.进程与线程的基本概念;进程 / 线程的状态与转换

        2.线程的实现:内核支持的线程,线程库支持的线程

        3.进程与线程的组织与控制

        4.进程间通信:共享内存,消息传递,管道,信号

(二)CPU 调度与上下文切换

        1.调度的基本概念;调度的目标

        2.调度的实现:调度器 / 调度程序(scheduler),调度的时机与调度方式(抢占式 / 非抢占式),闲逛进程,内核级线程与用户级线程调度

        3.CPU 调度算法

        4.多处理机调度

        5.上下文及其切换机制

(三)同步与互斥

        1.同步与互斥的基本概念

        2.基本的实现方法:软件方法;硬件方法

        3.锁;信号量;条件变量

        4.经典同步问题:生产者 - 消费者问题,读者 - 写者问题;哲学家进餐问题

(四)死锁

        1.死锁的基本概念;死锁预防

        2.死锁避免;死锁检测和解除

【复习提示】

        进程管理是操作系统的核心,也是每年必考的重点。其中,进程的概念、进程调度、信号量机制实现同步和互斥、进程死锁等更是重中之重,必须深入掌握。需要注意的是,除选择题外,本章还容易出综合题,其中信号量机制实现同步和互斥、进程调度算法和死锁等都可能命制综合题,如利用信号量进行进程同步就在往年的统考中频繁出现。

2.1 进程与线程

在学习本节时,请读者思考以下问题:

1.为什么要引入进程?

2.什么是进程?进程由什么组成?

3.进程是如何解决问题的?

        希望读者带着上述问题去学习本节内容,并在学习的过程中多思考,从而更深入地理解本节内容。进程本身是一个比较抽象的概念,它不是实物,看不见、摸不着,初学者在理解进程概念时存在一定困难,在介绍完进程的相关知识后,我们会用比较直观的例子帮助大家理解。

2.1.1 进程的概念和特征

1.进程的概念
        在多道程序环境下,允许多个程序并发执行,此时它们将失去封闭性,并具有间断性及不可再现性的特征。为此引入了进程(Process)的概念,以便更好地描述和控制程序的并发执行,实现操作系统的并发性和共享性(最基本的两个特性)

        为了使参与并发执行的每个程序(含数据)都能独立地运行,必须为之配置一个专门的数据结构,称为进程控制块(Process Control Block,PCB)。系统利用 PCB 来描述进程的基本情况和运行状态,进而控制和管理进程。相应地,由程序段、相关数据段和 PCB 三部分构成了进程实体(也称进程映像)。所谓创建进程,就是创建进程的 PCB;而撤销进程,就是撤销进程的 PCB。

从不同的角度,进程可以有不同的定义,比较典型的定义有:

1)进程是一个正在执行程序的实例。

2)进程是一个程序及其数据从磁盘加载到内存后,在 CPU 上的执行过程。

3)进程是一个具有独立功能的程序在一个数据集上运行的过程。

        引入进程实体的概念后,我们可将传统操作系统中的进程定义为:“进程是进程实体的运行过程,是系统进行资源分配和调度的一个独立单位。”

        读者要准确理解这里说的系统资源。它指 CPU、存储器和其他设备服务于某个进程的 “时间”,例如将 CPU 资源理解为 CPU 的时间片才是准确的。因为进程是这些资源分配和调度的独立单位,即 “时间片” 分配的独立单位,这就决定了进程一定是一个动态的、过程性的概念。

2.进程的特征
        进程是由多道程序的并发执行而引出的,它和程序是两个截然不同的概念。程序是静态的,进程是动态的,进程的基本特征是对比单个程序的顺序执行提出的。

        1)动态性:进程是程序的一次执行,它有着创建、活动、暂停、终止等过程,具有一定的生命周期,是动态地产生、变化和消亡的。动态性是进程最基本的特征。

         2)并发性:指多个进程同存于内存中,能在一段时间内同时运行。引入进程的目的就是使进程能和其他进程并发执行。并发性是进程的重要特征,也是操作系统的重要特征。

         3)独立性:指进程是一个能独立运行、独立获得资源和独立接受调度的基本单位。凡未建立 PCB 的程序,都不能作为一个独立的单位参与运行。

        4)异步性:由于进程的相互制约,使得进程按各自独立的、不可预知的速度向前推进。异步性会导致执行结果的不可再现性,为此在操作系统中必须配置相应的进程同步机制。

        通常不会直接考查进程有什么特性,所以读者对上面的 4 个特性不必记忆,只求理解。

2.1.2 进程的组成

        进程是一个独立的运行单位,也是操作系统进行资源分配和调度的基本单位。它由以下三部分组成,其中最核心的是进程控制块(PCB)。

1.进程控制块
        进程创建时,操作系统为它新建一个 PCB,该结构之后常驻内存,任意时刻都可以存取,并在进程结束时删除。PCB 是进程实体的一部分,是进程存在的唯一标志。

        进程执行时,系统通过其 PCB 了解进程的现行状态信息,以便操作系统对其进行控制和管理;进程结束时,系统收回其 PCB,该进程随之消亡。

        当操作系统希望调度某个进程运行时,要从该进程的 PCB 中查出其现行状态及优先级;在调度到某个进程后,要根据其 PCB 中所保存的 CPU 状态信息,设置该进程恢复运行的现场,并根据其 PCB 中的程序和数据的内存始址,找到其程序和数据;进程在运行过程中,当需要和与之合作的进程实现同步、通信或访问文件时,也需要访问 PCB;当进程由于某种原因暂停运行时,又需将其断点的 CPU 环境保存在 PCB 中。可见,在进程的整个生命期中,系统总是通过 PCB 对进程进行控制的,亦即系统唯有通过进程的 PCB 才能感知到该进程的存在。

        表 2.1 是一个 PCB 的实例。PCB 主要包括进程描述信息、进程控制和管理信息、资源分配清单和 CPU 相关信息等。各部分的主要说明如下:

        1)进程描述信息:进程标识符标志各个进程,每个进程都有一个唯一的标识号。用户标识符指进程所归属的用户,用户标识符主要为共享和保护服务。

        2)进程控制和管理信息:进程当前状态描述进程的状态信息,作为 CPU 分配调度的依据。进程优先级描述进程抢占 CPU 的优先级,优先级高的进程可优先获得 CPU。

        3)资源分配清单:用于说明有关内存地址空间或虚拟地址空间的状况,所打开文件的列表和所使用的输入 / 输出设备信息。

        4)处理机相关信息:也称 CPU 上下文,主要指 CPU 中各寄存器的值。当进程处于执行态时,CPU 的许多信息都在寄存器中。当进程被切换时,CPU 状态信息都必须保存在相应的 PCB 中,以便在该进程重新执行时,能从断点继续执行。

        在一个系统中,通常存在着许多进程的 PCB,有的处于就绪态,有的处于阻塞态,而且阻塞的原因各不相同。为了方便进程的调度和管理,需要将各个进程的 PCB 用适当的方法组织起来。目前,常用的组织方式有链接方式和索引方式两种。链接方式将同一状态的 PCB 链接成一个队列,不同状态对应不同的队列,也可将处于阻塞态的进程的 PCB,根据其阻塞原因的不同,排成多个阻塞队列。索引方式将同一状态的进程组织在一个索引表中,索引表的表项指向相应的 PCB,不同状态对应不同的索引表,如就绪索引表和阻塞索引表等。

2.程序段
        程序段就是能被进程调度程序调度到 CPU 执行的程序代码段。注意,程序可被多个进程共享,即多个进程可以运行同一个程序。

3.数据段
        一个进程的数据段,可以是进程对应的程序加工处理的原始数据,也可以是程序执行时产生的中间或最终结果。

2.1.3 进程的状态与转换

        进程在其生命周期内,由于系统中各个进程之间的相互制约及系统的运行环境的变化,使得进程的状态也在不断地发生变化。通常进程有以下 5 种状态,前 3 种是进程的基本状态。

1.运行态:进程正在 CPU 上运行。在单 CPU 中,每个时刻只有一个进程处于运行态。

2.就绪态:进程获得了除 CPU 外的一切所需资源,一旦得到 CPU,便可立即运行。系统中处于就绪态的进程可能有多个,通常将它们排成一个队列,称为就绪队列。

命题追踪 执行中断处理程序时进程的状态(2023)

3.阻塞态:也称等待态。进程正在等待某一事件而暂停运行,如等待某个资源可用(不包括 CPU)或等待 I/O 完成。即使 CPU 空闲,该进程也不能运行。系统通常将处于阻塞态的进程也排成一个队列,甚至根据阻塞原因的不同,设置多个阻塞队列。

4.创建态:进程正在被创建,尚未转到就绪态。创建进程需要多个步骤:首先申请一个空白 PCB,并向 PCB 中填写用于控制和管理进程的信息;然后为该进程分配运行时所必须的资源;最后将该进程转入就绪态并插入就绪队列。但是,若进程所需的资源尚不能得到满足,如内存不足,则创建工作尚未完成,进程此时所处的状态称为创建态。

5.终止态:进程正从系统中消失,可能是进程正常结束或其他原因退出运行。进程需要结束运行时,系统首先将该进程置为终止态,然后进一步处理资源释放和回收等工作。

        区分就绪态和阻塞态:就绪态是指进程仅缺少 CPU,只要获得 CPU 就立即运行;而阻塞态是指进程需要其他资源(除了 CPU)或等待某一事件。之所以将 CPU 和其他资源分开,是因为在分时系统的时间片轮转机制中,每个进程分到的时间片是若干毫秒。也就是说,进程得到 CPU 的时间很短且非常频繁,进程在运行过程中实际上是频繁地转换到就绪态的;而其他资源(如外设)的使用和分配或某一事件的发生(如 I/O 完成)对应的时间相对来说很长,进程转换到阻塞态的次数也相对较少。这样来看,就绪态和阻塞态是进程生命周期中两个完全不同的状态。

命题追踪 引起进程状态转换的事件(2014、2015、2018、2023)

图 2.1 说明了 5 种进程状态的转换,而 3 种基本状态之间的转换如下:

·就绪态→运行态:处于就绪态的进程被调度后,获得 CPU 资源(分派 CPU 的时间片),于是进程由就绪态转换为运行态。

·运行态→就绪态:处于运行态的进程在时间片用完后,不得不让出 CPU,从而进程由运行态转换为就绪态。此外,在可剥夺的操作系统中,当有更高优先级的进程就绪时,调度程序将正在执行的进程转换为就绪态,让更高优先级的进程执行。

·运行态→阻塞态:进程请求某一资源(如外设)的使用和分配或等待某一事件的发生(如 I/O 操作的完成)时,它就从运行态转换为阻塞态。进程以系统调用的形式请求操作系统提供服务,这是一种特殊的、由运行用户态程序调用操作系统内核过程的形式。

·阻塞态→就绪态:进程等待的事件到来时,如 I/O 操作完成或中断结束时,中断处理程序必须将相应进程的状态由阻塞态转换为就绪态。

        需要注意的是,一个进程从运行态变为阻塞态是主动的行为,而从阻塞态变为就绪态是被动的行为,需要其他相关进程的协助

2.1.4 进程控制

        进程控制的主要功能是对系统中的所有进程实施有效的管理,它具有创建新进程、撤销已有进程、实现进程状态转换等功能。在操作系统中,一般将进程控制用的程序段称为原语,原语的特点是执行期间不允许中断,它是一个不可分割的基本单位。

1.进程的创建

命题追踪 父进程与子进程的关系和特点(2020、2024)

        允许一个进程创建另一个进程,此时创建者称为父进程,被创建的进程称为子进程。子进程可以继承父进程所拥有的资源。当子进程终止时,应将其从父进程那里获得的资源还给父进程。

命题追踪 导致创建进程的操作(2010)

        在操作系统中,终端用户登录系统、作业调度、系统提供服务、用户程序的应用请求等都会引起进程的创建。

命题追踪 创建新进程时的操作(2021)

i.为新进程分配一个唯一的进程标识号,并申请一个空白 PCB(PCB 是有限的)。若 PCB 申请失败,则创建失败。

ii.为进程分配其运行所需的资源,如内存、文件、I/O 设备和 CPU 时间等(在 PCB 中体现)。这些资源或从操作系统获得,或仅从其父进程获得。若资源不足(如内存),则并不是创建失败,而是处于创建态,等待内存资源。

iii.初始化 PCB,主要包括初始化标志信息、初始化 CPU 状态信息和初始化 CPU 控制信息,以及设置进程的优先级等。

iv.若进程就绪队列能够接纳新进程,则将新进程插入就绪队列,等待被调度运行。

2.进程的终止

引起进程终止的事件:

①正常结束,表示进程的任务已完成并准备退出运行。

②异常结束,表示进程在运行时,发生了某种异常事件,使程序无法继续运行,如存储区越界、保护错、非法指令、特权指令错、运行超时、算术运算错、I/O 故障等。

③外界干预,指进程应外界的请求而终止运行,如操作员或操作系统干预、父进程请求和父进程终止。

命题追踪 终止进程时的操作(2024)

i.根据被终止进程的标识符,检索出该进程的 PCB,从中读出该进程的状态。

ii.若被终止进程处于运行状态,立即终止该进程的执行,将 CPU 资源分配给其他进程。

iii.若该进程还有子孙进程,则通常需将其所有子孙进程终止(有些系统无此要求)。

iv.将该进程所拥有的全部资源,或归还给其父进程,或归还给操作系统。

v.将该 PCB 从所在队列(链表)中删除。

        有些系统不允许子进程在父进程终止的情况下存在,对于这类系统,若一个进程终止,则它的所有子进程也终止,这种现象称为级联终止。然而,不是所有操作系统都是这么设计的。
3. 进程的阻塞和唤醒

命题追踪 I/O事件阻塞或唤醒进程的过程(2023)

命题追踪 进程阻塞的事件与时机(2018、2022、2023)

        正在执行的进程,由于期待的某些事件未发生,如请求系统资源失败、等待某种操作的完成、新数据尚未到达或无新任务可做等,进程便通过调用阻塞原语(Block),使自己由运行态变为阻塞态。可见,阻塞是进程自身的一种主动行为,也因此只有处于运行态的进程(获得 CPU),才可能将其转为阻塞态。阻塞原语的执行过程如下:

i.找到将要被阻塞进程的标识号(PID)对应的 PCB。

ii.若该进程为运行态,则保护其现场,将其状态转为阻塞态,停止运行。

iii.将该 PCB 插入相应事件的等待队列,将 CPU 资源调度给其他就绪进程。

命题追踪 进程唤醒的事件与时机(2014、2019)

        当被阻塞进程所期待的事件出现时,如它所期待的 I/O 操作已完成或其所期待的数据已到达,由有关进程(比如,释放该 I/O 设备的进程,或提供数据的进程)调用唤醒原语(Wakeup),将等待该事件的进程唤醒。唤醒原语的执行过程如下:

i.在该事件的等待队列中找到相应进程的 PCB。

ii.将其从等待队列中移出,并置其状态为就绪态。

iii.将该 PCB 插入就绪队列,等待调度程序调度。

        应当注意,Block 原语和 Wakeup 原语是一对作用刚好相反的原语,必须成对使用。若在某个进程中调用了 Block 原语,则必须在与之合作的或其他相关的进程中安排一条相应的 Wakeup 原语,以便唤醒阻塞进程;否则,阻塞进程将因不能被唤醒而永久地处于阻塞态。

2.1.5 进程的通信

        进程通信是指进程之间的信息交换。PV 操作(见 2.3 节)是低级通信方式,高级通信方式是指以较高的效率传输大量数据的通信方式。高级通信方法主要有以下三类。

1.共享存储
        在通信的进程之间存在一块可直接访问的共享空间,通过对这片共享空间进行读 / 写操作实现进程之间的信息交换,如图2.2所示。在对共享空间进行读 / 写操作时,需要使用同步互斥工具(如 P 操作、V 操作)对共享空间的读 / 写进行控制。共享存储又分为两种:低级方式的共享是基于数据结构的共享;高级方式的共享则是基于存储区的共享。操作系统只负责为通信进程提供可共享使用的存储空间和同步互斥工具,而数据交换则由用户自己安排读 / 写指令完成。

        注意,进程空间一般都是独立的,进程运行期间一般不能访问其他进程的空间,想让两个进程共享空间,必须通过特殊的系统调用实现,而进程内的线程是自然共享进程空间的。简单理解就是,甲和乙中间有一个大布袋,甲和乙交换物品是通过大布袋进行的,甲将物品放在大布袋里,乙拿走。但乙不能直接到甲的手中拿东西,甲也不能直接到乙的手中拿东西。
2. 消息传递
        若通信的进程之间不存在可直接访问的共享空间,则必须利用操作系统提供的消息传递方法实现进程通信。在消息传递系统中,进程间的数据交换以格式化的消息(Message)为单位。进程通过操作系统提供的发送消息和接收消息两个原语进行数据交换。这种方式隐藏了通信实现细节,使通信过程对用户透明,简化了通信程序的设计,是当前应用最广泛的进程间通信机制。在微内核操作系统中,微内核与服务器之间的通信就采用了消息传递机制。该机制能很好地支持多 CPU 系统、分布式系统和计算机网络,因此也成为这些领域最主要的通信工具。

1)直接通信方式:发送进程直接将消息发送给接收进程,并将它挂在接收进程的消息缓冲队列上,接收进程从消息缓冲队列中取得消息,如图2.3所示。

2)间接通信方式:发送进程将消息发送到某个中间实体,接收进程从中间实体取得消息。这种中间实体一般称为信箱。该通信方式广泛应用于计算机网络中。简单理解就是,甲要将某些事情告诉乙,就要写信,然后通过邮差送给乙。直接通信就是邮差将信直接送到乙的手上;间接通信就是乙家门口有一个邮箱,邮差将信放到邮箱里。

3.管道通信

命题追踪 管道通信的特点(2014)

        管道是一个特殊的共享文件,也称 pipe 文件,数据在管道中是先进先出的。管道通信允许两个进程按生产者 - 消费者方式进行通信(见图2.4),只要管道不满,写进程就能向管道的一端写入数据;只要管道非空,读进程就能从管道的一端读出数据。为了协调双方的通信,管道机制必须提供三方面的协调能力:①互斥,指当一个进程对管道进行读 / 写操作时,其他进程必须等待。②同步,指写进程向管道写入一定数量的数据后,写进程阻塞,直到读进程取走数据后再将它唤醒;读进程将管道中的数据取空后,读进程阻塞,直到写进程将数据写入管道后才将其唤醒。③确定对方的存在。

        在 Linux 中,管道是一种使用非常频繁的通信机制。从本质上说,管道也是一种文件,但它又和一般的文件有所不同,管道可以克服使用文件进行通信的两个问题,具体表现如下:

        1)限制管道的大小。管道文件是一个固定大小的缓冲区,在 Linux 中该缓冲区的大小为 4KB,这使得它的大小不像普通文件那样不加检验地增长。使用单个固定缓冲区也会带来问题,比如在写管道时可能变满,这种情况发生时,随后对管道的 write () 调用将默认地被阻塞,等待某些数据被读取,以便腾出足够的空间供 write () 调用。

        2)读进程也可能工作得比写进程快。当管道内的数据已被读取时,管道变空。当这种情况发生时,一个随后的 read () 调用将被阻塞,等待某些数据的写入。

        管道只能由创建进程所访问,当父进程创建一个管道后,管道是一种特殊文件,子进程会继承父进程的打开文件,因此子进程也继承父进程的管道,并可用它来与父进程进行通信。

        注意:从管道读数据是一次性操作,数据一旦被读取,就释放空间以便写更多数据。普通管道只允许单向通信,若要实现两个进程双向通信,则需要定义两个管道。
4. 信号
        信号(Signal)是一种用于通知进程发生了某个事件的机制。不同的系统事件对应不同的信号类型,每类信号对应一个序号。例如,Linux 定义了 30 种信号,分别用序号 1 - 30 表示。

        在进程的 PCB 中,用至少 n 位向量记录该进程的待处理信号,如 Linux 使用一个 32 位的 int 型变量表示。若给某个进程发送一个信号,则把该类信号对应的位修改为 1。一旦该信号被处理,就把对应的位修改为 0。此外,还用另一个 n 位向量记录被阻塞(被屏蔽)的信号。当某个位为 1 时,表示该位对应的信号类型将被进程忽略,无须响应。

        接下来探讨信号是如何发送的,主要有两种方式:

        1)内核给某个进程发送信号。当内核检测到某个特定的系统事件时,就给进程发送信号。例如,若进程使用非法指令,则内核给该进程发送 SIGILL 信号(序号为 4)。

        2)一个进程给另一个进程发送信号。进程可以调用 kill 函数,要求内核发送一个信号给目的进程(需要指明接收进程的 PID 和信号的序号)。当然,进程也可给自己发送信号。

        当操作系统把一个进程从内核态切换到用户态时(如系统调用返回时),会检查该进程是否有未被阻塞的待处理信号,若有,则强制进程接收信号,并立即处理信号(若有多个待处理信号,则通常先处理序号更小的信号)。信号的处理方式有两种:

        1)执行默认的信号处理程序。操作系统为每类信号预设了默认的信号处理程序。例如,收到 SIGILL 信号的默认操作就是终止进程。

        2)执行进程定义的信号处理程序。进程可为某类信号自定义信号处理程序。例如,进程可以定义收到 SIGILL 信号时输出 “hello world”,而不是终止进程。

        信号处理程序运行结束后,通常会返回进程的下一条指令继续执行。

2.1.6 线程和多线程模型

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

        线程最直接的理解就是轻量级进程,它是一个基本的 CPU 执行单元,也是程序执行流的最小单元,由线程 ID、程序计数器、寄存器集合和堆栈组成。线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一丁点儿在运行中必不可少的资源,但它可与同一个进程的其他线程共享进程所拥有的全部资源。一个线程可以创建和撤销另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。

        引入线程后,进程的内涵发生了改变,进程只作为除 CPU 外的系统资源的分配单元,而线程则作为 CPU 的分配单元。由于一个进程内部有多个线程,若线程的切换发生在同一个进程内部,则只需要很少的时空开销。下面从几个方面对线程和进程进行比较。
2. 线程与进程的比较

命题追踪 线程与进程的比较(2012)

        1)调度:在传统的操作系统中,拥有资源和独立调度的基本单位都是进程,每次调度都要进行上下文切换,开销较大。在引入线程的操作系统中,线程是独立调度的基本单位,而线程切换的代价远低于进程。在同一进程中,线程的切换不会引起进程切换。但从一个进程中的线程切换到另一个进程中的线程时,会引起进程切换。

        2)并发性。在引入线程的操作系统中,不仅进程之间可以并发执行,一个进程中的多个线程之间也可并发执行,甚至不同进程中的线程也能并发执行,从而使操作系统具有更好的并发性,提高了系统资源的利用率和系统的吞吐量。
        3)拥有资源。进程是系统中拥有资源的基本单位,而线程不拥有系统资源(仅有一点必不可少、能保证独立运行的资源),但线程可以访问其隶属进程的系统资源,这主要表现在属于同一进程的所有线程都具有相同的地址空间。要知道,若线程也是拥有资源的单位,则切换线程就需要较大的时空开销,线程这个概念的提出就没有意义。
        4)独立性。每个进程都拥有独立的地址空间和资源,除了共享全局变量,不允许其他进程访问。某个进程中的线程对其他进程不可见。同一进程中的不同线程是为了提高并发性及进行相互之间的合作而创建的,它们共享进程的地址空间和资源。
        5)系统开销。在创建或撤销进程时,系统都要为之分配或回收进程控制块(PCB)及其他资源,如内存空间、I/O 设备等。操作系统为此所付出的开销,明显大于创建或撤销线程时的开销。类似地,在进程切换时涉及进程上下文的切换,而线程切换时只需保存和设置少量寄存器内容,开销很小。此外,同一进程内的多个线程共享进程的地址空间,因此这些线程之间的同步与通信非常容易实现,甚至无须操作系统的干预。
        6)支持多处理器系统。对于传统单线程进程,不管有多少个 CPU,进程只能运行在一个 CPU 上。对于多线程进程,可将进程中的多个线程分配到多个 CPU 上执行。
3. 线程的属性
        多线程操作系统中的进程已不再是一个基本的执行实体,但它仍具有与执行相关的状态。所谓进程处于 “执行” 状态,实际上是指该进程中的线程正在执行。线程的主要属性如下:

命题追踪 线程所拥有资源的特点(2011、2024)

        1)线程是一个轻型实体,它不拥有系统资源,但每个线程都应有一个唯一的标识符和一个线程控制块,线程控制块记录线程执行的寄存器和栈等现场状态。

        2)不同的线程可以执行相同的程序,即同一个服务程序被不同的用户调用时,操作系统将它们创建成不同的线程。

        3)同一进程中的各个线程共享该进程所拥有的资源。

        4)线程是 CPU 的独立调度单位,多个线程是可以并发执行的。在单 CPU 的计算机系统中,各线程可交替地占用 CPU;在多 CPU 的计算机系统中,各线程可同时占用不同的 CPU,若各个 CPU 同时为一个进程内的各线程服务,则可缩短进程的处理时间。

        5)一个线程被创建后,便开始了它的生命周期,直至终止。线程在生命周期内会经历阻塞态、就绪态和运行态等各种状态变化。

        为什么线程的提出有利于提高系统并发性?可以这样来理解:由于有了线程,线程切换时,有可能发生进程切换,也有可能不发生进程切换,平均而言每次切换所需的开销就变小了,因此能够让更多的线程参与并发,而不会影响到响应时间等问题。
4. 线程的状态与转换
        与进程一样,各线程之间也存在共享资源和相互合作的制约关系,致使线程在运行时也具有间断性。相应地,线程在运行时也具有下面三种基本状态。

·执行态:线程已获得 CPU 而正在运行。

·就绪态:线程已具备各种执行条件,只需再获得 CPU 便可立即执行。

·阻塞态:线程在执行中因某事件受阻而处于暂停状态。

        线程这三种基本状态之间的转换和进程基本状态之间的转换是一样的。
5. 线程的组织与控制

(1)线程控制块

命题追踪 线程的特点(2019、 2024)

        与进程类似,系统也为每个线程配置一个线程控制块 TCB,用于记录控制和管理线程的信息。线程控制块通常包括:①线程标识符;②一组寄存器,包括程序计数器、状态寄存器和通用寄存器;③线程运行状态,用于描述线程正处于何种状态;④优先级;⑤线程专有存储区,线程切换时用于保存现场等;⑥堆栈指针,用于过程调用时保存局部变量及返回地址等。

        同一进程中的所有线程都能访问进程的地址空间和全局变量。但是,每个线程都拥有自己的堆栈,且互不共享(可以这么理解:线程的堆栈被包含在进程的地址空间内,因此同一进程中的各个线程事实上可以访问彼此的堆栈,但编程规范通常不推荐这么做)。

(2)线程的创建

        线程也是具有生命周期的,它由创建而产生,由调度而执行,由终止而消亡。相应地,在操作系统中就有用于创建线程和终止线程的函数(或系统调用)。

        用户程序启动时,通常仅有一个称为初始化线程的线程正在执行,其主要功能是用于创建新线程。在创建新线程时,需要利用一个线程创建函数,并提供相应的参数,如指向线程主程序的入口指针、堆栈的大小、线程优先级等。线程创建函数执行完后,将返回一个线程标识符。

(3)线程的终止

        当一个线程完成自己的任务后,或线程在运行中出现异常而要被强制终止时,由终止线程调用相应的函数执行终止操作。但是有些线程(主要是系统线程)一旦被建立,便一直运行而不会被终止。通常,线程被终止后并不立即释放它所占有的资源,只有当进程中的其他线程执行了分离函数后,被终止线程才与资源分离,此时的资源才能被其他线程利用。

        被终止但尚未释放资源的线程仍可被其他线程调用,以使被终止线程重新恢复运行。
6. 线程的实现方式
        线程的实现可以分为两类:用户级线程(User - Level Thread,ULT)和内核级线程(Kernel - Level Thread,KLT)。内核级线程也称内核支持的线程。

(1)用户级线程(ULT)

        通俗地说,用户级线程就是 “从用户视角能看到的线程”。在用户级线程中,有关线程管理(创建、撤销和切换等)的所有工作都由应用程序在用户空间内(用户态)完成,无须操作系统干预,内核意识不到线程的存在。应用程序可以通过使用线程库设计成多线程程序。通常,应用程序从单线程开始,在该线程中开始运行,在其运行的任何时刻,可以通过调用线程库中的派生例程创建一个在相同进程中运行的新线程。

        对于设置了用户级线程的系统,其调度仍说明了以进程为单位进行,各个进程轮流执行一个时间片。假设进程 A 包含 1 个用户级线程,进程 B 包含 100 个用户级线程,这样,进程 A 中线程的运行时间将是进程 B 中各线程运行时间的 100 倍,因此对线程来说实质上是不公平的。

        这种实现方式的优点如下:①线程切换不需要转换到内核空间,节省了模式切换的开销。②调度算法可以是进程专用的,不同的进程可根据自身的需要,对自己的线程选择不同的调度算法。③用户级线程的实现与操作系统平台无关,对线程管理的代码是属于用户程序的一部分。

        这种实现方式的缺点如下:①系统调用的阻塞问题,当线程执行一个系统调用时,不仅该线程被阻塞,进程内的所有线程也都被阻塞。②不能发挥多 CPU 的优势,内核每次分配给一个进程的仅有一个 CPU,因此进程中仅有一个线程能执行。

(2)内核级线程(KLT)

        在操作系统中,无论是系统进程还是用户进程,都是在操作系统内核的支持下运行的,与内核紧密相关。内核级线程同样也是在内核的支持下运行的,线程管理的所有工作也是在内核空间内(内核态)实现的。操作系统也为每个内核级线程设置一个线程控制块 TCB,内核根据该控制块感知某线程的存在,并对其加以控制。

        这种实现方式的优点如下:①能发挥多 CPU 的优势,内核能同时调度同一进程中的多个线程并行执行。②若进程中的一个线程被阻塞,则内核可以调度该进程中的其他线程占用 CPU,也可运行其他进程中的线程。③内核支持线程具有很小的数据结构和堆栈,线程切换比较快、开销小。④内核本身也可采用多线程技术,可以提高系统的执行速度和效率。

        这种实现方式的缺点如下:同一进程中的线程切换,需要从用户态转到内核态进行,系统开销较大。这是因为用户进程的线程在用户态运行,而线程调度和管理是在内核实现的。

(3)组合方式

        有些系统使用组合方式的多线程实现。在组合实现方式中,内核支持多个内核级线程的建立、调度和管理,同时允许用户程序建立、调度和管理用户级线程。一些内核级线程对应多个用户级线程,这是用户级线程通过时分多路复用内核级线程实现的。同一进程中的多个线程可以同时在多 CPU 上并行执行,且在阻塞一个线程时不需要将整个进程阻塞,所以组合方式能结合 KLT 和 ULT 的优点,并且克服各自的不足。

        在线程实现方式的介绍中,提到了通过线程库来创建和管理线程。线程库(thread library)是为程序员提供创建和管理线程的 API。实现线程库主要的方法有如下两种:

        ①在用户空间中提供一个没有内核支持的库。这种库的所有代码和数据结构都位于用户空间中。这意味着,调用库内的一个函数只导致用户空间中的一个本地函数的调用。

        ②实现由操作系统直接支持的内核级的一个库。对于这种情况,库内的代码和数据结构位于内核空间。调用库中的一个 API 函数通常会导致对内核的系统调用。

        目前使用的三种主要线程库是:POSIX Pthreads、Windows API、Java。Pthreads 作为 POSIX 标准的扩展,可以提供用户级或内核级的库。Windows API 是用于 Windows 系统的内核级线程库。Java 线程 API 允许线程在 Java 程序中直接创建和管理。JVM 实例通常运行在宿主操作系统之上,Java 线程 API 通常采用宿主系统的线程库来实现,因此在 Windows 系统中 Java 线程通常采用 Windows API 来实现,在类 UNIX 系统中采用 Pthreads 来实现。
7. 多线程模型
        在同时支持用户级线程和内核级线程的系统中,用户级线程和内核级线程连接方式的不同,形成了下面三种不同的多线程模型。

        1)多对一模型:将多个用户级线程映射到一个内核级线程,如图2.6(a)所示。每个进程只被分配一个内核级线程,线程的调度和管理在用户空间完成。仅当用户线程需要访问内核时,才将其映射到一个内核级线程上,但每次只允许一个线程进行映射。

        优点:线程管理是在用户空间进行的,无须切换到内核态,因此效率比较高。

        缺点:若一个线程在访问内核时发生阻塞,则整个进程都会被阻塞;在任何时刻,只有一个线程能够访问内核,多个线程不能同时在多个 CPU 上运行。

        2)一对一模型:将每个用户级线程映射到一个内核级线程,如图2.6(b)所示。每个进程有与用户级线程数量相同的内核级线程,线程切换由内核完成,需要切换到内核态。

        优点:当一个线程被阻塞后,允许调度另一个线程运行,所以并发能力较强。

        缺点:每创建一个用户线程,相应地就要创建一个内核线程,开销较大。

        3)多对多模型:将 n 个用户级线程映射到 m 个内核级线程上,要求 n≥m ,如图2.6(c)所示。

        特点:既克服了多对一模型并发度不高的缺点,又克服了一对一模型的一个用户进程占用太多内核级线程而开销太大的缺点。此外,还拥有上述两种模型各自的优点。

2.1.7 本节小结

本节开头提出的问题的参考答案如下。

1)为什么要引入进程?
        在多道程序设计的背景下,进程之间需要共享系统资源,因此会导致各程序在执行过程中出现相互制约的关系,程序的执行会表现出间断性等特征。这些特征都是在程序的执行过程中发生的,是动态的过程,而传统的程序本身是一组指令的集合,是静态的概念,无法描述程序在内存中的执行情况,即无法从程序的字面上看出它何时执行、何时停顿,也无法看出它与其他执行程序的关系,因此,程序这个静态概念已不能如实反映程序并发执行过程的特征。为了深刻描述程序动态执行过程的性质乃至更好地支持和管理多道程序的并发执行,便引入了进程的概念。

2)什么是进程?进程由什么组成?

        进程是一个具有独立功能的程序关于某个数据集合的一次运行活动。它可以申请和拥有系统资源,是一个动态的概念,是一个活动的实体。它不只是程序的代码本身,还包括当前的活动,通过程序计数器的值和处理寄存器的内容来表示。

        一个进程实体由程序段、相关数据段和 PCB 三部分构成,其中 PCB 是标志一个进程存在的唯一标识,程序段是进程运行的程序的代码,数据段则存储程序运行过程中相关的一些数据。

3)进程是如何解决问题的?

        进程将能够识别程序运行状态的一些变量存放在 PCB 中,通过这些变量系统能够更好地了解进程的状况,并在适当时机进行进程的切换,以避免一些资源的浪费,甚至划分为更小的调度单位 —— 线程来提高系统的并发性。

        本节主要介绍什么是进程,并围绕这个问题进行一些阐述和讨论,为下一节讨论的内容做铺垫,但之前未学过相关课程的读者可能比较费解,到现在为止对进程这个概念还未形成比较清晰的认识。接下来,我们再用一个比较熟悉的概念来类比进程,以便大家能彻底理解本节的内容到底在讲什么,到底解决了什么问题。

        我们用 “人的生命历程” 来类比进程。首先,人的生命历程一定是一个动态的、过程性的概念,要研究人的生命历程,先要介绍经历这个历程的主体是什么。主体当然是人,相当于经历进程的主体是进程映像,人有自己的身份,相当于进程映像里有 PCB;人生历程会经历好几种状态:出生的时候、弥留的时候、充满斗志的时候、发愤图强的时候及失落的时候,相当于进程有创建、撤销、就绪、运行、阻塞等状态,这几种状态会发生改变,人会充满斗志而转向发愤图强,发愤图强获得进步之后又会充满斗志预备下一次发愤图强,或者发愤图强后遇到阻碍会进入失落状态,然后在别人的开导之下又重新充满斗志。类比进程,会由就绪态转向运行态,运行态转向就绪态,或者运行态转向阻塞态,然后在别的进程帮助下返回就绪态。若我们用 “人生历程” 这个过程的概念去类比进程,则对进程的理解就更深一层。前面生活化的例子可以帮我们理解进程的实质,但它毕竟有不严谨的地方。一种较好的方式是,在类比进程和人生历程后,再看一遍前面较为严谨的书面阐述和讨论,这样对知识的掌握会更加准确而全面。

        这里再给出一些学习计算机科学知识的建议。学习时,很多同学会陷入一个误区,即只注重对定理、公式的应用,而忽视对基础概念的理解。这是我们从小到大应付考试而培养出的一个毛病,因为熟练应用公式和定理对考试有立竿见影的效果。公式、定理的应用固然重要,但基础概念的理解能让我们透彻地理解一门学科,更利于我们产生兴趣,培养创造性思维。

2.1.8 本节习题精选

一、单项选择题

1.一个进程映像是( )。
A. 由协处理器执行的一个程序
B. 一个独立的程序 + 数据集
C. PCB 结构与程序和数据的组合
D. 一个独立的程序

01. C
进程映像是 PCB、程序段和数据的组合,其中 PCB 是进程存在的唯一标志。

2.进程之间交换数据不能通过( )途径进行。
A. 共享文件
B. 消息传递
C. 访问进程地址空间
D. 访问共享存储区

02. C
        每个进程包含独立的地址空间,进程各自的地址空间是私有的,只能执行自己地址空间中的程序,且只能访问自己地址空间中的数据,相互访问会导致指针的越界错误(学完内存管理将有更好的认识)。因此,进程之间不能直接交换数据,但可利用操作系统提供的共享文件、消息传递、共享存储区等进行通信。

3.进程与程序的根本区别是( )。
A. 静态和动态特点
B. 是不是被调入内存
C. 是不是具有就绪、运行和等待三种状态
D. 是不是占有处理器

03. A
        动态性是进程最重要的特性,以此来区分文件形式的静态程序。操作系统引入进程的概念,是为了从变化的角度动态地分析和研究程序的执行。

4.下列关于进程的描述中,最不符合操作系统对进程的理解的是( )。
A. 进程是在多程序环境中的完整程序
B. 进程可以由程序、数据和 PCB 描述
C. 线程(Thread)是一种特殊的进程
D. 进程是程序在一个数据集上的运行过程,它是系统进行资源分配和调度的一个独立单元

04. A
        进程是一个独立的运行单位,也是操作系统进行资源分配和调度的基本单位,它包括 PCB、程序和数据以及执行栈区,仅仅说进程是在多程序环境下的完整程序是不合适的,因为程序是静态的,它以文件形式存放在计算机的硬盘内,而进程是动态的。

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

05. D
        并发进程可能因等待资源或因被抢占 CPU 而暂停运行,其生命周期是不连续的。执行速度会影响进程之间的执行顺序和内存冲突问题,从而导致不同的操作结果。并发进程之间存在相互竞争和制约,导致每次运行可能得到不同的结果,选项 D 正确。

6.下列关于进程的叙述中,正确的是( )。
A. 进程获得处理器运行是通过调度得到的
B. 优先级是进程调度的重要依据,一旦确定就不能改动
C. 在单处理器系统中,任何时刻都只有一个进程处于运行态
D. 进程申请处理器而得不到满足时,其状态变为阻塞态

06. A
        选项 B 错在优先级分静态和动态两种,动态优先级是根据运行情况而随时调整的。选项 C 错在系统发生死锁时有可能进程全部都处于阻塞态,CPU 空闲。选项 D 错在进程申请处理器得不到满足时就处于就绪态,等待处理器的调度。

7.并发进程的执行相对速度是( )。
A. 由进程的程序结构决定的
B. 由进程自己来控制的
C. 与进程调度策略有关
D. 在进程被创建时确定的

07. C
        并发进程执行的相对速度与进程调度策略有关,因为进程调度策略决定了哪些进程可以获得处理机,以及获得处理机的时间长短,从而影响进程执行的速度和效率。

8.下列任务中,( )不是由进程创建原语完成的。
A. 申请 PCB 并初始化
B. 为进程分配内存空间
C. 为进程分配 CPU
D. 将进程插入就绪队列

08. C
        进程创建原语的执行过程:申请空白 PCB,并为新进程申请一个唯一的数字标识符。为新进程分配资源,包括内存、I/O 设备等。初始化 PCB,将新进程插入就绪队列。从上述过程可以看出,为进程分配 CPU 不是由进程创建原语完成的,而是由进程调度实现的。

9.下列关于进程和程序的叙述中,错误的是( )。
A. 一个进程在其生命周期中可执行多个程序
B. 一个进程在同一时刻可执行多个程序
C. 一个程序的多次运行可形成多个不同的进程
D. 一个程序的一次执行可产生多个进程

09. B
        一个进程可以顺序地执行一个或多个程序,只要在执行过程中改变其 CPU 状态和内存空间即可,但不能同时执行多个程序,选项 B 错误,选项 A 正确。一个程序可以对应多个进程,即多个进程可以执行同一个程序。例如,同一个文本编辑器可以被多个用户或多个窗口同时运行,每次运行都形成一个新进程。一个程序在执行过程中也可产生多个进程。例如,一个程序可以通过系统调用 fork () 或 create () 来创建子进程,从而实现并发处理或分布式计算。选项 C 和 D 正确。

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

10. D
        用户登录时,操作系统会为用户创建一个登录进程,用于验证用户身份和提供用户界面。高级调度即作业调度,会从后备队列上选择一个作业调入内存,并为之创建相应的进程。操作系统响应用户提出的请求时,通常会为用户创建一个子进程,用于执行用户指定的任务或程序。用户打开一个浏览器程序时,也是一种操作系统响应用户请求的情况,同样会创建一个新进程。

11.操作系统是根据( )来对并发执行的进程进行控制和管理的。
A. 进程的基本状态
B. 进程控制块
C. 多道程序设计
D. 进程的优先权

11. B
        在进程的整个生命周期中,系统总是通过其 PCB 对进程进行控制。也就是说,系统是根据进程的 PCB 而非任何其他因素来感知到进程存在的,PCB 是进程存在的唯一标志。同时 PCB 常驻内存。A 和 D 选项的内容都包含在进程 PCB 中。

12.在任何时刻,一个进程的状态变化( )引起另一个进程的状态变化。
A. 必定
B. 一定不
C. 不一定
D. 不可能

12. C
        一个进程的状态变化可能引起另一个进程的状态变化。例如,一个进程时间片用完,可能引起另一个就绪进程的运行。同时,一个进程的状态变化也可能不会引起另一个进程的状态变化。例如,一个进程由阻塞态转变为就绪态就不会引起其他进程的状态变化。

13.在单处理器系统中,若同时存在 10 个进程,则处于就绪队列中的进程最多有( )个。
A. 1
B. 8
C. 9
D. 10

13. C
        在单处理器系统中,不可能出现 10 个进程都处于就绪态的情况。但 9 个进程处于就绪态、1 个进程处于运行态是可能的。此外还要想到,可能 10 个进程都处于阻塞态。

14.一个进程释放了一台打印机,它可能改变( )的状态。
A. 自身进程
B. 输入 / 输出进程
C. 另一个等待打印机的进程
D. 所有等待打印机的进程

14. C
        由于打印机是独占资源,当一个进程释放打印机后,另一个等待打印机的进程就可能从阻塞态转到就绪态。当然,也存在一个进程执行完毕后由运行态转为终止态时释放打印机的情况,但这并不是由于释放打印机引起的,相反是因为运行完成才释放了打印机。

15.系统进程所请求的一次 I/O 操作完成后,将使进程状态从( )。
A. 运行态变为就绪态
B. 运行态变为阻塞态
C. 就绪态变为运行态
D. 阻塞态变为就绪态

15. D
I/O 操作完成之前进程在等待结果,状态为阻塞态;完成后进程等待事件就绪,变为就绪态。

16.一个进程的基本状态可以从其他两种基本状态转变过去,这个基本的状态一定是( )。
A. 运行态
B. 阻塞态
C. 就绪态
D. 终止态

16. C
        只有就绪态既可以由运行态转变过去又能由阻塞态转变过去。时间片到,运行态变为就绪态;当所需要资源到达时,进程由阻塞态转变为就绪态。

17.在分时系统中,通常处于( )的进程最多。
A. 运行态
B. 就绪态
C. 阻塞态
D. 终止态

17. B
        分时系统中处于就绪态的进程最多,这些进程都在争夺 CPU 的使用权,而 CPU 的数量是有限的。处于运行态的进程只能有一个或少数几个。处于阻塞态的进程也不会太多,阻塞事件的发生频率不会太高。处于终止态的进程也不多,这些进程已释放资源,不再占用内存空间。

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

18. D
        程序封闭性是指进程执行的结果只取决于进程本身,不受外界影响。也就是说,进程在执行过程中不管是不停顿地执行,还是走走停停,进程的执行速度都不会改变它的执行结果。失去封闭性后,不同速度下的执行结果不同。

19.通常用户进程被建立后,( )。
A. 便一直存在于系统中,直到被操作人员撤销
B. 随着进程运行的正常或不正常结束而撤销
C. 随着时间片轮转而撤销与建立
D. 随着进程的阻塞或者唤醒而撤销与建立

19. B
        进程有它的生命周期,不会一直存在于系统中,也不一定需要用户显式地撤销。进程在时间片结束时只是就绪,而不是撤销。阻塞和唤醒是进程生存期的中间状态。进程可在完成时撤销,或在出现内存错误等时撤销。

20.进程在处理器上执行时,( )。
A. 进程之间是无关的,具有封闭特性
B. 进程之间都有交互性,相互依赖、相互制约,具有并发性
C. 具有并发性,即同时执行的特性
D. 进程之间可能是无关的,但也可能是有交互性的

20. D
        选项 A 和 B 都说得太绝对,进程之间可能具有相关性,也可能是相互独立的。选项 C 错在 “同时”。

21.下列关于父进程和子进程的叙述中,正确的是( )。
A. 为了标志父子关系,可让子进程和父进程拥有相同的 PID
B. 父进程和子进程是相互独立的,可以并发执行
C. 撤销子进程时,一定会同时撤销父进程
D. 父进程创建了子进程,要等父进程执行完后,子进程才能执行

21. B
        虽然父进程创建了子进程,它们有一定的关系,但仍然是两个不同的进程,拥有各自的 PID,选项 A 错误。父进程和子进程是相互独立的,两个进程能并发执行,且互不影响,选项 B 正确,选项 D 错误。撤销一个进程并不一定会导致另一个进程也被撤销,父进程撤销后,子进程可能有两种状态:①子进程一并被终止;②子进程成为孤儿进程,被 init 进程领养。子进程撤销不会导致父进程撤销,C 错误。

22.若一个进程实体由 PCB、共享正文段、数据堆栈段和数据栈段组成,请指出下列 C 语言程序中的内容及相关数据结构各位于哪一段中。
I. 全局赋值变量( )
II. 未赋值的局部变量( )
III. 函数调用实参传递值( )
IV. 用 malloc () 要求动态分配的存储区( )
V. 常量值(如 1995、"string")( )
VI. 进程的优先级( )
A. PCB
B. 正文段
C. 堆段
D. 栈段

22. B、D、D、C、B、A
        C 语言编写的程序在使用内存时一般分为三个段:正文段(代码和赋值数据段)、数据堆段和数据栈段。二进制代码和常量存放在正文段,动态分配的存储区存放在数据堆段,临时使用的变量存放在数据栈段。由此,我们可以确定全局赋值变量在正文段赋值数据段,未赋值的局部变量和实参传递值在栈段,动态内存分配在堆段,常量在正文段,进程的优先级只能在 PCB 内。

23.同一程序经过多次创建,运行在不同的数据集上,形成了( )的进程。
A. 不同
B. 相同
C. 同步
D. 互斥

23. A
        进程是程序的一次执行过程,它不仅包括程序的代码,还包括程序的数据和状态。同一个程序经过多次创建,运行在不同的数据集上,会形成不同的进程,它们之间没有必然的联系。

24.PCB 是进程存在的唯一标志,下列( )不属于 PCB。
A. 进程 ID
B. CPU 状态
C. 堆栈指针
D. 全局变量

24. D
        进程实体主要是代码、数据和 PCB。因此,要清楚了解 PCB 内所含的数据结构内容,主要有四大类:进程标志信息、进程控制信息、进程资源信息、CPU 现场信息。由此可知,全局变量与 PCB 无关,而只与用户代码有关。

25.一个计算机系统中,进程的最大数量主要受到( )限制。
A. 内存大小
B. 用户数目
C. 打开的文件数
D. 外部设备数量

25. A
        进程创建需要占用系统内存来存放 PCB 的数据结构,因此一个系统能够创建的进程总数是有限的,进程的最大数量取决于系统内存的大小,它在系统安装时就已确定(若后期内存增加,则系统能够创建的进程总数也会增加)。而用户数量、外设数量和文件等均与此无关。

26.进程创建完成后会进入一个序列,这个序列称为( )。
A. 阻塞队列
B. 挂起序列
C. 就绪队列
D. 运行队列

26. C
        我们先要考虑创建进程的过程,当该进程所需的资源分配完成而只等 CPU 时,进程的状态为就绪态,因此所有的就绪 PCB 一般以链表方式链成一个序列,称为就绪队列。

27.进程自身决定( )。
A. 从运行态到阻塞态
B. 从运行态到就绪态
C. 从就绪态到运行态
D. 从阻塞态到就绪态

27. A
        只有从运行态到阻塞态的转换是由进程自身决定的。从运行态到就绪态的转换是由于进程的时间片用完,“主动” 调用程序转向就绪态。虽然从就绪态到运行态的转换同样是由调度程序决定的,但进程是 “被动的”。从阻塞态到就绪态的转换是由协作进程决定的。

28.下列关于原语操作的叙述中,错误的是( )。
A. 操作系统使用原语对进程进行管理和控制
B. 原语在执行过程中不允许被中断
C. 原语在内核态下执行,常驻内存
D. 原语被定义为 “原子操作”,意思是其执行速度非常快

28. D
        原语是由若干条指令组成的、用于实现某个特定功能的程序段。它与一般的程序的区别如下:它是 “原子操作”,即一个操作中的所有动作要么全做,要么全不做,在执行过程中不允许被中断,因此 “原子操作” 并不是指执行速度快,选项 D 错误。对进程的管理和控制功能是通过各种原语实现的,如创建原语等。原语是操作系统内核的组成部分,它常驻内存,且在内核态下执行。

29.用信箱实现进程间互通信息的通信机制要有两个通信原语,它们是( )。
A. 发送原语和执行原语
B. 就绪原语和执行原语
C. 发送原语和接收原语
D. 就绪原语和接收原语

29. C
用信箱实现进程间互通信息的通信机制要有两个通信原语,它们是发送原语和接收原语。

30.速度最快的进程通信方式是( )。
A. 消息传递
B. Socket
C. 共享内存
D. 管道

30. C
        消息传递需要在内核和用户空间中进行数据的拷贝,而且需要对消息进行格式化和排队,这些都会增加通信的开销。套接字(Socket)通常用于不同机器之间的进程通信,需要经过传输层以下的协议栈,而且可能涉及数据的加密和压缩,这些都会降低通信的速度。共享内存允许多个进程直接访问同一块物理内存,不需要任何数据的拷贝和中介,是最快的进程通信方式。管道需要在内核和用户空间进行数据的拷贝,而且一般是单向传输,降低了通信的效率。

31.信箱通信是一种( )通信方式。
A. 直接通信
B. 间接通信
C. 低级通信
D. 信号量

31. B
        信箱通信属于消息传递中的间接通信方式,因为信箱通信借助于收发双方进程之外的共享数据结构作为通信中转,发送方和接收方不直接建立联系,没有处理时间上的限制,发送方可以在任何时间发送信息,接收方也可以在任何时间接收信息。

32.下列关于信号发送和处理的描述中,错误的是( )。
A. 一个进程可以给自己发送信号
B. 操作系统的内核可以给进程发送信号
C. 操作系统的内核对每种信号都有默认处理程序
D. 用户可以对每种信号自定义处理函数

32. D
有些信号是不能被用户自定义处理函数的,只能执行操作系统默认的处理程序,选项 D 错误。

33.下列关于信号的处理的描述中,错误的是( )。
A. 当进程从内核态转为用户态时,会检查是否有待处理的信号
B. 当进程从用户态转为内核态时,也会检查是否有待处理的信号
C. 操作系统对某些信号的处理是可以忽略的
D. 操作系统允许进程通过系统调用,自定义某些信号的处理程序

33. B
        信号的处理时机只会在进程从内核态转为用户态时。当进程从用户态转为内核态时,不会检查是否有待处理的信号,选项 B 错误。操作系统对某些信号的默认处理可能就是忽略。

34.下面的叙述中,正确的是( )。
A. 引入线程后,处理器只能在线程间切换
B. 引入线程后,处理器仍在进程间切换
C. 线程的切换,不会引起进程的切换
D. 线程的切换,可能引起进程的切换

34. D
        在同一进程中,线程的切换不会引起进程的切换。当从一个进程中的线程切换到另一个进程中的线程时,才会引起进程的切换,因此选项 A、B、C 错误。

35.下列关于线程的叙述中,正确的是( )。
A. 线程包含 CPU 现场,可以独立执行程序
B. 每个线程都有自己独立的地址空间
C. 每个进程只能包含一个线程
D. 同一进程中的线程间通信也必须使用系统调用函数

35. A
        线程的 CPU 现场是指线程运行时所需的一组寄存器的值,包括程序计数器、状态寄存器、通用寄存器和栈指针等。当线程切换时,操作系统会保存当前线程的 CPU 现场,并恢复下一个线程的 CPU 现场。线程是 CPU 调度的基本单位,当然可以独立执行程序,选项 A 正确。线程没有自己独立的地址空间,它共享其所属进程的空间,选项 B 错误。进程可以创建多个线程,选项 C 错误。同一个进程中的线程间通信可以直接通过它们共享的存储空间,选项 D 错误。

36.下面的叙述中,正确的是( )。
A. 线程是比进程更小的能独立运行的基本单位,可以脱离进程独立运行
B. 引入线程可提高程序并发执行的程度,可进一步提高系统效率
C. 线程的引入增加了程序执行时的时空开销
D. 一个进程一定包含多个线程

36. B
        线程是进程内一个相对独立的执行单元,但不能脱离进程单独运行,只能在进程中运行。引入线程是为了减少程序执行时的时空开销。一个进程可包含一个或多个线程。

37.下面的叙述中,正确的是( )。
A. 同一进程内的线程可并发执行,不同进程的线程只能串行执行
B. 同一进程内的线程只能串行执行,不同进程的线程可并发执行
C. 同一进程或不同进程内的线程都只能串行执行
D. 同一进程或不同进程内的线程都可以并发执行

37. D
同一个进程或不同进程内的线程可以并发执行,并发是指多个线程在一段时间内交替执行,        而不一定是同时执行的。在多核 CPU 中,同一个进程或不同进程内的线程可以并行执行,并行是指多个线程在同一时刻同时执行。若实现了并行,则一定也实现了并发。

38. 下列选项中,( )不是线程的优点。

A. 提高系统并发性
B. 节约系统资源
C. 便于进程通信
D. 增强进程安全性

38. D
        线程的优点有提高系统并发性、节约系统资源、便于进程通信等,但线程并不能增强进程安全性,因为线程共享进程的地址空间和资源,若一个线程出错,则可能影响整个进程的运行。

39. 下列关于进程和线程的说法中,正确的是( )。

A. 一个进程可以包含一个或多个线程,一个线程可以属于一个或多个进程
B. 多线程技术具有明显的优越性,如速度快、通信简便、设备并行性高等
C. 由于线程不作为资源分配单位,线程之间可以无约束地并行执行
D. 线程也称轻量级进程,因为线程都比进程小

39. B
        一个进程可以包含一个或多个线程,但一个线程只能属于一个进程,选项 A 错误。线程共享进程的资源,但线程之间不能无约束地并行执行,因为线程之间还需要进行同步和互斥,以免造成数据的不一致和冲突,选项 C 错误。线程也称轻量级进程,但并不能说所有线程都比进程小,选项 D 错误。

40. 在下列描述中,( )并不是多线程系统的特长。

A. 利用线程并行地执行矩阵乘法运算
B. Web 服务器利用线程响应 HTTP 请求
C. 键盘驱动程序为每个正在运行的应用配备一个线程,用以响应该应用的键盘输入
D. 基于 GUI 的调试程序用不同的线程分别处理用户输入、计算和跟踪等操作

40. C
        整个系统只有一个键盘,而且键盘输入是人的操作,速度比较慢,完全可以使用一个线程来处理整个系统的键盘输入。

41. 在进程转换时,下列( )转换是不可能发生的。

A. 就绪态→运行态
B. 运行态→就绪态
C. 运行态→阻塞态
D. 阻塞态→运行态

41. D
阻塞的进程在获得所需资源时只能由阻塞态转变为就绪态,并插入就绪队列,而不能直接转变为运行态。

42. 当( )时,进程从执行状态转变为就绪态。

A. 进程被调度程序选中
B. 时间片到
C. 等待某一事件
D. 等待的事件发生

42. B
当进程的时间片到时,进程由运行态转变为就绪态,等待下一个时间片的到来。

43. 两个合作进程(Cooperating Processes)无法利用( )交换数据。

A. 文件系统
B. 共享内存
C. 高级语言程序设计中的全局变量
D. 消息传递系统

43. C
        不同的进程拥有不同的代码段和数据段,全局变量是对同一进程而言的,在不同的进程中是不同的变量,没有任何联系,所以不能用于交换数据。此题也可用排除法做,选项 A、B、D 均是课本上所讲的。管道是一种文件。

44. 以下可能导致一个进程从运行态变为就绪态的事件是( )。

A. 一次 I/O 操作结束
B. 运行进程需做 I/O 操作
C. 运行进程结束
D. 出现了比现在进程优先级更高的进程

44. D

        进程处于运行态时,它必须已获得所需的资源,在运行结束后就撤销。只有在时间片到或出现了比现在进程优先级更高的进程时才转变成就绪态。选项 A 使进程从阻塞态到就绪态,选项 B 使进程从运行态到阻塞态,选项 C 使进程撤销。

45. ( )必会引起进程切换。

A. 一个进程创建后,进入就绪态
B. 一个进程从运行态变为就绪态
C. 一个进程从阻塞态变为就绪态
D. 以上答案都不对

45. B

进程切换是指 CPU 调度不同的进程执行,当一个进程从运行态变为就绪态时,CPU 调度另一个进程执行,引起进程切换。

46. 进程处于( )时,它处于非阻塞态。

A. 等待从键盘输入数据
B. 等待协作进程的一个信号
C. 等待操作系统分配 CPU 时间
D. 等待网络数据进入内存

46. C

        进程有三种基本状态,处于阻塞态的进程由于某个事件不满足而等待。这样的事件一般是 I/O 操作,如键盘等,或是因互斥或同步数据引起的等待,如等待信号或等待进入互斥临界区代码段等,等待网络数据进入内存是为了进程同步。而等待 CPU 调度的进程处于就绪态,只有它是非阻塞态。

47. 一个进程被唤醒,意味着( )。

A. 该进程可以重新竞争 CPU
B. 优先级变大
C. PCB 移动到就绪队列之首
D. 进程变为运行态

47. A

        当一个进程被唤醒时,这个进程就进入了就绪态,等待进程调度而占有 CPU 运行。进程被唤醒在某种情形下优先级可以增大,但一般不会变为最大,而由固定的算法来计算。也不会在唤醒后位于就绪队列的队首,就绪队列是按照一定的规则赋予其位置的,如先来先服务,或者高优先级优先,或者短进程优先等,更不能直接占有处理器运行。

48. 进程创建时,不需要做的是( )。

A. 填写一个该进程的进程表项
B. 分配该进程适当的内存
C. 将该进程插入就绪队列
D. 为该进程分配 CPU

48. D

        进程创建原语完成的工作是:向系统申请一个空闲 PCB,为被创建进程分配必要的资源,然后将其 PCB 初始化,并将此 PCB 插入就绪队列,最后返回一个进程标志号。当调度程序为进程分配 CPU 后,进程开始运行。所以进程创建的过程中不会包含分配 CPU 的过程,这不是进程创建者的工作,而是调度程序的工作。

49. 计算机系统中两个协作进程之间不能用来进行进程间通信的是( )。

A. 数据库
B. 共享内存
C. 消息传递机制
D. 管道

49. A

        进程之间的通信方式主要有管道、消息传递、共享内存、文件映射和套接字等。数据库不能直接作为进程之间的通信方式。

50. 下面关于用户级线程和内核级线程的描述中,错误的是( )。

A. 采用轮转调度算法,进程中设置内核级线程和用户级线程的效果完全不同
B. 跨进程的用户级线程调度也不需要内核参与,控制简单
C. 用户级线程可以在任何操作系统中运行
D. 若系统中只有用户级线程,则 CPU 的调度对象是进程

50. B

        用户级线程的调度仍以进程为单位,各个进程轮流执行一个时间片,假设进程 A 包含 1 个用户级线程,而进程 B 包含 100 个用户级线程,此时进程 A 中单个线程的运行时间将是进程 B 中各个线程平均运行时间的 100 倍;内核级线程的调度是以线程为单位的,各个线程轮流执行一个时间片,同样假设进程 A 包含 1 个内核级线程,而进程 B 包含 100 个内核级线程,此时进程 B 的运行时间将是进程 A 的 100 倍,选项 A 正确。用户级线程的调度单位是进程,跨进程的线程调度需要内核支持,选项 B 错误。用户级线程是由用户程序或函数库实现的,不依赖于操作系统的支持,选项 C 正确。用户级线程对操作系统是透明的,CPU 调度的对象仍然是进程,选项 D 正确。

51. 在内核级线程相对于用户级线程的优点的如下描述中,错误的是( )

A. 同一进程内的线程切换,系统开销小
B. 当内核线程阻塞时,CPU 将调度同一进程中的其他内核线程执行
C. 内核级线程的程序实体可以在内核态运行
D. 对多处理器系统,核心可以同时调度同一进程的多个线程并行运行

51. A

        在内核级线程中,同一进程中的线程切换,需要从用户态转到内核态进行,系统开销较大,选项 A 错误。CPU 调度是在内核中进行的,在内核级线程中,调度是在线程一级进行的,因此内核可以同时调度同一进程的多个线程在多 CPU 上并行运行(用户级线程则不行),选项 B 正确、选项 D 正确。内核级线程可以在内核态执行系统调用子程序,直接利用系统调用为它服务,因此选项 C 正确。注意,用户级线程是在用户空间中实现的,不能直接利用系统调用获得内核的服务,当用户级线程要获得内核服务时,必须借助于操作系统的帮助,因此用户级线程只能在用户态运行。

52. 下列关于用户级线程相对于内核级线程的优点的描述中,错误的是( )

A. 一个线程阻塞不影响另一个线程的运行
B. 线程的调度不需要内核直接参与,控制简单
C. 线程切换代价小
D. 允许每个进程定制自己的调度算法,线程管理比较灵活

52. A

        进程中的某个用户级线程被阻塞,则整个进程也被阻塞,即进程中的其他用户级线程也被阻塞,选项 A 错误。用户级线程的调度是在用户空间进行的,节省了模式切换的开销,不同进程可以根据自身的需要,对自己的线程选择不同的调度算法,因此选项 B、C 和 D 都正确。

53. 下列关于用户级线程的优点的描述中,不正确的是( )。

A. 线程切换不需要切换到内核态
B. 支持不同的应用程序采用不同的调度算法
C. 在不同操作系统上不经修改就可直接运行
D. 同一个进程内的多个线程可以同时调度到多个处理器上执行

53. D

        用户级线程是不需要内核支持而在用户程序中实现的线程,不能利用多处理器的并行性,因为操作系统只能看到进程。其余说法均正确。

54. 下列选项中,可能导致用户级线程切换的事件是( )。

A. 系统调用
B. I/O 请求
C. 异常处理
D. 线程同步

54. D

        本题可用排除法。用户级线程的切换是由应用程序自己控制的,不需要操作系统的干预,操作系统感受不到用户级线程的存在。因此,系统调用、I/O 请求和异常处理这些涉及内核态的事件都不会导致用户级线程切换,但会导致内核级线程切换。线程同步是指多个线程之间协调执行顺序的机制,如互斥锁、信号量、条件变量等。当一个线程在等待同步条件时,应用程序可以选择切换到另一个就绪的用户级线程,以提高 CPU 的利用率。

55. 下列关于用户级线程的描述中,错误的是( )。

A. 用户级线程由线程库进行管理
B. 用户级线程只有在创建和调度时需要内核的干预
C. 操作系统无法直接调度用户级线程
D. 线程库中线程的切换不会导致进程切换

55. B

        用户级线程不依赖于操作系统内核,而是由用户程序自己实现的,选项 A 正确。用户级线程的创建和调度都是在用户态下实现的,不需要切换到内核态,选项 B 错误。操作系统只能看到一个单线程进程,而不知道进程内部有多个用户级线程,选项 C 正确。线程库中线程的切换只涉及用户栈和寄存器等上下文的保存和恢复,不涉及内核栈和页表等内核上下文的切换,选项 D 正确。

56. 下面的说法中,正确的是( )。

A. 不论是系统支持的线程还是用户级线程,其切换都需要内核的支持
B. 线程是资源分配的单位,进程是调度和分派的单位
C. 不管系统中是否有线程,进程都是拥有资源的独立单位
D. 在引入线程的系统中,进程仍是资源调度和分派的基本单位

56. C

        引入线程后,进程仍然是资源分配的单位。内核级线程是处理器调度和分派的单位,线程本身不具有资源,它可以共享所属进程的全部资源,选项 C 正确,选项 B、D 明显错误。至于选项 A,可以这样来理解:假如有一个内核进程,它映射到用户级后有多个线程,那么这些线程之间的切换不需要在内核级切换进程,也就不需要内核的支持。

57. 在多对一的线程模型中,当一个多线程进程中的某个线程被阻塞后,( )。

A. 该进程的其他线程仍可继续运行
B. 整个进程都将阻塞
C. 该阻塞线程将被撤销
D. 该阻塞线程将永远不可能再执行

57. B

        在多对一的线程模型中,只有一个内核级线程,用户级线程的 “多” 对操作系统透明,因此操作系统内核只能感知到一个调度单位的存在。因此,该进程的一个线程被阻塞后,该进程就被阻塞,进程的其他线程当然也被阻塞。注意,作为对比,在一对一模型中将每个用户级线程都映射到一个内核级线程,因此当某个线程被阻塞时,不会导致整个进程被阻塞。

58. 并发性较好的多线程模型有( )。

I. 一对一模型
II. 多对一模型
III. 多对多模型
A. 仅 I
B. I 和 II
C. I 和 III
D. I、II 和 III

58. C

        一对一模型和多对多模型能充分利用内核级线程,发挥多处理机的优势,能同时调度同一个进程中的多个线程并发执行,具有较好的并发性。

59. 下列关于多对一模型的叙述中,错误的是( )。

A. 一个进程的多个线程不能并行运行在多个处理器上
B. 进程中的用户级线程由进程自己管理
C. 线程切换会导致进程切换
D. 一个线程的系统调用会导致整个进程阻塞

59. C

多对一模型中的线程切换不会导致进程切换,而是在用户空间进行的。其余说法均正确。

60. 【2010 统考真题】下列选项中,导致创建新进程的操作是( )。

I. 用户登录成功
II. 设备分配
III. 启动程序执行
A. 仅 I 和 II
B. 仅 II 和 III
C. 仅 I 和 III
D. I、II、III

60. C

        创建进程的原因主要有:①用户登录;②高级调度;③系统处理用户程序的请求;④用户程序的应用请求。对于选项 I,用户登录成功后,系统要为此创建一个用户管理的进程,包括用户桌面、环境等,所有用户进程都会在该进程下创建和管理。对于选项 II,设备分配是通过在系统中设置相应的数据结构实现的,不需要创建进程,这是操作系统中 I/O 核心子系统的内容。对于选项 III,启动程序执行是引起创建进程的典型事件,启动程序执行属于③或④。

61. 【2011 统考真题】在支持多线程的系统中,进程 P 创建的若干线程不能共享的是( )。

A. 进程 P 的代码段
B. 进程 P 中打开的文件
C. 进程 P 的全局变量
D. 进程 P 中某线程的栈指针

61. D

        进程是资源分配的基本单位,线程是 CPU 调度的基本单位。进程的代码段、进程打开的文件、进程的全局变量等都是进程的资源,唯有进程中某线程的栈指针(包含在线程 TCB 中)是属于线程的,属于进程的资源可以共享,属于线程的栈指针是独享的,对其他线程透明。

62. 【2012 统考真题】下列关于进程和线程的叙述中,正确的是( )。

A. 不管系统是否支持线程,进程都是资源分配的基本单位
B. 线程是资源分配的基本单位,进程是调度的基本单位
C. 系统级线程和用户级线程的切换都需要内核的支持
D. 同一进程中的各个线程拥有各自不同的地址空间

62. A

        在引入线程后,进程依然是资源分配的基本单位,线程是调度的基本单位,同一进程中的各个线程共享进程的地址空间。在用户级线程中,有关线程管理的所有工作都由应用程序完成,无须内核的干预,内核意识不到线程的存在。

63. 【2014 统考真题】一个进程的读磁盘操作完成后,操作系统针对该进程必做的是( )。

A. 修改进程状态为就绪态
B. 降低进程优先级
C. 给进程分配用户内存空间
D. 增加进程时间片大小

63. A

        进程申请读磁盘操作时,因为要等待 I/O 操作完成,会把自身阻塞,此时进程变为阻塞态;I/O 操作完成后,进程得到了想要的资源,会从阻塞态转换到就绪态(这是操作系统的行为)。而降低进程优先级、分配用户内存空间和增加进程的时间片大小都不一定会发生,选择选项 A。

64. 【2014 统考真题】下列关于管道(Pipe)通信的叙述中,正确的是( )。

A. 一个管道可实现双向数据传输
B. 管道的容量仅受磁盘容量大小限制
C. 进程对管道进行读操作和写操作都可能被阻塞
D. 一个管道只能有一个读进程或一个写进程对其操作

64. C

        普通管道只允许单向通信,数据只能往一个方向流动,要实现双向数据传输,就需要定义两个方向相反的管道,选项 A 错误。管道是一种存储在内存中的、固定大小的缓冲区,管道的大小通常为内存的一页,其大小并不是受磁盘容量大小的限制,选项 B 错误。由于管道的读 / 写操作都可能遇到缓冲区满或空的情况,当管道满时,写操作会被阻塞,直到有数据读出;而当管道空时,读操作会被阻塞,直到有数据写入,因此选项 C 正确。一个管道可以有多个读进程或多个写进程对其进行操作,但是这会增加数据竞争和混乱的风险,为了避免这种情况,应使用互斥锁或信号量等同步机制来保证每次只有一个进程对管道进行读或写操作,选项 D 错误。

65. 【2015 统考真题】下列选项中,会导致进程从执行态变为就绪态的事件是( )。

A. 执行 P (wait) 操作
B. 申请内存失败
C. 启动 I/O 设备
D. 被高优先级进程抢占

65. D

        P (wait) 操作表示进程请求某一资源,选项 A、B 和 C 都因为请求某一资源会进入阻塞态,而选项 D 只是被剥夺了 CPU 资源,进入就绪态,一旦得到 CPU 即可运行。

66. 【2018 统考真题】下列选项中,可能导致当前进程 P 阻塞的事件是( )。

I. 进程 P 申请临界资源
II. 进程 P 从磁盘读数据
III. 系统将 CPU 分配给高优先权的进程
A. 仅 I
B. 仅 II
C. 仅 I、II
D. I、II、III

66. C

        进程等待某资源为可用(不包括 CPU)或等待输入 / 输出完成均会进入阻塞态,因此选项 I、II 正确;选项 III 中的情况发生时,进程进入就绪态,因此选项 III 错误。

67. 【2019 统考真题】下列选项中,可能将进程唤醒的事件是( )。

I. I/O 结束
II. 某进程退出临界区
III. 当前进程的时间片用完
A. 仅 I
B. 仅 III
C. 仅 I、II
D. I、II、III

67. C

        当被阻塞进程等待的某资源(不包括 CPU)可用时,进程将被唤醒。I/O 结束后,等待该 I/O 结束而被阻塞的有关进程会被唤醒,选项 I 正确;某进程退出临界区后,之前因需要进入该临界区而被阻塞的有关进程会被唤醒,选项 II 正确;当前进程的时间片用完后进入就绪队列等待重新调度,优先级最高的进程获得 CPU 资源从就绪态变成执行态,选项 III 错误。

68. 【2019 统考真题】下列关于线程的描述中,错误的是( )。

A. 内核级线程的调度由操作系统完成
B. 操作系统为每个用户级线程建立一个线程控制块
C. 用户级线程间的切换比内核级线程间的切换效率高
D. 用户级线程可以在不支持内核级线程的操作系统上实现

68. B

        应用程序没有进行内核级线程管理的代码,只有一个到内核级线程的编程接口,内核为进程及其内部的每个线程维护上下文信息,调度也是在内核中由操作系统完成的,选项 A 正确。用户级线程的控制块是由用户空间的库函数维护的,操作系统并不知道用户级线程的存在,用户级线程的控制块一般存放在用户空间的数据结构中,如链表或数组,由用户空间的线程库来管理。操作系统只负责为每个进程建立一个进程控制块,操作系统只能看到进程,而看不到用户级线程,所以不会为每个用户级线程建立一个线程控制块。但是,内核级线程的线程控制块是由操作系统创建的,当一个进程创建一个内核级线程时,操作系统会为该线程分配一个线程控制块,并将其加入内核的线程管理数据结构,选项 B 错误。用户级线程的切换可以在用户空间完成,内核级线程的切换需要操作系统帮助进行调度,因此用户级线程的切换效率更高,选项 C 正确。用户级线程的管理工作可以只在用户空间中进行,因此可以在不支持内核级线程的操作系统上实现,选项 D 正确。

69. 【2020 统考真题】下列关于父进程与子进程的叙述中,错误的是( )。

A. 父进程与子进程可以并发执行
B. 父进程与子进程共享虚拟地址空间
C. 父进程与子进程有不同的进程控制块
D. 父进程与子进程不能同时使用同一临界资源

69. B

        父进程与子进程当然可以并发执行,选项 A 正确。父进程可与子进程共享一部分资源,但不能共享虚拟地址空间,在创建子进程时,会为子进程分配资源,如虚拟地址空间等,选项 B 错误。临界资源一次只能为一个进程所用,选项 D 正确。进程控制块(PCB)是进程存在的唯一标志,每个进程都有自己的 PCB,选项 C 正确。

70. 【2021 统考真题】下列操作中,操作系统在创建新进程时,必须完成的是( )。

I. 申请空白的进程控制块
II. 初始化进程控制块
III. 设置进程状态为执行态
A. 仅 I
B. 仅 I、II
C. 仅 I、III
D. 仅 II、III

70. B

        操作系统感知进程的唯一方式是通过进程控制块(PCB),所以创建一个新进程就是为其申请一个空白的进程控制块,并且初始化一些必要的进程信息,如初始化进程标志信息、初始化 CPU 状态信息、设置进程优先级等。选项 I、II 正确。创建一个进程时,一般会为其分配除 CPU 外的大多数资源,所以一般将其设置为就绪态,让它等待调度程序的调度。

71. 【2022 统考真题】下列事件或操作中,可能导致进程 P 由执行态变为阻塞态的是( )。

I. 进程 P 读文件
II. 进程 P 的时间片用完
III. 进程 P 申请外设
IV. 进程 P 执行信号量的 wait () 操作
A. 仅 I、IV
B. 仅 II、III
C. 仅 III、IV
D. 仅 I、III、IV

71. D

        进程 P 读文件时,进程从执行态进入阻塞态,等待磁盘 I/O 完成,选项 I 正确。进程 P 的时间片用完,导致进程从执行态进入就绪态,转入就绪队列等待下次被调度,选项 II 错误。进程 P 申请外设,若外设是独占设备且正在被其他进程使用,则进程 P 从执行态进入阻塞态,等待系统分配外设,选项 III 正确。进程 P 执行信号量的 wait () 操作,若信号量的值小于或等于 0,则进程进入阻塞态,等待其他进程用 signal () 操作唤醒,选项 IV 正确。

72. 【2023 统考真题】下列操作完成时,导致 CPU 从内核态转为用户态的是( )。

A. 阻塞进程
B. 执行 CPU 调度
C. 唤醒进程
D. 执行系统调用

72. D

        操作系统通过执行软中断指令陷入内核态执行系统调用,系统调用执行完成后,恢复被中断的进程或设置新进程的 CPU 现场,然后返回被中断进程或新进程。只有系统调用是用户进程调用内核功能,CPU 从用户态切换到内核态,执行完后再返回到用户态。选项 A、B、C 的操作都是在内核态进行的,执行前后都可能处在内核态,只有中断返回时才切换为用户态。

73. 【2023 统考真题】下列由当前线程引起的事件或执行的操作中,可能导致该线程由执行态变为就绪态的是( )。

A. 键盘输入
B. 缺页异常
C. 主动出让 CPU
D. 执行信号量的 wait () 操作

73. C

        在等待键盘输入的操作中,当前线程处于阻塞态,键盘输入完成后,再调出相应的中断服务程序进行处理,由中断服务程序负责唤醒当前线程,选项 A 错误。当线程检测到缺页异常时,会调用缺页异常处理程序从外存调入缺失的页面,线程状态从执行态转为阻塞态,选项 B 错误。当线程的时间片用完后,主动放弃 CPU,此时若线程还未执行完,就进入就绪队列等待下次调度,此时线程状态从执行态转为就绪态,选项 C 正确。线程执行 wait () 后,若成功获取资源,则线程状态不变,若未能获取资源,则线程进入阻塞态,选项 D 错误。

74. 【2024 统考真题】下列选项中,操作系统在终止进程时不一定执行的是( )。

A. 终止子进程
B. 回收进程占用的设备
C. 释放进程控制块
D. 回收为进程分配的内存

74. A

        当操作系统终止进程时,所有的进程资源(如内存空间、进程控制块、设备、打开文件、I/O 缓冲区等)都会被释放。有些系统不允许子进程在父进程已终止的情况下存在,对于这类系统,若一个进程终止,则它的所有子进程也终止,这种现象称为级联终止。但是,不是所有操作系统都是这么设计的,因此终止子进程不一定在终止进程时执行。

75. 【2024 统考真题】若进程 P 中的线程 T 先打开文件,得到文件描述符 fd,再创建两个线程 Ta 和 Tb,则在下列资源中,Ta 与 Tb 可共享的是( )。

I. 进程 P 的地址空间
II. 线程 T 的栈
III. 文件描述符 fd
A. 仅 I
B. 仅 I、III
C. 仅 II、III
D. I、II、III

75. B

        线程可理解为轻量级进程,仅拥有一点必不可少、能保证独立运行的资源。例如,在每个线程中都有一个用于控制线程运行的线程控制块(TCB)、用于指示被执行指令序列的程序计数器、保留局部变量、少数状态参数和返回地址等的一组寄存器和堆栈。因此,线程 T 的栈空间是线程 T 所独有的,不会被线程 Ta 和 Tb 共享。多个线程共享所属进程所拥有的资源,进程 P 的线程可以访问进程 P 的全部地址空间和资源(如已打开的文件、定时器、信号量机构等)以及它申请到的 I/O 设备等。综上所述,Ta 和 Tb 可共享的是进程 P 的地址空间和文件描述符 fd。

二、综合应用题

01. 为何进程之间的通信必须借助于操作系统内核功能?简单说明进程通信的几种主要方式。

01. 【解答】

        在操作系统中,进程是竞争和分配计算机系统资源的基本单位。每个进程都有自己的独立地址空间。为了保证多个进程能够彼此互不干扰地共享物理内存,操作系统利用硬件地址机制对进程的地址空间进行了严格的保护,限制每个进程只能访问自己的地址空间。

具体解答如下。
        每个进程有自己独立的地址空间。在操作系统和硬件的地址保护机制下,进程无法访问其他进程的地址空间,必须借助于系统调用函数实现进程之间的通信。进程通信的主要方式有:

        1.共享内存区:通过系统调用创建共享内存区。多个进程可以(通过系统调用)连接同一个共享内存区,通过访问共享内存区实现进程之间的数据交换。使用共享内存区时需要利用信号量解决同步互斥问题。

        2.消息传递:通过发送 / 接收消息,系统调用实现进程之间的通信。当进程发送消息时,系统将消息从用户缓冲区复制到内核中的消息缓冲区,然后将消息缓冲区挂入消息队列。进程发送的消息保持在消息队列中,直到被另一进程接收。当进程接收消息时,系统从消息队列中解挂消息缓冲区,将消息从内核的消息缓冲区中复制到用户缓冲区,然后释放消息缓冲区。

        3.管道系统:管道允许两个进程按标准的生产者 - 消费者方式进行通信:生产者向管道的一端(写入端)写,消费者从管道的另一端(读出端)读。管道只允许单向通信。在读 / 写过程中,操作系统保证数据的写入顺序和读出顺序是一致的。

        4.共享文件:利用操作系统提供的文件共享功能实现进程之间的通信。这时,也需要信号量来解决文件共享操作中的同步和互斥问题。

02. 什么是多线程?多线程与多任务有什么区别?

02. 【解答】

        多线程是指在一个程序中可以定义多个线程并同时运行它们,每个线程可以执行不同的任务。
        多线程与多任务的区别:多任务是针对操作系统而言的,代表操作系统可以同时执行的程序个数;多线程是针对一个程序而言的,代表一个程序可以同时执行的线程个数,而每个线程可以完成不同的任务。

03. 回答下列问题:

1)若系统中没有运行进程,是否一定没有就绪进程?为什么?
2)若系统中既没有运行进程,又没有就绪进程,系统中是否就没有进程?为什么?
3)在采用优先级进程调度时,运行进程是否一定是系统中优先级最高的进程?

03. 【解答】

        1)是。若系统中未运行进程,则系统很快会选择一个就绪进程运行。只有就绪队列中无进程时,CPU 才可能处于空闲状态。
        2)不一定。因为系统中的所有进程可能都处于等待态,可能处于死锁状态,也有可能因为等待的事件未发生而进入循环等待态。
        3)不一定。因为高优先级的进程有可能正处在等待队列中,进程调度会从就绪队列中选择一个进程占用 CPU,这个被选中的进程可能优先级较低。

04. 某分时系统中的进程可能出现如下图所示的状态变化,请回答下列问题:

1)根据图示,该系统应采用什么进程调度策略?
2)将图中每个状态变化的可能原因填写在下表中。

04. 【解答】

        根据题意,首先由图进行分析,进程由运行态可以直接回到就绪队列的末尾,而且就绪队列中是先来先服务。那么,什么情况才能发生这样的变化呢?只有采用单一时间片轮转的调度系统,分配的时间片用完后,才会发生上述情况。因此,该系统一定采用时间片轮转调度算法,采用时间片轮转算法的操作系统一般均为交互式操作系统。由图可知,进程被阻塞时,可以进入不同的阻塞队列,等待打印机输出结果和等待磁盘读取文件。所以,它是一个多阻塞队列的时间片轮转法的调度系统。
具体解答如下。
1)根据题意,该系统采用的是时间片轮转法调度进程策略。
2)可能的变化见下表。

变化

原因

1

进程被调度,获得 CPU,进入运行态

2

进程需要读文件,因 I/O 操作进入阻塞态

3

进程打印输出结果,因打印机未结束而阻塞

4

打印机打印结束,进程重新回归就绪态,并排在尾部

5

进程所需数据已从磁盘进入内存,进程回到就绪态

6

运行的进程因为时间片用完而让出 CPU,排到就绪队列尾部

2.2 CPU 调度

在学习本节时,请读者思考以下问题:
1)为什么要进行 CPU 调度?
2)调度算法有哪几种?结合第 1 章学习的分时操作系统和实时操作系统,思考哪种调度算法比较适合这两种操作系统。
        希望读者能够在学习调度算法前,先自己思考一些调度算法,在学习的过程中注意将自己的想法与这些经典的算法进行比对,并学会计算一些调度算法的周转时间。

2.2.1 调度的概念

1. 调度的基本概念

        在多道程序系统中,进程的数量往往多于 CPU 的个数,因此进程争用 CPU 的情况在所难免。CPU 调度是对 CPU 进行分配,即从就绪队列中按照一定的算法(公平、高效的原则)选择一个进程并将 CPU 分配给它运行,以实现进程并发地执行。
        CPU 调度是多道程序操作系统的基础,是操作系统设计的核心问题。

2. 调度的层次

一个作业从提交开始直到完成,往往要经历以下三级调度,如图 2.7 所示。

(1)高级调度(作业调度)
        按照某种规则从外存上处于后备队列的作业中挑选一个(或多个),给它(们)分配内存、I/O 设备等必要的资源,并建立相应的进程,以使它(们)获得竞争 CPU 的权利。简言之,作业调度就是内存与辅存之间的调度。每个作业只调入一次、调出一次。
        多道批处理系统中大多配有作业调度,而其他系统中通常不需要配置作业调度。

(2)中级调度(内存调度)
        引入中级调度的目的是提高内存利用率和系统吞吐量。为此,将那些暂时不能运行的进程调至外存等待,此时进程的状态称为挂起态。当它们已具备运行条件且内存又稍有空闲时,由中级调度来决定将外存上的那些已具备运行条件的挂起进程再重新调入内存,并修改其状态为就绪态,挂在就绪队列上等待。中级调度实际上是存储器管理中的对换功能。

(3)低级调度(进程调度)
        按照某种算法从就绪队列中选取一个进程,将 CPU 分配给它。进程调度是最基本的一种调度,在各种操作系统中都必须配置这级调度。进程调度的频率很高,一般几十毫秒一次。

3. 三级调度的联系

        作业调度从外存的后备队列中选择一批作业进入内存,为它们建立进程,这些进程被送入就绪队列,进程调度从就绪队列中选出一个进程,并将其状态改为运行态,将 CPU 分配给它。中级调度是为了提高内存的利用率,系统将那些暂时不能运行的进程挂起来。
1)作业调度为进程活动做准备,进程调度使进程正常活动起来。
2)中级调度将暂时不能运行的进程挂起,中级调度处于作业调度和进程调度之间。
3)作业调度次数少,中级调度次数略多,进程调度频率最高。
4)进程调度是最基本的,不可或缺。

2.2.2 调度的实现

1. 调度程序(调度器)

用于调度和分派 CPU 的组件称为调度程序,它通常由三部分组成,如图 2.8 所示。

1)排队器。将系统中的所有就绪进程按照一定的策略排成一个或多个队列,以便于调度程序选择。每当有一个进程转变为就绪态时,排队器便将它插入相应的就绪队列。
2)分派器。依据调度程序所选的进程,将其从就绪队列中取出,将 CPU 分配给新进程。
3)上下文切换器。在对 CPU 进行切换时,会发生两对上下文的切换操作:第一对,将当前进程的上下文保存到其 PCB 中,再装入分派程序的上下文,以便分派程序运行;第二对,移 出分派程序的上下文,将新选进程的 CPU 现场信息装入 CPU 的各个相应寄存器。
        在上下文切换时,需要执行大量 load 和 store 指令,以保存寄存器的内容,因此会花费较多时间。现在已有硬件实现的方法来减少上下文切换时间。通常采用两组寄存器,其中一组供内核使用,一组供用户使用。这样,上下文切换时,只需改变指针,让其指向当前寄存器组即可。

2. 调度的时机、切换与过程

        调度程序是操作系统内核程序。请求调度的事件发生后,才可能运行调度程序,调度了新的就绪进程后,才会进行进程切换。理论上这三件事情应该顺序执行,但在实际的操作系统内核程序运行中,若某时刻发生了引起进程调度的因素,则不一定能马上进行调度与切换。

命题追踪 可以进行 CPU 调度的事件或时机(2012、2021)

现代操作系统中,应该进行进程调度与切换的情况如下:
1)创建新进程后,父进程和子进程都处于就绪态,因此需要决定是运行父进程还是运行子进程,调度程序可以合法地决定其中一个进程先运行。
2)进程正常结束或异常终止后,必须从就绪队列中选择某个进程运行。若没有就绪进程,则通常运行一个系统提供的闲逛进程。
3)当进程因 I/O 请求、信号量操作或其他原因被阻塞时,必须调度其他进程运行。
4)当 I/O 设备准备就绪后,发出 I/O 中断,原先等待 I/O 的进程从阻塞态变为就绪态,此时需要决定是让新的就绪进程投入运行,还是让中断发生时运行的进程继续执行。
        此外,在有些系统中,当有更紧急的任务(如更高优先级的进程进入就绪队列)需要处理时,或者当前进程的时间片用完时,也会被强行剥夺 CPU。
        进程切换往往在调度完成后立刻发生,它要求保存原进程当前断点的现场信息,恢复被调度进程的现场信息。现场切换时,操作系统内核将原进程的现场信息推入当前进程的内核堆栈来保存它们,并更新堆栈指针。内核完成从新进程的内核栈中装入新进程的现场信息、更新当前运行进程空间指针、重设 PC 寄存器等相关工作之后,开始运行新的进程。

不能进行进程的调度与切换的情况如下:
1)在处理中断的过程中。中断处理过程复杂,在实现上很难做到进程切换,而且中断处理是系统工作的一部分,逻辑上不属于某一进程,不应被剥夺 CPU 资源。
2)需要完全屏蔽中断的原子操作过程中。如加锁、解锁、中断现场保护、恢复等原子操作。在原子过程中,连中断都要屏蔽,更不应该进行进程调度与切换。
若在上述过程中发生了引起调度的条件,则不能马上进行调度和切换,应置系统的请求调度标志,直到上述过程结束后才进行相应的调度与切换。

3. 进程调度的方式

        所谓进程调度方式,是指当某个进程正在 CPU 上执行时,若有某个更为重要或紧迫的进程需要处理,即有优先权更高的进程进入就绪队列,此时应如何分配 CPU。
通常有以下两种进程调度方式:
        1)非抢占调度方式,也称非剥夺方式。是指当一个进程正在 CPU 上执行时,即使有某个更为重要或紧迫的进程进入就绪队列,仍然让正在执行的进程继续执行,直到该进程运行完成(如正常结束、异常终止)或发生某种事件(如等待 I/O 操作、在进程通信或同步中执行了 Block 原语)而进入阻塞态时,才将 CPU 分配给其他进程。
非抢占调度方式的优点是实现简单、系统开销小,适用于早期的批处理系统,但它不能用于分时系统和大多数的实时系统。
        2)抢占调度方式,也称剥夺方式。是指当一个进程正在 CPU 上执行时,若有某个更为重要或紧迫的进程需要使用 CPU,则允许调度程序根据某种原则去暂停正在执行的进程,将 CPU 分配给这个更为重要或紧迫的进程。
抢占调度方式对提高系统吞吐率和响应效率都有明显的好处。但 “抢占” 不是一种任意性行为,必须遵循一定的原则,主要有优先权、短进程优先和时间片原则等。

4. 闲逛进程

        当进程切换时,若系统中没有就绪进程,则会调度闲逛进程(Idle Process)运行,它的 PID 为 0。若没有其他进程就绪,则该进程就一直运行,并在指令周期后测试中断。闲逛进程的优先级最低,没有就绪进程时才会运行闲逛进程,只要有进程就绪,就会立即让出 CPU。
        闲逛进程不需要 CPU 之外的资源,它不会被阻塞。

5. 两种线程的调度

        1)用户级线程调度。因为内核并不知道线程的存在,所以内核还是和以前一样,选择一个进程,并给予时间控制。由进程中的调度程序决定哪个线程运行。
        2)内核级线程调度。内核选择一个特定线程运行,通常不用考虑该线程属于哪个进程。对被选择的线程赋予一个时间片,若超过了时间片,则会强制挂起该线程。
        用户级线程的线程切换在同一进程中进行,仅需少量的机器指令;内核级线程的线程切换需要完整的上下文切换、修改内存映像、使高速缓存失效,这就导致了若干数量级的延迟。

2.2.3 调度的目标

        不同的调度算法具有不同的特性,在选择调度算法时,必须考虑算法的特性。为了比较 CPU 调度算法的性能,人们提出了很多评价标准,下面介绍其中主要的几种:

命题追踪 作业执行的相关计算(2012、2016、2018、2019、2023、2024)
        1)CPU 利用率。CPU 是计算机系统中最重要和昂贵的资源之一,所以应尽可能使 CPU 保持 “忙” 状态,使这一资源利用率最高。CPU 利用率的计算方法如下:

注意:计算作业完成时间时,要注意 CPU 与设备、设备与设备之间是可以并行的。
        2)系统吞吐量。表示单位时间内 CPU 完成作业的数量。长作业需要消耗较长的 CPU 时间,因此会降低系统的吞吐量。而对于短作业,需要消耗的 CPU 时间较短,因此能提高系统的吞吐量。调度算法和方式的不同,也会对系统的吞吐量产生较大的影响。
        3)周转时间。指从作业提交到作业完成所经历的时间,是作业等待、在就绪队列中排队、在 CPU 上运行及 I/O 操作所花费时间的总和。周转时间的计算方法如下:
                                        周转时间=作业完成时间-作业提交时间
平均周转时间是指多个作业周转时间的平均值:

带权周转时间是指作业周转时间与作业实际运行时间的比值:

平均带权周转时间是指多个作业带权周转时间的平均值:
  
        4)等待时间。指进程处于等待 CPU 的时间之和,等待时间越长,用户满意度越低。CPU 调度算法实际上并不影响作业执行或 I/O 操作的时间,只影响作业在就绪队列中等待所花的时间。因此,衡量一个调度算法的优劣,常常只需简单地考察等待时间。
        5)响应时间。指从用户提交请求到系统首次产生响应所用的时间。在交互式系统中,周转时间不是最好的评价准则,一般采用响应时间作为衡量调度算法的重要准则之一。从用户角度来看,调度策略应尽量降低响应时间,使响应时间处在用户能接受的范围之内。
        要想得到一个满足所有用户和系统要求的算法几乎是不可能的。设计调度程序,一方面要满足特定系统用户的要求(如某些实时和交互进程的快速响应要求),另一方面要考虑系统整体效率(如减少整个系统的进程平均周转时间),同时还要考虑调度算法的开销。

2.2.4 进程切换

命题追踪 进程调度前后 CPU 模式的变化(2023)
        对通常的进程而言,其创建、撤销及要求由系统设备完成的 I/O 操作,都是利用系统调用而进入内核,再由内核中的相应处理程序予以完成的。进程切换同样是在内核的支持下实现的,因此可以说,任何进程都是在操作系统内核的支持下运行的,是与内核紧密相关的。

(1)上下文切换

命题追踪 切换进程时的操作(2024)
        切换 CPU 到另一个进程需要保存当前进程状态并恢复另一个进程的状态,这个任务称为上下文切换。进程上下文采用进程 PCB 表示,包括 CPU 寄存器的值、进程状态和内存管理信息等。当进行上下文切换时,内核将旧进程状态保存在其 PCB 中,然后加载经调度而要执行的新进程的上下文。在切换过程中,进程的运行环境产生实质性的变化。上下文切换的        流程如下:
1)挂起一个进程,将 CPU 上下文保存到 PCB,包括程序计数器和其他寄存器。
2)将进程的 PCB 移入相应的队列,如就绪、在某事件阻塞等队列。
3)选择另一个进程执行,并更新其 PCB。
4)恢复新进程的 CPU 上下文。
5)跳转到新进程 PCB 中的程序计数器所指向的位置执行。

(2)上下文切换的消耗

        上下文切换通常是计算密集型的,即它需要相当可观的 CPU 时间,在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间,所以上下文切换对系统来说意味着消耗大量的 CPU 时间。有些 CPU 提供多个寄存器组,这样,上下文切换就只需要简单改变当前寄存器组的指针。

(3)上下文切换与模式切换

        模式切换与上下文切换是不同的,模式切换时,CPU 逻辑上可能还在执行同一进程。用户进程最开始都运行在用户态,若进程因中断或异常进入内核态运行,执行完后又回到用户态刚被中断的进程运行。用户态和内核态之间的切换称为模式切换,而不是上下文切换,因为没有改变当前的进程。上下文切换只能发生在内核态,它是多任务操作系统中的一个必需的特性。

        注意:调度和切换的区别:调度是指决定资源分配给哪个进程的行为,是一种决策行为;切换是指实际分配的行为,是执行行为。一般来说,先有资源的调度,然后才有进程的切换。

2.2.5 CPU 调度算法

命题追踪 各种调度算法的特点与对比(2009、2011、2014)
        操作系统中存在多种调度算法,有的调度算法适用于作业调度,有的调度算法适用于进程调度,有的调度算法两者都适用。下面介绍几种常用的调度算法。

1. 先来先服务(FCFS)调度算法

        FCFS 调度算法是一种最简单的调度算法,它既可用于作业调度,又可用于进程调度。
命题追踪 FCFS 调度算法的思想(2017)
        在作业调度中,FCFS 调度算法每次从后备作业队列中选择最先进入该队列的一个或几个作业,将它们调入内存,分配必要的资源,创建进程并放入就绪队列。
        在进程调度中,FCFS 调度算法每次从就绪队列中选择最先进入该队列的进程,将 CPU 分配给它,使之投入运行,直到运行完成或因某种原因阻塞时才释放 CPU。
命题追踪 批处理系统中作业完成时间的分析(2012、2016)
下面通过一个实例来说明 FCFS 调度算法的性能。假设系统中有 4 个作业,它们的提交时间分别是 8, 8.4, 8.8, 9,运行时间依次是 2, 1, 0.5, 0.2,系统采用 FCFS 调度算法,这组作业的平均等待时间、平均周转时间和平均带权周转时间见表 2.2。
        FCFS 调度算法属于不可剥夺算法。从表面上看,它对所有作业都是公平的,但若一个长作业先到达系统,就会使后面的许多短作业等待很长时间,因此它不能作为分时系统和实时系统的主要调度策略。但它常被结合在其他调度策略中使用。例如,在使用优先级作为调度策略的系统中,往往对多个具有相同优先级的进程按 FCFS 原则处理。
        FCFS 调度算法的特点是算法简单,但效率低;对长作业比较有利,但对短作业不利(相对 SJF 和高响应比);有利于 CPU 繁忙型作业,而不利于 I/O 繁忙型作业。

2. 短作业优先(SJF)调度算法

命题追踪 SJF 调度算法的思想(2017)
        短作业(进程)优先调度算法是指对短作业(进程)优先调度的算法。短作业优先(SJF)调度算法从后备队列中选择一个或几个估计运行时间最短的作业,将它们调入内存运行;短进程优先(SPF)调度算法从就绪队列中选择一个估计运行时间最短的进程,将 CPU 分配给它,使之立即执行,直到完成或发生某事件而阻塞时才释放 CPU。
        例如,考虑表 2.2 中给出的一组作业,若系统采用短作业优先调度算法,其平均等待时间、平均周转时间和平均带权周转时间见表 2.3。

SJF 算法也存在不容忽视的缺点:
命题追踪 饥饿现象的含义(2016)
        1)该算法对长作业不利,由表 2.2 和表 2.3 可知,SJF 调度算法中长作业的周转时间会增加。更严重的是,若有一长作业进入系统的后备队列,由于调度程序总是优先调度那些(即使是后进来的)短作业,将导致长作业长期不被调度,产生饥饿现象(注意区分死锁,后者是系统环形等待,前者是调度策略问题)。
        2)该算法完全未考虑作业的紧迫程度,因此不能保证紧迫性作业会被及时处理。
        3)由于作业的长短是根据用户所提供的估计执行时间而定的,而用户又可能有意或无意地缩短其作业的估计运行时间,致使该算法不一定能真正做到短作业优先调度。
        SPF 算法也可以是抢占式的(若未特别说明,则默认为非抢占式)。当一个新进程到达就绪队列时,若其估计执行时间比当前进程的剩余时间小,则立即暂停当前进程,将 CPU 分配给新进程。因此,抢占式 SPF 调度算法也称最短剩余时间优先调度算法。
        注意:短作业(SJF)调度算法的平均等待时间、平均周转时间是最优的。

3. 高响应比优先调度算法

        高响应比优先调度算法主要用于作业调度,是对 FCFS 调度算法和 SJF 调度算法的一种综合平衡,同时考虑了每个作业的等待时间和估计的运行时间。在每次进行作业调度时,先计算后备作业队列中每个作业的响应比,从中选出响应比最高的作业投入运行。
响应比的变化规律可描述为:

        根据公式可知:①作业的等待时间相同时,要求服务时间越短,响应比越高,有利于短作业,因此类似于 SJF。②要求服务时间相同时,作业的响应比由其等待时间决定,等待时间越长,其响应比越高,因此类似于 FCFS。③对于长作业,作业的响应比可以随等待时间的增加而提高,当其等待时间足够长时,也可获得 CPU,克服了 “饥饿” 现象。

4. 优先级调度算法

        优先级调度算法既可用于作业调度,又可用于进程调度。该算法中的优先级用于描述作业的紧迫程度。在作业调度中,优先级调度算法每次从后备作业队列中选择优先级最高的一个或几个作业,将它们调入内存,分配必要的资源,创建进程并放入就绪队列。在进程调度中,优先级调度算法每次从就绪队列中选择优先级最高的进程,将 CPU 分配给它,使之投入运行。

        根据新的更高优先级进程能否抢占正在执行的进程,可将该调度算法分为如下两种:
命题追踪 非抢占式优先级调度算法的应用分析(2018)
        1)非抢占式优先级调度算法。当一个进程正在 CPU 上运行时,即使有某个优先级更高的进程进入就绪队列,仍让正在运行的进程继续运行,直到由于其自身的原因让出 CPU 时(任务完成或等待事件),才将 CPU 分配给就绪队列中优先级最高的进程。
命题追踪 抢占式优先级调度算法的应用分析(2022、2023)
        2)抢占式优先级调度算法。当一个进程正在 CPU 上运行时,若有某个优先级更高的进程进入就绪队列,则立即暂停正在运行的进程,将 CPU 分配给优先级更高的进程。

而根据进程创建后其优先级是否可以改变,可将进程优先级分为以下两种:
命题追踪 静态优先级和动态优先级的分析(2016)
1)静态优先级。优先级是在创建进程时确定的,且在进程的整个运行期间保持不变。确定静态优先级的主要依据有进程类型、进程对资源的要求、用户要求。优点是简单易行,系统开销小;缺点是不够精确,可能出现优先级低的进程长期得不到调度的情况。
命题追踪 调整进程优先级的合理时机(2010)
2)动态优先级。创建进程时先赋予进程一个优先级,但优先级会随进程的推进或等待时间的增加而改变,以便获得更好的调度性能。例如,规定优先级随等待时间的增加而提高,于是,对于优先级初值较低的进程,在等待足够长的时间后也可获得 CPU。

一般来说,进程优先级的设置可以参照以下原则:
1)系统进程 > 用户进程。系统进程作为系统的管理者,理应拥有更高的优先级。
2)交互型进程 > 非交互型进程(或前台进程 > 后台进程)。大家平时在使用手机时,在前台运行的正在和你交互的进程应该更快速地响应你,因此自然需要被优先处理。
命题追踪 进程优先级的设置:I/O 型和计算型(2013)
3)I/O 型进程 > 计算型进程。所谓 I/O 型进程,是指那些会频繁使用 I/O 设备的进程,而计算型进程是那些频繁使用 CPU 的进程(很少使用 I/O 设备)。我们知道,I/O 设备(如打印机)的处理速度要比 CPU 慢得多,因此若将 I/O 型进程的优先级设置得更高,就更有可能让 I/O 设备尽早开始工作,进而提升系统的整体效率。

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

命题追踪 时间片轮转调度算法的原理(2021、2024)
        时间片轮转(RR)调度算法主要适用于分时系统。这种算法最大的特点是公平,系统将所有的就绪进程按 FCFS 策略排成一个就绪队列,每隔一定的时间(如 30ms)便产生一次时钟中断,激活调度程序进行调度,将 CPU 分配给就绪队列的队首进程,并令其执行一个时间片。在执行完一个时间片后,即使进程并未运行完成,它也必须释放出(被剥夺)CPU 给就绪队列的新队首进程。

        在 RR 调度算法中,若一个时间片尚未用完而当前进程已运行完成,则调度程序会被立即激活;若一个时间片用完,则产生一个时钟中断,由时钟中断处理程序来激活调度程序。
命题追踪 时间片轮转调度算法的特点(2017)
        在 RR 调度算法中,时间片的大小对系统性能的影响很大。若时间片足够大,以至于所有进程都能在一个时间片内执行完毕,则时间片轮转调度算法就退化为先来先服务调度算法。若时间片很小,则 CPU 将在进程间过于频繁地切换,使 CPU 的开销增大,而真正用于运行用户进程的时间将减少。因此,时间片的大小应选择适当,时间片的长短通常由以下因素确定:系统的响应时间、就绪队列中的进程数目和系统的处理能力。

6. 多级队列调度算法

        前述的各种调度算法,由于系统中仅设置一个进程的就绪队列,即调度算法是固定且单一的,无法满足系统中不同用户对进程调度策略的不同要求。在多 CPU 系统中,这种单一调度策略实现机制的缺点更为突出,多级队列调度算法能在一定程度上弥补这一缺点。
        该算法在系统中设置多个就绪队列,将不同类型或性质的进程固定分配到不同的就绪队列。每个队列可实施不同的调度算法,因此,系统针对不同用户进程的需求,很容易提供多种调度策略。同一队列中的进程可以设置不同的优先级,不同的队列本身也可以设置不同的优先级。在多 CPU 系统中,可以很方便为每个 CPU 设置一个单独的就绪队列,每个 CPU 可实施各自不同的调度策略,这样就能根据用户需求将多个线程分配到一个或多个 CPU 上运行。

7. 多级反馈队列调度算法(融合了前几种算法的优点)

命题追踪 多级反馈队列调度算法的应用分析(2019)
        多级反馈队列调度算法是时间片轮转调度算法和优先级调度算法的综合与发展,通过动态调整进程优先级和时间片大小,多级反馈队列调度算法可以兼顾多方面的系统目标。例如,为提高系统吞吐量和缩短平均周转时间而照顾短进程;为获得较好的 I/O 设备利用率和缩短响应时间而照顾 I/O 型进程;同时,也不必事先估计进程的执行时间。

命题追踪 多级反馈队列调度算法的实现思想(2020)
多级反馈队列调度算法的实现思想如下:
1)设置多个就绪队列,并为每个队列赋予不同的优先级。第 1 级队列的优先级最高,第 2 级队列的优先级次之,其余队列的优先级逐个降低。
2)赋予各个队列的进程运行时间片的大小各不相同。在优先级越高的队列中,每个进程的时间片就越小。例如,第 i + 1 级队列的时间片要比第 i 级队列的时间片长 1 倍。
3)每个队列都采用 FCFS 算法。新进程进入内存后,首先将它放入第 1 级队列的末尾,按 FCFS 原则等待调度。当轮到该进程执行时,如它能在该时间片内完成,便可撤离系统。若它在一个时间片结束时尚未完成,调度程序将其转入第 2 级队列的末尾等待调度;若它在第 2 级队列中运行一个时间片后仍未完成,再将它放入第 3 级队列,以此类推。当进程最后被降到第 n 级队列后,在第 n 级队列中便采用时间片轮转方式运行。
4)按队列优先级调度。仅当第 1 级队列为空时,才调度第 2 级队列中的进程运行;仅当第 1~i - 1 级队列均为空时,才会调度第 i 级队列中的进程运行。若 CPU 正在执行第 i 级队列中的某个进程时,又有新进程进入任何一个优先级较高的队列,此时须立即将正在运行的进程放回到第 i 级队列的末尾,而将 CPU 分配给新到的高优先级进程。

多级反馈队列的优势有以下几点:
1)终端型作业用户:短作业优先。
2)短批处理作业用户:周转时间较短。
3)长批处理作业用户:经过前面几个队列得到部分执行,不会长期得不到处理。

8. 基于公平原则的调度算法

        前面介绍的几种调度算法都只能保证满足要求的进程优先运行,但不能保证进程占用了多少 CPU 时间,也未考虑调度的公平性。本节介绍两种相对公平的调度算法。

(1)保证调度算法

        保证调度算法向用户做出明确的性能保证,而非优先运行保证。一种很实际且很容易实现的保证是:若系统中有 n 个用户登录,则每个用户都保证获得 1/n 的 CPU 时间;又如,若在单用户系统中有 n 个进程正在运行,则每个进程都保证获得 1/n 的 CPU 时间。
为了实现保证调度算法,系统必须具有下列功能:
1)跟踪各个进程自创建以来已获得了多少 CPU 时间。
2)计算各个进程应获得的 CPU 时间,即自创建以来的时间除以 n。
3)计算各个进程真正获得的 CPU 时间和应获得的 CPU 时间之比。若比率为 0.5,则说明一个进程只获得了应得时间的一半,而若比率为 2.0,则说明它获得了应得时间的 2 倍。
4)调度比率最小的进程持续运行,直到该进程的比率超过最接近它的进程的比率为止。

(2)公平分享调度算法

        保证对进程公平,但并不意味着对用户也公平。假设各个用户所拥有的进程数不同,如用户 1 启动 4 个进程而用户 2 只启动 1 个进程,采用 RR 调度,那么对每个进程而言很公平,用户 1 得到 80% 的 CPU 时间,而用户 2 只得到 20% 的 CPU 时间,显然对用户 2 有失公平。
        公平分享调度算法保证所有用户能获得相同的 CPU 时间,或所要求的时间比例。在这种方式下,不论用户启动多少进程,都能保证每个用户分配到应得的 CPU 份额。例如,系统中有两个用户,用户 1 有 4 个进程 A、B、C 和 D,而用户 2 只有 1 个进程 E,若采用 RR 调度,为保证两个用户能获得相同的 CPU 时间,一个满足条件的调度序列是

         A E B E C E D E A E B E C E D E …
若用户 1 获得的 CPU 时间是用户 2 的两倍,则可能的调度序列是

          A B E C D E A B E C D E …

表 2.4 总结了几种常见进程调度算法的特点,读者要在理解的基础上掌握。

表 2.4 几种常见进程调度算法的特点

先来先服务

短作业优先

高响应比优先

时间片轮转

多级反馈队列

能否可抢占

可以

可以

队列内算法不一定

优点

公平,实现简单

平均等待时间、平均周转时间最优

兼顾长短作业

兼顾长短作业

兼顾长短作业,有较好的响应时间,可行性强

缺点

不利于短作业

长作业会饥饿,估计时间不易确定

计算响应比的开销大

平均等待时间较长,上下文切换浪费时间

最复杂

适用于

批处理系统

分时系统

相当通用

2.2.6 多处理机调度

        多处理机系统的调度较单处理机系统复杂,它与系统结构有关。
        非对称多处理机(Asymmetric MultiProcessing,AMP)大多采用主从式操作系统,内核驻留在主机上,而从机上只运行用户程序,进程调度由主机负责。当从机空闲时,便向主机发送一个索求进程的信号,在主机中有一个就绪队列,只要队列不为空,主机便从队首摘下一个进程分配给索求进程的从机。这种分配方式实现简单,缺点是主机太忙,容易成为系统瓶颈。
        对称多处理机(Symmetric MultiProcessing,SMP)的所有处理机都是相同的,因此由调度程序将任何一个进程分配给任何一个 CPU。本节主要讨论 SMP 系统的调度问题。

1. 亲和性和负载平衡

        当一个进程从一个 CPU 移到其他 CPU 上时,应将第一个 CPU 的缓存设置为无效,然后重新填充第二个 CPU 的缓存,这种操作的代价较高,因此系统应尽量避免将进程从一个 CPU 移到另一个 CPU,而应试图让一个进程运行在同一个 CPU 上,这称为处理器亲和性。
        对于 SMP 系统,应尽量保证所有 CPU 的负载平衡(也称负载均衡),以便充分利用多处理机的优点,否则,一个或多个 CPU 会空闲,而其他 CPU 会处于高负载状态,且有一些进程处于等待状态。负载平衡应设法将负载平均分配到 SMP 系统的所有 CPU 上。
        然而,负载平衡通常会抵消处理器亲和性带来的好处,保持一个进程运行在同一个 CPU 上的好处是可以利用它在该 CPU 的缓存。而将进程从一个 CPU 迁移到另一个 CPU 会失去这个好处。因此,在某些系统中,只有当不平衡达到一定程度后才移动进程。

2. 多处理机调度方案

方案一:公共就绪队列
        系统中仅设置一个公共就绪队列,所有 CPU 共享同一个就绪队列。这种方案很好地实现了负载平衡,因为 CPU 一旦空闲,它立刻从公共就绪队列中选择一个进程运行。缺点是各进程可能频繁地在不同的 CPU 上运行,处理器亲和性不好。
        提升处理器亲和性的方法有两种:软亲和和硬亲和。软亲和是指由调度程序尽量保持一个进程到某个 CPU 上,但这个进程也可以迁移到其他 CPU 上。硬亲和是指由用户进程通过系统调用,主动请求系统分配到固定的 CPU 上。例如,Linux 系统实现了软亲和,也支持硬亲和的系统调用。

方案二:私有就绪队列
        系统为每个 CPU 设置一个私有就绪队列,当 CPU 空闲时,就从各自的私有就绪队列中选择一个进程运行。这种方案很好地实现了处理器亲和性,缺点是必须进行负载平衡。
        平衡负载的方法通常有两种:推迁移和拉迁移。对于推迁移,一个特定的系统程序周期性检查每个 CPU 的负载,若发现不平衡,则从超载 CPU 的就绪队列中 “推” 一些进程到空闲 CPU 的就绪队列,从而平均分配负载。若一个 CPU 负载很低,则从超载 CPU 的就绪队列中 “拉” 一些进程到自己的就绪队列,发生拉迁移。在系统中,推迁移和拉迁移常被并行实现。

2.2.7 本节小结

本节开头提出的问题的参考答案如下。
1)为什么要进行 CPU 调度?
        若没有 CPU 调度,则意味着要等到当前运行的进程执行完毕后,下一个进程才能执行,而实际情况中,进程时常需要等待一些外部设备的输入,而外部设备的速度与 CPU 相比是非常缓慢的,若让 CPU 总是等待外部设备,则对 CPU 的资源是极大的浪费。而引进 CPU 调度后,可在运行进程等待外部设备时,将 CPU 调度给其他进程,从而提高 CPU 的利用率。用一句简单的话说,就是为了合理地处理计算机的软 / 硬件资源。
2)调度算法有哪几种?结合第 1 章学习的分时操作系统和实时操作系统,思考有没有哪种调度算法比较适合这两种操作系统。
        本节介绍的调度算法有先来先服务调度、短作业优先调度、优先级调度、高响应比优先调度、时间片轮转调度、多级队列调度、多级反馈队列调度 7 种。
        先来先服务算法和短作业优先算法无法保证及时地接收和处理问题,因此无法保证在规定的时间间隔内响应每个用户的需求,也同样无法达到实时操作系统的及时性需求。优先级调度算法按照任务的优先级进行调度,对于更紧急的任务给予更高的优先级,适合实时操作系统。
        高响应比优先调度算法、时间片轮转调度算法、多级反馈队列调度算法都能保证每个任务在一定时间内分配到时间片,并轮流占用 CPU,适合分时操作系统。
        本节主要介绍了 CPU 调度的概念。操作系统主要管理 CPU、内存、文件、设备几种资源,只要对资源的请求大于资源本身的数量,就会涉及调度。例如,在单处理器系统中,CPU 只有一个,而请求的进程却有多个,因此就需要 CPU 调度。出现调度的概念后,又有了一个问题,即如何调度、应该满足谁、应该让谁等待,这是调度算法所面对的问题;而应该满足谁、应该让谁等待,要遵循一定的准则。调度这一概念贯穿于操作系统的始终,读者在接下来的学习中,将接触到几种资源的调度问题。将它们与 CPU 调度的内容相对比,将发现有异曲同工之妙。

2.2.8 本节习题精选

一、单项选择题

1.中级调度的目的是( )。
A. 提高 CPU 的效率
B. 降低系统开销
C. 提高 CPU 的利用率
D. 节省内存

1.答案:D
        解析:中级调度主要目的是节省内存,将内存中处于阻塞态或长期不运行的进程挂起到外存,从而腾出空间给其他进程使用。当这些进程重新具备运行条件时,再从外存调入内存,恢复运行。

2.进程从创建态转换到就绪态的工作由( )完成。
A. 进程调度
B. 中级调度
C. 高级调度
D. 低级调度

2.答案:C
        解析:进程从创建态转换到就绪态是由高级调度完成的。高级调度(作业调度)的主要任务是从后备队列中选择一个或一批作业,为其创建 PCB,分配内存等其他资源,并将其插入就绪队列。

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

3.答案:B
        解析:设计调度算法时应考虑的指标有很多,比较常见的有公平性、资源利用率、平均周转时间、平均等待时间、平均响应时间。互斥性不是调度算法设计时需要考虑的指标,而是一种同步机制,用来保证多个进程访问临界资源时不会发生冲突。

4.时间片轮转调度算法是为了( )。
A. 多个用户能及时干预系统
B. 使系统变得高效
C. 优先级较高的进程得到及时响应
D. 需要 CPU 时间最少的进程最先做

4.答案:A
        解析:时间片轮转的主要目的是,使得多个交互的用户能够得到及时响应,使得每个用户以为 “独占” 计算机的使用,因此它并没有偏好,也不会对特殊进程做特殊服务。时间片轮转增加了系统开销,所以不会使得系统高效运转,吞吐量和周转时间均不如批处理。但其较快速的响应时间使得用户能够与计算机进行交互,改善了人机环境,满足用户需求。

5.在单处理器系统中,进程什么时候占用处理器及占用时间的长短是由( )决定的。
A. 进程相应的代码长度
B. 进程总共需要运行的时间
C. 进程特点和进程调度策略
D. 进程完成什么功能

5.答案:C
        解析:进程调度的时机与进程特点有关,如进程是 CPU 繁忙型还是 I/O 繁忙型、自身的优先级等。但仅有这些特点是不够的,能否得到调度还取决于进程调度策略,若采用优先级调度算法,则进程的优先级才起作用。至于占用处理器运行时间的长短,则要看进程自身,若进程是 I/O 繁忙型,运行过程中要频繁访问 I/O 端口,即可能频繁放弃 CPU,所以占用 CPU 的时间不会长,一旦放弃 CPU,则必须等待下次调度。若进程是 CPU 繁忙型,则一旦占有 CPU,就可能运行很长时间,但运行时间还取决于进程调度策略,大部分情况下,交互式系统为改善用户的响应时间,大多数采用时间片轮转算法,这种算法在进程占用 CPU 达到一定时间后,会强制将其换下,以保证其他进程的 CPU 使用权。因此选择选项 C。

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

6.答案:D
        解析:处于临界区的进程也可能因中断或抢占而导致调度。此外,若进程在临界区内请求的是一个需要等待的资源,比如打印机,则它主动放弃 CPU,让其他进程运行。

7.下列内容中,不属于进程上下文的是( )。
A. 进程现场信息
B. 进程控制信息
C. 中断向量
D. 用户堆栈

7.答案:C
        解析:当一个进程被执行时,CPU 的所有寄存器中的值(进程的现场信息)、进程的状态和控制信息以及堆栈中的内容被称为该进程的上下文。中断向量不属于进程上下文的一部分,而是一组指向中断处理程序的指针,存放在内存的固定位置。

8.下列关于进程上下文切换的叙述中,错误的是( )。
A. 进程上下文指进程的代码、数据以及支持进程执行的所有运行环境
B. 进程上下文切换机制实现了不同进程在一个处理器中交替运行的功能
C. 进程上下文切换过程中必须保存换下进程在切换处的程序计数器的值
D. 进程上下文切换过程中必须将换下进程的代码和数据从主存保存到磁盘

8.答案:D
        解析:上下文切换发生在操作系统调度一个新进程到处理器上运行的时候。一个重要的上下文信息就是程序计数器(PC)的值,当前进程被打断的 PC 值作为寄存器上下文的一部分保存在进程现场信息中。进程上下文切换过程中不涉及主存和磁盘的数据交换,选项 D 错误。

9.在支持页式存储管理和多线程技术的系统中,当一个进程中的线程 T1 切换到同一个进程中的线程 T2 执行时,操作系统需要执行的操作是( )。
I. 更新程序计数器的值 II. 更新栈基址寄存器的值
III. 更新页基址寄存器的值 IV. 更新进程打开文件表
A. I、II、III、IV
B. II、IV
C. I、II
D. I、III、IV

9.答案:C
        解析:当线程切换时,操作系统需要保存当前线程的程序计数器的值,并恢复新线程的程序计数器,以便从新线程的正确位置开始执行。在多线程条件下,每个线程拥有各自独立的栈,当线程切换时,需要保存当前线程的栈基址寄存器的值,并加载新线程的栈基址,以便正确管理栈空间。同一个进程中的线程共享进程的虚拟地址空间,因此不需要更新页基址寄存器的值。一个进程所打开的文件也是被这个进程中的所有线程共享的,因此不需要更新进程打开文件表。

10.( )有利于 CPU 繁忙型的作业,而不利于 I/O 繁忙型的作业。
A. 时间片轮转调度算法
B. 先来先服务调度算法
C. 短作业(进程)优先算法
D. 优先权调度算法

10.答案:B
        解析:FCFS 调度算法比较有利于长作业,而不利于短作业。CPU 繁忙型作业是指该类作业需要占用很长的 CPU 时间,而很少请求 I/O 操作,因此 CPU 繁忙型作业类似于长作业,采用 FCFS 可从容完成计算。I/O 繁忙型作业是指作业执行时需频繁请求 I/O 操作,即可能频繁放弃 CPU,所以占用 CPU 的时间不会太长,一旦放弃 CPU,则必须重新排队等待调度,所以采用 SJF 比较适合。时间片轮转法对于短作业和长作业的时间片都一样,所以地位也几乎一样。优先级调度有利于优先级高的进程,而优先级和作业时间长度是没有必然联系的。因此选择选项 B。

11.下面有关选择进程调度算法的准则中,不正确的是( )。
A. 尽快响应交互式用户的请求
B. 尽量提高处理器利用率
C. 尽可能提高系统吞吐量
D. 适当增长进程就绪队列的等待时间

11.答案:D
        解析:在选择进程调度算法时应考虑以下几个准则:①公平:确保每个进程获得合理的 CPU 份额;②有效:使 CPU 尽可能地忙碌;③响应时间:使交互用户的响应时间尽可能短;④周转时间:使批处理用户等待输出的时间尽可能短;⑤吞吐量:使单位时间处理的进程数尽可能最多。由此可见选项 D 不正确。

12.实时系统的进程调度,通常采用( )算法。
A. 先来先服务
B. 时间片轮转
C. 抢占式的优先级高者优先
D. 高响应比优先

12.答案:C
解析:实时系统必须能够及时地处理某些紧急的外部事件,因此普遍用高优先级,并用 “可抢占” 来确保实时处理。

13.支持多道程序设计的操作系统在运行过程中,不断地选择新进程运行来实现 CPU 的共享,但其中( )不是引起操作系统选择新进程的直接原因。
A. 运行进程的时间片用完
B. 运行进程出错
C. 运行进程要等待某一事件发生
D. 有新进程被创建进入就绪态

13.答案:D
        解析:操作系统选择新进程的直接原因是当前运行的进程不能继续运行。当运行的进程由于时间片用完、运行结束、出错、需要等待事件的发生、自我阻塞等,均可以激活调度程序进行重新调度,选择就绪队列的队首进程投入运行。新进程加入就绪队列不是引起调度的直接原因,当 CPU 正在运行其他进程时,该进程仍需等待。即使是在采用高优先级调度算法的系统中,一个最高优先级的进程进入就绪队列,也需要考虑是否允许抢占,当不允许抢占时,仍需等待。

14.进程(线程)调度的时机有( )。
I. 运行的进程(线程)运行完毕 II. 运行的进程(线程)所需资源未准备好
III. 运行的进程(线程)的时间片用完 IV. 运行的进程(线程)自我阻塞
V. 运行的进程(线程)出现错误
A. II、III、IV 和 V
B. I 和 III
C. II、IV 和 V
D. 全部都是

14.答案:D
        解析:进程(线程)调度的时机包括:运行的进程(线程)运行完毕、运行的进程(线程)自我阻塞、运行的进程(线程)的时间片用完、运行的进程(线程)所需的资源没有准备好(会阻塞进程)、运行的进程(线程)出现错误(会终止进程)。因此,选项 I、II、III、IV 和 V 都正确。

15.设有 4 个作业同时到达,每个作业的执行时间均为 2h,它们在一台处理器上按单道式运行,则平均周转时间为( )。
A. 1h
B. 5h
C. 2.5h
D. 8h

15.答案:B
解析:4 个作业的周转时间分别是 2h, 4h, 6h, 8h,所以 4 个作业的总周转时间为2+4+6+8=20h。此时,平均周转时间 = 各个作业周转时间之和 / 作业数 =20/4=5小时。

16.若每个作业只能建立一个进程,为了照顾短作业用户,应采用( );为了照顾紧急作业用户,应采用( );为了能实现人机交互,应采用( );而能使短作业、长作业和交互作业用户都满意,应采用( )。
A. FCFS 调度算法
B. 短作业优先调度算法
C. 时间片轮转调度算法
D. 多级反馈队列调度算法
E. 剥夺式优先级调度算法

16.答案:B、E、C、D
        解析:照顾短作业用户,选择短作业优先调度算法;照顾紧急作业用户,即选择优先级高的作业优先调度,采用基于优先级的剥夺调度算法;实现人机交互,要保证每个作业都能在一定时间内轮到,采用时间片轮转法;使各种作业用户满意,要处理多级反馈,所以选择多级反馈队列调度算法。

17.( )优先级是在创建进程时确定的,确定之后在整个运行期间不再改变。
A. 先来先服务
B. 动态
C. 短作业
D. 静态

17.答案:D
解析:优先级调度算法分静态和动态两种。静态优先级在进程创建时确定,之后不再改变。

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

18.答案:B
        解析:系统采用短作业优先调度算法,作业的执行顺序为J1​、J2​、J3​,J1​的周转时间为T1​,J2​的周转时间为T1​+T2​,J3​的周转时间为T1​+T2​+T3​,则平均周转时间为(T1​+T1​+T2​+T1​+T2​+T3​)/3=(3T1​+2T2​+T3​)/3。

19.设有三个作业,其运行时间分别是 2h, 5h, 3h,假定它们同时到达,并在同一台处理器上以单道方式运行,则平均周转时间最小的执行顺序是( )。
A. J1, J2, J3
B. J3, J2, J1
C. J2, J1, J3
D. J1, J3, J2

19.答案:D
        解析:在同一台处理器上以单道方式运行时,要想获得最短的平均周转时间,用短作业优先调度算法会有较好的效果。就本题目而言:选项 A 的平均周转时间 = (2+7+10)/3h=19/3h;选项 B 的平均周转时间 = (3+8+10)/3h=7h。选项 C 的平均周转时间 = (5+7+10)/3h=22/3h;选项 D 的平均周转时间 = (2+5+10)/3h=17/3h。

20.采用时间片轮转调度算法分配 CPU 时,当处于运行态的进程用完一个时间片后,它的状态是( )状态。
A. 阻塞
B. 运行
C. 就绪
D. 消亡

20.答案:C
        解析:处于运行态的进程用完一个时间片后,其状态会变为就绪态,等待下一次处理器调度。进程执行完最后的语句并使用系统调用 exit 请求操作系统删除它或出现一些异常情况时,进程才会终止。

21.一个作业 8:00 到达系统,估计运行时间为 1h。若 10:00 开始执行该作业,其响应比是( )。
A. 2
B. 1
C. 3
D. 0.5

21.答案:C

22.关于优先权大小的论述中,正确的是( )。
A. 计算型作业的优先权,应高于 I/O 型作业的优先权
B. 用户进程的优先权,应高于系统进程的优先权
C. 在动态优先权中,随着作业等待时间的增加,其优先权将随之下降
D. 在动态优先权中,随着进程执行时间的增加,其优先权降低

22.答案:D
        解析:优先级算法中,I/O 繁忙型作业要优于计算繁忙型作业,系统进程的优先权应高于用户进程的优先权。作业的优先权与长作业、短作业或系统资源要求的多少没有必然的关系。在动态优先权中,随着进程执行时间的增加其优先权随之降低,随着作业等待时间的增加其优先权相应上升。

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

23.答案:B
        解析:时间片轮转算法是按固定的时间配额来运行的,时间一到,不管是否完成,当前的进程必须撤下,调度新的进程,因此它是由时间配额决定的、是绝对可抢占的。而优先级算法和短进程优先算法都可分为抢占式和不可抢占式。

24.作业是用户提交的,进程是由系统自动生成的,除此之外,两者的区别是( )。
A. 两者执行不同的程序段
B. 前者以用户任务为单位,后者以操作系统控制为单位
C. 前者是批处理的,后者是分时的
D. 后者是可并发执行,前者则不同

24.答案:B
        解析:作业是从用户角度出发的,它由用户提交,以用户任务为单位;进程是从操作系统出发的,由系统生成,是操作系统的资源分配和独立运行的基本单位。

25.进程调度算法采用固定时间片轮转调度算法,当时间片过大时,就会使时间片轮转法算法转化为( )调度算法。
A. 高响应比优先
B. 先来先服务
C. 短进程优先
D. 以上选项都不对

25.答案:B
解析:时间片轮转调度算法在实际运行中也按先后顺序使用时间片,时间片过大时,我们可以认为其大于进程需要的运行时间,即转变为先来先服务调度算法。

26.有以下的进程需要调度执行(见下表):

1)若非抢占式短进程优先调度算法,问这 5 个进程的平均周转时间是多少?
2)若采用抢占式短进程优先调度算法,问这 5 个进程的平均周转时间是多少?
A. 8.62; 6.34
B. 8.62; 6.8
C. 10.62; 6.34
D. 10.62; 6.8

26.D
        对于这种类型的题目,我们可以采用广义甘特图来求解,甘特图的画法在 1.2 节的习题中已经有所介绍。我们直接给出甘特图(见下图),以非抢占为例。

        在 0 时刻,进程P1​到达,于是处理器分配给P1​,因为是不可抢占的,所以P1​一旦获得处理器,就会运行直到结束;在时刻 9,所有进程已经到达,根据短进程优先调度,会把处理器分配给P3​,接下来就是P5​;然后,因为P2​,P4​的预计运行时间一样,所以在P2​和P4​之间用先来先服务调度,优先把处理器分配给P2​,最后再分配给P4​,完成任务。
周转时间 = 完成时间 - 作业到达时间,从图中显然可以得到各进程的完成时间,于是P1​的周转时间是9−0=9;P2​的周转时间是16−0.4=15.6;P3​的周转时间是10−1=9;P4​的周转时间是20−5.5=14.5;P5​的周转时间是12−7=5;平均周转时间为(9+15.6+9+14.5+5)/5=10.62。
同理,抢占式的周转时间也可通过画甘特图求得,而且直观、不易出错。
抢占式的平均周转时间为 6.8。
甘特图在操作系统中有着广泛的应用,本节习题中会有不少这种类型的题目,若读者按照上面的方法求解,则解题时就可以做到胸有成竹。

27.有 5 个批处理作业 A, B, C, D, E 几乎同时到达,其预计运行时间分别为 10, 6, 2, 4, 8,其优先级(由外部设定)分别为 3, 5, 2, 1, 4,这里 5 为最高优先级。以下各种调度算法中,平均周转时间为 14 的是( )调度算法。
A. 时间片轮转(时间片为 1)
B. 优先级调度
C. 先来先服务(按照顺序 10, 6, 2, 4, 8)
D. 短作业优先

27.D
        当这五个批处理作业采用短作业优先调度算法时,平均周转时间 = [2+(2+4)+(2+4+6)+(2+4+6+8)+(2+4+6+8+10)]/5=14。
        这道题主要考查读者对各种优先调度算法的认识。若按照 18 题中的方法求解,则可能要花费一定的时间,但这是值得的,因为可以起到熟练基本方法的效果。在考试中很少会遇到操作量和计算量如此大的题目,所以读者不用担心。

28.使用抢占式最短剩余时间优先调度算法对下列进程进行调度,总周转时间是( )。

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

28. 答案:C

解析:根据各个进程的到达时间和预计运行时间,画出甘特图如下。可知各个进程的周转时间分别为4h、1h、8h、12h、2h,所以总周转时间为4+1+8+12+2=27h 。

29.假设系统采用多级反馈队列调度算法,系统中设置了三个不同优先级的队列 A、B 和 C,优先级 A > B > C,A 的时间片为 10ms,B 的时间片为 20ms,C 的时间片为 30ms。当 t = 0 时,进程 P1 到达,P1 所需的运行时间为 90ms;当 t = 30ms 时,进程 P2 到达,P2 所需的运行时间为 30ms,不考虑任何其他系统开销,进程 P1 的周转时间为( )。
A. 90ms
B. 100ms
C. 110ms
D. 120ms

29. 答案:D

        解析:新进程先进入第一级队列队尾。0−10ms时段,P1​在队列A运行;10−30ms时段,P1​在队列B运行;t=30ms时,P2​进入队列A,30−40ms时段,P2​在队列A运行;40−60ms时段,P2​在队列B运行并结束;最后P1​在60−90ms和90−120ms时段,于队列C运行 2 个时间片,P1​总周转时间为120ms 。

30.分时操作系统通常采用( )调度算法来为用户服务。
A. 时间片轮转
B. 先来先服务
C. 短作业优先
D. 优先级

30. 答案:A

解析:分时系统需同时满足多个用户需求,把处理器时间轮流分配给多个用户作业,采用时间片轮转调度算法。

31.在进程调度算法中,对短进程不利的是( )。
A. 短进程优先调度算法
B. 先来先服务调度算法
C. 高响应比优先调度算法
D. 多级反馈队列调度算法

31. 答案:B

解析:先来先服务调度算法中,长进程(作业)先到达会使后面短进程(作业)等待很久,对短进程(作业)不利。

32.假设系统中所有进程同时到达,则使进程平均周转时间最短的是( )调度算法。
A. 先来先服务
B. 短进程优先
C. 时间片轮转
D. 优先级

32. 答案:B

解析:短进程优先调度算法平均周转时间最短。因为各进程执行时间固定,变化的是等待时间,短进程优先算法能最小化等待时间。

33.多级反馈队列调度算法不具备的特性是( )。
A. 资源利用率高
B. 响应速度快
C. 系统开销小
D. 并发度高

33. 答案:C

解析:系统开销小不是多级反馈队列调度算法特性,该算法需设置多个就绪队列,在队列间转移和抢占进程,增加了系统开销。

34.下列调度算法中,系统开销最小的调度算法是( )。
A. 高响应比优先调度算法
B. 多级反馈队列调度算法
C. 先来先服务调度算法
D. 时间片轮转调度算法

34. 答案:C

        解析:高响应比优先算法需根据进程等待和服务时间计算响应比;多级反馈队列算法涉及多队列管理及进程转移,系统开销大;时间片轮转算法要为进程分配固定时间片和进行上下文切换,开销也不小;先来先服务算法按进程到达先后顺序调度,无需进行优先级或时间片判断分配,系统开销最小。

35.下列进程调度算法中,可能导致饥饿现象的有( )。
I. 先来先服务调度算法 II. 短作业优先调度算法
III. 优先级调度算法 IV. 时间片轮转调度算法
A. I 和 II
B. II 和 III
C. II、III 和 IV
D. III

35. 答案:B

        解析:先来先服务算法和时间片轮转算法按进程到达顺序或固定时间片调度,不会因进程特征忽略某些进程,无饥饿现象;短作业优先算法(特殊优先级算法)和优先级算法根据进程服务时间或优先级调度,可能使长作业或低优先级进程长期得不到调度,产生饥饿现象。

36.与单处理机调度相比,多处理机调度需要额外考虑的调度目标是( )。
I. 负载平衡 II. 处理器亲和性 III. 进程周转时间
A. I 和 II
B. II 和 III
C. I 和 III
D. I、II 和 III

36. 答案:A

        解析:多处理机调度需额外考虑负载平衡和处理器亲和性。负载平衡指让每个 CPU 同等忙碌;处理器亲和性指让进程尽量在同一 CPU 上运行。

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

37. 答案:D

        解析:响应比 =(等待时间 + 执行时间)÷ 执行时间 ,综合考虑进程等待和执行时间,短进程优先执行提高系统吞吐量,长进程响应比随等待时间增加而提高,不会无法调度。

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

38. 答案:A

解析:选项 A 中进程时间片用完,可降低优先级让其他进程调度执行;选项 B 中进程刚完成 I/O 进入就绪队列,为尽快处理 I/O 结果应提高优先级;选项 C 中进程长期在就绪队列,为避免饥饿应提高优先级;选项 D 中进程优先级不应此时降低,应在时间片用完后降低 。

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

39. 答案:B

解析:高响应比优先算法在等待时间相同情况下,作业执行时间越短响应比越高,满足短任务优先,长作业等待时间增加响应比变大,执行机会增大,不会发生饥饿现象;先来先服务和时间片轮转不符短任务优先,非抢占式短任务优先会产生饥饿现象。

40.【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

40. 答案:B

解析:因为P2​比P1​晚5ms到达,P1​先占用 CPU,通过画作业运行甘特图可得总时间为260ms 。

41.【2012 统考真题】
若某单处理器多进程系统中有多个就绪态进程,则下列关于处理机调度的叙述中,错误的是( )。
A. 在进程结束时能进行处理机调度
B. 创建新进程后能进行处理机调度
C. 在进程处于临界区时不能进行处理机调度
D. 在系统调用完成并返回用户态时能进行处理机调度

41.C
选项 A、B、D 显然属于可以进行 CPU 调度的情况。对于选项 C,处于临界区的进程也可能因中断或抢占而导致调度,此外,若进程在临界区内请求的是一个需要等待的资源,比如打印机,则它主动放弃 CPU,让其他进程运行。

42.【2013 统考真题】
某系统正在执行三个进程P1​,P2​和P3​,各进程的计算(CPU)时间和 I/O 时间比例如下表所示。

为提高系统资源利用率,合理的进程优先级设置应为( )。
A. P1​>P2​>P3​
B. P3​>P2​>P1​
C. P2​>P1​=P3​
D. P1​>P2​=P3​

42.B
为了合理地设置进程优先级,应综合考虑进程的 CPU 时间和 I/O 时间。对于优先级调度算法,一般来说,I/O 型作业的优先权高于计算型作业的优先权,这是由于 I/O 操作需要及时完成,它没有办法长时间地保存所要输入 / 输出的数据,所以考虑到系统资源利用率,要选择 I/O 繁忙型作业有更高的优先级。

43.【2014 统考真题】
下列调度算法中,不可能导致饥饿现象的是( )。
A. 时间片轮转
B. 静态优先数调度
C. 非抢占式短任务优先
D. 抢占式短任务优先

43.A
采用静态优先级调度且系统总是出现优先级高的任务时,优先级低的任务总是得不到 CPU 而产生饥饿现象;而短任务优先调度不管是抢占式的还是非抢占的,当系统总是出现新来的短任务时,长任务会总是得不到 CPU,产生饥饿现象,因此选项 B、C、D 都错误。

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

44.B
这类调度题目最好画图。因 CPU、输入设备、输出设备都只有一个,因此各操作步骤不能重叠,画出运行时的甘特图后,就能清楚地看到不同作业间的时序关系,如下图所示。

45.【2017 统考真题】
假设 4 个作业到达系统的时刻和运行时间如下表所示。

系统在t=2时开始作业调度。若分别采用先来先服务和短作业优先调度算法,则选中的作业分别是( )。
A. J2​,J3​
B. J1​,J4​
C. J2​,J4​
D. J1​,J3​

45. 答案:D

解析:系统在t=2时开始作业调度,此时J4​未到达。FCFS 调度算法作业来得越早优先级越高,选J1​;SJF 调度算法作业运行时间越短优先级越高,选J3​。

46.【2017 统考真题】下列有关基于时间片的进程调度的叙述中,错误的是( )。
A. 时间片越短,进程切换的次数越多,系统开销越大
B. 当前进程的时间片用完后,该进程状态由执行态变为阻塞态
C. 时钟中断发生后,系统会修改当前进程在时间片内的剩余时间
D. 影响时间片大小的主要因素包括响应时间、系统开销和进程数量等

46. 答案:B

        解析:进程切换有系统开销,切换次数越多开销越大,选项 A 正确;当前进程时间片用完后状态由执行态变为就绪态,选项 B 错误;时钟中断是系统周期性节拍,操作系统借此确定时间间隔等,选项 C 正确;现代操作系统根据响应时间、系统开销、进程数量等确定时间片大小,选项 D 正确。

47.【2018 统考真题】某系统采用基于优先权的非抢占式进程调度策略,完成一次进程调度和进程切换的系统时间开销为1μs。在T时刻就绪队列中有 3 个进程P1​、P2​和P3​,其在就绪队列中的等待时间、需要的 CPU 时间和优先权如下表所示。

若优先权值大的进程优先获得 CPU,从T时刻起系统开始进程调度,则系统的平均周转时间为( )。
A. 54μs
B. 73μs
C. 74μs
D. 75μs

47. 答案:D

        解析:由优先权可知进程执行顺序为P2​→P3​→P1​ 。P2​周转时间为1+15+24=40μs;P3​周转时间为18+1+24+1+36=80μs;P1​周转时间为30+1+24+1+36+1+12=105μs;平均周转时间为(40+80+105)÷3=225÷3=75μs 。

48.【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

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

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

50. 答案:C

        解析:时钟中断处理程序在每个时钟周期结束时执行操作,如更新内核时钟变量、当前进程占用 CPU 时间、时间片内剩余执行时间等。在分时系统时间片轮转调度中,时钟中断处理程序检查到时间片用完时触发进程调度,从就绪队列选进程分配时间片,修改进程控制块信息,将时间片用完进程放入就绪队列或结束其运行,涉及进程控制块、时钟中断处理程序、进程就绪队列;阻塞队列进程唤醒进入就绪队列后才参与调度,该调度过程不使用阻塞队列。

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

51. 答案:D

        解析:中断处理结束后,需返回原程序或重新选程序运行,在时间片轮转调度中,时钟中断处理结束且当前进程时间片用完会发生进程调度;当前进程阻塞时,放入阻塞队列,若就绪队列不空,调度新进程;进程执行结束会使当前进程释放 CPU,从就绪队列选进程获得 CPU;进程时间片用完,当前进程让出 CPU,选择就绪队列队首进程获得 CPU 。

52.【2022 统考真题】进程P0​、P1​、P2​和P3​进入就绪队列的时刻、优先级(值越小优先权越高)及 CPU 执行时间如下表所示。

若系统采用基于优先权的抢占式进程调度算法,则从0ms时刻开始调度,到 4 个进程都运行结束为止,发生进程调度的总次数为( )。
A. 4
B. 5
C. 6
D. 7

52. 答案:C

解析:0 时刻调度进程P0​获得 CPU ;10ms 时P2​进入就绪队列,调度P2​抢占获得 CPU ;15ms 时P3​进入就绪队列,调度P3​抢占获得 CPU ;25ms 时P3​执行完毕,调度P2​获得 CPU ;40ms 时P2​执行完毕,调度P0​获得 CPU ;130ms 时P0​执行完毕,调度P1​获得 CPU ;190ms 时P1​执行完毕,总共调度 6 次 。

53.【2023 统考真题】进程P1​、P2​和P3​进入就绪队列的时刻、优先级(值越大优先权越高)和 CPU 执行时间如下表所示。

若系统采用基于优先权的抢占式 CPU 调度算法,从0ms时刻开始进行调度,则P1​、P2​和P3​的平均周转时间为( )。
A. 60ms
B. 61ms
C. 70ms
D. 71ms

53. 答案:B

        解析:采用抢占式优先权调度算法,根据作业执行顺序如下图所示。

周转时间 = 完成时间 - 到达时间 。P1​周转时间 = 115−0=115ms;P2​周转时间 = 75−20=55ms;P3​周转时间 = 43−30=13ms,平均周转时间 = (115+55+13)÷3=61ms 。

54.【2024 统考真题】假设某系统使用时间片轮转调度算法进行 CPU 调度,时间片大小为5ms,系统共有 10 个进程,初始时均处于就绪队列,执行结束前仅处于执行态或就绪态。若队尾的进程P所需的 CPU 时间最短,时间为25ms,不考虑系统开销,则进程P的周转时间为( )。
A. 200ms
B. 205ms
C. 250ms
D. 295ms

54. 答案:C

        解析:系统用时间片轮转调度算法,最初 10 个进程在就绪队列,进程 P 在队尾,时间片 5ms 。假设t=0开始调度,前 9 个进程依次运行一个时间片,t=45ms时调度进程 P,运行 5ms 后,t=50ms开始新循环。进程 P 需 CPU 时间 25ms,共 5 轮循环,其他进程 CPU 时间比 P 长,这 5 轮循环中其余 9 个进程不会运行结束,进程 P 周转时间为50×5=250ms 。

55.【2024 统考真题】在支持页式存储管理的系统中,进程切换时操作系统需要执行的操作是( )。
I. 更新程序计数器的值
II. 更新栈基址寄存器的值
III. 更新页表基地址寄存器的值
A. 仅 III
B. 仅 I、II
C. 仅 I、III
D. I、II、III

55. 答案:D

        解析:进程切换需上下文切换,包括将当前 CPU 寄存器内容保存到当前进程 PCB;将新进程现场信息装入 CPU 寄存器;更新程序计数器(PC)值。栈基址寄存器指明内核栈位置,页表基址寄存器指明顶级页表基址,属进程控制信息,进程切换时选项 I、II、III 均需更新 。

二、综合应用题

1.为什么说多级反馈队列调度算法能较好地满足各类用户的需要?

01. 解答

        多级反馈队列调度算法能较好满足各类用户需求。对于终端型作业用户,其作业多为交互型且较短,若能在第 1 级队列规定时间片内完成,用户会满意;对于短批处理作业用户,作业开始类似终端型作业,若在第 1 级队列一个时间片内可完成,能获与终端型作业相同响应时间,稍长作业在第 2、3 级队列各执行一个时间片也可完成,周转时间仍短;对于长批处理作业用户,长作业依次在第 1, 2, …, n级队列运行,按时间片轮转方式,不用担心作业长期得不到处理 。

2.将一组进程分为 4 类,如下图所示。各类进程之间采用优先级调度算法,而各类进程的内部采用时间片轮转调度算法。请简述P1​、P2​、P3​、P4​、P5​、P6​、P7​、P8​进程的调度过程。

02. 解答

        各类进程间用优先级调度算法,同类进程内部用时间片轮转调度算法。系统先对优先级为 4 的P1​、P2​、P3​用时间片轮转调度算法运行;当P1​、P2​、P3​都运行结束或无运行进程(处于等待态或部分结束、其余等待)时,对优先级为 3 的P4​、P5​用时间片轮转调度算法运行。期间若未结束的P1​、P2​、P3​转为就绪态,时间片用完后回到优先级 4 调度。类似地,当P1​ - P5​都运行结束或无运行进程时,对优先级为 2 的P6​、P7​、P8​用时间片轮转调度算法运行,若P1​ - P5​中有转为就绪态的,时间片用完后回到相应优先级调度 。

3.有一个 CPU 和两台外设D1​、D2​,且在能够实现抢占式优先级调度算法的多道程序环境中,同时进入优先级由高到低的P1​、P2​、P3​三个作业,每个作业的处理顺序和使用资源的时间如下:
P1​:D2​(30ms),CPU(10ms),D1​(30ms),CPU(10ms)
P2​:D1​(20ms),CPU(20ms),D2​(40ms)
P3​:CPU(30ms),D1​(20ms)
假设忽略不计其他辅助操作的时间,每个作业的周转时间T1​、T2​、T3​分别为多少?CPU 和D1​的利用率各是多少?

03. 解答

        采用抢占式优先级调度算法,根据作业执行顺序如下图所示。

        作业P1​优先级最高,周转时间等于运行时间,T1​=80ms;作业P2​等待时间为 10ms,运行时间为 80ms,周转时间T2​=(10+80)ms=90ms;作业P3​等待时间为 40ms,运行时间为 50ms,周转时间T3​=90ms 。

        三个作业从进入系统到全部运行结束时间为 90ms 。CPU 与外设是独占设备,运行时间为各作业使用时间之和 。CPU 运行时间为[(10+10)+20+30]ms=70ms,D1​为(30+20+20)ms=70ms,D2​为(30+40)ms=70ms,利用率均为70÷90=77.8% 。

4.有三个作业A、B、C,它们分别单独运行时的 CPU 和 I/O 占用时间如下图所示。

现在请考虑三个作业同时开始执行。系统中的资源有一个 CPU 和两台输入 / 输出设备(IO1​和IO2​)同时运行。三个作业的优先级为A最高,B次之,C最低,一旦低优先级的进程开始占用 CPU 或 I/O 设备,高优先级进程也要等待到其结束后方可占用。
请回答下面的问题:
1)最早结束的作业是哪个?
2)最后结束的作业是哪个?
3)计算这段时间 CPU 的利用率(三个作业全部结束为止)。

04. 解答

作业 A、B、C 优先级依次递减,采用不可抢占的优先级调度 。时刻 40,作业 C 释放 CPU,作业 A 获得 CPU;时刻 60,作业 A 释放 CPU,作业 B 获得 CPU;时刻 100,作业 B 释放 CPU,作业 A 获得 CPU;时刻 110,作业 A 释放 CPU,作业 C 获得 CPU;时刻 130,作业 C 释放 CPU,作业 B 获得 CPU;时刻 160,作业 B 释放 CPU,作业 A 获得 CPU 。运行图可知 :

1.最早结束的是作业 B 。

2.最后结束的是作业 A 。

3.三个作业从开始到全部执行结束经历 210ms,单 CPU 系统中,CPU 运行时间为各作业 CPU 运行时间之和,即[(20+10+20)+(40+30)+(40+20)]ms=180ms ,CPU 利用率为180÷210=85.7% 。

5.假定要在一台处理器上执行下表所示的作业,且假定这些作业在时刻0以1,2,3,4,5的顺序到达。说明分别使用 FCFS、RR(时间片=1)、SJF 及非剥夺式优先级调度算法时,这些作业的执行情况(优先级的高低顺序依次为1到5)。针对上述每种调度算法,给出平均周转时间和平均带权周转时间。

05. 解答

        注意:SJF 平均周转时间最短,可用于检验计算 。

6.有一个具有两道作业的批处理系统,作业调度采用短作业优先调度算法,进程调度采用抢占式优先级调度算法。作业的运行情况见下表,其中作业的优先数即进程的优先数,优先数越小,优先级越高。

1)列出所有作业转入内存的时间及结束的时间(以分为单位)。
2)计算平均周转时间。

06.解答:
1)具有两道作业的批处理系统,内存只存放两道作业,它们采用抢占式优先级调度算法竞争 CPU,而将作业调入内存采用的是短作业优先调度。8:00,作业 1 到来,此时内存和 CPU 空闲,作业 1 进入内存并占用 CPU;8:20,作业 2 到来,内存仍有一个位置空闲,因此将作业 2 调入内存,又由于作业 2 的优先数高,相应的进程抢占 CPU,在此期间 8:30 作业 3 到来,但内存此时已无空闲,因此等待。直至 8:50,作业 2 执行完毕,此时作业 3、4 竞争空出的一道内存空间,作业 4 的运行时间短,因此先调入,但它的优先数低于作业 1,因此作业 1 先执行。到 9:10 时,作业 1 执行完毕,再将作业 3 调入内存,且由于作业 3 的优先数高而占用 CPU。所有作业进入内存的时间及结束的时间见下表。

2)平均周转时间为(70+30+90+90)/4=70min。

7.假设某计算机系统有 4 个进程,各进程的预计运行时间和到达就绪队列的时刻见下表(相对时间,单位为 “时间配额”)。试用可抢占式短进程优先调度算法和时间片轮转调度算法进行调度(时间配额为 2)。分别计算各个进程的调度次序及平均周转时间。

07.解答:
1)按照可抢先式短进程优先调度算法,进程运行时间见下表。
| 进程名 | 到达就绪队列时刻 | 预计执行时间 | 执行时间段 | 周转时间 |

时刻 0,进程P1​到达并占用处理器运行。

时刻 1,进程P2​到达,因其预计运行时间短,因此抢夺处理器进入运行,P1​等待。

时刻 2,进程P3​到达,因其预计运行时间长于正在运行的进程,进入就绪队列等待。

时刻 3,进程P4​到达,因其预计运行时间长于正在运行的进程,进入就绪队列等待。

时刻 5,进程P2​运行结束,调度器在就绪队列中选择短进程,P4​符合要求,进入运行,进程P1​和进程P3​则还在就绪队列等待。

时刻 10,进程P4​运行结束,调度器在就绪队列中选择短进程,P1​符合要求,再次进入运行,而进程P3​则还在就绪队列等待。

时刻 17,进程P1​运行结束,只剩下进程P3​,调度其运行。

时刻 26,进程P3​运行结束。
平均周转时间 = [(17−0)+(5−1)+(26−2)+(10−3)]/4=13。
2)时间片轮转算法按就绪队列的 FCFS 进行轮转,在时刻 2,P1​被挂到就绪队列队尾,队列顺序为P2​,P3​,P1​,此时P4​还未到达。按时间片轮转算法的进程时间分配见下表。

平均周转时间 = [(22−0)+(12−1)+(26−2)+(23−3)]/4=19.25。

08.假设一个计算机系统具有如下性能特征:处理一次中断平均需要500μs,一次进程调度平均需要花费1ms,进程的切换平均需要花费2ms。若该计算机系统的定时器每秒发出 120 次时钟中断,忽略其他 I/O 中断的影响,请问:
1)操作系统将百分之几的 CPU 时间分配给时钟中断处理程序?
2)若系统采用时间片轮转调度算法,24 个时钟中断为一个时间片,操作系统每进行一次进程的切换,需要花费百分之几的 CPU 时间?
3)根据上述结果,说明为了提高 CPU 的使用效率,可以采用什么对策。

08.解答:
在时间片轮转调度算法中,系统将所有就绪进程按到达时间的先后次序排成一个队列。进程调度程序总是选择队列中的第一个进程运行,且仅能运行一个时间片。在使用完一个时间片后,即使进程并未完成其运行,也必须将处理器交给下一个进程。时间片轮转调度算法是绝对可抢先的算法,由时钟中断来产生。
时间片的长短对计算机系统的影响很大。若时间片大到让一个进程足以完成其全部工作,则这种算法就退化为先来先服务算法。若时间片很小,则处理器在进程之间的转换工作会过于频繁,处理器真正用于运行用户程序的时间将减少,系统开销将增大。时间片的大小应能使分时用户得到好的响应时间,同时也使系统具有较高的效率。
由题目给定条件可知:
1)每秒产生 120 个时钟中断,每次中断的时间为1/120≈8.3ms,其中中断处理耗时为 500μs,那么其开销为500μs/8.3ms=6%。
2)每次进程切换需要 1 次调度、1 次切换,所以需要耗时1ms+2ms=3ms,每 24 个时钟为一个时间片,24×8.3ms=200ms。一次切换所占 CPU 的时间比3ms/200ms=1.5%。
3)为提高 CPU 的效率,一般情况下要尽量减少时钟中断的次数,如由每秒 120 次降低到 100 次,以延长中断的时间间隔。或将每个时间片的中断数量(时钟数)加大,如由 24 个中断加大到 36 个。也可优化中断处理程序,减少中断处理开销,如将每次 500μs 的时间降低到 400μs。若能这样,则时钟中断和进程切换的总开销占 CPU 的时间比为(36×400μs+1ms+2ms)/(1/100×36)≈4.8%。

09.设有 4 个作业J1​、J2​、J3​、J4​,它们的到达时间和计算时间见下表。若这 4 个作业在一台处理器上按单道方式运行,采用高响应比优先调度算法,试写出各作业的执行顺序、各作业的周转时间及平均周转时间。

作业号

到达时间

计算时间

J1​

8:00

2h

J2​

8:30

40min

J3​

9:00

25min

J4​

9:30

30min

09.解答:
作业的响应比可表示为

在时刻 8:00,系统中只有一个作业J1​,因此系统将它投入运行。在J1​完成(10:00)时,J2​、J3​、J4​的响应比分别为(90+40)/40,(60+25)/25,(30+30)/30,即3.25,3.4,2,因此应先将J3​投入运行。在J3​完成(10:25)时,J2​、J4​的响应比分别为(115+40)/40,(55+30)/30,即3.875,2.83,因此应先将J2​投入运行,待它运行完毕时(11:05),再将J4​投入运行,J4​的结束时间为 11:35。
可见作业的执行次序为J1​,J3​,J2​,J4​,各作业的运行情况见下表,它们的周转时间分别为 120min,155min,85min,125min,平均周转时间为 121.25min。

10.在一个有两道作业的批处理系统中,有一作业序列,其到达时间及估计运行时间见下表。系统作业采用最高响应比优先调度算法响应比等待时间估计运行时间估计运行时间。进程的调度采用短进程优先的抢占式调度算法。

作业号

到达时间 /min

估计运行时间 /min

J1​

10:00

35

J2​

10:10

30

J3​

10:15

45

J4​

10:20

20

J5​

10:30

30

1)列出各作业的执行时间,即列出每个作业运行的时间片段,如作业i的运行时间序列为 10:00—10:40,11:00—11:20,11:30-11:50结束。

2)计算这批作业的平均周转时间。

10.解答:
上述 5 个作业的运行情况如下图所示。

        本题涉及作业和进程两方面的调度,一个作业首先需要被调入内存,创建相应的进程,然后竞争 CPU,获得 CPU 的资源来执行。本题的条件是有两道作业的批处理系统,所以内存中同时最多只有两个进程存在,且同时最多只有一个进程能够获得 CPU 资源。
        在 10:00,因为只有J1​到达,因此将它调入内存,并将 CPU 调度给它。
        在 10:10,J2​到达,因此将J2​调入内存,但J1​只需再执行 25min,因此J1​继续执行。
虽然J3​、J4​、J5​分别在 10:15、10:20 和 10:30 到达,但因当时内存中已存放了两道作业,因此不能马上将它们调入内存。
        在 10:35,J1​结束。此时J3​、J4​、J5​的响应比[根据题意,响应比 = (等待时间 + 估计运行时间)/ 估计运行时间]分别为65/45,35/20,35/30,因此将J4​调入内存,并将 CPU 分配给内存中运行时间最短者,即J4​。
        在 10:55,J4​结束。此时J3​、J5​的响应比分别为85/45,55/30,因此将J3​调入内存,并将 CPU 分配给估计运行时间较短的J2​。
        在 11:25,J2​结束,作业调度程序将J5​调入内存,并将 CPU 分配给估计运行时间较短的J5​。
        在 11:55,J5​结束,将 CPU 分配给J3​。
        在 12:40,J3​结束。
通过上述分析,可知:
1)作业 1 的执行时间片段为 10:00—10:35(结束)。
作业 2 的执行时间片段为 10:55—11:25(结束)。
作业 3 的执行时间片段为 11:55—12:40(结束)。
作业 4 的执行时间片段为 10:35—10:55(结束)。
作业 5 的执行时间片段为 11:25—11:55(结束)。
2)它们的周转时间分别为 35min,75min,145min,35min,85min,因此它们的平均周转时间为 75min。

11.【2016 统考真题】

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

11.解答:
1)因为采用了静态优先数,当就绪队列中总有优先数较小的进程时,优先数较大的进程一直没有机会运行,所以会出现饥饿现象。
2)一种动态优先数计算方法为priority=nice+k1​×cpuTime−k2​×waitTime,其中k1​>0,k2​>0,分别用来调整cpuTime和waitTime在priority中所占的比例。若一个进程的运行时间较长,则其cpuTime就增加,进而降低其优先级;若一个进程的等待时间较长,则其waitTime增加,进而会提高其优先级。于是,waitTime就可使长时间等待的进程优先数减少,进而避免出现饥饿现象。

 2.3 同步与互斥

在学习本节时,请读者思考以下问题:

1.为什么要引入进程同步的概念?

2.不同的进程之间会存在什么关系?

3.当单纯用本节介绍的方法解决这些问题时会遇到什么新的问题吗?
        用 PV 操作解决进程之间的同步互斥问题是这一节的重点,统考中频繁考查这一内容,请读者务必多加练习,掌握好求解该类问题的方法。

2.3.1 同步与互斥的基本概念

        在多道程序环境下,进程是并发执行的,不同进程之间存在着不同的相互制约关系。为了协调进程之间的相互制约关系,引入了进程同步的概念。下面举一个简单的例子来帮大家理解这个概念。例如,让系统计算1+2×3,假设系统产生两个进程:一个是加法进程,一个是乘法进程。要让计算结果是正确的,一定要让加法进程发生在乘法进程之后,但实际上操作系统具有异步性,若不加以制约,加法进程发生在乘法进程之前是绝对有可能的,因此要制定一定的机制去约束加法进程,让它在乘法进程完成之后才发生,而这种机制就是本节要讨论的内容。

1.临界资源

命题追踪 给定代码的同步互斥分析(2016、 2021、 2023)

        虽然多个进程可以共享系统中的各种资源,但其中许多资源一次只能为一个进程所用,我们将一次仅允许一个进程使用的资源称为临界资源。许多物理设备都属于临界资源,如打印机等。此外,还有许多变量、数据等都可以被若干进程共享,也属于临界资源。

命题追踪 临界区和临界资源的分析(2024)

        对临界资源的访问,必须互斥地进行,在每个进程中,访问临界资源的那段代码称为临界区。为了保证临界资源的正确使用,可将临界资源的访问过程分成 4 个部分:

1.进入区。为了进入临界区使用临界资源,在进入区要检查可否进入临界区,若能进入临界区,则应设置正在访问临界区的标志,以阻止其他进程同时进入临界区。

2.临界区。进程中访问临界资源的那段代码,也称临界段。

3.退出区。将正在访问临界资源的标志清除。

4.剩余区。代码中的其余部分。

while(true){
    entry section;    //进入区
    critical section; //临界区
    exit section;     //退出区
    remainder section;//剩余区
}

2.同步
        同步亦称直接制约关系,是指为完成某种任务而建立的两个或多个进程,这些进程因为需要协调它们的运行次序而等待、传递信息所产生的制约关系。同步关系源于进程之间的相互合作。
        例如,输入进程 A 通过单缓冲向进程 B 提供数据。当该缓冲区空时,进程 B 不能获得所需数据而阻塞,一旦进程 A 将数据送入缓冲区,进程 B 就被唤醒。反之,当缓冲区满时,进程 A 被阻塞,仅当进程 B 取走缓冲数据时,才唤醒进程 A。

3.互斥
        互斥也称间接制约关系。当一个进程进入临界区使用临界资源时,另一个进程必须等待,当占用临界资源的进程退出临界区后,另一进程才允许去访问此临界资源。
        例如,在仅有一台打印机的系统中,有两个进程 A 和进程 B,若当进程 A 需要打印时,系统已将打印机分配给进程 B,则进程 A 必须阻塞。一旦进程 B 将打印机释放,系统便将进程 A 唤醒,并将其由阻塞态变为就绪态。

命题追踪 实现临界区互斥必须遵循的准则(2020)

为禁止两个进程同时进入临界区,同步机制应遵循以下准则:

1.空闲让进:临界区空闲时,可以允许一个请求进入临界区的进程立即进入临界区。

2.忙则等待:当已有进程进入临界区时,其他试图进入临界区的进程必须等待。

3.有限等待:对请求访问的进程,应保证能在有限时间内进入临界区,防止进程无限等待。

4.让权等待(原则上应该遵循,但非必须):当进程不能进入临界区时,应立即释放处理器,防止进程忙等待。

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

命题追踪 实现互斥的软 / 硬件方法的特点(2018)

1.软件实现方法

        在进入区设置并检查一些标志来标明是否有进程在临界区中,若已有进程在临界区,则在进入区通过循环检查进行等待,进程离开临界区后则在退出区修改标志。

(1)算法一:单标志法

        该算法设置一个公用整型变量turn,指示允许进入临界区的进程编号,当turn=0时,表示允许P0​进入临界区;当turn=1时,表示允许P1​进入临界区。进程退出临界区时将临界区的使用权赋予给另一个进程,当Pi​退出临界区时,将turn置为j(i=0、j=1或i=1、j=0)。

进程P0​:
while(turn!=0);  //进入区
critical section; //临界区
turn=1;          //退出区
remainder section;//剩余区
进程P1:
while(turn!=1);  //进入区
critical section; //临界区
turn=0;          //退出区
remainder section;//剩余区

        该算法可实现每次只允许一个进程进入临界区,但两个进程必须交替进入临界区,若某个进程不再进入临界区,则另一个进程也将无法进入临界区(违背 “空闲让进” 准则),易造成资源利用不充分。若P0​顺利进入临界区并离开,此时临界区空闲,但P1​没打算进入,turn=1一直成立,P0​就无法再次进入。

(2算法二:双标志先检查法

        该算法设置一个布尔型数组flag[2],用来标记各个进程想进入临界区的意愿,flag[i]=true表示Pi​想要进入临界区(i=0或1)。Pi​进入临界区前,先检查对方是否想进入临界区,若想,则等待;否则,将flag[i]置为true后,再进入临界区;当Pi​退出临界区时,将flag[i]置为false。

进程P0​:
while(flag[1]);  //进入区 ①
flag[0]=true;    //进入区 ③
critical section; //临界区
flag[0]=false;   //退出区
remainder section;//剩余区
进程P1​:
while(flag[0]);  //进入区 ②
flag[1]=true;    //进入区 ④
critical section; //临界区
flag[1]=false;   //退出区
remainder section;//剩余区

        优点:不用交替进入,可连续使用。缺点是P0​和P1​可能同时进入临界区(违背 “忙则等待” 准则)。原因是检查对方标志和设置自己标志的操作不是一气呵成,可能在这期间发生进程切换,导致双方都检查通过。

(3算法三:双标志后检查法

        算法二先检查对方标志再设置自己标志,会导致两个进程同时进入临界区。所以算法三先设置自己的标志,再检查对方的标志,若对方标志为true,则等待;否则,进入临界区。

进程P0​:
plaintext
flag[0]=true;    //进入区 ①
while(flag[1]);  //进入区 ③
critical section; //临界区
flag[0]=false;   //退出区
remainder section;//剩余区
进程P1​:
plaintext
flag[1]=true;    //进入区 ②
while(flag[0]);  //进入区 ④
critical section; //临界区
flag[1]=false;   //退出区
remainder section;//剩余区

        按此顺序执行,两个进程依次设置自己标志并检查对方标志,发现对方也想进入临界区,就会都争着进,结果谁也进不了(违背 “空闲让进” 准则),导致各进程长期无法访问临界区而 “饥饿”(违背 “有限等待” 准则)。

(4)算法四:Peterson 算法

        Peterson 算法结合了算法一和算法三的思想,利用flag[]解决互斥访问问题,利用turn解决 “饥饿” 问题。若双方都争着进入临界区,则可让进程将进入临界区的机会谦让给对方。即每个进程进入临界区之前,先设置自己的flag标志,再设置允许进入turn标志;之后,同时检测对方的flag和turn标志,保证双方同时要求进入临界区时,只允许一个进程进入。

进程P0​:
plaintext
flag[0]=true; 
turn=1; 
while(flag[1]&&turn==1);  //进入区
critical section; 
flag[0]=false; 
remainder section;
进程P1​:
plaintext
flag[1]=true; 
turn=0; 
while(flag[0]&&turn==0);  //进入区
critical section; 
flag[1]=false; 
remainder section;

        为进入临界区,Pi​先将flag[i]置为true,并将turn置为j,表示优先让对方Pj​进入临界区(i=0、j=1或i=1、j=0)。若双方试图同时进入,则turn几乎同时被置为i和j,但只有一个赋值语句的结果会保持,另一个也会执行,但会被立即重写。变量turn的最终值决定了哪个进程被允许先进入临界区,若turn的值为i,则Pi​进入临界区。当Pi​退出临界区时,将flag[i]置为false,允许Pj​进入临界区,Pj​在Pi​退出后很快就能进入。

        此算法很好地遵循了 “空闲让进”“忙则等待”“有限等待” 三个准则,但未遵循 “让权等待” 准则。相比于前三种算法,该算法是最好的,但仍不够完善。

2.硬件实现方法

        ·理解硬件实现,对学习后面的信号量很有帮助。计算机提供特殊硬件指令,允许对一个字的内容进行检测和修正,或对两个字的内容进行交换等。

(1)中断屏蔽方法

        当一个进程正在执行它的临界区代码时,防止其他进程进入其临界区的最简单方法是关中断。因为 CPU 只在发生中断时引起进程切换,所以屏蔽中断能保证当前运行的进程让临界区代码顺利执行完,进而保证互斥的正确实现,然后执行开中断。典型模式为:

……
关中断;
临界区;
开中断;
……

        ·缺点:①限制了 CPU 交替执行程序的能力,系统效率明显降低。②对内核来说,在它执行更新变量的几条指令期间,关中断很方便,但将关中断的权限交给用户不明智,若一个进程关中断后不再开中断,系统可能因此终止。③不适用于多处理器系统,因为在一个 CPU 上关中断不能防止进程在其他 CPU 上执行相同的临界区代码。

(2)硬件指令方法 ——TestAndSet 指令

        ·借助 TestAndSet 指令(简称 TS 指令)实现互斥,该指令是原子操作。其功能是读出指定标志后将该标志设置为真。指令功能描述如下:

boolean TestAndSet(boolean *lock){
    boolean old;
    old=*lock;        //old用来存放lock的旧值
    *lock=true;       //无论之前是否已加锁,都将lock置为true
    return old;       //返回lock的旧值
}

命题追踪 TestAndSet指令实现互斥的分析(2016)

        用 TS 指令管理临界区时,为每个临界资源设置一个共享布尔变量lock,表示资源的两种状态:true表示正被占用(已加锁);false表示空闲(未加锁),初值为false,可将lock视为一把锁。进程在进入临界区之前,先用 TS 指令检查lock值:①若为false,表示没有进程在临界区,可以进入,并将lock置为true,即关闭临界资源(加锁),使其他进程不能进入临界区;②若为true,表示有进程在临界区,进入循环等待,直到当前访问临界区的进程退出时解锁(将lock置为false)。利用 TS 指令实现互斥的过程描述如下:

while TestAndSet(&lock);  //加锁并检查
进程的临界区代码段;
lock=false;               //解锁
进程的其他代码;

        ·相比于软件实现方法,TS 指令将 “加锁” 和 “检查” 操作用硬件方式变成了一气呵成的原子操作。相比于关中断方法,由于 “锁” 是共享的,这种方法适用于多处理器系统。缺点是,暂时无法进入临界区的进程会占用 CPU 循环执行 TS 指令,不能实现 “让权等待” 。

(3)硬件指令方法 ——Swap 指令

·Swap 指令的功能是交换两个字(字节)的内容。其功能描述如下:

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

注意:以上对TS和Swap指令的描述仅为功能描述,它们由硬件逻辑实现,不会被中断。      

命题追踪        Swap指令与函数实现的分析(2023)

         ·用 Swap 指令管理临界区时,为每个临界资源设置一个共享布尔变量lock,初值为false;在每个进程中再设置一个局部布尔变量key,初值为true,用于与lock交换信息。从逻辑上看,Swap 指令和 TS 指令实现互斥的方法差别不大,都是先记录此时临界区是否已加锁(记录在变量key中),再将锁标志lock置为true,最后检查key,若key为false,说明之前没有其他进程对临界区加锁,于是跳出循环,进入临界区。其处理过程描述如下:

boolean key=true;
while(key!=false)
    Swap(&lock, &key);
进程的临界区代码段;
lock=false;
进程的其他代码;

        用硬件指令方法实现互斥的优点:①简单、容易验证其正确性;②适用于任意数量的进程,支持多处理器系统;③支持系统中有多个临界区,只需为每个临界区设立一个布尔变量。缺点:①等待进入临界区的进程会占用 CPU 执行 while 循环,不能实现 “让权等待”;②从等待进程中随机选择一个进程进入临界区,有的进程可能一直选不上,从而导致 “饥饿” 现象。

2.3.3 互斥锁

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

acquire(){                //获得锁的定义
    while(!available)     //忙等待
        ;
    available=false;      //获得锁
}
release(){                //释放锁的定义
    available=true;       //释放锁
}

        acquire()或release()的执行必须是原子操作,因此互斥锁通常采用硬件机制来实现。
        上述描述的互斥锁也称自旋锁,其主要缺点是忙等待,当有一个进程在临界区时,其他进程在进入临界区前必须连续循环调用acquire()。类似的还有前面介绍的单标志法、TS 指令和 Swap 指令。当多个进程共享同一 CPU 时,这种连续循环浪费了 CPU 周期。因此,互斥锁通常用于多处理器系统,一个线程可以在一个处理器上旋转,而不影响其他线程的执行。自旋锁的优点是,进程在等待锁期间,没有上下文切换,若上锁的时间较短,则等待代价不高。
        本节后面将研究如何使用互斥锁解决经典同步问题。

2.3.4 信号量

        信号量机制是一种功能较强的机制,可用来解决互斥与同步问题,它只能被两个标准的原语wait()和signal()访问,也可简写为P()和V(),或者简称P操作和V操作。
        原语是指完成某种功能且不被分割、不被中断执行的操作序列,通常可由硬件来实现。例如,前述的 TS 指令和 Swap 指令就是由硬件实现的原子操作。原语功能的不被中断执行特性在单处理机上可由软件通过屏蔽中断方法实现。原语之所以不能被中断执行,是因为原语对变量的操作过程若被打断,可能去运行另一个对同一变量的操作过程,从而出现临界段问题。

命题追踪 信号量的含义(2010)

1.整型信号量
        整型信号量被定义为一个用于表示资源数量的整型量S,相比于普通整型变量,对整型信号量的操作只有三种:初始化、wait操作和signal操作。wait操作和signal操作可描述为:

wait(S){                //相当于进入区
    while(S<=0);        //若资源数不够,则一直循环等待
    S=S - 1;            //若资源数够,则占用一个资源
}
signal(S){             //相当于退出区
    S=S + 1;            //使用完后,就释放一个资源
}

        在整型信号量机制中的wait操作,只要信号量S≤0,就会不断循环测试。因此,该机制并未遵循 “让权等待” 的准则,而是使进程处于 “忙等” 的状态。

2. 记录型信号量

        记录型信号量机制是一种不存在 “忙等” 现象的进程同步机制。除了需要一个用于代表资源数量的整型变量value外,再增加一个进程链表L,用于链接所有等待该资源的进程。记录型信号量得名于采用了记录型的数据结构。记录型信号量可描述为:

typedef struct{
    int value;
    struct process *L;
} semaphore;

命题追踪 wait () 操作导致线程状态的变化(2023)

命题追踪 遵循 “让权等待” 的互斥方法(2018)

相应的wait(S)和signal(S)的操作如下:

void wait(semaphore S){    //相当于申请资源
    S.value--;
    if(S.value<0){
        add this process to S.L;
        block(S.L);
    }
}

        对信号量S的一次P操作,表示进程请求一个该类资源,因此执行S.value−−,使系统中可供分配的该类资源数减1。当S.value<0时,表示该类资源已分配完毕,因此应调用block原语进行自我阻塞(当前运行的进程:运行态→阻塞态),主动放弃CPU,并插入该类资源的等待队列S.L,可见该机制遵循了 “让权等待” 准则。

void signal(semaphore S){  //相当于释放资源
    S.value++;
    if(S.value<=0){
        remove a process P from S.L;
        wakeup(P);
    }
}

        对信号量S的一次V操作,表示进程释放一个该类资源,因此执行S.value++,使系统中可供分配的该类资源数加1。若加1后仍是S.value≤0,则表示仍有进程在等待该类资源,因此应调用wakeup原语将S.L中的第一个进程唤醒(被唤醒进程:阻塞态→就绪态)。

3. 利用信号量实现进程互斥

命题追踪 利用信号量实现互斥的实现(2024)

        为了使多个进程能互斥地访问某个临界资源,需要为该资源设置一个互斥信号量S,其初值为1(可用资源数为1),然后将各个进程访问该资源的临界区置于P(S)和V(S)之间。这样,每个要访问该资源的进程在进入临界区之前,都要先对S执行P操作,若该资源此刻未被访问,则本次P操作必然成功,进程便可进入自己的临界区。这时,若再有其他进程也要进入自己的临界区,对S执行P操作必然失败,因此主动阻塞,从而保证了该资源能被互斥访问。当访问该资源的进程退出临界区后,要对S执行V操作,以便释放该临界资源。其实现如下:

semaphore S = 1;            //初始化信号量,初值为1
P1(){
   ...
    P(S);                   //准备访问临界资源,加锁
    进程P1的临界区;
    V(S);                   //访问结束,解锁
   ...
}
P2(){
   ...
    P(S);                   //准备访问临界资源,加锁
    进程P2的临界区;
    V(S);                   //访问结束,解锁
   ...
}

        S的取值范围为(−1,0,1)。当S=1时,表示两个进程都未进入临界区;当S=0时,表示有一个进程已进入临界区;当S=−1时,表示有一个进程正在临界区,另一个进程因等待而阻塞在阻塞队列中,需要被当前已在临界区运行的进程退出时唤醒。

        注意:对不同的临界资源需要设置不同的互斥信号量。P(S)和V(S)必须成对出现,缺少P(S)就不能保证对临界资源的互斥访问;缺少V(S)会使临界资源永远不被释放,从而使因等待该资源而阻塞的进程永远不能被唤醒。考试还可能考查多个资源的问题,有多少资源就将信号量初值设为多少,申请资源时执行P操作,释放资源时执行V操作。

4. 利用信号量实现同步

命题追踪 利用信号量实现同步(2024)

        同步源于进程之间的相互合作,要让本来异步的并发进程相互配合,有序推进。例如,进程P1​和P2​并发执行,存在异步性,因此二者交替推进的次序是不确定的,若P2​的语句y要使用P1​的语句x的运行结果,则必须保证语句y一定在语句x之后执行。为了实现这种同步关系,需要设置一个同步信号量S,其初值为0(可以这么理解:刚开始是没有这种资源的,P2​需要使用这种资源,而又只能由P1​产生这种资源)。其实现如下:

semaphore S = 0;            //初始化信号量,初值为0
P1(){
    x;                      //执行语句x
    V(S);                   //告诉进程P2,语句x已经完成
   ...
}
P2(){
   ...
    P(S);                   //检查语句x是否运行完成
    y;                      //获得x的运行结果,执行语句y
   ...
}

        若先执行到V(S),则执行S++后S=1。之后P2​执行到P(S)时,由于S=1,表示此时有可用资源,执行S−−后S=0,P操作中不会执行block原语,而继续往下执行语句y。
        若先执行到P(S),执行S−−后S=−1,表示此时没有可用资源,因此P操作中会执行block原语,P2​请求阻塞。P1​的语句x执行完后,执行V(S),执行S++后S=0,因此V操作中会执行wakeup原语,唤醒在该信号量对应的阻塞队列中的P2​,这样P2​就可以继续执行语句y。
        PV 操作实现同步互斥的简单总结:在同步问题中,若某个行为会提供某种资源,则在这个行为之后V这种资源;若某个行为要用到这种资源,则在这个行为之前P这种资源。在互斥问题中,P、V操作要紧夹使用临界资源的那个行为,中间不能有其他冗余代码。

5. 利用信号量实现前驱关系

命题追踪 信号量实现前驱关系的应用题(2020、2022)

        信号量也可用来描述程序或语句之间的前驱关系。图 2.10 给出了一个前驱关系举例,其中S1​,S2​,S3​,⋯,S6​是简单的程序段(只有一条语句)。

        其实,每对前驱关系都是一个同步问题,因此要为每对前驱关系设置一个同步信号量,其初值均为0。在 “前驱操作” 之后,对相应的同步信号量执行V操作,在 “后继操作” 之前,对相应的同步信号量执行P操作。以图 2.10 为例,S2​是S1​的后继,要用到S1​的资源,前面总结过,在同步问题中,要用到某种资源,就要在行为之前P这种资源;S2​是S4​,S5​的前驱,给S4​,S5​提供资源,因此要在S2​之后V由S4​和S5​所产生的资源。为保证S1​→S2​,S1​→S3​,S2​→S4​,S2​→S5​,S3​→S6​,S4​→S6​,S5​→S6​的前驱关系,需分别设置同步信号量

a12,a13,a24,a25,a36,a46,a56。其实现如下:

semaphore a12 = a13 = a24 = a25 = a36 = a46 = a56 = 0;  //初始化信号量
S1(){
   ...
    V(a12); V(a13);            //S1已经运行完成
}
S2(){
    P(a12);                    //检查S1是否运行完成
   ...
    V(a24); V(a25);            //S2已经运行完成
}
S3(){
    P(a13);                    //检查S1是否已经运行完成
   ...
    V(a36);                    //S3已经运行完成
}
S4(){
    P(a24);                    //检查S2是否已经运行完成
   ...
    V(a46);                    //S4已经运行完成
}
S5(){
    P(a25);                    //检查S2是否已经运行完成
   ...
    V(a56);                    //S5已经运行完成
}
S6(){
    P(a36);                    //检查S3是否已经运行完成
    P(a46);                    //检查S4是否已经运行完成
    P(a56);                    //检查S5是否已经运行完成
   ...
}

6. 分析进程同步和互斥问题的方法步骤

1)关系分析:找出问题中的进程数,并分析它们之间的同步和互斥关系。同步、互斥、前驱关系直接按照上面例子中的经典范式改写。

2)整理思路:找出解决问题的关键点,并根据做过的题目找出求解的思路。根据进程的操作流程确定P操作、V操作的大致顺序。

3)设置信号量:根据上面的两步,设置需要的信号量,确定初值,完善整理。

2.3.5 经典同步问题

命题追踪 程序并发执行的分析(2011、2018)

命题追踪 PV 操作的应用题(2009、2011、2013、2014、2015、2017、2019)

1. 生产者 - 消费者问题

问题描述:
        系统中有一组生产者进程和一组消费者进程,生产者每次生产一个产品并放入缓冲区,消费者每次从缓冲区中取出一个产品并消费。生产者和消费者共享一个初始为空、大小为n的缓冲区。只有当缓冲区不满时,生产者才能将产品放入缓冲区;否则必须等待。只有当缓冲区不空时,消费者才能从中取出产品;否则必须等待。缓冲区是临界资源,各进程必须互斥访问。

问题分析:

1)关系分析:生产者和消费者对缓冲区的访问是互斥关系,同时生产者和消费者又是一个相互协作的关系,只有生产者生产之后,消费者才能消费,它们也是同步关系。

2)整理思路:这里比较简单,只有生产者和消费者两个进程,正好是这两个进程存在着互斥关系和同步关系。那么需要解决的是互斥和同步 PV 操作的位置。

3)信号量设置:信号量mutex作为互斥信号量,用于控制互斥访问缓冲池,互斥信号量初值为1;信号量full用于记录当前缓冲池中的 “满” 缓冲区数,初值为0。信号量empty用于记录当前缓冲池中的 “空” 缓冲区数,初值为n。

        我们对同步互斥问题的介绍是一个循序渐进的过程。上面介绍了一个同步问题的例子和一个互斥问题的例子,下面来看生产者 - 消费者问题的例子是什么样的。

生产者 - 消费者进程的描述如下:

semaphore mutex = 1;        //临界区互斥信号量
semaphore empty = n;        //空闲缓冲区
semaphore full = 0;         //缓冲区初始化为空
producer(){
    while(1){
        生产一个产品
        P(empty); (要用什么,P一下)   //获取空缓冲区单元
        P(mutex); (互斥夹紧)         //进入临界区
        将产品放入缓冲区
        V(mutex); (互斥夹紧)         //离开临界区,释放互斥信号量
        V(full); (提供什么,V一下)   //满缓冲区数加1
    }
}
consumer(){
    while(1){
        P(full);                    //获取满缓冲区单元
        P(mutex);                   //进入临界区
        从缓冲区中取出一个产品
        V(mutex);                   //离开临界区,释放互斥信号量
        V(empty);                   //空缓冲区数加1
        消费产品
    }
}

        该类问题要注意对缓冲区大小为n的处理,当缓冲区中有空时,便可对empty变量执行P操作,一旦取走一个产品便要执行V操作以释放空闲区。对empty和full变量的P操作必须放在对mutex的P操作之前。若生产者进程先执行P(mutex),然后执行P(empty),消费者执行P(mutex),然后执行P(full),这样不可以。设想生产者进程已将缓冲区放满,消费者进程并没有取产品,即empty=0,当下次仍然是生产者进程运行时,它先执行P(mutex)封锁信号量,再执行P(empty)时将被阻塞,希望消费者取出产品后将其唤醒。轮到消费者进程运行时,它先执行P(mutex),然而生产者进程已经封锁mutex信号量,消费者进程也会被阻塞,这样一来生产者、消费者进程都将阻塞,都指望对方唤醒自己,因此陷入了无休止的等待。同理,若消费者进程已将缓冲区取空,即full=0,下次若还是消费者进程运行时,也会出现类似的死锁。不过生产者释放信号量时,mutex、full先释放哪一个无所谓,消费者先释放mutex或empty都可以。

        其实生产者 - 消费者问题只是一个同步互斥问题的综合而已。

问题描述:

        桌子上有一个盘子,每次只能向其中放入一个水果。爸爸专向盘子中放苹果,妈妈专向盘子中放橘子,儿子专等吃盘子中的橘子,女儿专等吃盘子中的苹果。只有盘子为空时,爸爸或妈妈才可向盘子中放一个水果;仅当盘子中有自己需要的水果时,儿子或女儿可以从盘子中取出。

问题分析:

        1)关系分析:由每次只能向盘中放一个水果可知,爸爸和妈妈是互斥关系。爸爸和女儿、妈妈和儿子是同步关系,而且这两对进程必须连起来,儿子和女儿之间没有互斥和同步关系,因为他们是选择条件执行,不可能并发。进程之间的关系如图 2.11 所示。

        2)整理思路:这里有 4 个进程,实际上可抽象为两个生产者和两个消费者被连接到大小为 1 的缓冲区上。

        3)信号量设置:首先将信号量plate设置互斥信号量,表示是否允许向盘子放入水果,初值为 1 表示允许放入,且只允许放入一个。信号量apple表示盘子中是否有苹果,初值为 0 表示盘子为空,不许取,apple=1表示可以取。信号量orange表示盘子中是否有橘子,初值为 0 表示盘子为空,不许取,orange=1表示可以取。

解决该问题的代码如下:

semaphore plate = 1, apple = 0, orange = 0;
dad(){                            //父亲进程
    while(1){
        准备一个苹果
        P(plate);                  //互斥向盘中取、放水果
        把苹果放入盘子
        V(apple);                  //允许取苹果
    }
}
mom(){                            //母亲进程
    while(1){
        准备一个橘子
        P(plate);                  //互斥向盘中取、放水果
        把橘子放入盘子
        V(orange);                 //允许取橘子
    }
}
son(){                             //儿子进程
    while(1){
        P(orange);                 //互斥向盘中取橘子
        从盘子中取出橘子
        V(plate);                  //允许向盘中取、放水果
        吃掉橘子
    }
}
daughter(){                        //女儿进程
    while(1){
        P(apple);                  //互斥向盘中取苹果
        从盘子中取出苹果
        V(plate);                  //允许向盘中取、放水果
        吃掉苹果
    }
}

        进程间的关系如图 2.11 所示。dad()和daughter()、mom()和son()必须连续执行,正因为如此,也只能在女儿拿走苹果后或儿子拿走橘子后才能释放盘子,即V(plate)操作。

2. 读者 - 写者问题

问题描述:

        有读者和写者两组并发进程,共享一个文件,当两个或以上的读进程同时访问共享数据时不会产生副作用,但若某个写进程和其他进程(读进程或写进程)同时访问共享数据时则可能导致数据不一致的错误。因此要求:①允许多个读者可以同时对文件执行读操作;②只允许一个写者往文件中写信息;③任意一个写者在完成写操作之前不允许其他读者或写者工作;④写者执行写操作前,应让已有的读者和写者全部退出。

问题分析:

        1.关系分析:由题目分析读者和写者是互斥的,写者和写者也是互斥的,而读者和读者不存在互斥问题。

        2.整理思路:两个进程,即读者和写者。写者是比较简单的,它和任何进程互斥,用互斥信号量的P操作、V操作即可解决。读者的问题比较复杂,它必须在实现与写者互斥的同时,实现与其他读者的同步,因此简单的一对P操作、V操作是无法解决问题的。这里用到了一个计数器,用它来判断当前是否有读者读文件。当有读者时,写者是无法写文件的,此时读者会一直占用文件,当没有读者时,写者才可以写文件。同时,这里不同读者对计数器的访问也应该是互斥的。

        3.信号量设置:首先设置信号量count为计数器,用于记录当前读者的数量,初值为 0;设置mutex为互斥信号量,用于保护更新count变量时的互斥;设置互斥信号量rw,用于保证读者和写者的互斥访问。

代码如下:

int count = 0;                     //用于记录当前的读者数量
semaphore mutex = 1;               //用于保护更新count变量时的互斥
semaphore rw = 1;                  //用于保证读者和写者互斥地访问文件
writer(){                          //写者进程
    while(1){
        P(rw);                     //互斥访问共享文件
        写文件
        V(rw);                     //释放共享文件
    }
}
reader(){                          //读者进程
    while(1){
        P(mutex);                  //互斥访问count变量
        if(count == 0)
            P(rw);                 //当第一个读进程读共享文件时,阻止写进程写
        count++;                   //读者计数器加1
        V(mutex);                  //释放互斥变量count
        读文件
        P(mutex);                  //互斥访问count变量
        count--;                   //读者计数器减1
        if(count == 0)
            V(rw);                 //当最后一个读进程读完共享文件,允许写进程写
        V(mutex);                  //释放互斥变量count
    }
}

        在上面的算法中,读进程是优先的,即当存在读进程时,写操作将被延迟,且只要有一个读进程活跃,随后而来的读进程都将被允许访问文件。这样的方式会导致写进程可能长时间等待,且存在写进程 “饿死” 的情况。

        若希望写进程优先,即当有读进程正在读共享文件时,有写进程请求访问,这时应禁止后续读进程的请求,等到已在共享文件的读进程执行完毕,立即让写进程执行,只有在无写进程执行的情况下才允许读进程再次运行。为此,增加一个信号量并在上面程序的writer()和reader()函数中各增加一对PV操作,就可以得到写进程优先的解决程序。

int count = 0;                     //用于记录当前的读者数量
semaphore mutex = 1;               //用于保护更新count变量时的互斥
semaphore rw = 1;                  //用于保证读者和写者互斥地访问文件
semaphore w = 1;                   //用于实现“写优先”
writer(){                          //写者进程
    while(1){
        P(w);                      //在无写进程请求时进入
        P(rw);                     //互斥访问共享文件
        写文件
        V(rw);                     //释放共享文件
        V(w);                      //恢复对共享文件的访问
    }
}
reader(){                          //读者进程
    while(1){
        P(w);                      //在无写进程请求时进入
        P(mutex);                  //互斥访问count变量
        if(count == 0)
            P(rw);                 //当第一个读进程读共享文件时,阻止写进程写
        count++;                   //读者计数器加1
        V(mutex);                  //释放互斥变量count
        V(w);                      //恢复对共享文件的访问
        读文件
        P(mutex);                  //互斥访问count变量
        count--;                   //读者计数器减1
        if(count == 0)
            V(rw);                 //当最后一个读进程读完共享文件,允许写进程写
        V(mutex);                  //释放互斥变量count
    }
}

        这里的写进程优先是相对而言的,有些书上将这个算法称为读 / 写公平法,即读 / 写进程具有一样的优先级。当一个写进程访问文件时,若先有一些读进程要求访问文件,后有另一个写进程要求访问文件,则当前访问文件的进程结束对文件的写操作时,会是一个读进程而不是一个写进程占用文件(在信号量w的阻塞队列上,因为读进程先来,因此排在阻塞队列队首,而V操作唤醒进程时唤醒的是队首进程),所以说这里的写优先是相对的,想要了解如何做到真正写者优先,可参考其他相关资料。

        读者 - 写者问题有一个关键的特征,即有一个互斥访问的计数器count,因此遇到一个不太好解决的同步互斥问题时,要想一想用互斥访问的计数器count能否解决问题。

3. 哲学家进餐问题

问题描述:

        一张圆桌边上坐着 5 名哲学家,每两名哲学家之间的桌上摆一根筷子,两根筷子中间是一碗米饭,如图 2.12 所示。哲学家们倾注毕生精力用于思考和进餐,哲学家在思考时,并不影响他人。只有当哲学家饥饿时,才试图拿起左、右两根筷子(一根一根地拿起)。若筷子已在他人手上,则需要等待。饥饿的哲学家只有同时拿到了两根筷子才可以开始进餐,进餐完毕后,放下筷子继续思考。

问题分析:

1)关系分析:5 名哲学家与左右邻居对其中间筷子的访问是互斥关系。

2)整理思路:显然,这里有 5 个进程。本题的关键是如何让一名哲学家拿到左右两根筷子而不造成死锁或饥饿现象。解决方法有两个:一是让他们同时拿两根筷子;二是对每名哲学家的动作制定规则,避免饥饿或死锁现象的发生。

3)信号量设置:定义互斥信号量数组chopstick[5]={1,1,1,1,1},用于对 5 个筷子的互斥访问。哲学家按顺序编号为0∼4,哲学家i左边筷子的编号为i,哲学家右边筷子的编号为(i+1)%5。

semaphore chopstick[5] = {1, 1, 1, 1, 1};  //定义信号量数组chopstick[5],并初始化
Pi(){                                      //i号哲学家的进程
    do{
        P(chopstick[i]);                    //取左边筷子
        P(chopstick[(i + 1)%5]);            //取右边筷子
        进餐
        V(chopstick[i]);                    //放回左边筷子
        V(chopstick[(i + 1)%5]);            //放回右边筷子
        思考
    } while(1);
}

        该算法存在以下问题:当 5 名哲学家都想要进餐并分别拿起左边的筷子时(都恰好执行完wait(chopstick[i])),筷子已被拿光,等到他们再想拿右边的筷子时(执行wait(chopstick[(i+1)%5]))就全被阻塞,因此出现了死锁。

        为防止死锁发生,可对哲学家进程施加一些限制条件,比如:①至多允许 4 名哲学家同时进餐,以保证至少有一名哲学家能拿到左右两边的筷子;②仅当一名哲学家左右两边的筷子都可用时,才允许他拿起筷子;③对哲学家顺序编号,要求奇数号哲学家先拿左边的筷子,然后拿右边的筷子,而偶数号哲学家刚好相反,以保证相邻的两名哲学家都想进餐时,只有一名哲学家可以拿起第一根筷子,而另一名哲学家会被阻塞。

        制定的正确规则如下:假设采用第二种方法,当一名哲学家左右两边的筷子都可用时,才允许他抓起筷子。

semaphore chopstick[5] = {1, 1, 1, 1, 1};  //初始化信号量
semaphore mutex = 1;                      //设置取筷子的信号量
Pi(){                                      //i号哲学家的进程
    do{
        P(mutex);                          //在取筷子前获得互斥量
        P(chopstick[i]);                    //取左边筷子
        P(chopstick[(i + 1)%5]);            //取右边筷子
        V(mutex);                          //释放取筷子的信号量
        进餐
        V(chopstick[i]);                    //放回左边筷子
        V(chopstick[(i + 1)%5]);            //放回右边筷子
        思考
    } while(1);
}

        熟悉 ACM 或有过相关训练的读者都应知道贪心算法,哲学家进餐问题的思想其实与贪心算法的思想截然相反,贪心算法强调争取眼前认为最好的,而不考虑后续会有什么后果。若哲学家进餐问题用贪心算法来解决,即只要眼前有筷子能拿起就拿起的话,就会出现死锁。然而,若不仅考虑眼前的一步,还考虑下一步,即不因为有筷子能拿起就拿起,而考虑能不能一次拿起两根筷子才做决定的话,就会避免死锁问题,这就是哲学家进餐问题的思维精髓。

        大部分习题和真题用消费者 - 生产者模型或读者 - 写者问题就能解决,但对哲学家进餐问题仍然要熟悉。考研复习的关键在于反复多次和全面,“偷工减料” 是要吃亏的。

2.3.6 管程

        在信号量机制中,每个要访问临界资源的进程都必须自备同步的 PV 操作,大量分散的同步操作给系统管理带来了麻烦,且容易因同步操作不当而导致系统死锁。于是,便产生了一种新的进程同步工具 —— 管程。管程的特性保证了进程互斥,无须程序员自己实现互斥,从而降低了死锁发生的可能性。同时管程提供了条件变量,可以让程序员灵活地实现进程同步。

1. 管程的定义

命题追踪 管程的特点(2016)

        系统中的各种硬件资源和软件资源,均可用数据结构抽象地描述其资源特性,即用少量信息和对资源所执行的操作来表征该资源,而忽略它们的内部结构和实现细节。

        利用共享数据结构抽象地表示系统中的共享资源,而将对该数据结构实施的操作定义为一组过程。进程对共享资源的申请、释放等操作,都通过这组过程来实现,这组过程还可以根据资源情况,或接受或阻塞进程的访问,确保每次仅有一个进程使用共享资源,这样就可以统一管理对共享资源的所有访问,实现进程互斥。这个代表共享资源的数据结构,以及由对该共享数据结构实施操作的一组过程所组成的资源管理程序,称为管程(monitor)。管程定义了一个数据结构和能为并发进程所执行(在该数据结构上)的一组操作,这组操作能同步进程和改变管程中的数据。

        由上述定义可知,管程由 4 部分组成:

        ·管程的名称;

        ·局部于管程内部的共享数据结构说明;

        ·对该数据结构进行操作的一组过程(或函数);

        ·对局部于管程内部的共享数据设置初始值的语句。

        管程的定义描述举例如下:

monitor Demo{
    //②定义共享数据结构,对应系统中的某种共享资源
    共享数据结构S;
    //④对共享数据结构初始化的语句
    init_code(){
        S = 5;    //初始资源数等于5
    }
    take_away(){  //③过程1:申请一个资源
        对共享数据结构S的一系列处理;
        S--;      //可用资源数-1
       ...
    }
    give_back(){  //③过程2:归还一个资源
        对共享数据结构S的一系列处理;
        S++;      //可用资源数+1
       ...
    }
}

        熟悉面向对象程序设计的读者看到管程的组成后,会立即联想到管程很像一个类(class)。

        ·管程将对共享资源的操作封装起来,管程内的共享数据结构只能被管程内的过程所访问。一个进程只有通过调用管程内的过程才能进入管程访问共享资源。对于上例,外部进程只能通过调用takea​way()过程来申请一个资源;归还资源也类似。

        ·每次仅允许一个进程进入管程,从而实现进程互斥。若多个进程同时调用takea​way()、giveb​ack(),则只有某个进程运行完它调用的过程后,下一进程才能开始运行它调用的过程。即各进程只能串行执行管程内的过程,这一特性保证了进程互斥访问S。

2. 条件变量

        当一个进程进入管程后被阻塞,直到阻塞的原因解除时,在此期间,若该进程不释放管程,则其他进程无法进入管程。为此,将阻塞原因定义为条件变量condition。通常,一个进程被阻塞的原因可以有多个,因此在管程中设置了多个条件变量。每个条件变量保存了一个等待队列,用于记录该条件变量而阻塞的所有进程,对条件变量只能进行两种操作,即wait和signal。

        ·x.wait:当x对应的条件不满足时,正在调用管程的进程调用x.wait将自己插入x条件的等待队列,并释放管程。此时其他进程可以使用该管程。

        ·x.signal:x对应的条件发生了变化,则调用x.signal,唤醒一个因x条件而阻塞的进程。

                下面给出条件变量的定义和使用:

monitor Demo{
    共享数据结构S;
    condition x;                    //定义一个条件变量x
    init_code(){... }
    take_away(){
        if(S <= 0) x.wait();        //资源不够,在条件变量x上阻塞等待
        资源足够,分配资源,做一系列相应处理;
    }
    give_back(){
        归还资源,做一系列相应处理;
        if(有进程在等待) x.signal();//唤醒一个阻塞进程
    }
}

条件变量和信号量的比较:

·相似点:条件变量的wait/signal操作类似于信号量的P/V操作,可以实现进程的阻塞 / 唤醒。

·不同点:条件变量是 “没有值” 的,仅实现了 “排队等待” 功能;而信号量是 “有值” 的,信号量的值反映了剩余资源数,而在管程中,剩余资源数用共享数据结构记录。

2.3.7 本节小结

本节开头提出的问题的参考答案如下。

1.为什么要引入进程同步的概念?
        在多道程序共同执行的条件下,进程与进程是并发执行的,不同进程之间存在不同的相互制约关系。为了协调进程之间的相互制约关系,引入了进程同步的概念。

2.不同的进程之间会存在什么关系?
进程之间存在同步与互斥的制约关系。

        ·同步是指为完成某种任务而建立的两个或多个进程,这些进程因为需要在某些位置上协调它们的工作次序而等待、传递信息所产生的制约关系。

        ·互斥是指当一个进程进入临界区使用临界资源时,另一个进程必须等待,当占用临界资源的进程退出临界区后,另一进程才允许去访问此临界资源。

3.当单纯用本节介绍的方法解决这些问题时会遇到什么新的问题吗?
当两个或两个以上的进程在执行过程中,因占有一些资源而又需要对方的资源时,会因为争夺资源而造成一种互相等待的现象,若无外力作用,它们都将无法推进下去。这种现象称为死锁,具体介绍和解决方案请参考下一节。

2.3.8 本节习题精选

一、单项选择题

1.下列对临界区的论述中,正确的是( )。
A. 临界区是指进程中用于实现进程互斥的那段代码
B. 临界区是指进程中用于实现进程同步的那段代码
C. 临界区是指进程中用于实现进程通信的那段代码
D. 临界区是指进程中用于访问临界资源的那段代码

01.答案:D
解析:多个进程可以共享系统中的资源,一次仅允许一个进程使用的资源称为临界资源。访问临界资源的那段代码称为临界区。

2.不需要信号量就能实现的功能是( )。
A. 进程同步
B. 进程互斥
C. 执行的前驱关系
D. 进程的并发执行

02.答案:D
解析:在多道程序技术中,信号量机制是一种有效实现进程同步和互斥的工具。进程执行的前趋关系实质上是指进程的同步关系。除此之外,只有进程的并发执行不需要信号量来控制,因此正确答案为选项 D。

3.若一个信号量的初值为 3,经过多次 PV 操作后当前值为 -1,这表示等待进入临界区的进程数是( )。
A. 1
B. 2
C. 3
D. 4

03.答案:A
        解析:信号量是一个特殊的整型变量,只有初始化和 PV 操作才能改变其值。通常,信号量分为互斥量和资源量,互斥量的初值一般为 1,表示临界区只允许一个进程进入,从而实现互斥。当互斥量等于 0 时,表示临界区已有一个进程进入,临界区外尚无进程等待;当互斥量小于 0 时,表示临界区中有一个进程,互斥量的绝对值表示在临界区外等待进入的进程数。同理,资源信号量的初值可以是任意整数,表示可用的资源数,当资源量小于 0 时,表示所有资源已全部用完,而且还有进程正在等待使用该资源,等待的进程数就是资源量的绝对值。

4.一个正在访问临界资源的进程由于申请等待 I/O 操作而被中断时,它( )。
A. 允许其他进程进入与该进程相关的临界区
B. 不允许其他进程进入任何临界区
C. 允许其他进程抢占处理器,但不得进入该进程的临界区
D. 不允许任何进程抢占处理器

04.答案:C
        解析:进程进入临界区必须满足互斥条件,当进程进入临界区但尚未离开时就被迫进入阻塞是可以的,系统中经常出现这样的情形。在此状态下,只要其他进程在运行过程中不寻求进入该进程的临界区,就应允许其运行,即分配 CPU。该进程所锁定的临界区是不允许其他进程访问的,其他进程若要访问,必定会在临界区的 “锁” 上阻塞,期待该进程下次运行时可以离开并将临界区交给它。所以正确答案为选项 C。

5.两个旅行社甲和乙为旅客到某航空公司订飞机票,形成互斥资源的是( )。
A. 旅行社
B. 航空公司
C. 飞机票
D. 旅行社与航空公司

05.答案:C
        解析:一张飞机票不能售给不同的旅客,因此飞机票是互斥资源,其他因素只是为完成飞机票订票的中间过程,与互斥资源无关。

6.临界区是指并发进程访问共享变量段的( )。
A. 管理信息
B. 信息存储
C. 数据
D. 代码程序

06.答案:D
        解析:所谓临界区,并不是指临界资源,如共享的数据、代码或硬件设备等,而是指访问临界资源的那段代码程序,如 P/V 操作、加减锁等。操作系统访问临界资源时,关心的是临界区的操作过程,具体对临界资源做何操作是应用程序的事情,操作系统并不关心。

7.以下不是同步机制应遵循的准则的是( )。
A. 让权等待
B. 空闲让进
C. 忙则等待
D. 无限等待

07.答案:D
解析:同步机制的 4 个准则是空闲让进、忙则等待、让权等待和有限等待。

8.以下( )不属于临界资源。
A. 打印机
B. 非共享数据
C. 共享变量
D. 共享缓冲区

08.答案:B
解析:临界资源是互斥共享资源,非共享数据不属于临界资源。打印机、共享变量和共享缓冲区都只允许一次供一个进程使用。

9.以下( )属于临界资源。
A. 磁盘存储介质
B. 公用队列
C. 私用数据
D. 可重入的程序代码

09.答案:B
解析:临界资源与共享资源的区别在于,在一段时间内能否允许被多个进程访问(并发使用),显然磁盘属于共享设备。公用队列可供多个进程使用,但一次只可供一个进程使用,试想若多个进程同时使用公用队列,势必造成队列中的数据混乱而无法使用。私用数据仅供一个进程使用,不存在临界区问题,可重入的程序代码一次可供多个进程使用。

10.在操作系统中,要对并发进程进行同步的原因是( )。
A. 进程必须在有限的时间内完成
B. 进程具有动态性
C. 并发进程是异步的
D. 进程具有结构性

10.答案:C
        解析:同步是指为了完成某项任务而建立的多个进程的相互合作关系,由于并发进程的执行是异步的(按各自独立的、不可预知的速度向前推进),需要保证进程之间操作的先后次序的约束。例如,读进程和写进程对同一段缓冲区的读和写就需要进行同步,以保证正确的执行顺序。

11.进程 A 和进程 B 通过共享缓冲区协作完成数据处理,进程 A 负责产生数据并放入缓冲区,进程 B 从缓冲区读数据并输出。进程 A 和进程 B 之间的制约关系是( )。
A. 互斥关系
B. 同步关系
C. 互斥和同步关系
D. 无制约关系

11.答案:C
        解析:并发进程因为共享资源而产生相互之间的制约关系,可以分为两类:①互斥关系,指进程之间因相互竞争使用独占型资源(互斥资源)所产生的制约关系;②同步关系,指进程之间为协同工作需要交换信息、相互等待而产生的制约关系。本题中两个进程之间的制约关系是同步关系,进程 B 必须在进程 A 将数据放入缓冲区后才能从缓冲区中读出数据。此外,共享的缓冲区一定是互斥访问的,所以它们也具有互斥关系。

12.在操作系统中,P,V 操作是一种( )。
A. 机器指令
B. 系统调用命令
C. 作业控制命令
D. 低级进程通信原语

12.答案:D
解析:P、V 操作是一种低级的进程通信原语,它是不能被中断的。

13.P 操作可能导致( )。
A. 进程就绪
B. 进程结束
C. 进程阻塞
D. 新进程创建

13.答案:C
解析:P 操作即 wait 操作,表示等待某种资源直到可用。若这种资源暂时不可用,则进程进入阻塞态。注意,执行 P 操作时的进程处于运行态。

14.原语是( )。
A. 运行在用户态的过程
B. 操作系统的内核
C. 可中断的指令序列
D. 不可分割的指令序列

14.答案:D
解析:原语(Primitive/Atomic Action),顾名思义,就是原子性的、不可分割的操作。其严格定义为:由若干机器指令构成的完成某种特定功能的一段程序,其执行必须是连续的,在执行过程中不允许被中断。

15.( )定义了共享数据结构和各种进程在该数据结构上的全部操作。
A. 管程
B. 类程
C. 线程
D. 程序

15.答案:A
解析:管程定义了一个数据结构和能为并发进程所执行(在该数据结构上)的一组操作,这组操作能同步进程并改变管程中的数据。

16.用 V 操作唤醒一个等待进程时,被唤醒进程变为( )态。
A. 运行
B. 等待
C. 就绪
D. 完成

16.答案:C
解析:只有就绪进程能获得处理器资源,被唤醒的进程并不能直接转换为运行态。

17.在信号量机制实现互斥时,互斥信号量的初值为( )。
A. 0
B. 1
C. 2
D. 3

17.答案:B
解析:互斥信号量的初值设置为 1,P 操作成功则将其减 1,禁止其他进程进入;V 操作成功则将其加 1,允许等待队列中的一个进程进入。

18.用 P,V 操作实现进程同步,信号量的初值为( )。
A. -1
B. 0
C. 1
D. 由用户确定

18.答案:D
解析:与互斥信号量初值一般置 1 不同,用 P,V 操作实现进程同步时,信号量的初值应根据具体情况来确定。若期望的消息尚未产生,则对应的初值应设为 0;若期望的消息已存在,则信号量的初值应设为一个非 0 的正整数。

19.可以被多个进程在任意时刻共享的代码必须是( )。
A. 顺序代码
B. 机器语言代码
C. 不允许任何修改的代码
D. 无转移指令代码

19.答案:C
        解析:若代码可被多个进程在任意时刻共享,则要求任意一个进程在调用此段代码时都以同样的方式运行;而且进程在运行过程中被中断后再继续执行,其执行结果不受影响。这必然要求代码不能被任何进程修改,否则无法满足共享的要求。这样的代码就是可重入代码,也称纯代码,即允许多个进程同时访问的代码。

20.一个进程映像由程序、数据及 PCB 组成,其中( )必须用可重入编码编写。
A. PCB
B. 程序
C. 数据
D. 共享程序段

20.答案:D
解析:共享程序段可能同时被多个进程使用,所以必须可重入编码,否则无法实现共享的功能。

21.下列关于互斥锁的说法中,正确的是( )。
A. 互斥锁只能用于多线程之间,不能用于多进程之间
B. 互斥锁只能用于多进程之间,不能用于多线程之间
C. 互斥锁可用于多线程或多进程之间,但只能由创建它的线程或进程来加锁和解锁
D. 互斥锁可用于多线程或多进程之间,但只能由对它加锁的线程或进程来解锁

21.答案:D
解析:互斥锁可用于多线程或多进程之间,但只有对它加锁的线程或进程才能解锁它。若一个线程或进程试图解锁一个不属于它(加锁)的互斥锁,则返回错误码。

22.在使用互斥锁进行同步互斥时,下列( )情况会导致死锁。
A. 一个线程对同一个互斥锁连续加锁两次
B. 一个线程尝试对一个已加锁的互斥锁再次加锁
C. 两个线程分别对两个不同的互斥锁先后加锁,但顺序相反
D. 一个线程对一个互斥锁加锁后忘记解锁

22.答案:C
解析:若两个线程分别对两个不同的互斥锁先后加锁,但顺序相反,则可能导致死锁,这是典型的循环等待现象。例如,线程 1 先对互斥锁 A 加锁,然后尝试对互斥锁 B 加锁;同时,线程 2 先对 B 加锁,然后尝试对 A 加锁,两个线程都在等待对方释放资源,从而无法继续推进。

23.用来实现进程同步与互斥的 PV 操作实际上是由( )过程组成的。
A. 一个可被中断的
B. 一个不可被中断的
C. 两个可被中断的
D. 两个不可被中断的

23.答案:D
解析:P 操作和 V 操作都属于原语操作,不可被中断。

24.对于两个并发进程,设互斥信号量为 mutex(初值为 1),若 mutex = 0,则表示( )。
A. 没有进程进入临界区
B. 有一个进程进入临界区
C. 有一个进程进入临界区,另一个进程等待进入
D. 有一个进程在等待进入

24.答案:B
解析:mutex 的初值为 1,表示允许一个进程进入临界区,当有一个进程进入临界区且没有进程等待进入时,mutex 减 1,变为 0。|mutex | 为等待进入的进程数。因此选择选项 B。

25.对于两个并发进程,设互斥信号量为 mutex(初值为 1),若 mutex = -1,则( )。
A. 表示没有进程进入临界区
B. 表示有一个进程进入临界区
C. 表示有一个进程进入临界区,另一个进程等待进入
D. 表示有两个进程进入临界区

25.答案:C
解析:当有一个进程进入临界区且有另一个进程等待进入临界区时,mutex = -1。当 mutex 小于 0 时,其绝对值等于等待进入临界区的进程数。

26.一个进程因在互斥信号量 mutex 上执行 V (mutex) 操作而导致唤醒另一个进程时,执行 V 操作后 mutex 的值为( )。
A. 大于 0
B. 小于 0
C. 大于或等于 0
D. 小于或等于 0

26.答案:D
解析:由题意可知,系统原来存在等待进入临界区的进程,mutex 小于或等于 -1,因此在执行 V (mutex) 操作后,mutex 的值小于或等于 0。

27.一个系统中共有 5 个并发进程涉及某个相同的变量 A,变量 A 的相关临界区是由( )个临界区构成的。
A. 1
B. 3
C. 5
D. 6

27.答案:C
解析:这里的临界区是指访问临界资源 A 的那段代码(临界区的定义)。那么 5 个并发进程共有 5 个操作共享变量 A 的代码段。

28.下述( )选项不是管程的组成部分。
A. 局限于管程的共享数据结构
B. 对管程内数据结构进行操作的一组过程
C. 管程外过程调用管程内数据结构的说明
D. 对局限于管程的数据结构设置初始值的语句

28.答案:C
解析:管程由局限于管程的共享变量说明、对管程内的数据结构进行操作的一组过程及对局限于管程的数据设置初始值的语句组成。

29.以下关于管程的叙述中,错误的是( )。
A. 管程是进程同步工具,解决信号量机制大量同步操作分散的问题
B. 管程每次只允许一个进程进入管程
C. 管程中 signal 操作的作用和信号量机制中的 V 操作相同
D. 管程是被进程调用的,管程是语法范围,无法创建和撤销

29.答案:C
解析:管程的 signal 操作与信号量机制中的 V 操作不同,信号量机制中的 V 操作一定会改变信号量的值 S = S + 1。而管程中的 signal 操作是针对某个条件变量的,若不存在因该条件而阻塞的进程,则 signal 不会产生任何影响。

30.对信号量 S 执行 P 操作后,使该进程进入资源等待队列的条件是( )。
A. S.value < 0
B. S.value <= 0
C. S.value > 0
D. S.value >= 0

30.答案:A
解析:参见记录型信号量的解析。此处极易出 S.value 的物理概念题,现在总结如下:
①S.value > 0,表示某类可用资源的数量。每次 P 操作,意味着请求分配一个单位的资源。
②S.value ≤ 0,表示某类资源已经没有,或者说还有因请求该资源而被阻塞的进程,S.value 的绝对值表示等待进程数量。
一定要看清题目中的陈述是执行 P 操作前还是执行 P 操作后。

31.若系统有 n 个进程,则就绪队列中进程的个数最多有(① )个;阻塞队列中进程的个数最多有(② )个。
① A. n + 1
B. n
C. n - 1
D. 1
② A. n + 1
B. n
C. n - 1
D. 1

31.①答案:C ②答案:B
解析:
①系统中有 n 个进程,只要这些进程不都处于阻塞态,则至少有一个进程正在处理器上运行(处理器至少有一个),因此就绪队列中的进程个数最多有 n - 1 个。选项 B 容易被错选,以为出现了处理器为空、就绪队列全满的情况,实际调度无此状态。
②本题易错选 C,阻塞队列有 n - 1 个进程是可能发生的,但不是最多的情况。不少读者会忽略死锁的情况,死锁就是 n 个进程都被阻塞,因此阻塞队列最多可以有 n 个进程。

32.下列关于 PV 操作的说法中,正确的是( )。
I. PV 操作是一种系统调用命令
II. PV 操作是一种低级进程通信原语
III. PV 操作是由一个不可被中断的过程组成
IV. PV 操作是由两个不可被中断的过程组成
A. I、III
B. II、IV
C. I、II、IV
D. I、IV

32.答案:B
解析:PV 操作是一种低级的进程通信原语,不是系统调用,因此选项 II 正确;P 操作和 V 操作都属于原子操作,所以 PV 操作由两个不可被中断的过程组成,因此选项 IV 正确。

33.下列关于临界区和临界资源的说法中,正确的是( )。
I. 银行家算法可以用来解决临界区(Critical Section)问题
II. 临界区是指进程中用于实现进程互斥的那段代码
III. 公用队列属于临界资源
IV. 私用数据属于临界资源
A. I、II
B. I、IV
C. 仅 III
D. 以上答案都错误

33.答案:C
解析:临界资源是指每次仅允许一个进程访问的资源。每个进程中访问临界资源的那段代码称为临界区。选项 I 错误,银行家算法是避免死锁的算法。选项 II 错误,每个进程中访问临界资源的那段代码称为临界区。选项 III 正确,公用队列可供多个进程使用,但一次只可供一个程序使用。选项 IV 错误,私用数据仅供一个进程使用,不存在临界区问题。综上分析,正确答案为选项 C。

34.有一个计数信号量 S:
1)假如若干进程对 S 进行 28 次 P 操作和 18 次 V 操作后,信号量 S 的值为 0。
2)假如若干进程对信号量 S 进行了 15 次 P 操作和 2 次 V 操作。请问此时有多少个进程等待在信号量 S 的队列中?( )
A. 2
B. 3
C. 5
D. 7

34.答案:B
解析:对 S 进行了 28 次 P 操作和 18 次 V 操作,即 S - 28 + 18 = 0,得信号量的初值为 10;然后,对信号量 S 进行了 15 次 P 操作和 2 次 V 操作,即 S - 15 + 2 = 10 - 15 + 2 = -3,S 信号量的负值的绝对值表示等待队列中的进程数。所以有 3 个进程等待在信号量 S 的队列中。

35.有两个并发进程 P1 和 P2,其程序代码如下:

P1 (){
x = 1; //A1
y = 2;
z = x + y;
print z; //A2
}
P2 (){
x = -3; //B1
c = x * x;
print c; //B2
}

可能打印出的 z 值有( ),可能打印出的 c 值有( )(其中 x 为 P1,P2 的共享变量)。
A. z = 1, -3; c = -1, 9
B. z = -1, 3; c = 1, 9
C. z = -1, 3, 1; c = 9
D. z = 3; c = 1, 9

35.答案:B
解析:本题的关键是,输出语句 A2、B2 中读取的 x 的值不同,由于 A1、B1 执行有先后问题,使得在执行 A2、B2 前,x 的可能取值有两个,即 1, -3;这样,输出 z 的值可能是 1 + 2 = 3 或 (-3) + 2 = -1;输出 c 的值可能是 1×1 = 1 或 (-3)×(-3) = 9。

36.并发进程之间的关系是( )。
A. 无关的
B. 相关的
C. 可能相关的
D. 可能是无关的,也可能是有交往的

36.答案:D
解析:并发进程之间的关系没有必然的要求,只有执行时间上的偶然重合,可能无关也可能有交往。

37.若系统中有 4 个进程共享 3 台打印机,采用信号量机制控制打印机的共享使用,则信号量的取值范围是( )。
A. [-1,4]
B. [-2,2]
C. [-1,3]
D. [-3,2]

37.答案:C
        解析:信号量的初值表示该类资源的总数,因此信号量的初值取 3。每分配一个该类资源,信号量减 1,当信号量等于 0 时,表示该类资源刚好被分完;当信号量小于 0 时,表示还有进程正在等待该类资源,信号量的绝对值就是等待进程的数量。因此,当没有进程使用时,信号量最大为 3;当有三个进程正在使用并有一个进程在等待时,信号量最小为 -1。

38.两个进程 P0、P1 互斥的 Peterson 算法描述如下:

进程 P0
flag [0]=1;
(1);
while (flag [1]&&turn==1);
临界区;
flag [0]=0;
其余代码;
进程 P1
flag [1]=1;
(2);
while (flag [0]&&turn==0);
临界区;
flag [1]=0;
其余代码;

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

38.答案:C
解析:根据 Peterson 算法的原理,可知 (1) 和 (2) 处分别为 turn = 1 和 turn = 0。

39.在 Peterson 算法中,flag 数组的作用是( )。
A. 表示每个线程是否想进入临界区
B. 表示每个线程是否已进入临界区
C. 表示每个线程是否已退出临界区
D. 表示每个线程是否已完成任务

39.答案:A
        解析:flag 数组用于标记各个线程想进入临界区的意愿。当一个线程想要进入临界区时,它将自己对应的 flag 值置为 true;当一个线程退出临界区时,它将自己对应的 flag 值置为 false。这样,若两个进程都争着想进入临界区,则可以让进程将进入临界区的机会谦让给对方。

40.在 Peterson 算法中,turn 变量的作用是( )。
A. 表示轮到哪个线程进入临界区
B. 表示哪个线程先发出访问请求
C. 表示哪个线程后发出访问请求
D. 表示哪个线程已进入临界区

40.答案:A
        解析:turn 变量用于指示允许进入临界区的线程编号。当一个线程想要进入临界区时,它将 turn 置为对方的编号,表示优先让对方先进入。当一个线程检查到对方的 flag 为 true 时,表示对方也想进入,这时就需要根据 turn 来决定谁先进入。若 turn 等于自己的编号,表示轮到自己进入,则可以直接进入。若 turn 等于对方的编号,表示轮到对方进入,则需等待对方退出。

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

41.答案:B
解析:进程并发带来问题不仅包括同步互斥问题,还包括死锁等其他问题。生产者 - 消费者问题用于解决进程的同步和互斥问题。共享一个数据对象仅涉及互斥访问的问题。

42.所有的消费者必须等待生产者先运行的前提条件是( )。
A. 缓冲区空
B. 缓中区满
C. 缓冲区不可用
D. 缓冲区半空

42.答案:A
解析:当缓冲区为空时,消费者进程取产品会被阻塞,此时需等待生产者进程生产新产品。

43.下列关于生产者 - 消费者问题的唤醒操作的说法中,正确的是( )。
I. 生产者唤醒其他生产者
II. 生产者唤醒消费者
III. 消费者唤醒其他消费者
IV. 消费者唤醒生产者
A. I 和 II
B. III 和 IV
C. II 和 III
D. I、II、III 和 IV

43.答案:D
解析:生产者和消费者共享缓冲区,每次只允许一个生产者或消费者进入缓冲区,当有一个生产者或消费者进入缓冲区时,其他生产者或消费者就必须阻塞等待。因此,生产者有可能唤醒其他生产者或消费者,消费者也有可能唤醒其他生产者或消费者,四个选项均正确。

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

44.答案:A
解析:所谓互斥使用某临界资源,是指在同一时间段只允许一个进程使用此资源,所以互斥信号量的初值都为 1。

45.消费者进程阻塞在 wait (m)(m 是互斥信号量)的条件是( )。
I. 没有空缓冲区
II. 没有满缓冲区
III. 有其他生产者已进入临界区
IV. 有其他消费者已进入临界区
A. I 和 II
B. III 和 IV
C. I 和 III
D. II 和 IV

45.答案:B
解析:在生产者 - 消费者问题中,每次只能有一个生产者或消费者进入缓冲区,需要用一个互斥信号量来控制,当有一个生产者或消费者进入缓冲区时,其他申请进入缓冲区的消费者会被阻塞。

46.在读者 - 写者问题中,能同时执行的是( )。
A. 读者和写者
B. 不同的写者
C. 不同的读者
D. 都不能

46.答案:C
解析:在读者 - 写者问题中,写者和写者之间、写者和读者之间必须互斥访问共享对象,读者和读者之间则可以同时访问。

47.在哲学家就餐问题中,若同时存在左撇子和右撇子(将先拿起左边筷子的人称为左撇子,而将先拿起右边筷子的人称为右撇子),则不会发生死锁,因为破坏了( )。
A. 互斥条件
B. 请求与保持条件
C. 不剥夺条件
D. 循环等待条件

47.答案:D
解析:在哲学家就餐问题中,若所有哲学家都是右撇子,则他们都会先拿起右边的筷子,然后等待左边的筷子,这样就形成了一个循环等待链。但是,若其中有一些哲学家是左撇子,则他们会先拿起左边的筷子,然后等待右边的筷子,这样就打破了循环等待链。

48.哲学家就餐问题的解决方案如下:

semaphore *chopstick [5];
semaphore *seat;
哲学家 i:
...
P (seat);
P (chopStick [i]);
P (chopStick [(i+1)%5]);
吃饭
V (chopStick [i]);
V (chopStick [(i+1)%5]);
V (seat)

其中,信号量 seat 的初值为( )。
A. 0
B. 1
C. 4
D. 5

48.答案:C
解析:信号量 seat 表示桌子上可以坐下的位置数,因为只有五个位置,所以每次只能有四位哲学家同时拿起左边的餐叉,才能保证不会发生死锁,所以 seat 的初值应该为 4。

49.有两个优先级相同的并发程序 P1 和 P2,它们的执行过程如下所示。假设当前信号量 s1 = 0,s2 = 0。当前的 z = 2,进程运行结束后,x、y 和 z 的值分别是( )。

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

A. 5, 9, 9
B. 5, 9, 4
C. 5, 12, 9
D. 5, 12, 4

49.答案:C
解析:因为进程并发,所以进程的执行具有不确定性,在 P1、P2 执行到第一个 P、V 操作前,应该是相互无关的。现在考虑第一个对 s1 的 P、V 操作,因为进程 P2 是 P (s1) 操作,所以它必须等待 P1 执行完 V (s1) 操作后才可继续运行,此时的 x, y, z 值分别是 2, 3, 4,当进程 P1 执行完 V (s1) 后便在 P (s2) 上阻塞,此时 P2 可以运行直到 V (s2),此时的 x, y, z 值分别是 5, 3, 9,进程 P1 继续运行到结束,最终的 x, y, z 值分别为 5, 12, 9。

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

50.答案:B
解析:信号量表示相关资源的当前可用数量。当信号量 K > 0 时,表示还有 K 个相关资源可用,所以该资源的可用个数是 1。而当信号量 K < 0 时,表示有 | K | 个进程在等待该资源。因为资源有剩余,可见没有其他进程等待使用该资源,所以进程数为 0。

51.进程 P0 和进程 P1 的共享变量定义及其初值为:

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

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

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

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

51.答案:D
解析:这是 Peterson 算法的实际实现,保证进入临界区的进程合理安全。
该算法为了防止两个进程为进入临界区而无限期等待,设置了变量 turn,表示允许进入临界区的编号,每个进程在先设置自己的标志后再设置 turn 标志,允许另一个进程进入。这时,再同时检测另一个进程状态标志和允许进入标志,就可保证当两个进程同时要求进入临界区时只允许一个进程进入临界区。保存的是较晚的一次赋值,因此较晚的进程等待,较早的进程进入。先到先入,后到等待,从而完成临界区访问的要求。
其实这里可想象为两个人进门,每个人进门前都会和对方客套一句 “你走先”。若进门时没别人,就当和空气说句废话,然后大步登门入室;若两人同时进门,就互相先请,但各自只客套一次,所以先客套的人请完对方,就等着对方请自己,然后光明正大地进门。

52.有两个并发执行的进程 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 // 取 x 到寄存器 R2
dec R2
store x, R2 // 将 R2 的内容存入 x

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

52.答案:C
解析:x 的值最终是多少,取决于最后是哪个进程对 x 进行了写操作。一个进程一旦拿到了 x 值,它最后对 x 写操作的值也就确定了。因此本题只需考虑两个进程拿到 x 值的所有可能情况。对于进程 P1,最初取到的 x 值可能是 1,也可能是进程 P2 完成后更新得到的 x 值 0,因此对于 P1,最终写入 x 的值可能是 2(当它最初取到 1 时)和 1(当它最初取到 0 时)。同理,对于 P2,最初取到的 x 值可能是 1,也可能是 P1 完成后更新得到的 x 值 2,因此对于 P2,最终写入 x 的值可能是 0(当它最初取到 1 时)和 1(当它最初取到 2 时)。因此,最终的 x 值可能是 0、1 或 2。

53.进程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

53.答案:C
解析:需要进行互斥的操作是对临界资源的访问,也就是说,不同线程对同一个进程内部的共享变量的访问才有可能需要进行互斥,不同进程的线程、代码段或变量不存在互斥访问的问题,同一个线程内部的局部变量也不存在互斥访问的问题。选项 A 中的 a 是线程内部的局部变量,不需要互斥访问。选项 D 是不同进程的线程代码段,不存在互斥访问的问题。选项 B 是对进程内部的共享变量 x 的读操作,不互斥也不影响执行结果,所以不需要互斥访问。选项 C 是不同线程对同一个进程内部的共享变量的写操作,需要互斥访问(类似于读者 - 写者问题)。

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

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

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

54.答案:B
解析:使用 TSL 指令实现进程互斥时,并没有阻塞态进程,等待进入临界区的进程一直停留在执行 while (TSL (&lock)) 的循环中,不会主动放弃 CPU,一直处于运行态,直到该进程的时间片用完放弃处理机,转为就绪态,此时切换另一个就绪态进程占用处理机。这不同于信号量机制实现的互斥。由此可知选项 A 和 C 错误,选项 B 正确。TSL 指令本身就是原子操作,不需要关中断来保证其不被打断。TSL 指令实现原子性的原理是,执行 TSL 指令的 CPU 锁住内存总线,以禁止其他 CPU 在本指令结束之前访问内存。此外,假如 while (TSL (&lock)) 在关中断状态下执行,若 TSL (&lock) 一直为 true,不再开中断,则系统可能因此终止。因此选项 D 错误。

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

55.答案:A
解析:管程是由一组数据及定义在这组数据之上的对这组数据的操作组成的软件模块,这组操作能初始化并改变管程中的数据和同步进程。管程不仅能实现进程间的互斥,还能实现进程间的同步,因此选项 A 错误、B 正确;管程具有如下特性:①局部于管程的数据只能被局部于管程内的过程所访问;②一个进程只有通过调用管程内的过程才能进入管程访问共享数据;③每次仅允许一个进程在管程内执行某个内部过程,因此选项 C 和 D 正确。

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

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

56.答案:B
解析:仔细阅读两个线程代码可知,thread1 和 thread2 均是对 x 进行加 1 操作,x 的初始值为 0,若要使得最终 x = 2,只能先执行完 thread1 再执行 thread2,或先执行完 thread2 再执行 thread1,因此仅有 2 种可能。

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

57.答案:D
解析:“条件变量” 是管程内部说明和使用的一种特殊变量,其作用类似于信号量机制中的 “信号量”,都用于实现进程同步。需要注意的是,在同一时刻,管程中只能有一个进程在执行。若进程 A 执行了 x.wait () 操作,则该进程会阻塞,并挂到条件变量 x 对应的阻塞队列上。这样,管程的使用权被释放,就可以有另一个进程进入管程。若进程 B 执行了 x.signal () 操作,则会唤醒 x 对应的阻塞队列的队首进程。只有一个进程要离开管程时才能调用 signal () 操作。

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

58.答案:C
解析:硬件方法实现进程同步时不能实现让权等待,选项 B 和 D 错误;Peterson 算法满足有限等待但不满足让权等待,选项 A 错误;记录型信号量由于引入阻塞机制,消除了不让权等待的情况,选项 C 正确。

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

59.答案:C
解析:实现临界区互斥需满足多个准则。“忙则等待” 准则,即两个进程不能同时访问临界区,选项 I 正确。“空闲让进” 准则,若临界区空闲,则允许其他进程访问,选项 II 正确。“有限等待” 准则,即进程应该在有限时间内访问临界区,选项 III 正确。选项 I、II 和 III 是互斥机制必须遵循的原则。选项 IV 是 “让权等待” 准则,不一定非得实现,如皮特森算法。

二、综合应用题

01.下面是两个并发执行的进程,它们能正确运行吗?若不能请举例说明并改正。

int x;
process_P1 {
int y, z;
x = 1;
y = 0;
if (x>= 1)
    y = y + 1;
z = y;
}
process_P2 {
int t, u;
x = 0;
t = 0;
if (x <= 1)
    t = t + 2;
u = t;
}

1.解答:
P1 和 P2 两个并发进程的执行结果是不确定的,它们都对同一变量 X 进程操作,X 是一个临界资源,而没有进行保护。例如:
1)若先执行完 P1 再执行 P2,结果是 x = 0, y = 1, z = 1, t = 2, u = 2。
2)若先执行 P1 到 “x = 1”,然后一个中断去执行完 P2,再一个中断回来执行完 P1,结果是 x = 0, y = 0, z = 0, t = 2, u = 2。
显然,两次执行结果不同,所以这两个并发进程不能正确运行。可将这个程序改为:

int x;
semaphore S = 1; //访问x的互斥信号量
process_P1{
    int y, z;
    P(S);
    x = 1;
    y = 0;
    if (x >= 1)
        y = y + 1;
    V(S);
    z = y;
}
process_P2{
    int t, u;
    P(S);
    x = 0;
    t = 0;
    if (x <= 1)
        t = t + 2;
    V(S);
    u = t;
}

02.在一个仓库中可以存放A和B两种产品,要求:
①每次只能存入一种产品。
②A产品数量 - B产品数量 < M,其中M是正整数。
③B产品数量 - A产品数量 < N,其中N是正整数。
假设仓库的容量是无限的,试用 P,V 操作描述产品A和B的入库过程。

02.解答:
使用信号量 mutex 控制两个进程互斥访问临界资源(仓库),使用同步信号量 Sa 和 Sb(分别代表产品 A 与 B 的还可容纳的数量差,以及产品 B 与 A 的还可容纳的数量差)满足条件 2 和条件 3。代码如下:

Semaphore Sa = M - 1, Sb = N - 1;
Semaphore mutex = 1; //访问仓库的互斥信号量
process A(){
    while(1){
        P(Sa);
        P(mutex);
        A产品入库;
        V(mutex);
        V(Sb);
    }
}
process B(){
    while(1){
        P(Sb);
        P(mutex);
        B产品入库;
        V(mutex);
        V(Sa);
    }
}

03.面包师有很多面包,由n名销售人员推销。每名顾客进店后按序取一个号,并且等待叫号,当一名销售人员空闲时,就按序叫下一个号。可以用两个整型变量来记录当前的取号值和叫号值,试设计一个使销售人员和顾客同步的算法。

03.解答:
顾客进店后按序取号,并等待叫号;销售人员空闲后也按序叫号,并销售面包。因此同步算法只要对顾客取号和销售人员叫号进行合理同步即可。我们使用两个变量 i 和 j 分别记录当前的取号值和叫号值,并各自使用一个互斥信号量用于对 i 和 j 进行访问和修改。

int i = 0, j = 0;
semaphore mutex_i = 1, mutex_j = 1;
Consumer(){ //顾客
    进入面包店;
    P(mutex_i); //互斥访问i
    取号i;
    i++;
    V(mutex_i); //释放对i的访问
    等待叫号i并购买面包;
}
Seller(){ //销售人员
    while(1){
        P(mutex_j); //互斥访问j
        if(j < i){ //号j已有顾客取走并等待
            叫号j;
            j++;
            V(mutex_j); //释放对j的访问
            销售面包;
        }
        else{ //暂时没有顾客在等待
            V(mutex_j); //释放对j的访问
            休息片刻;
        }
    }
}

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

04.解答:
本题是生产者 - 消费者问题的变体,生产者 “车间 A” 和消费者 “装配车间” 共享缓冲区 “货架 F1”;生产者 “车间 B” 和消费者 “装配车间” 共享缓冲区 “货架 F2”。因此,可为它们设置 6 个信号量:empty1 对应货架 F1 上的空闲空间,初值为 10;full1 对应货架 F1 上面的 A 产品,初值为 0;empty2 对应货架 F2 上的空闲空间,初值为 10;full2 对应货架 F2 上面的 B 产品,初值为 0;mutex1 用于互斥地访问货架 F1,初值为 1;mutex2 用于互斥地访问货架 F2,初值为 1。
A 车间的工作过程可描述为:

while(1){
    生产一个产品A;
    P(empty1); //判断货架F1是否有空
    P(mutex1); //互斥访问货架F1
    将产品A存放到货架F1上;
    V(mutex1); //释放货架F1
    V(full1); //货架F1上的零件A的个数加1
}

B 车间的工作过程可描述为:

plaintext
while(1){
    生产一个产品B;
    P(empty2); //判断货架F2是否有空
    P(mutex2); //互斥访问货架F2
    将产品B存放到货架F2上;
    V(mutex2); //释放货架F2
    V(full2); //货架F2上的零件B的个数加1
}

装配车间的工作过程可描述为:

plaintext
while(1){
    P(full1); //判断货架F1上是否有产品A
    P(mutex1); //互斥访问货架F1
    从货架F1上取一个A产品;
    V(mutex1); //释放货架F1
    V(empty1); //货架F1上的空闲空间数加1
    P(full2); //判断货架F2上是否有产品B
    P(mutex2); //互斥访问货架F2
    从货架F2上取一个B产品;
    V(mutex2); //释放货架F2
    V(empty2); //货架F2上的空闲空间数加1
    将取得的A产品和B产品组装成产品;
}

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

05.解答:
从井中取水并放入水缸是一个连续的动作,可视为一个进程;从缸中取水可视为另一个进程。设水井和水缸为临界资源,引入 well 和 vat;三个水桶无论是从井中取水还是将水倒入水缸都是一次一个,应该给它们一个信号量 pail,抢不到水桶的进程只好等待。水缸满时,不可以再放水,设置 empty 信号量来控制入水量;水缸空时,不可以取水,设置 full 信号量来控制。本题需要设置 5 个信号量来进行控制:

semaphore well = 1; //用于互斥地访问水井
semaphore vat = 1; //用于互斥地访问水缸
semaphore empty = 10; //用于表示水缸中剩余空间能容纳的水的桶数
semaphore full = 0; //表示水缸中的水的桶数
semaphore pail = 3; //表示有多少个水桶可以用,初值为3
//老和尚
while(1){
    P(full);
    P(pail);
    P(vat);
    从水缸中打一桶水;
    V(vat);
    V(empty);
    喝水;
    V(pail);
}
//小和尚
while(1){
    P(empty);
    P(pail);
    P(well);
    从井中打一桶水;
    V(well);
    P(vat);
    将水倒入水缸中;
    V(vat);
    V(full);
    V(pail);
}

06.如下图所示,三个合作进程P1​、P2​、P3​,它们都需要通过同一设备输入各自的数据a、b、c,该输入设备必须互斥地使用,而且其第一个数据必须由P1​进程读取,第二个数据必须由P2​进程读取,第三个数据必须由P3​进程读取。然后,三个进程分别对输入数据进行下列计算:

最后,P1​进程通过所连接的打印机将计算结果x、y、z的值打印出来。请用信号量实现它们的同步。

06.解答:
为了控制三个进程依次使用输入设备进行输入,需分别设置三个信号量 S1, S2, S3,其中 S1 的初值为 1,S2 和 S3 的初值为 0。使用上述信号量后,三个进程不会同时使用输入设备,因此不必再为输入设备设置互斥信号量。另外,还需要设置信号量 Sb, Sy, Sz 来表示数据 b 是否已经输入,以及 y, z 是否已计算完成,它们的初值均为 0。三个进程的动作可描述为:

P1(){
    P(S1);
    从输入设备输入数据a;
    V(S2);
    P(Sb);
    x = a + b;
    P(Sy);
    P(Sz);
    使用打印机打印出x, y, z的结果;
}
P2(){
    P(S2);
    从输入设备输入数据b;
    V(S3);
    V(Sb);
    y = a * b;
    V(Sy);
    V(Sy);
}
P3(){
    P(S3);
    从输入设备输入数据c;
    P(Sy);
    Z = y + c - a;
    V(Sz);
}

07.有桥如下图所示。车流方向如箭头所示。回答如下问题:
1)假设桥上每次只能有一辆车行驶,试用信号灯的P,V操作实现交通管理。
2)假设桥上不允许两车交会,但允许同方向多辆车一次通过(桥上可有多辆同方向行驶的车 )。试用信号灯的P,V操作实现桥上的交通管理。

07.解答:
1)桥上每次只能有一辆车行驶,所以只要设置一个信号量 bridge 就可判断桥是否使用,若在使用中,等待;若无人使用,则执行 P 操作进入;出桥后,执行 V 操作。

semaphore bridge = 1; //用于互斥地访问桥
NtoS(){ //从北向南
    P(bridge);
    通过桥;
    V(bridge);
}
StoN(){ //从南向北
    P(bridge);
    通过桥;
    V(bridge);
}

2)桥上可以同方向多车行驶,需要设置 bridge,还需要对同方向车辆计数。为了防止同方向计数中同时申请 bridge 造成同方向不可同时行车的问题,要对计数过程加以保护,因此设置信号量 mutexSN 和 mutexNS。

int countSN = 0; //用于表示从南到北的汽车数量
int countNS = 0; //用于表示从北到南的汽车数量
semaphore mutexSN = 1; //用于保护countSN
semaphore mutexNS = 1; //用于保护countNS
semaphore bridge = 1; //用于互斥地访问桥
StoN(){ //从南向北
    P(mutexSN);
    if(countSN == 0)
        P(bridge);
    countSN++;
    V(mutexSN);
    过桥;
    P(mutexSN);
    countSN--;
    if(countSN == 0)
        V(bridge);
    V(mutexSN);
}
NtoS(){ //从北向南
    P(mutexNS);
    if(countNS == 0)
        P(bridge);
    countNS++;
    V(mutexNS);
    过桥;
    P(mutexNS);
    countNS--;
    if(countNS == 0)
        V(bridge);
    V(mutexNS);
}

08.假设有两个线程(编号为0和1)需要去访问同一个共享资源,为避免竞争状态的问题,我们必须实现一种互斥机制,使得在任何时候只能有一个线程访问这个资源。假设有如下一段代码:

bool flag[2];  //flag数组,初始化为FALSE

Enter_Critical_Section(int my_thread_id,int other_thread_id){

    while(flag[other_thread_id]==TRUE);  //空循环语句

    flag[my_thread_id]=TRUE;

}

Exit_Critical_Section(int my_thread_id,int other_thread_id){

    flag[my_thread_id]=FALSE;

}

当一个线程想要访问临界资源时,就调用上述的这两个函数。例如,线程 0 的代码可能是这样的:

Enter_Critical_Section(0,1);

使用这个资源;

Exit_Critical_Section(0,1);

做其他的事情;

试问:
1)以上的这种机制能够实现资源互斥访问吗?为什么?
2)若把EnterC​riticalS​ection()函数中的两条语句互换位置,可能发生死锁吗?

08.解答:
1)这种机制不能实现资源的互斥访问。考虑如下情形:
① 初始化时,flag 数组的两个元素值均为 FALSE。
② 线程 0 先执行,执行 while 循环语句时,因为 flag [1] 为 FALSE,所以顺利结束,不会被卡住。假设这时来了一个时钟中断,打断了它的运行。
③ 线程 1 去执行,执行 while 循环语句时,因为 flag [0] 为 FALSE,所以顺利结束,不会被卡住,然后进入临界区。
④ 后来当线程 0 再执行时,也进入临界区,这样就同时有两个线程在临界区。
        总结:不能成功的根本原因是无法保证 Enter_Critical_Section () 函数执行的原子性。我们从上面的软件实现方法中可以看出,对于两个进程间的互斥,最主要的问题是标志的检查和修改不能作为一个整体来执行,因此容易导致无法保证互斥访问的问题。
2)可能出现死锁。考虑如下情形:
① 初始化时,flag 数组的两个元素值均为 FALSE。
② 线程 0 先执行,flag [0] 为 TRUE,假设这时来了一个时钟中断,打断了它的运行。
③ 线程 1 去执行,flag [1] 为 TRUE,在执行 while 循环语句时,因为 flag [0] = TRUE,所以在这个地方被卡住,直到时间片用完。
④ 线程 0 再执行时,因为 flag [1] 为 TRUE,它也在 while 循环语句的地方被卡住,所以这两个线程都无法执行下去,从而死锁。


09.设自行车生产线上有一个箱子,其中有N个位置(N≥3),每个位置可存放一个车架或一个车轮;又设有3名工人,其活动分别为:

工人1活动:

do{
    加工一个车架;
    车架放入箱中;
}while(1)



工人2活动:
do{
    加工一个车轮;
    车轮放入箱中;
}while(1)



工人3活动:
do{
    箱中取一个车架;
    箱中取二个车轮;
    组装为一台车;
}while(1)

试分别用信号量与PV操作实现三名工人的合作,要求解中不含死锁。

09.解答:
用信号量与 PV 操作实现三名工人的合作。
首先不考虑死锁问题,工人 1 与工人 3、工人 2 与工人 3 构成生产者与消费者关系,这两对生产 / 消费关系通过共同的缓冲区相联系。从资源的角度来看,箱子中的空位置相当于工人 1 和工人 2 的资源,而车架和车轮相当于工人 3 的资源。
分析上述解法易见,当工人 1 推进速度较快时,箱中空位置可能完全被车架占满或只留有一个存放车轮的位置,此时工人 3 同时取 2 个车轮将无法得到,而工人 2 又无法将新加工的车轮放入箱中;当工人 2 推进速度较快时,箱中空位置可能完全被车轮占满,而此时工人 3 取车架将无法得到,而工人 1 又无法将新加工的车架放入箱中。上述两种情况都意味着死锁。为防止死锁的发生,箱中车架的数量不可超过N−2,车轮的数量不可超过N−1,这些限制可以用两个信号量来表达。具体解答如下。

semaphore empty = N; //空位置
semaphore wheel = 0; //车轮
semaphore frame = 0; //车架
semaphore s1 = N - 2; //车架最大数
semaphore s2 = N - 1; //车轮最大数
工人1活动:
do{
    加工一个车架;
    P(s1); //检查车架数是否达到最大值
    P(empty); //检查是否有空位
    车架放入箱中;
    V(frame); //车架数加1
}while(1);
工人2活动:
do{
    加工一个车轮;
    P(s2); //检查车轮数是否达到最大值
    P(empty); //检查是否有空位
    车轮放入箱中;
    V(wheel); //车轮数加1
}while(1);
工人3活动:
do{
    P(frame); //检查是否有车架
    箱中取一个车架;
    V(empty); //空位数加1
    V(s1); //可装入车架数加1
    P(wheel); //检查是否有一个车轮
    P(wheel); //检查是否有另一个车轮
    箱中取二个车轮;
    V(empty); //取走一个车轮,空位数加1
    V(empty); //取走另一个车轮,空位数加1
    V(s2); //可装入车轮数加1
    V(s2); //可装入车轮数再加1
    组装为一台车;
}while(1);

10.设P、Q、R共享一个缓冲区,P、Q构成一对生产者 - 消费者,R既为生产者又为消费者,若缓冲区为空,则可以写入;若缓冲区不空,则可以读出。使用P,V操作实现其同步。

10.解答:
P, Q 构成消费者 - 生产者关系,因此设三个信号量 full, empty, mutex。full 和 empty 用来控制缓冲池状态,mutex 用来互斥进入。R 既为消费者又为生产者,因此必须在执行前判断状态,若 empty==1,则执行生产者功能;若 full==1,执行消费者功能。

semaphore full = 0; //表示缓冲区的产品
semaphore empty = 1; //表示缓冲区的空位
semaphore mutex = 1; //互斥信号量
Procedure P
{
    while(TRUE){
        p(empty);
        P(mutex);
        Product one;
        v(mutex);
        v(full);
    }
}
Procedure Q
{
    while(TRUE){
        p(full);
        P(mutex);
        consume one;
        v(mutex);
        v(empty);
    }
}
Procedure R
{
    if(empty==1){
        p(empty);
        P(mutex);
        product one;
        v(mutex);
        v(full);
    }
    if(full==1){
        p(full);
        p(mutex);
        consume one;
        v(empty);
    }
}

11.理发店里有一位理发师、一把理发椅和n把供等候理发的顾客坐的椅子。若没有顾客,理发师便在理发椅上睡觉,一位顾客到来时,顾客必须叫醒理发师,若理发师正在理发时又有顾客来到,若有空椅子可坐,则坐下来等待,否则就离开。试用P,V操作实现,并说明信号量的定义和初值。

11.解答:
1)控制变量 waiting 用来记录等候理发的顾客数,初值为 0,进来一名顾客时,waiting 加 1,一名顾客理发时,waiting 减 1。
2)信号量 customers 用来记录等候理发的顾客数,并用作阻塞理发师进程,初值为 0。
3)信号量 barbers 用来记录正在等候顾客的理发师数,并用作阻塞顾客进程,初值为 0。
4)信号量 mutex 用于互斥,初值为 1。

int waiting = 0; //等候理发的顾客数
int chairs = n; //为顾客准备的椅子数
semaphore customers = 0, barbers = 0, mutex = 1;
barber() { //理发师进程
    while(1) { //理完一人,还有顾客吗?
        P(customers); //若无顾客,理发师睡眠
        P(mutex); //进程互斥
        waiting = waiting - 1; //等候顾客数少一个
        V(barbers); //理发师去为一名顾客理发
        V(mutex); //开放临界区
        Cut_hair(); //正在理发
    }
}
customer() { //顾客进程
    P(mutex); //进程互斥
    if(waiting < chairs) { //若有空的椅子,就找到椅子坐下等待
        waiting = waiting + 1; //等候顾客数加1
        V(customers); //呼唤理发师
        V(mutex); //开放临界区
        P(barbers); //无理发师,顾客坐着
        get_haircut(); //一名顾客坐下等待理发
    }
    else
        V(mutex); //人满,离开
}

12.假设一个录像厅有1、2、3三种不同的录像片可由观众选择放映,录像厅的放映规则如下:
1)任意时刻最多只能放映一种录像片,正在放映的录像片是自动循环放映的,最后一名观众主动离开时结束当前录像片的放映。
2)选择当前正在放映的录像片的观众可立即进入,允许同时有多位选择同一种录像片的观众同时观看,同时观看的观众数量不受限制。
3)等待观看其他录像片的观众按到达顺序排队,当一种新的录像片开始放映时,所有等待观看该录像片的观众可依次序进入录像厅同时观看。用一个进程代表一个观众,要求:用信号量方法PV操作实现,并给出信号量定义和初始值。

12.解答:
电影院一次只能放映一部影片,希望观看的观众可能有不同的爱好,但每次只能满足部分观众的需求,即希望观看另外两部影片的用户只能等待。分别为三部影片设置三个信号量 s0, s1, s2,初值分别为 1, 1, 1。电影院一次只能放一部影片,因此需要互斥使用。观看影片的观众有多个,因此必须分别设置三个计数器(初值都是 0),用来统计观众个数。当然,计数器是个共享变量,需要互斥使用。

semaphore s = 1, s0 = 1, s1 = 1, s2 = 1;
int count0 = 0, count1 = 0, count2 = 0;
process videoshow1 { //看第一部影片的观众
    P(s0);
    count0 = count0 + 1;
    if(count0 == 1)
        P(s);
    V(s0);
    看影片;
    P(s0);
    count0 = count0 - 1;
    if(count0 == 0) //没人看了,就结束放映
        V(s);
    V(s0);
}
process videoshow2 { //看第二部影片的观众
    P(s1);
    count1 = count1 + 1;
    if(count1 == 1)
        P(s);
    V(s1);
    看影片;
    P(s1);
    count1 = count1 - 1;
    if(count1 == 0) //没人看了,就结束放映
        V(s);
    V(s1);
}
process videoshow3 { //看第三部影片的观众
    P(s2);
    count2 = count2 + 1;
    if(count2 == 1)
        P(s);
    V(s2);
    看影片;
    P(s2);
    count2 = count2 - 1;
    if(count2 == 0) //没人看了,就结束放映
        V(s);
    V(s2);
}

13.设公共汽车上驾驶员和售票员的活动分别如下图所示。驾驶员的活动:启动车辆,正常行车,到站停车;售票员的活动:关车门,售票,开车门。在汽车不断地到站、停车、行驶的过程中,这两个活动有什么同步关系?用信号量和P,V操作实现它们的同步。

13.解答:
在汽车行驶过程中,驾驶员活动与售票员活动之间的同步关系为:售票员关车门后,向驾驶员发开车信号,驾驶员接到开车信号后启动车辆,在汽车正常行驶过程中售票员售票,到站时驾驶员停车,售票员在车停后开门让乘客上下车。因此,驾驶员启动车辆的动作必须与售票员关车门的动作同步;售票员开车门的动作也必须与驾驶员停车同步。应设置两个信号量 S1, S2:S1 表示是否允许驾驶员启动汽车(初值为 0);S2 表示是否允许售票员开门(初值为 0)。

semaphore S1 = 0, S2 = 0;
Procedure driver
{
    while(1)
    {
        P(S1);
        Start;
        Driving;
        Stop;
        V(S2);
    }
}
Procedure Conductor
{
    while(1)
    {
        关车门;
        V(S1);
        售票;
        P(S2);
        开车门;
        上下乘客;
    }
}

14.一组进程的执行顺序如下图所示,圆圈P1​、P2​、P3​、P4​、P5​、P6​表示进程,弧上的字母a、b、c、d、e、f、g、h表示同步信号量,请用P,V操作实现进程的同步。

14.解答:
本题是一个典型的利用信号量实现前驱关系的同步问题。

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的任务;
    V(c); //实现先P2后P4的同步关系
    V(d); //实现先P2后P5的同步关系
}
process P3(){
    P(b); //检查P1是否已运行完成
    执行P3的任务;
    V(e); //实现先P3后P4的同步关系
    V(f); //实现先P3后P5的同步关系
}
process P4(){
    P(c); //检查P2是否已运行完成
    P(e); //检查P3是否已运行完成
    执行P4的任务;
    V(g); //实现先P4后P6的同步关系
}
process P5(){
    P(d); //检查P2是否已运行完成
    P(f); //检查P3是否已运行完成
    执行P5的任务;
    V(h); //实现先P5后P6的同步关系
}
process P6(){
    P(g); //检查P4是否已运行完成
    P(h); //检查P5是否已运行完成
    执行P6的任务;
}
CoEnd

15.有3个进程P、P1​、P2​合作处理数据,P从输入设备读数据到缓冲区,缓冲区可存1000个字。P1​和P2​的功能一样,都是从缓冲区取出数据并计算,再打印结果。请用信号量的P,V操作实现。其中,语句read()从输入设备读入20个字到缓冲区;get()从缓冲区取出20个字;comp()计算40个字输出并得到结果的1个字;print()打印结果的2个字。

15.解答:
本题是经典的生产者 - 消费者问题。把缓冲区的每 20 个字视为一个基本单位,因此缓冲区共有1000/20=50个空位。设置互斥信号量 mutex,用于对缓冲区的访问;还需要设置两个同步信号量:full 表示缓冲区已有多少数据,empty 表示缓冲区还有多少空位置。

Semaphore mutex = 1; //互斥使用缓冲区
Semaphore full = 0; //表示缓冲区已有多少个20字数据,初值为0
Semaphore empty = 50; //表示缓冲区还有多少个20字空位,初值为50
process P(){
    P(empty);
    P(mutex);
    read();
    V(mutex);
    V(full);
}
process P1/P2(){ //P1、P2完全一样
    for(int i = 0;i < 2;++i){
        p(full);P(mutex);
        get();
        V(mutex);V(empty);
        p(full);P(mutex);
        get();V(mutex);V(empty);
        comp();
        print();
    }
}

16.假设有3个抽烟者和1个供应者。每个抽烟者不停地卷烟并抽掉它,但要卷起并抽掉一支烟,抽烟者需要有三种材料:烟草、纸和胶水。三个抽烟者中,第一个拥有烟草,第二个拥有纸,第三个拥有胶水。供应者无限提供三种材料,供应者每次将两种材料放到桌子上,拥有剩下那种材料的抽烟者卷一根烟并抽掉它,并给供应者一个信号告诉已完成,此时供应者就将另外两种材料放到桌上,如此重复,让3个抽烟者轮流抽烟。

16.解答:
供应者与 3 个抽烟者分别是同步关系。由于供应者无法同时满足两个或以上的抽烟者,3 个抽烟者对抽烟这个动作互斥(或者理解为互斥访问桌子)。显然这里有 4 个进程。供应者作为生产者向 3 个抽烟者提供材料。设置信号量 offer1, offer2, offer3 分别表示烟草和纸组合的资源、烟草和胶水组合的资源、纸和胶水组合的资源,信号量 finish 用于互斥进行抽烟动作。代码如下:

int num = 0; //存储随机数
semaphore offer1 = 0; //定义信号量对应烟草和纸组合的资源
semaphore offer2 = 0; //定义信号量对应烟草和胶水组合的资源
semaphore offer3 = 0; //定义信号量对应纸和胶水组合的资源
semaphore finish = 0; //定义信号量表示抽烟是否完成
process P1(){ //供应者
    while(1){
        num++;
        num = num % 3;
        if(num == 0)
            V(offer1); //提供烟草和纸
        else if(num == 1)
            V(offer2); //提供烟草和胶水
        else
            V(offer3); //提供纸和胶水
        任意两种材料放在桌子上;
        P(finish);
    }
}
process P2(){ //拥有烟草者
    while(1){
        P(offer3);
        拿纸和胶水,卷成烟,抽掉;
        V(finish);
    }
}
process P3(){ //拥有纸者
    while(1){
        P(offer2);
        拿烟草和胶水,卷成烟,抽掉;
        V(finish);
    }
}
process P4(){ //拥有胶水者
    while(1){
        P(offer1);
        拿烟草和纸,卷成烟,抽掉;
        V(finish);
    }
}

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

17.解答:
互斥资源:缓冲区只能互斥访问,因此设置互斥信号量 mutex。
同步问题:P1​、P2​因为奇数的放置与取用而同步,设同步信号量 odd;P1​、P3​因为偶数的放置与取用而同步,设置同步信号量 even;P1​、P2​、P3​因为共享缓冲区,设同步信号量 empty,初值为N。程序如下:

semaphore mutex = 1; //缓冲区操作互斥信号量
semaphore odd = 0, even = 0; //奇数、偶数进程的同步信号量
semaphore empty = N; //空缓冲区单元个数信号量
cobegin
    Process P1()
    while(True)
    {
        x = produce(); //生成一个数
        P(empty); //判断缓冲区是否有空单元
        P(mutex); //缓冲区是否被占用
        Put();
        V(mutex); //释放缓冲区
        if(x % 2 == 0)
            V(even); //若是偶数,向P3发出信号
        else
            V(odd); //若是奇数,向P2发出信号
    }
    Process P2()
    while(True)
    {
        P(odd); //收到P1发来的信号,已产生一个奇数
        P(mutex); //缓冲区是否被占用
        getodd();
        V(mutex); //释放缓冲区
        V(empty); //向P1发信号,多出一个空单元
        countodd();
    }
    Process P3()
    while(True)
    {
        P(even); //收到P1发来的信号,已产生一个偶数
        P(mutex); //缓冲区是否被占用
        geteven();
        V(mutex); //释放缓冲区
        V(empty); //向P1发信号,多出一个空单元
        counteven();
    }
}coend

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

cobegin

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

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

18.解答:
互斥资源:取号机(一次只有一位顾客领号),因此设置互斥信号量 mutex。
同步问题:顾客需要获得空座位等待叫号。营业员空闲时,将选取一位顾客并为其服务。空座位的有无影响等待顾客的数量,顾客的有无决定了营业员是否能开始服务,因此分别设置信号量 empty 和 full 来实现这一同步关系。另外,顾客获得空座位后,需要等待叫号和被服务。这样,顾客与营业员就服务何时开始又构成了一个同步关系,定义信号量 service 来完成这一同步过程。

semaphore empty = 10; //空座位的数量,初值为10
semaphore mutex = 1; //互斥使用取号机
semaphore full = 0; //已占座位的数量,初值0
semaphore service = 0; //等待叫号
cobegin
    Process 顾客i
    {
        P(empty); //等空位
        P(mutex); //申请使用取号机
        从取号机上取号;
        V(mutex); //取号完毕
        V(full); //通知营业员有新顾客
        P(service); //等待营业员叫号
        接受服务;
    }
    Process 营业员
    {
        while(True){
            P(full); //没有顾客则休息
            V(empty); //离开座位
            V(service); //叫号
            为顾客服务;
        }
    }
}coend

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

cobegin
    参观者进程i:
    {
       ...
        进门;
       ...
        参观;
       ...
        出门;
       ...
    }
coend

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

19.解答:
出入口一次仅允许一人通过,设置互斥信号量 mutex,初值为 1。博物馆最多可以同时容纳 500 人,因此设置信号量 empty,初值为 500。

semaphore empty = 500; //博物馆可以容纳的最多人数
semaphore mutex = 1; //用于出入口资源的控制
cobegin
参观者进程i:
{
    P(empty); //可容纳人数减1
    P(mutex); //互斥使用门
    进门;
    V(mutex);
    参观;
    P(mutex); //互斥使用门
    出门;
    V(mutex);
    V(empty); //可容纳人数增1
}
coend

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

20.解答:
这是典型的生产者和消费者问题,只对典型问题加了一个条件,只需在标准模型上加一个信号量,即可完成指定要求。
设置 4 个变量 mutex1, mutex2, empty 和 full,mutex1 用于控制一个消费者进程在一个周期(10 次)内对缓冲区的访问,初值为 1;mutex2 用于控制进程单次互斥地访问缓冲区,初值为 1;empty 代表缓冲区的空位数,初值为 1000;full 代表缓冲区的产品数,初值为 0。伪代码如下:

semaphore empty = 1000; //空缓冲区数
semaphore full = 0; //非空缓冲区数
semaphore mutex1 = 1; //用于实现生产者之间的互斥
semaphore mutex2 = 1; //用于实现消费者之间的互斥
int in = 0;
int out = 0;
生产者进程
while(TRUE){
    produce;
    P(empty);
    P(mutex1);
    put an item into buf[in];
    in = (in + 1) mod n;
    V(mutex1);
    V(full);
}
消费者进程
while(TRUE){
    P(mutex2);
    for(int i = 0;i < 10;i++){
        P(full);
        get an item from buf[out];
        out = (out + 1) mod n;
        V(empty);
    }
    V(mutex2);
    Consume;
}

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


CoBegin
{
    A{
        while(TRUE){
            从A的信箱中取出一个邮件;
            回答问题并提出一个新问题;
            将新邮件放入B的信箱;
        }
    }
    B{
        while(TRUE){
            从B的信箱中取出一个邮件;
            回答问题并提出一个新问题;
            将新邮件放入A的信箱;
        }
    }
}
CoEnd

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

21.解答:
本题是一个典型的生产者 - 消费者问题。A 和 B 既是生产者,又是消费者。首先分析题中的互斥关系,信箱 A 和信箱 B 作为生产者 - 消费者问题中的缓冲区,需要互斥访问,因此设置两个信号量 mutex_A 和 mutex_B。然后分析 A 和 B 的同步关系,A 有两个动作,分别是从信箱 A 中取出邮件和将新邮件放入信箱 B,因此设置信号量 Full_A 和 Empty_B,用来保证信箱 A 中有邮件可以取,信箱 B 中有空间可以放。B 的分析同理。因此本题的所有信号量设置如下:

semaphore Full_A = x; //Full_A表示A的信箱中的邮件数量
semaphore Empty_A = M - x; //Empty_A表示A的信箱中还可存放的邮件数量
semaphore Full_B = y; //Full_B表示B的信箱中的邮件数量
semaphore Empty_B = N - y; //Empty_B表示B的信箱中还可存放的邮件数量
semaphore mutex_A = 1; //mutex_A用于A的信箱互斥
semaphore mutex_B = 1; //mutex_B用于B的信箱互斥

互斥信号量的 PV 操作直接放置在对信箱操作的前后。在 A 取邮件之前需执行 P (Full_A) 来检查信箱 A 中是否有邮件可取,取出邮件之后需执行 V (Empty_A),表示新增了一个邮件大小的空间;在 A 将邮件放入信箱 B 之前需执行 P (Empty_B) 来检车信箱 B 中是否有空间可以放,放完邮件之后需执行 V (Full_B),表示信箱 B 增加了一个邮件。B 的分析同理。

Cobegin
A{
    while(TRUE){
        P(Full_A);
        P(mutex_A);
        从A的信箱中取出一个邮件;
        V(mutex_A);
        V(Empty_A);
        回答问题并提出一个新问题;
        P(Empty_B);
        P(mutex_B);
        将新邮件放入B的信箱;
        V(mutex_B);
        V(Full_B);
    }
}
B{
    while(TRUE){
        P(Full_B);
        P(mutex_B);
        从B的信箱中取出一个邮件;
        V(mutex_B);
        V(Empty_B);
        回答问题并提出一个新问题;
        P(Empty_A);
        P(mutex_A);
        将新邮件放入A的信箱;
        V(mutex_A);
        V(Full_A);
    }
}
Coend

22.【2017 统考真题】某进程中有 3 个并发执行的线程 thread1, thread2 和 thread3,其伪代码如下所示。


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

22.解答:
对于这类问题:
        首先,找出有可能需要互斥访问的变量,只有全局变量才可能需要互斥访问,也就是说,含全局变量的代码段才可能是临界区。而局部变量是不需要互斥访问的,就算不同的进程之间都有相同名字的局部变量,但对不同的进程来说,它们只认识属于自己的局部变量。
        其次,有关互斥的难点不在于找出哪些变量有可能需要互斥访问,而要理解互斥是进程之间对某个变量的互斥,不能脱离具体的进程来谈某个变量需不需要互斥访问。前面说了全局变量有可能需要互斥访问,但也不一定,因为这要看进程对它的操作,因此,一组互斥关系需要指出是哪些进程对哪个变量的互斥。例如,考虑两种情况:第一种情况是,进程 1 和进程 2 需要对共享变量 C 互斥访问,进程 1 和进程 3 也需要对变量 C 互斥访问,这是两组不同的互斥关系,而不能简单地理解为对 C 的互斥;第二种情况是,进程 1、2、3 两两之间都需要对变量 C 互斥访问。这种情况与第一种情况的差别是,第一种情况允许进程 2、3 对变量 C 的访问是不互斥的。若题目给出的是第一种情况,而我们采用了第二种情况去解题,则可能有同学认为,我依然满足了题目的要求啊,进程 1、2 之间以及进程 1、3 之间对 C 的访问都是互斥的,但这样降低了并发度,因为第二种情况允许进程 2、3 对变量 C 的访问是不互斥的,这也是 PV 互斥的最大难点,就是在没有准确找准互斥关系的条件下,容易导致程序的并发度降低,从而扣分。
本题的分析如下。
        首先,全局变量是 x、y、z,只有对这三个变量的访问才可能需要互斥。线程 1 涉及 x、y 的访问(只读),线程 2 涉及 y、z 的访问(只读),线程 3 涉及 y、z 的访问(读和写)。
其次,找互斥关系,根据什么原则呢?答案是读者 - 写者原则。读和读之间不需要互斥,读和写之间、写和写之间需要互斥。因此,本题的互斥关系如下:第一对,thread1 和 thread3 之间需要对变量 y 的访问互斥;第二对,线程 2 和线程 3 之间需要对变量 y 的访问互斥;第三对,线程 2 和线程 3 之间需要对变量 z 访问的互斥。正确找出所有互斥关系后,接下来的操作就很简单。因为有三组互斥关系,所以定义三个互斥信号量,分别为 mutex_y1 = 1、mutex_y2 = 1 和 mutex_z = 1,然后分别将它们加到线程代码段的相应位置。例如,在线程 1 的 w = add (x, y) 和线程 3 的 y = add (y, w) 上下用 mutex_y1 夹住;在线程 2 的 w = add (y, z) 和线程 3 的 z = add (z, w) 上下用 mutex_y2 夹住;在线程 2 的 w = add (y, z) 和线程 3 的 y = add (y, w) 上下用 mutex_z 夹住。

semaphore mutex_y1 = 1; //mutex_y1用于thread1与thread3对变量y的互斥访问
semaphore mutex_y2 = 1; //mutex_y2用于thread2与thread3对变量y的互斥访问
semaphore mutex_z = 1; //mutex_z用于thread2与thread3对变量z的互斥访问

互斥代码如下:

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

23.解答:
        回顾传统的哲学家问题,假设餐桌上有 n 名哲学家、n 根筷子,那么可以用这种方法避免死锁(本书考点讲解中提供了这一思路):限制至多允许 n - 1 名哲学家同时 “抢” 筷子,那么至少会有 1 名哲学家可以获得两根筷子并顺利进餐,于是不可能发生死锁的情况。
本题可以用碗这个限制资源来避免死锁:当碗的数量 m 小于哲学家的数量 n 时,可以直接让碗的资源量等于 m,确保不会出现所有哲学家都拿一侧筷子而无限等待另一侧筷子进而造成死锁的情况;当碗的数量 m 大于或等于哲学家的数量 n 时,为了让碗起到同样的限制效果,我们让碗的资源量等于 n - 1,这样就能保证最多只有 n - 1 名哲学家同时进餐,所以得到碗的资源量为 min {n - 1, m}。在进行 PV 操作时,碗的资源量起限制哲学家取筷子的作用,所以需要先对碗的资源量进行 P 操作。具体过程如下:

//信号量
semaphore bowl; //用于协调哲学家对碗的使用
semaphore chopsticks[n]; //用于协调哲学家对筷子的使用
for(int i = 0;i < n;i++)
    chopsticks[i]=1; //设置两名哲学家之间筷子的数量
bowl = min(n - 1,m); //bowl≤n - 1,确保不死锁
CoBegin
while(TRUE){ //哲学家i的程序
    思考;
    P(bowl); //取碗
    P(chopsticks[i]); //取左边筷子
    P(chopsticks[(i + 1) % n]); //取右边筷子
    就餐;
    V(chopsticks[i]);
    V(chopsticks[(i + 1) % n]);
    V(bowl);
}
CoEnd

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

24.解答:
本题是一个典型的利用信号量实现前驱关系的同步问题。首先画出各个操作之间的执行顺序图,可以看出,A、B、D 的执行不需要任何前提条件。执行完 A 和 B 之后才能执行 C,存在两对同步关系:A→C 和 B→C,设置两个同步变量 SAC = 0 和 SBC = 0,完成 A 和 B 之后分别执行 V (SAC) 和 V (SBC),表示 A 或 B 已完成;执行 C 之前需要执行 P (SAC) 和 P (SBC),检查 A 和 B 是否完成。执行完 C 和 D 之后才能执行 E,也存在两对同步关系:C→E 和 D→E,因此再设置两个同步变量 SCE = 0 和 SDE = 0,完成 C 和 D 之后分别执行 V (SCE) 和 V (SDE),表示 C 或 D 已完成;执行 E 之前需要执行 P (SCE) 和 P (SDE),检查 C 和 D 是否完成。

semaphore SAC = 0; //控制A和C的执行顺序
semaphore SBC = 0; //控制B和C的执行顺序
semaphore SCE = 0; //控制C和E的执行顺序
semaphore SDE = 0; //控制D和E的执行顺序

5 个操作可描述为如下。

CoBegin
A(){
    完成动作A;
    V(SAC); //实现A、C之间的同步关系
}
B(){
    完成动作B;
    V(SBC); //实现B、C之间的同步关系
}
C(){
    //C必须在A、B都完成后才能完成
    P(SAC);
    P(SBC);
    完成动作C;
    V(SCE); //实现C、E之间的同步关系
}
D(){
    完成动作D;
    V(SDE); //实现D、E之间的同步关系
}
E(){
    //E必须在完成C、D之后执行
    P(SCE);
    P(SDE);
    完成动作E;
}
CoEnd

25.【2021 统考真题】下表给出了整型信号量S的 wait () 和 signal () 操作的功能描述,以及采用开 / 关中断指令实现信号量操作互斥的两种方法。

请回答下列问题。
1)为什么在 wait () 和 signal () 操作中对信号量S的访问必须互斥执行?
2)分别说明方法 1 和方法 2 是否正确。若不正确,请说明理由。
3)用户程序能否使用开 / 关中断指令实现临界区互斥?为什么?

25.解答:
1)信号量 S 是能被多个进程共享的变量,多个进程都可通过 wait () 和 signal () 对 S 进行读、写操作。所以,wait () 和 signal () 操作中对 S 的访问必须是互斥的。
2)方法 1 错误。在 wait () 中,当 S <= 0 时,关中断后,其他进程无法修改 S 的值,while 语句陷入死循环。方法 2 正确。方法 2 在循环体中有一个开中断操作,这样就可以使其他进程修改 S 的值,从而避免 while 语句陷入死循环。
3)用户程序不能使用开 / 关中断指令实现临界区互斥。因为开中断和关中断指令都是特权指令,不能在用户态下执行,只能在内核态下执行。

26.【2022 统考真题】某进程的两个线程T1和T2并发执行A、B、C、D、E和F共 6 个操作,其中T1执行A、E和F,T2执行B、C和D。下图表示上述 6 个操作的执行顺序所必须满足的约束:C在A和B完成后执行,D和E在C完成后执行,F在E完成后执行。请使用信号量的 wait ()、signal () 操作描述T1和T2之间的同步关系,并说明所用信号量的作用及其初值。

26.解答:
本题是一个典型的利用信号量实现前驱关系的同步问题。需要强调的是,只有不同进程之间的操作才需要进行同步。进程 T1 依次执行 A、E、F,进程 T2 依次执行 B、C、D。我们需要分析哪些操作必须在另一个进程的某个操作完成之后才能执行。由图可知,对进程 T1 来说,E 必须在进程 T2 执行完 C 后才能执行;对进程 T2 来说,C 必须在进程 T1 执行完 A 后才能执行。因此,有两对同步关系:A→C 和 C→E。为了实现这两对同步关系,定义两个同步信号量SAC​和SCE​。进程 T1 执行完 A 后,发出信号 signal (SAC​),表示 A 已执行完成;进程 T2 准备执行 C 之前,等待信号 wait (SAC​),检查 A 是否执行完成。同理,进程 T2 执行完 C 后,发出信号 signal (SCE​),表示 C 已执行完成;进程 T1 准备执行 E 之前,等待信号 wait (SCE​),检查 C 是否执行完成。这样就保证了两个进程之间的同步。

27.【2023 统考真题】现要求学生使用 swap 指令和布尔型变量 lock 实现临界区互斥。lock 为线程间共享的变量,当 lock 的值为 TRUE 时线程不能进入临界区,为 FALSE 时线程能够进入临界区。某同学编写的实现临界区互斥的伪代码如下图所示。

请回答下列问题。
1)图 (a) 的伪代码中哪些语句存在错误?将其改为正确的语句(不增加语句的条数)。
2)图 (b) 给出了交换两个变量值的函数 newSwap () 的代码,是否可以用函数调用语句 “newSwap (&key, &lock)” 代替指令 “swap key, lock” 以实现临界区互斥?为什么?

27.解答:
1)if 语句无法实现对临界区的互斥访问,因为 if 语句执行后,不论结果如何,线程都能访问临界区。本题使用 swap 指令和 lock 变量来实现对临界区的互斥访问,当线程不能进入临界区时,本身并不会主动放弃 CPU,因此需要让线程在进入区中循环检查 lock 值,可以使用 while 循环,当 lock 值为 TRUE 时,线程一直执行 while 循环的内容,直到 lock 值被修改为 FALSE 时,线程才能进入临界区,因此将进入区中的语句 “if (key == TRUE) swap key, lock” 修改为 “while (key == TRUE) swap key, lock”。在退出区中,代表该线程对临界资源的访问已经结束,此时需要将 lock 值设为 FALSE,代表其他线程可以访问临界区,因此将退出区中的语句 “lock = TRUE” 修改为 “lock = FALSE”。
2)否。因为多个线程可以并发执行 newSwap (),newSwap () 执行时传递给形参 b 的是共享变量 lock 的地址,在 newSwap () 中对 lock 既有读操作又有写操作,并发执行时不能保证实现两个变量值的原子交换,从而导致并发执行的线程同时进入临界区。例如,线程 A 和线程 B 并发执行,初始时 lock 值为 FALSE,当线程 A 执行完 * a = *b 后发生了进程调度,切换到线程 B 执行,线程 B 执行完 newSwap 后发生线程切换,此时线程 A 和 B 都能进入临界区,不能实现互斥访问。

28.【2024 统考真题】计算机系统中的进程之间往往需要相互协作来完成一个任务。在某网络系统中,缓冲区B用于存放一个数据分组,对B的操作有C1​、C2​和C3​。C1​将一个数据分组写入B,C2​从B中读出一个数据分组,C3​对B中的数据分组进行修改。要求B为空时才能执行C1​,B非空时才能执行C2​和C3​。请回答下列问题。
1)假设进程P1​和P2​均需要执行C1​,实现C1​的代码是否为临界区?为什么?
2)假设B初始为空,进程P1​执行C1​一次,进程P2​执行C2​一次。请定义尽可能少的信号量,并用 wait ()、signal () 操作描述进程P1​和P2​之间的同步或互斥关系,说明所用信号量的作用及其初值。
3)设B初始不为空,进程P1​和P2​各执行C3​一次。定义尽可能少的信号量,并用 wait ()、signal () 操作描述进程P1​和P2​之间的同步或互斥关系,说明所用信号量的作用及其初值。

28.解答:
1)代码C1​执行对 B 的写操作,且P1​和P2​需要互斥执行C1​,因此C1​的代码是临界区。
2)有一组互斥关系和一组同步关系。互斥关系为:P1​和P2​需要互斥访问缓冲区 B;同步关系为:P1​执行C1​后,P2​才能执行C2​。缓冲区 B 初始为空,且最大容量为 1,因此可不用定义互斥信号量 mutex。因为在缓冲区大小为 1 的条件下,同步信号量 S 就可同时保证P1​和P2​互斥访问 B,符合题意 “尽可能定义少的信号量” 的要求。进程P1​和P2​的同步伪代码如下。

3)缓冲区 B 初始非空,因此仅需一个互斥信号量 mutex 来保证P1​和P2​互斥访问 B 即可。进程P1​和P2​的互斥伪代码如下。

 2.4 死锁

        在学习本节时,请读者思考以下问题:
        1)为什么会产生死锁?产生死锁有什么条件?
        2)有什么办法可以解决死锁问题?
        学完本节,读者应了解死锁的由来、产生条件及基本解决方法,区分避免死锁和预防死锁。

2.4.1 死锁的概念

        在多道程序系统中,由于进程的并发执行,极大提升了系统效率。然而,多个进程的并发执行也带来了新的问题 —— 死锁。所谓死锁,是指多个进程因竞争资源而造成的一种僵局(互相等待对方手里的资源),使得各个进程都被阻塞,若无外力干涉,这些进程都无法向前推进。

        下面通过一些实例来说明死锁现象。
        先看生活中的一个实例。在一条河上有一座桥,桥面很窄,只能容纳一辆汽车通行。若有两辆汽车分别从桥的左右两端驶上该桥,则会出现下述冲突情况:此时,左边的汽车占有桥面左边的一段,右边的汽车占有桥面右边的一段,要想过桥则需等待左边或右边的汽车向后行驶以退出桥面。但若左右两边的汽车都只想向前行驶,则两辆汽车都无法过桥。

        在计算机系统中也存在类似的情况。例如,某计算机系统中只有一台打印机和一台输入设备,进程 P1正占用输入设备,同时又提出使用打印机的请求,但此时打印机正被进程 P2 所占用,而 P2 在未释放打印机之前,又提出请求使用正被 P1 占用的输入设备。这样,两个进程相互无休止地等待下去,均无法继续向前推进,此时两个进程陷入死锁状态。

2. 死锁与饥饿

        一组进程处于死锁状态是指组内的每个进程都在等待一个事件,而该事件只可能由组内的另一个进程产生。与死锁相关的另一个问题是饥饿,即进程在信号量内无穷等待的情况。

         产生饥饿的主要原因是:当系统中有多个进程同时申请某类资源时,由分配策略确定资源分配给进程的次序,有的分配策略可能是不公平的,即不能保证等待时间上界的存在。在这种情况下,即使系统未发生死锁,某些进程也可能长时间等待。当等待时间给进程的推进带来明显影响时,称发生了饥饿。例如,当有多个进程需要打印文件时,若系统分配打印机的策略是最短文件优先,则长文件的打印任务将因短文件的源源不断到来而被无限期推迟,最终导致饥饿,甚至“饿死”。饥饿并不表示系统一定会死锁,但至少有一个进程的执行被无限期推迟。

        死锁和饥饿的共同点都是进程无法顺利向前推进的现象。

        死锁和饥饿的主要差别:

        ①发生饥饿的进程可以只有一个;而死锁是因循环等待对方手里的资源而导致的,因此,若有死锁现象,则发生死锁的进程必然大于或等于两个。

        ②发生饥饿的进程可能处于就绪态(长期得不到CPU,如SJF算法的问题),也可能处于阻塞态(如长期得不到所需的I/O设备,如上述举例);而发生死锁的进程必定处于阻塞态。

3. 死锁产生的原因

**命题追踪** 单类资源竞争时发生死锁的临界条件的分析(2009、2014)

(1)系统资源的竞争

        通常系统中拥有的不可剥夺资源(如磁带机、打印机等),其数量不足以满足多个进程运行的需要,使得进程在运行过程中,会因争夺资源而陷入僵局。只有对不可剥夺资源的竞争才可能产生死锁,对可剥夺资源(如CPU和主存)的竞争是不会引起死锁的。

(2)进程推进顺序非法

        请求和释放资源的顺序不当,也同样会导致死锁。例如,进程 P1,P2 分别保持了资源 R1,R2,而 P1申请资源 R2、P2 申请资源 R1时,两者都会因为所需资源被占用而阻塞,于是导致死锁。

        信号量使用不当也会造成死锁。进程间彼此相互等待对方发来的消息,也会使得这些进程无法继续向前推进。例如,进程A等待进程B发的消息,进程B又在等待进程A发的消息,可以看出进程A和B不是因为竞争同一资源,而是在等待对方的资源导致死锁。

4. 死锁产生的必要条件

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

        1)互斥条件。进程要求对所分配的资源(如打印机)进行排他性使用,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。

        2)不可剥夺条件。进程所获得的资源在未使用完之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放(只能是主动释放)。

         3)请求并保持条件。进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。

        资源分配图含圈而系统又不一定有死锁的原因是,同类资源数大于1。但若系统中每类资源都只有一个资源,则资源分配图含圈就变成了系统出现死锁的充分必要条件。

         要注意区分不可剥夺条件与请求并保持条件。下面用一个简单的例子进行说明:若你手上拿着一个苹果(即便你不打算吃),别人不能将你手上的苹果拿走,则这就是不可剥夺条件;若你左手拿着一个苹果,允许你右手再去拿一个苹果,则这就是请求并保持条件。

01. 下列情况中,可能导致死锁的是( )。

 A. 进程释放资源

B. 一个进程进入死循环

C. 多个进程竞争资源出现了循环等待

D. 多个进程竞争使用共享型设备

01.C
        引起死锁的 4 个必要条件是:互斥、占有并等待、非剥夺和循环等待。本题中,出现了循环等待的现象,意味着可能导致死锁。进程释放资源不会导致死锁,进程自己进入死循环只能产生 “饥饿”,不涉及其他进程。共享型设备允许多个进程申请使用,因此不会造成死锁。再次提醒,死锁一定要有两个或两个以上的进程才会导致,而饥饿可能由一个进程导致。

5. 死锁的处理策略

        为使系统不发生死锁,必须设法破坏产生死锁的4个必要条件之一,或允许死锁产生,但当死锁发生时能检测出死锁,并有能力实现恢复。

         1)死锁预防。设置某些限制条件,破坏产生死锁的4个必要条件中的一个或几个。

         2)避免死锁。在资源的动态分配过程中,用某种方法防止系统进入不安全状态。

        3)死锁的检测及解除。无须采取任何限制性措施,允许进程在运行过程中发生死锁。通过系统的检测机构及时地检测出死锁的发生,然后采取某种措施解除死锁。

         预防死锁和避免死锁都属于事先预防策略,预防死锁的限制条件比较严格,实现起来较为简单,但往往导致系统的效率低,资源利用率低;避免死锁的限制条件相对宽松,资源分配后需要通过算法来判断是否进入不安全状态,实现起来较为复杂。

        死锁的几种处理策略的比较见表2.5。

2.4.2 死锁预防

**命题追踪** 死锁预防的特点(2019)

        预防死锁的发生只需破坏死锁产生的4个必要条件之一即可。

1. 破坏互斥条件

        若将只能互斥使用的资源改造为允许共享使用,则系统不会进入死锁状态。但有些资源根本不能同时访问,如打印机等临界资源只能互斥使用。所以,破坏互斥条件而预防死锁的方法不太可行,而且为了系统安全,很多时候还必须保护这种互斥性。

2. 破坏不可剥夺条件

        当一个已经保持了某些不可剥夺资源的进程,请求新的资源而得不到满足时,它必须释放已经保持的所有资源,待以后需要时再重新申请。这意味着,进程已占有的资源会被暂时释放,或者说是被剥夺了,从而破坏了不可剥夺条件。

         该策略实现起来比较复杂。释放已获得的资源可能造成前一阶段工作的失效,因此这种方法常用于状态易于保存和恢复的资源,如CPU的寄存器及内存资源,一般不能用于打印机之类的资源。反复地申请和释放资源既影响进程推进速度,又增加系统开销,进而降低了系统吞吐量。

3. 破坏请求并保持条件

        要求进程在请求资源时不能持有不可剥夺资源,可以通过两种方法实现:

        1)采用预先静态分配方法,即进程在运行前一次申请完它所需要的全部资源。在它的资源未满足前,不让它投入运行。在进程的运行期间,不会再提出资源请求,从而破坏了“请求”条件。在等待期间,进程不占有任何资源,从而破坏了“保持”条件。

        2)允许进程只获得运行初期所需的资源后,便可开始运行。进程在运行过程中再逐步释放已分配给自己且已使用完毕的全部资源后,才能请求新的资源。

        方法一的实现简单,但缺点也显而易见,系统资源被严重浪费,其中有些资源可能仅在运行初期或快结束时才使用,而且会导致“饥饿”现象,由于个别资源长期被其他进程占用,将导致等待该资源的进程迟迟不能开始运行。方法二则改进了这些缺点。

4. 破坏循环等待条件

        为了破坏循环等待条件,可以采用顺序资源分配法。首先给系统中的各类资源编号,规定每个进程必须按编号递增的顺序请求资源,同类资源(编号相同的资源)一次申请完。也就是说,一个进程只在已经占有小编号的资源时,才有资格申请更大编号的资源。按此规则,已持有大编号资源的进程不可能再逆向申请小编号的资源,因此不会产生循环等待的现象。

        这种方法的缺点:编号必须相对稳定,因此不便于增加新类型设备;尽管在编号时已考虑到大多数进程使用这些资源的顺序,但是进程实际使用资源的顺序还是可能和编号的次序不一致,这就会造成资源的浪费;此外,必须按规定次序申请资源,也会给用户编程带来麻烦。

2.4.3 死锁避免

        避免死锁同样属于事先预防策略,但并不是事先采取某种限制措施破坏死锁的必要条件,而是在每次分配资源的过程中,都要分析此次分配是否会带来死锁风险,只有在不产生死锁的情况下,系统才会为其分配资源。这种方法所施加的限制条件较弱,可以获得较好的系统性能。

1. 系统安全状态

        避免死锁的方法中,允许进程动态地申请资源,但系统在进行资源分配之前,应先计算此次

分配的安全性。若此次分配不会导致系统进入不安全状态,则允许分配;否则让进程等待。

        所谓安全状态,是指系统能按某种进程推进顺序(P1,P2,…,Pn)为每个进程Pi分配其所需的资源,直至满足每个进程对资源的最大需求,使每个进程都可顺利完成。此时称P1,P2,…,Pn为安全序列(可能有多个)。若系统无法找到一个安全序列,则称系统处于不安全状态。

**命题追踪** 系统安全状态的分析(2018)

        假设系统有三个进程P1,P2和P3,共有12台磁带机。P1需要10台,P2和P3分别需要4台和9台。假设在T0时刻,P1,P2和P3已分别获得5台、2台和2台,尚有3台未分配,见表2.6。


        在T0时刻是安全的,因为存在一个安全序列P2,P1,P3,只要系统按此进程序列分配资源,那么每个进程都能顺利完成。也就是说,当前可用资源数为3,先将2台分配给P2以满足其最大需求,P2结束并归还资源后,系统有5台可用;接下来给P1分配5台以满足其最大需求,P1结束并归还资源后,剩余10台可用;最后分配7台给P3,这样P3也能顺利完成。

        若在T0时刻后,系统分配1台给P3,剩余可用资源数为2,此时系统进入不安全状态,因为此时已无法再找到一个安全序列。当系统进入不安全状态后,便可能导致死锁。例如,将剩下的2台分配给P2,这样,P2完成后只能释放4台,既不能满足P1又不能满足P3,致使它们都无法推进到完成,彼此都在等待对方释放资源,陷入僵局,即导致死锁。

        若系统处于安全状态,则一定不会发生死锁;若系统进入不安全状态,则有可能发生死锁(处于不安全状态未必就会发生死锁,但发生死锁时一定是处于不安全状态)。

2. 银行家算法

**命题追踪** 银行家算法的特点(2013、2019)

         银行家算法是最著名的死锁避免算法,其思想是:将操作系统视为银行家,操作系统管理的资源视为银行家管理的资金,进程请求资源相当于用户向银行家贷款。进程运行之前先声明对各种资源的最大需求量,其数量不应超过系统的资源总量。当进程在运行中申请资源时,系统必须先确定是否有足够的资源分配给该进程。若有,再进一步试探在将这些资源分配给进程后,是否会使系统处于不安全状态。若不会,则将资源分配给它,否则让进程等待。

 

2.4.4 死锁检测和解除

**命题追踪** 死锁避免和死锁检测的区分(2015)

        前面介绍的死锁预防和避免算法,都是在为进程分配资源时施加限制条件或进行检测,若系统为进程分配资源时不采取任何预防或避免措施,则应该提供死锁检测和解除的手段。

1. 死锁检测

**命题追踪** 死锁避免和死锁检测对比(2015)

        死锁避免和死锁检测的对比。死锁避免需要在进程的运行过程中一直保证之后不可能出现死锁,因此需要知道进程从开始到结束的所有资源请求。而死锁检测是检测某个时刻是否发生死锁,不需要知道进程在整个生命周期中的资源请求,只需知道对应时刻的资源请求。

**命题追踪** 多在资源竞争时发生死锁的临界条件分析(2016、2021)

        可用资源分配图来检测系统所处的状态是否为死锁状态。如图2.15(a)所示,用圆圈表示一个进程,用框表示一类资源。一种类型的资源可能有多个,因此用框中的一个圆表示一类资源中的一个资源。从进程到资源的有向边称为请求边,表示该进程申请一个单位的该类资源;从资源到进程的有向边称为分配边,表示该类资源已有一个资源分配给了该进程。

        在图2.15(a)所示的资源分配图中,进程P1已分得了两个R1资源,并又请求一个R2资源;进程P2分得了一个R1资源和一个R2资源,并又请求一个R1资源。

简化资源分配图可检测系统状态S是否为死锁状态。简化方法如下:

        1)在资源分配图中,找出既不阻塞又不孤立的进程Pi(找出一条有向边与它相连,且该有向边对应资源的申请数量小于或等于系统中已有的空闲资源数量,如在图2.15(a)中,R1没有空闲资源,R2有一个空闲资源。若所有连接该进程的边均满足上述条件,则这个进程能继续运行直至完成,然后释放它所占有的所有资源)。消去它所有的请求边和分配边,使之成为孤立的节点。在图2.15(a)中,P1是满足这一条件的进程节点,将P1的所有边消去,便得到图2.15(b)所示的情况。 这里要注意一个问题,判断某种资源是否有空闲,应该用它的资源数量减去它在资源分配图中的出度,例如在图2.15(a)中,R1的资源数为3,而出度也为3,所以R1没有空闲资源,R2的资源数为2,出度为1,所以R2有一个空闲资源。

        2)进程Pi所释放的资源,可以唤醒某些因等待这些资源而阻塞的进程,原来的阻塞进程可能变为非阻塞进程。在图2.15(a)中,$P_2$就满足这样的条件。根据1)中的方法进行一系列简化后,若能消去图中所有的边,则称该图是可完全简化的,如图2.15(c)所示。

        S为死锁的条件是当且仅当S状态的资源分配图是不可完全简化的,该条件为死锁定理。

2. 死锁解除

**命题追踪** 解除死锁的方式(2019)

        一旦检测出死锁,就应立即采取相应的措施来解除死锁。死锁解除的主要方法有:

        1)资源剥夺法。挂起某些死锁进程,并抢占它的资源,将这些资源分配给其他的死锁进程。但应防止被挂起的进程长时间得不到资源而处于资源匮乏的状态。

        **注意** 在资源分配图中,用死锁定理化简后,还有边相连的那些进程就是死锁进程。

        2)撤销进程法。强制撤销部分、甚至全部死锁进程,并剥夺这些进程的资源。撤销的原则可以按进程优先级和撤销进程代价的高低进行。这种方式实现简单,但付出的代价可能很大,因为有些进程可能已经接近结束,一旦被终止,以后还得从头再来。

        3)进程回退法。让一个或多个死锁进程回退到足以回避死锁的地步,进程回退时自愿释放资源而非被剥夺。要求系统保持进程的历史信息,设置还原点。

2.4.5 本节小结

        本节开头提出的问题的参考答案如下。

        1)为什么会产生死锁?产生死锁有什么条件?

        由于系统中存在一些不可剥夺资源,当两个或两个以上的进程占有自身的资源并请求对方的资源时,会导致每个进程都无法向前推进,这就是死锁。死锁产生的必要条件有4个,分别是互斥条件、不剥夺条件、请求并保持条件和循环等待条件。

        互斥条件是指进程要求分配的资源是排他性的,即最多只能同时供一个进程使用。

        不剥夺条件是指进程在使用完资源之前,资源不能被强制夺走。 请求并保持条件是指进程占有自身本来拥有的资源并要求其他资源。

        循环等待条件是指存在一种进程资源的循环等待链。

         2)有什么办法可以解决死锁问题?

        死锁的处理策略可以分为预防死锁、避免死锁及死锁的检测与解除。

        死锁预防是指通过设立一些限制条件,破坏死锁的一些必要条件,让死锁无法发生。

        死锁避免指在动态分配资源的过程中,用一些算法防止系统进入不安全状态,从而避免死锁。

        死锁的检测和解除是指在死锁产生前不采取任何措施,只检测当前系统有没有发生死锁,若有,则采取一些措施解除死锁。

2.4.6 本节习题精选

一、单项选择题

01. 下列情况中,可能导致死锁的是( )。

 A. 进程释放资源

B. 一个进程进入死循环

C. 多个进程竞争资源出现了循环等待

D. 多个进程竞争使用共享型设备

        01.C
        引起死锁的 4 个必要条件是:互斥、占有并等待、非剥夺和循环等待。本题中,出现了循环等待的现象,意味着可能导致死锁。进程释放资源不会导致死锁,进程自己进入死循环只能产生 “饥饿”,不涉及其他进程。共享型设备允许多个进程申请使用,因此不会造成死锁。再次提醒,死锁一定要有两个或两个以上的进程才会导致,而饥饿可能由一个进程导致。

02. 在哲学家进餐问题中,若所有哲学家同时拿起左筷子,则发生死锁,因为他们都需要右筷子才能用餐。为了让尽可能多的哲学家可以同时用餐,并且不发生死锁,可以利用信号量PV操作实现同步互斥,下列说法中正确的是( )。

A. 使用信号量进行控制的方法一定可以避免死锁

B. 同时检查两支筷子是否可用的方法可以预防死锁,但是会导致饥饿问题

C. 限制允许拿起筷子的哲学家数量可以预防死锁,它破坏了“循环等待”条件

D. 对哲学家顺序编号,奇数号哲学家先拿左筷子,然后拿右筷子,而偶数号哲学家刚好相反,可以预防死锁,它破坏了“互斥”条件

        02.C

        信号量机制能确保临界资源的互斥访问,不能完全避免死锁,选项 A 错误。

        同时检查两支筷子是否可用的方法可以预防死锁,但是会导致资源浪费,因为可能有一些空闲的筷子无法使用,但拿到筷子的哲学家用完餐后,释放筷子,其他哲学家就可以正常用餐,因此不会导致饥饿现象,选项 B 错误。

        若限制允许拿起筷子的哲学家数量,则不被允许的哲学家左边的哲学家一定可以拿到两边的筷子,从而破坏 “循环等待” 条件,选项 C 正确。

        对哲学家顺序编号,奇数号哲学家先拿左筷子,然后拿右筷子,而偶数号哲学家刚好相反,则相邻的哲学家总有一个可以拿起两边的筷子,但这破坏的是 “循环等待” 条件,而不是 “互斥条件” ,选项 D 错误。

03. 下列关于进程死锁的描述中,错误的是( )。

A. 若每个进程只能同时申请或拥有一个资源,则不会发生死锁

B. 若多个进程可以无冲突共享访问所有资源,则不会发生死锁

C. 若所有进程的执行严格区分优先级,则不会发生死锁

D. 若进程资源请求之间不存在循环等待,则不会发生死锁

        03. C

        进程的执行优先级并不能破坏死锁的四个必要条件。即使有高优先级和低优先级的进程,若它们都请求或占有了不可抢占的资源,且形成了环路等待,则死锁仍可能发生。选项 A 可以破坏请求并保持条件,选项 B 可以破坏互斥条件,选项 D 可以破坏循环等待条件。

04. 一次分配所有资源的方法可以预防死锁的发生,它破坏死锁4个必要条件中的( )。

A. 互斥

B. 占有并请求

C. 非剥夺

D. 循环等待

        04. B

        发生死锁的 4 个必要条件:互斥、占有并请求、非剥夺和循环等待。一次分配所有资源的方法是当进程需要资源时,一次性提出所有的请求,若请求的所有资源均满足则分配,只要有一项不满足,就不分配任何资源,该进程阻塞,直到所有的资源空闲后,满足进程的所有需求时再分配。这种分配方式不会部分地占有资源,因此打破了死锁的 4 个必要条件之一,实现了对死锁的预防。但是,这种分配方式需要凑齐所有资源,因此当一个进程所需的资源较多时,资源的利用率会较低,甚至会造成进程 “饥饿” 。

05. 系统产生死锁的可能原因是( )。

A. 独占资源分配不当

B. 系统资源不足

C. 进程运行太快

D. CPU内核太多

        05. A

        系统死锁的可能原因主要是时间上和空间上的。时间上由于进程运行中推进顺序不当,即调度时机不合适,不该切换进程时进行了切换,可能造成死锁;空间上的原因是对独占资源分配不当,互斥资源部分分配又不可剥夺,极易造成死锁。那么,为什么系统资源不足不是造成死锁的原因呢?系统资源不足只会对进程造成 “饥饿” 。例如,某系统只有三台打印机,若进程运行中要申请四台,显然不能满足,该进程会永远等待下去。若该进程在创建时便声明需要四台打印机,则操作系统立即就会拒绝,这实际上是资源分配不当的一种表现。不能以系统资源不足来描述剩余资源不足的情形。

06. 死锁的避免是根据( )采取措施实现的。

A. 配置足够的系统资源

B. 使进程的推进顺序合理

C. 破坏死锁的四个必要条件之一

D. 防止系统进入不安全状态

        06. D

        死锁避免是指在资源动态分配过程中用某些算法加以限制,防止系统进入不安全状态从而避免死锁的发生。选项 B 是避免死锁后的结果,而不是措施的原理。

07. 死锁预防是保证系统不进入死锁状态的静态策略,其解决办法是破坏产生死锁的四个必要条件之一。下列方法中破坏了“循环等待”条件的是( )。

A. 银行家算法

B. 一次性分配策略

C. 剥夺资源法

D. 资源有序分配策略

        07. D

        资源有序分配策略可以限制循环等待条件的发生。选项 A 判断是否为不安全状态;选项 B 破坏了占有请求条件;选项 C 破坏了非剥夺条件。

08. 可以防止系统出现死锁的手段是( )。

A. 用PV操作管理共享资源

B. 使进程互斥地使用共享资源

C. 采用资源静态分配策略

D. 定时运行死锁检测程序

        08. C

        选项A,PV 操作不能破坏死锁条件,反而可能加强互斥和占有并等待条件。选项 B 同理。选项 C 可以破坏请求并保持条件。选项 D 只能在系统出现死锁时检测,却不能防止系统出现死锁。

09. 某系统中有三个并发进程都需要四个同类资源,则该系统必然不会发生死锁的最少资源是( )。

A. 9

B. 10

C. 11

D. 12

        09. B

        资源数为 9 时,存在三个进程都占有三个资源,为死锁;资源数为 10 时,必然存在一个进程能拿到 4 个资源,然后可以顺利执行完其他进程。

10. 某系统中共有11台磁带机,X个进程共享此磁带机设备,每个进程最多请求使用3台,则系统必然不会死锁的最大X值是( )。

A. 4

B. 5

C. 6

D. 7

        10. B

        考虑一下极端情况:每个进程已经分配了两台磁带机,那么其中任何一个进程只要再分配一台磁带机即可满足它的最大需求,该进程总能运行下去直到结束,然后将磁带机归还给系统再次分配给其他进程使用。因此,系统中只要满足 2X + 1 = 11 这个条件即可认为系统不会死锁,解得 X = 5,也就是说,系统中最多可以并发 5 个这样的进程是不会死锁的。或者,根据死锁公式,资源数大于进程个数乘以 “每个进程需要的最大资源数减 1” 就不会发生死锁,即 m > n (w - 1),其中 m 是磁带机的数量,n 是进程的数量,w 是每个进程最多请求的磁带机数量。代入可得 11 > n (3 - 1),即 n < 5.5,n 是正整数,因此系统必然不会死锁的最大 n 值是 5。

11. 若系统中有5个某类资源供若干进程共享,则不会引起死锁的情况是( )。

A. 有6个进程,每个进程需1个资源

B. 有5个进程,每个进程需2个资源

C. 有4个进程,每个进程需3个资源

D. 有3个进程,每个进程需4个资源

        11. A

        A 项的每个进程只申请一个资源,破坏了请求并保持条件,必然不会发生死锁。或者,根据死锁公式,假设系统共有 m 个资源,n 个进程,每个进程需要 k 个资源,若满足 m > n (k - 1),则系统一定不会发生死锁,代入公式可知选项 B、C、D 均可能发生死锁。

12. 解除死锁通常不采用的方法是( )。

A. 终止一个死锁进程

B. 终止所有死锁进程

C. 从死锁进程处抢夺资源

D. 从非死锁进程处抢夺资源

        12. D

        解除死锁的方法有,①剥夺资源法:挂起某些死锁进程,并抢占它的资源,将这些资源分配给其他的死锁进程;②撤销进程法:强制撤销部分甚至全部死锁进程并剥夺这些进程的资源。

13. 采用资源剥夺法可以解除死锁,还可以采用( )方法解除死锁。

A. 执行并行操作         B. 撤销进程         C. 拒绝分配新资源         D. 修改信号量

        13. B

        资源剥夺法允许一个进程强行剥夺其他进程所占有的系统资源。而撤销进程强行释放一个进程已占有的系统资源,与资源剥夺法同理,都通过破坏死锁的 “请求和保持” 条件来解除死锁。拒绝分配新资源只能维持死锁的现状,无法解除死锁。

14. 在下列死锁的解决方法中,属于死锁预防策略的是( )。

A. 银行家算法         B. 资源有序分配算法         C. 死锁检测算法         D. 资源分配图化简法

        14. B

        其中,银行家算法为死锁避免算法,死锁检测算法和资源分配图化简法为死锁检测,根据排除法可以得出资源有序分配算法为死锁预防策略。

15. 三个进程共享四个同类资源,这些资源的分配与释放只能一次一个。已知每个进程最多需要两个该类资源,则该系统( )。

A. 有些进程可能永远得不到该类资源

B. 必然有死锁

C. 进程请求该类资源必然能得到

D. 必然是死锁

        15. C

        不会发生死锁。因为每个进程都分得一个资源时,还有一个资源可以让任意一个进程满足,这样这个进程可以顺利运行完成进而释放它的资源。

16. 以下有关资源分配图的描述中,正确的是( )。

A. 有向边包括指向资源类的分配边和资源类指向进程申请边两类

B. 矩形框表示进程,其中圆点表示申请同一类资源的各个进程

C. 圆圈节点表示资源类

D. 资源分配图是一个有向图,用于表示某时刻系统资源与进程之间的状态

        16. D

        进程指向资源的有向边称为申请边,资源指向进程的有向边称为分配边,选项 A 张冠李戴;矩形框表示资源,其中的圆点表示资源的数量,选项 B 错;圆圈节点表示进程,选项 C 错;选项 D 的说法是正确的。

17. 死锁的四个必要条件中,无法破坏的是( )。

A. 环路等待资源 B. 互斥使用资源 C. 占有且等待资源 D. 非抢夺式分配

        17. B

        所谓破坏互斥使用资源,是指允许多个进程同时访问资源,但有些资源根本不能同时访问,如打印机只能互斥使用。因此,破坏互斥条件而预防死锁的方法不太可行,而且在有的场合应该保护这种互斥性。其他三个条件都可以实现。

18. 死锁与安全状态的关系是( )。

A. 死锁状态有可能是安全状态

B. 安全状态有可能成为死锁状态

C. 不安全状态就是死锁状态

D. 死锁状态一定是不安全状态

        18. D

        如下图所示,并非所有不安全状态都是死锁状态,但当系统进入不安全状态后,便可能进入死锁状态;反之,只要系统处于安全状态,系统便可避免进入死锁状态;死锁状态必定是不安全状态。

不安全状态

死锁状态

包含死锁状态

是不安全状态的一部分

19. 死锁检测时检查的是( )。

A. 资源有向图 B. 前驱图 C. 搜索树 D. 安全图

        19. A

        死锁检测一般采用两种方法:资源有向图法和资源矩阵法。前驱图只是说明进程之间的同步关系,搜索树用于数据结构的分析,安全图并不存在。注意死锁避免和死锁检测的区别:死锁避免是指避免死锁发生,即死锁没有发生;死锁检测是指死锁已出现,要把它检测出来。

20. 某个系统采用下列资源分配策略。若一个进程提出资源请求得不到满足,而此时没有由于等待资源而被阻塞的进程,则自己就被阻塞。而当此时已有等待资源而被阻塞的进程,则检查所有由于等待资源而被阻塞的进程。若它们有申请进程所需要的资源,则将这些资源取出并分配给申请进程。这种分配策略会导致( )。

A. 死锁 B. 颠簸 C. 回退 D. 饥饿

        20. D

        某个进程主动释放资源不会导致死锁,因为破坏了请求并保持条件,选项 A 错。颠簸也就是抖动,这是请求分页系统中页面调度不当而导致的现象,是下一章讨论的问题,这里权且断定选项 B 是错的。回退是指从此时此刻的状态退回到一分钟之前的状态,假如一分钟之前拥有资源 X,它有可能释放了资源 X,那就不称回到一分钟之前的状态,也就不是回退,选项 C 错。因为进程过于 “慷慨” ,不断把自己已得到的资源送给别人,导致自己长期无法完成,所以是饥饿,选项 D 正确。

21. 系统的资源分配图在下列情况下,无法判断是否处于死锁状态的有( )。

I. 出现了环路 II. 没有环路

III. 每种资源只有一个,并出现环路 IV. 每个进程节点至少有一条请求边

A. I、II、III、IV

B. I、III、IV

C. I、IV

D. 以上答案都不正确

        21. C

        出现了环路,只是满足了循环等待的必要条件,而满足必要条件不一定会导致死锁,选项 I 对;没有环路,破坏了循环等待条件,一定不会发生死锁,选项 II 错;每种资源只有一个,又出现了环路,这是死锁的充分条件,可以确定是否有死锁,选项 III 错;即使每个进程至少有一条请求边,若资源足够,则不会发生死锁,但若资源不充足,则有发生死锁的可能,选项 IV 对。
综上所述,选择选项 C。

22. 下列关于死锁的说法中,正确的有( )。

I. 死锁状态一定是不安全状态

II. 产生死锁的根本原因是系统资源分配不足和进程推进顺序非法

III. 资源的有序分配策略可以破坏死锁的循环等待条件

IV. 采用资源剥夺法可以解除死锁,还可以采用撤销进程方法解除死锁

A. I、III B. II C. IV D. 四个说法都对

22. D

选项 I 正确。

选项 II 正确:这是产生死锁的两大原因。

选项 III 正确:在对资源进行有序分配时,进程间不可能出现环形链,即不会出现循环等待。

选项 IV 正确:资源剥夺法允许一个进程强行剥夺其他进程占有的系统资源。而撤销进程强行释放一个进程已占有的系统资源,与资源剥夺法同理,都通过破坏死锁的 “请求和保持” 条件来解除死锁,所以选择选项 D。

23. 下面是并发进程的程序代码,正确的是( )。

 A. 进程不会死锁,也不会“饥饿”

B. 进程不会死锁,但是会“饥饿”

C. 进程会死锁,但是不会“饥饿”

D. 进程会死锁,也会“饥饿”

23. B

遇到这种问题时千万不要慌张,下面我们来慢慢分析,给读者一个清晰的解题过程:
        仔细考察程序代码,可以看出这是一个扩展的单行线问题。也就是说,某单行线只允许单方向的车辆通过,在单行线的入口设置信号量 y,在告示牌上显示某一时刻各方向来车的数量 c1 和 c2,用 c2 修改告示牌上的车辆数量必须互斥进行,为此设置信号量 x1 和 x2。若某方向的车辆需要通过时,则首先要将该方向来车数量 c1 或 c2 增加 1,并查看自己是否是第一个进入单行线的车辆,若是,则获取单行线的信号量 y,并进入单行线。通过此路段以后出单行线时,将该方向的车辆数 c1 或 c2 减 1(当然是利用 x1 或 x2 来互斥修改),并查看自己是否是最后一辆车,若是,则释放单行线的互斥量 y,否则保留信号量 y,让后继车辆继续通过。双方的操作如出一辙。考虑出现一个极端情况,即当某方向的车辆首先占据单行线并后来者络绎不绝时,另一个方向的车辆就再没有机会通过该单行线了。而这种现象是由于算法本身的缺陷造成的,不属于因为特殊序列造成的饥饿,所以它是真正的饥饿现象。因为有信号量的控制,所以死锁的可能性没有了(双方同时进入单行线,在中间相遇,造成双方均无法通过的情景)。

        ①假设 P1 进程稍快,P2 进程稍慢,同时运行;②P1 进程首先进入 if 条件语句,因此获得了 y 的互斥访问权,P2 被阻塞;③在第一个 P1 进程未释放 y 之前,又有另一个 P1 进入,c1 的值变成 2,当第一个 P1 离开时,P2 仍然被阻塞,这种情形不断发生;④在这种情况下会发生什么事?P1 顺利执行,P2 很郁闷,长期被阻塞。
综上所述,不会发生死锁,但会出现饥饿现象。因此选 B。

24. 有两个并发进程,对于如下这段程序的运行,正确的说法是( )。

A. 程序能正确运行,结果唯一

B. 程序不能正确运行,可能有两种结果

C. 程序不能正确运行,结果不确定

D. 程序不能正确运行,可能死锁

24. C

本题中两个进程不能正确地工作,运行结果的可能性详见下面的说明。

        不确定的原因是由于使用了公共变量 x,考查与变量 x 有关的语句共四处,执行的顺序是 1→2→3→4→5→6→7→8 时,结果是 y = 1, z = 1, t = 2, u = 2, x = 0;并发执行过程是 1→2→5→6→3→4→7→8 时,结果是 y = 0, z = 0, t = 2, u = 2, x = 0;执行的顺序是 5→6→7→8 →1→2→3→4 时,结果是 y = 1, z = 1, t = 2, u = 2, x = 1;执行的顺序是 5→6→1→ 2→7→8→3→4 时,结果是 y = 1, z = 1, t = 2, u = 2, x = 1。可见结果有多种可能性。
        显然,无论执行顺序如何,x 的结果只能是 0 或 1,因此语句 7 的条件一定成立,即 t = u = 2 的结果是一定的;而 y = z 必定成立,只可能有 0, 1 两种情况,又不可能出现 x = 1, y = z = 0 的情况,所以总共有 3 种结果(答案中的 3 种)。

25. 一个进程在获得资源后,只能在使用完资源后由自己释放,这属于死锁必要条件的( )。

A. 互斥条件 B. 请求和释放条件 C. 不剥夺条件 D. 防止系统进入不安全状态

25. C

        一个进程在获得资源后,只能在使用完资源后由自己释放,即它的资源不能被系统剥夺,答案为选项 C。

26. 假设具有5个进程的进程集合P = {P₀, P₁, P₂, P₃, P₄},系统中有三类资源A, B, C,假设在某时刻有如下状态,见下表。

系统是处于安全状态的,则x, y, z的取值可能是( )。

I. 1, 4, 0

II. 0, 6, 2

III. 1, 1, 1

IV. 0, 4, 7

A. I、II、IV         B. I、II         C. 仅I         D. I、III

26. C

        选项 I:根据 need 矩阵可知,当 Available 为 (1, 4, 0) 时,可满足 P2 的需求;P2 结束后释放资源,Available 为 (2, 7, 5) 可以满足 P0, P1, P3, P4 中任意一个进程的需求,所以系统不会出现死锁,处于安全状态。

        选项 II:当 Available 为 (0, 6, 2) 时,可以满足进程 P0, P3 的需求;这两个进程结束后释放资源,Available 为 (0, 6, 7),仅可以满足进程 4 的需求;P4 结束并释放后,Available 为 (0, 6, 8),此时不能满足余下任意一个进程的需求,因此当前处在非安全状态。

        选项 III:当 Available 为 (1, 1, 1) 时,可以满足进程 P0, P2 的需求;这两个进程结束后释放资源,Available 为 (2, 4, 9),此时不能满足余下任意一个进程的需求,处于非安全状态。

        选项 IV:当 Available 为 (0, 4, 7) 时,可以满足 P0 的需求,进程结束后释放资源,Available 为 (0, 4, 10),此时不能满足余下任意一个进程的需求,处于非安全状态。
        综上分析:只有选项 I 处于安全状态。

27. 死锁定理是用于处理死锁的( )方法。

A. 预防死锁         B. 避免死锁         C. 检测死锁         D. 解除死锁

27. C

死锁定理是用于检测死锁的方法。

28. 某系统有m个同类资源供n个进程共享,若每个进程最多申请k个资源(k≥1),采用银行家算法分配资源,为保证系统不发生死锁,则各进程的最大需求量之和应( )。

A. 等于m         B. 等于m + n         C. 小于m + n         D. 大于m + n

28. C

        按照银行家算法,只要保证系统中进程申请的最大资源数小于或等于 m,就一定存在一个安全序列。考虑最极端的情况,假如有 n - 1 个进程都申请了 1 个资源,剩下一个进程申请了 m 个资源,则各进程的最大需求量之和为 m + n - 1,此时能保证一定不会发生死锁。

29.采用银行家算法可以避免死锁的发生,这是因为该算法( )。
A. 可以抢夺已分配的资源
B. 能及时为各进程分配资源
C. 任何时刻都能保证每个进程能得到所需的资源
D. 任何时刻都能保证至少有一个进程可以得到所需的全部资源

29. D

        任何时刻都能保证至少有一个进程可以得到所需的全部资源,这意味着银行家算法可以保证系统中至少存在一个安全序列,使每个进程都能按该顺序得到所需的全部资源并正常结束,不会出现死锁的情况。这也是银行家算法避免死锁的核心思想。

30.用银行家算法避免死锁时,检测到( )时才分配资源。
A. 进程首次申请资源时对资源的最大需求量超过系统现存的资源量
B. 进程已占有的资源数与本次申请的资源数之和超过对资源的最大需求量
C. 进程已占有的资源数与本次申请的资源数之和不超过对资源的最大需求量,且现存资源量能满足尚需的最大资源量
D. 进程已占有的资源数与本次申请的资源数之和不超过对资源的最大需求量,且现存资源量能满足本次申请量,但不能满足尚需的最大资源量

30. C

        银行家算法要求,进程运行之前先声明它对各类资源的最大需求量,并保证它在任何时刻对每类资源的请求量不超过它所声明的最大需求量。当进程已占有的资源数与本次申请的资源数之和不超过对资源的最大需求量,且现存资源量能满足尚需的最大资源量时,才分配资源。

31.下列各种方法中,可用于解除已发生死锁的是( )。
A. 撤销部分或全部死锁进程
B. 剥夺部分或全部死锁进程的资源
C. 降低部分或全部死锁进程的优先级
D. A 和 B 都可以

31. D

        解除死锁的方法有两种:撤销死锁进程和剥夺死锁进程的资源。降低死锁进程的优先级是无效的方法,因为它不能改变死锁进程对资源的需求和占有,也不能打破循环等待条件。

32.假定某计算机系统有R1​和R2​两类可使用资源(其中R1​有两个单位,R2​有一个单位),它们被进程P1​和P2​所共享,且已知两个进程均按下列顺序使用两类资源:申请R1​→申请R2​→申请R1​→释放R1​→释放R2​→释放R1​,则在系统运行过程中,( )。
A. 不可能产生死锁
B. 有可能产生死锁,因为R1​资源不足
C. 有可能产生死锁,因为R2​资源不足
D. 只有一种进程执行序列可能导致死锁

32. B

        当两个进程都执行完第一步后,即进程 P1 和进程 P2 都申请到了一个 R1 类资源时,随着两个进程向前推进,无论哪个进程执行完第二步,系统都将进入死锁状态,进入死锁状态的原因是两个进程都需要两个 R1 类资源才能运行,但系统中此时已经没有可供分配的 R1 类资源。假定进程 P1 成功执行了第二步,则死锁发生时资源分配图如下:

进程

已分配资源

申请资源

P1

R1、R2

R1

P2

R1

R1

33.【2009 统考真题】某计算机系统中有 8 台打印机,由K个进程竞争使用,每个进程最多需要 3 台打印机。该系统可能发生死锁的K的最小值是( )。
A. 2 B. 3 C. 4 D. 5

33. C

        这类题可用到组合数学中鸽巢原理的思想。考虑最极端的情况,因为每个进程最多需要 3 台打印机,若每个进程已经占有了 2 台打印机,则只要还有多的打印机,总能满足一个进程达到 3 台的条件,然后顺利执行,所以将 8 台打印机分给 K 个进程,每个进程有 2 台打印机,这个情况就是极端情况,K 为 4。或者,假设 M 是打印机的数量,K 是进程的数量,R 是每个进程最多需要打印机的数量。根据死锁公式逆推可得,若 M ≤ K (R - 1),则系统可能发生死锁。将本题的数据代入,可得8≤K×(3−1),即K≥4,所以系统可能发生死锁的K的最小值是4。

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。
此外,本题还可以使用银行家算法求解(对选择题来说显得过于复杂)。

        本题可根据银行家算法的安全性检查来判断是否存在安全序列。

步骤一:计算各进程的尚需资源数
已知各进程的已分配资源和尚需分配资源,根据公式 “尚需资源数 = 最大需求资源数 - 已分配资源数”,结合题目表格信息,可得到各进程的尚需资源数。

步骤二:进行安全性检查
从可用资源(0,2,1)开始,逐一检查各进程的尚需资源数是否能被当前可用资源满足。

对于P1​,尚需(0,0,1),可用资源(0,2,1)可以满足,P1​完成后释放资源,此时可用资源变为(2,2,1)。

对于P2​,尚需(1,3,2),当前可用资源(2,2,1)不能满足。

对于P3​,尚需(2,0,0),当前可用资源(2,2,1)可以满足,P3​完成后释放资源,此时可用资源变为(2,3,2)。

对于P4​,尚需(2,0,0),当前可用资源(2,3,2)可以满足,P4​完成后释放资源,此时可用资源变为(2,3,3)。

此时,仍无法满足P2​的需求,所以不存在一个安全序列能让所有进程顺利完成。

        

综上,答案选 D。

35.【2012 统考真题】假设 5 个进程P0​,P1​,P2​,P3​,P4​共享三类资源R1​,R2​,R3​,这些资源总数分别为 18, 6, 22。T0​时刻的资源分配情况如下表所示,此时存在的一个安全序列是( )。

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与可利用资源向量Available:

进程名

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(R1​,R2​,R3​)

2,3,3

        比较Need和Available发现,初始时进程P1​与P3​可满足需求,排除选项 A、C。尝试给P1​分配资源时,P1​完成后Available将变为(6,3,6),无法满足P0​的需求,排除选项 B。尝试给P3​分配资源时,P3​完成后Available将变为(4,3,7),该向量能满足其他所有进程的需求。因此以P3开头的所有序列都是安全序列。​

36.【2013 统考真题】下列关于银行家算法的叙述中,正确的是( )。
A. 银行家算法可以预防死锁
B. 当系统处于安全状态时,系统中一定无死锁进程
C. 当系统处于不安全状态时,系统中一定会出现死锁进程
D. 银行家算法破坏了死锁必要条件中的 “请求和保持” 条件

36. B

        银行家算法是用于避免死锁的算法,而不是预防死锁,选项 A 错误。
        当系统处于安全状态时,意味着存在一个安全序列,按照这个序列可以为各个进程分配资源,使它们都能顺利完成,所以系统中一定无死锁进程,选项 B 正确。
        当系统处于不安全状态时,只是说明可能会进入死锁状态,但不一定会出现死锁进程,选项 C 错误。
        银行家算法并没有破坏死锁必要条件中的 “请求和保持” 条件,它是通过在资源分配前进行安全性检查来避免系统进入不安全状态,从而避免死锁,选项 D 错误。

37.【2014 统考真题】某系统有n台互斥使用的同类设备,三个并发进程分别需要 3, 4, 5 台设备,可确保系统不发生死锁的设备数n最小为( )。
A. 9 B. 10 C. 11 D. 12

37. B

        根据死锁公式,当资源数量大于各个进程所需资源数 - 1 的总和时,不发生死锁,三个进程分别需要 3, 4, 5 台设备,即当资源数量大于(3−1)+(4−1)+(5−1)=9时,不发生死锁。而当系统中只有 9 台设备时,第一个进程分配 2 台,第二个进程分配 3 台,第三个进程分配 4 台,这种情况下,三个进程均无法继续执行下去,发生死锁。当系统再增加 1 台设备,最后 1 台设备分配给任意一个进程都可以顺利执行完成,因此保证系统不发生死锁的最小设备数为 10。

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

        死锁的处理采用三种策略:死锁预防、死锁避免、死锁检测和解除。
        死锁预防采用破坏产生死锁的 4 个必要条件中的一个或几个来防止发生死锁。其中之一的 “破坏循环等待条件”,一般采用顺序资源分配法,即限制了用户申请资源的顺序,因此 I 的前半句属于死锁预防的范畴。此外,银行家算法虽然会通过检测是否存在安全序列来判断申请资源的请求是否合法,但安全序列并不是唯一的,也不是固定的,它只是一种可能的分配方案,而不是一种必须遵循的规则,银行家算法更没有给出固定的申请资源的顺序,因此 I 错误。
        银行家算法是最著名的死锁避免算法,其中的最大需求矩阵 Max 定义了每个进程对 m 类资源的最大需求量,系统在执行安全性算法中都会检查此次资源试分配后,系统是否处于安全状态,若不安全则将本次的试探分配作废。在死锁的检测和解除中,系统为进程分配资源时不采取任何措施,但提供死锁检测和解除的手段,一旦检测到系统发生死锁,就立即采取相应的措施来解除死锁,因此不用关心进程所需的总资源量。选项 II、III 正确。

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

对于本题,需先画出如下所示的资源分配图。若系统出现死锁,则必然出现循环等待的情况。

        从图中可以看出,若出现循环等待的情况,则至少有P1​、P2​、P3​三个进程在循环等待环中,在该图中不可能出现两个进程发生循环等待的情况。现在考察P1​、P2​、P3​三个进程形成环路的情况,若此时P1​、P2​、P3​三个进程分别拥有R1​、R2​和R3​,则会形成P1​等待P2​释放R2​,P2​等待P3​释放R3​,P3​等待P1​释放R1​的循环等待情况。P1​、P2​、P3​三个进程分别拥有R2​、R3​和R1​的情况的分析类似。以上两种情况都会形成循环等待情况,至少有三个进程陷入死锁状态。若P4​事先已获取R2​,成功运行,则死锁进程数为 3;若P4​尚未获取R2​,未运行,则死锁进程数为 4。因此,若系统出现死锁,则处于死锁状态的进程至少是3个。

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

        由题意可知,仅剩最后一个同类资源,若将其分给P1​或P2​,则均无法正常执行;若分给P3​,则P3​正常执行完成后,释放的这一个资源仍无法使P1​、P2​正常执行,因此不存在安全序列。

41.【2019 统考真题】下列关于死锁的叙述中,正确的是( )。
I. 可以通过剥夺进程资源解除死锁
II. 死锁的预防方法能确保系统不发生死锁
III. 银行家算法可以判断系统是否处于死锁状态
IV. 当系统出现死锁时,必然有两个或两个以上的进程处于阻塞态
A. 仅 II、III B. 仅 I、II、IV C. 仅 I、II、III D. 仅 I、III、IV

41. B

        剥夺进程资源,将其分配给其他死锁进程,可以解除死锁,选项 I 正确。

        死锁预防是死锁处理策略(死锁预防、死锁避免、死锁检测)中最为严苛的一种策略,破坏死锁产生的 4 个必要条件之一,可以确保系统不发生死锁,选项 II 正确。

        银行家算法是一种死锁避免算法,用于计算动态资源分配的安全性以避免系统进入死锁状态,不能用于判断系统是否处于死锁,选项 III 错误。

        通过简化资源分配图可以检测系统是否为死锁状态,当系统出现死锁时,资源分配图不可完全简化,只有两个或两个以上的进程才会出现 “环” 而不能被简化,选项 IV 正确。

42.【2020 统考真题】某系统中有 A、B 两类资源各 6 个,t时刻的资源分配及需求情况如下表所示。

t时刻安全性检测结果是( )。
A. 存在安全序列P1​、P2​、P3​
B. 存在安全序列P2​、P1​、P3​
C. 存在安全序列P2​、P3​、P1​
D. 不存在安全序列

42. B

        首先求出需求矩阵:

由Allocation得知当前Available为(1,0)。由需求矩阵可知,初始只能满足P2​的需求,选项 A 错误。P2​释放资源后Available变为(3,1),此时仅能满足P1​的需求,选项 C 错误。P1​释放资源后Available变为(5,4),可以满足P3​的需求,得到的安全序列为P2​,P1​,P3​,B 正确,选项 D 错误。

43.【2021 统考真题】若系统中有n(n≥2)个进程,每个进程均需要使用某类临界资源 2 个,则系统不会发生死锁所需的该类资源总数至少是( )。
A. 2         B. n         C. n+1         D. 2n

43. C

        考虑极端情况,当临界资源数为n时,每个进程都拥有 1 个临界资源并等待另一个资源,会发生死锁。当临界资源数为n+1时,则n个进程中至少有一个进程可以获得 2 个临界资源,顺利运行完后释放自己的临界资源,使得其他进程也能顺利运行,不会产生死锁。或者,根据死锁公式m>n(r−1),其中m是系统中临界资源的总数,n是并发进程的个数,r是每个进程所需临界资源的个数。若这个不等式成立,则系统不发生死锁。将本题的数据代入,得到m>n(2−1),即只要系统中临界资源的总数至少是n+1,就可避免死锁。

44.【2022 统考真题】系统中有三个进程P0​、P1​、P2​及三类资源 A、B、C。若某时刻系统分配资源的情况如下表所示,则此时系统中存在的安全序列的个数为( )。

A. 1 B. 2 C. 3 D. 4

44. B

        初始时系统中的可用资源数为<1,3,2>,只能满足P0​的需求<0,2,1>,所以安全序列第一个只能是P0​,将资源分配给P0​后,P0​执行完释放所占资源,可用资源数变为<1,3,2>+<2,0,1>=<3,3,3>,此时可用资源数既能满足P1​,又能满足P2​,可以先分配给P1​,P1​执行完释放资源再分配给P2​;也可以先分配给P2​,P2​执行完释放资源再分配给P1​。所以安全序列可以是①P0​、P1​、P2​或②P0​、P2​、P1​。

二、综合应用题

1.设系统中有下述解决死锁的方法:
        1)银行家算法。
        2)检测死锁,终止处于死锁状态的进程,释放该进程占有的资源。
        3)资源预分配。
        简述哪种办法允许最大的并发性,即哪种办法允许更多的进程无等待地向前推进。请按 “并发性” 从大到小对上述三种办法排序。

01. 【解答】

        死锁在系统中不可能完全消灭,但我们要尽可能地减少死锁的发生。对死锁的处理有 4 种方法:忽略、检测与恢复、避免和预防,每种方法对死锁的处理从宽到严,同时系统并发性由大到小。这里银行家算法属于避免死锁,资源预分配属于预防死锁。
        死锁检测方法可以获得最大的并发性。并发性排序:死锁检测方法、银行家算法、资源预分配法。

2.某银行计算机系统要实现一个电子转账系统,基本业务流程是:首先对转出方和转入方的账户进行加锁,然后进行转账业务,最后对转出方和转入方的账户进行解锁。若不采取任何措施,系统会不会发生死锁?为什么?请设计一个能够避免死锁的办法。

02. 【解答】

        系统会死锁。因为对两个账户进行加锁操作是可以分割进行的,若此时有两个用户同时进行转账,P1​先对账户A进行加锁,再申请账户B;P2​先对账户B进行加锁,再申请账户A,此时产生死锁。解决的办法是:可以采用资源顺序分配法对A、B账户进行编号,用户转账时只能按照编号由小到大进行加锁;也可采用资源预分配法,要求用户在使用资源前将所有资源一次性申请到。

3.设有进程P1​和进程P2​并发执行,都需要使用资源R1​和R2​,使用资源的情况见下表。

试判断是否会发生死锁,并解释和说明产生死锁的原因与必要条件。

03. 【解答】

        这段程序在不同的运行推进速度下,可能产生死锁。例如,进程P1​先申请资源R1​,得到资源R1​,然后进程P2​申请资源R2​,得到资源R2​,进程P1​又申请资源R2​,因资源R2​已分配,使得进程P1​阻塞。进程P1​和进程P2​都因申请不到资源而形成死锁。若改变进程的运行顺序,则这两个进程就不会出现死锁现象。
        产生死锁的原因可归结为两点:
        1)竞争资源。
        2)进程推进顺序非法。
        产生死锁的必要条件:
        1)互斥条件。
        2)请求并保持条件。
        3)不剥夺条件。
        4)环路等待条件。

4.系统有同类资源m个,供n个进程共享,若每个进程对资源的最大需求量为k,试问:当m,n,k的值分别为下列情况时(见下表),是否会发生死锁?

04. 【解答】

        不发生死锁要求,必须保证至少有一个进程得到所需的全部资源并执行完毕,m≥n(k−1)+1时,一定不会发生死锁。

序号

m

n

k

是否会死锁

说明

1

6

3

3

可能会

6<3(3−1)+1

2

9

3

3

不会

9>3(3−1)+1

3

13

6

3

不会

13=6(3−1)+1

5.有三个进程P1​、P2​和P3​并发工作。进程P1​需要资源S3​和资源S1​;进程P2​需要资源S2​和资源S1​;进程P3​需要资源S3​和资源S2​。问:
1)若对资源分配不加限制,会发生什么情况?为什么?
2)为保证进程正确运行,应采用怎样的分配策略?列出所有可能的方法。

05. 【解答】

1)可能发生死锁。满足发生死锁的 4 大条件,例如,P1​占有S1​申请S3​,P2​占有S2​申请S1​,P3​占有S3​申请S2​。
 2)可有以下几种答案:
        A. 采用静态分配:因为执行前已获得所需的全部资源,所以不会出现占有资源又等待别的资源的现象(或不会出现循环等待资源的现象)。
        B. 采用按序分配:不会出现循环等待资源的现象。
        C. 采用银行家算法:因为在分配时,保证了系统处于安全状态。

6.某系统有R1​、R2​和R3​共三种资源,在T0​时刻P1​、P2​、P3​和P4​这四个进程对资源的占用和需求情况见下表,此时系统的可用资源向量为 (2, 1, 2)。试问:
1)系统是否处于安全状态?若安全,则请给出一个安全序列。

2)若此时进程P1​和进程P2​均发出资源请求向量 Request (1, 0, 1),为了保证系统的安全性,应如何分配资源给这两个进程?说明所采用策略的原因。
3)若 2)中两个请求立即得到满足后,系统此刻是否处于死锁状态?

进程名

资源情况

最大资源需求量

已分配资源数量

R1​

R2​

R3​

R1​

R2​

R3​

P1​

3

2

2

1

0

0

P2​

6

1

3

4

1

1

P3​

3

1

4

2

1

1

P4​

4

2

2

0

0

2

06. 【解答】

1)利用安全性算法对T0​时刻的资源分配情况进行分析,可得到如下表所示的安全性检测情况。可以看出,此时存在一个安全序列{P2​,P3​,P4​,P1​},所以该系统是安全的。

        此处要注意,一般大多数题目中的安全序列并不唯一。

2)若此时P1​发出资源请求Request1​(1,0,1),则按银行家算法进行检查:

        Request1​(1,0,1)≤Need1​(2,2,2)

        Request1​(1,0,1)≤Available(2,1,2)

        试分配并修改相应数据结构,由此形成的进程P1​请求资源后的资源分配情况见下表。

        再利用安全性算法检查系统是否安全,可用资源Available(1,1,1)已不能满足任何进程,系统进入不安全状态,此时系统不能将资源分配给进程P1​。

        若此时进程P2​发出资源请求Request2​(1,0,1),则按银行家算法进行检查:

        Request2​(1,0,1)≤Need2​(2,0,2)

        Request2​(1,0,1)≤Available(2,1,2)

试分配并修改相应数据结构,由此形成的进程P2​请求资源后的资源分配情况下表:

        再利用安全性算法检查系统是否安全,可得到如下表中所示的安全性检测情况。注意表中各个进程对应的 Work + Allocation 向量表示在该进程释放资源之后更新的 Work 向量。

        从上表中可以看出,此时存在一个安全序列{P2​,P3​,P4​,P1​},因此该状态是安全的,可以立即将进程P2​所申请的资源分配给它。

        3)若 2)中的两个请求立即得到满足,则此刻系统并未立即进入死锁状态,因为这时所有的进程未提出新的资源申请,全部进程均未因资源请求没有得到满足而进入阻塞态。只有当进程提出资源申请且全部进程都进入阻塞态时,系统才处于死锁状态。

7.考虑某个系统在下表时刻的状态。

进程名

Allocation

Max

Available

A

B

C

D

A

B

C

D

A

B

C

D

P0​

0

0

1

2

0

0

1

2

1

5

2

0

P1​

1

0

0

0

1

7

5

0

P2​

1

3

5

4

2

3

5

6

P3​

0

0

1

4

0

6

5

6

使用银行家算法回答下面的问题:
1)Need 矩阵是怎样的?
2)系统是否处于安全状态?如安全,请给出一个安全序列。
3)若从进程P1​发来一个请求 (0, 4, 2, 0),这个请求能否立刻被满足?如安全,请给出一个安全序列。

07. 【解答】

1)

2)Work向量初始化值 = Available(1,5,2,0)。
系统安全性分析:

因为存在一个安全序列<P0​,P2​,P1​,P3​>,所以系统处于安全状态。

3)Request1​(0,4,2,0)<Need1​(0,7,5,0)

Request1​(0,4,2,0)<Available(1,5,2,0)

假设先试着满足进程P1​的这个请求,则Available变为(1,1,0,0)。

系统状态变化见下表:

8.假设具有 5 个进程的进程集合P={P0​,P1​,P2​,P3​,P4​},系统中有三类资源 A, B, C,假设在某时刻有如下状态:

进程名

Allocation

Max

Available

A

B

C

A

B

C

A

B

C

P0​

0

0

3

0

0

4

1

4

0

P1​

1

0

0

1

7

5

P2​

1

3

5

2

3

5

P3​

0

0

2

0

6

4

P4​

0

0

1

0

6

5

当前系统是否处于安全状态?若系统中的可利用资源 Available 为 (0, 6, 2),系统是否安全?若系统处在安全状态,请给出安全序列;若系统处在非安全状态,简要说明原因。

08. 【解答】

        1)根据Need矩阵可知,初始Work等于Available为(1,4,0),可以满足进程P2​的需求;进程P2​结束后释放资源,Work为(2,7,5),可以满足P0​,P1​,P3​和P4​中任意一个进程的需求,所以系统不会出现死锁,当前处于安全状态。

2)若初始Work=Available为(0,6,2),可满足进程P0​,P3​的需求;这两个进程结束后释放资源,Work为(0,6,7),仅可满足进程P4​的需求;P4​结束后释放资源,Work为(0,6,8),此时不能满足余下任意一个进程的需求,系统出现死锁,因此当前系统处在非安全状态。

注意:

        在银行家算法中,当实际计算分析系统安全状态时,并不需要逐个进程进行。如本题中,在情况 1)下,当计算到进程P2​结束并释放资源时,系统当前空闲资源可满足余下任意一个进程的最大需求量,这时已经不需要考虑进程的执行顺序。系统分配任意一个进程所需的最大需求资源,在其执行结束释放资源后,系统当前空闲资源会增加,所以余下的进程仍然可以满足最大需求量。因此,在这里可以直接判断系统处于安全状态。在情况 2)下,系统当前可满足进程P0​,P3​的需求,所以可以直接让系统推进到P0​,P3​执行完并释放资源后的情形,这时系统出现死锁;因为此时是系统空闲资源所能达到的最大值,所以按照其他方式推进,系统必然还是出现死锁。因此,在计算过程中,将每步中可满足需求的进程作为一个集合,同时执行并释放资源,可以简化银行家算法的计算。

2.5 本章疑难点

1. 进程与程序的区别与联系

1)进程是程序及其数据在计算机上的一次运行活动,是一个动态的概念。进程的运行实体是程序,离开程序的进程没有存在的意义。从静态角度看,进程是由程序、数据和进程控制块(PCB)三部分组成的。而程序是一组有序的指令集合,是一种静态的概念。
2)进程是程序的一次执行过程,它是动态地创建和消亡的,具有一定的生命周期,是暂时存在的;而程序则是一组代码的集合,是永久存在的,可长期保存。
3)一个进程可以执行一个或几个程序,一个程序也可构成多个进程。进程可创建进程,而程序不可能形成新的程序。
4)进程与程序的组成不同。进程的组成包括程序、数据和 PCB。

2. 银行家算法的工作原理

银行家算法的主要思想是避免系统进入不安全状态。在每次进行资源分配时,它首先检查系统是否有足够的资源满足要求,若有则先进行试分配,并对分配后的新状态进行安全性检查。若新状态安全,则正式分配上述资源,否则拒绝分配上述资源。这样,它保证系统始终处于安全状态,从而避免了死锁现象的发生。

3. 进程同步、互斥的区别和联系

并发进程的执行会产生相互制约的关系:一种是进程之间竞争使用临界资源,只能让它们逐个使用,这种现象称为互斥,是一种竞争关系;另一种是进程之间协同完成任务,在关键点上等待另一个进程发来的消息,以便协同一致,是一种协作关系。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值