操作系统 - 进程与线程

进程与线程

考纲内容

  • 进程与线程
    进程与线程的基本概念;进程/线程的状态与转换
    线程的实现;内核支持的线程,线程库支持的线程
    进程与线程的组织与控制
    进程间通信;共享内存,消息传递,管道
  • CPU调度与上下文切换
    调度的基本概念;调度的目标
    调度的实现:调度器/调度程序,调度的时机与方式(抢占式/非抢占式),闲逛进程,内核级线程与用户级线程调度
    典型调度算法:先来先服务调度算法;短作业(短进程、短线程)优先调度算法;时间轮转调度算法;优先级调度算法;高响应比优先算法,多级队列调度算法,多级反馈队列调度算法
    上下文及其切换机制
  • 同步与互斥
    同步与互斥的基本概念
    基本的实现方法:软件方法;硬件方法
    锁:信号量;条件变量
    经典同步问题:生产者-消费者问题,读者-写者问题;哲学家进餐问题
  • 死锁
    死锁的基本概念;死锁的预防
    死锁避免;死锁检测和解除

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

进程与线程

进程的概念和特征

1、进程的概念

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

为了使参与并发执行的每个程序(含数据)都能独立地运行,必须为之配置一个专⻔的数据结构,称为**进程控制块(PCB)
系统利用PCB来描述进程的基本情况和运行状态,进而控制和管理进程
程序段、相关数据段和PCB三部分构成了进程实体**(进程映像)

注:进程映像是静态的,进程则是动态的

创建进程,实质上是创建进程实体中的PCB
撤销进程,实质上是撤销进程实体中的PCB

注:PCB是进程存在的唯一标志

进程经典的定义:

  1. 进程是程序的一次执行过程
  2. 进程是一个程序及其数据在处理机上顺序执行时所发生的活动
  3. 进程是具有独立功能的程序在一个数据集合上运行的过程,它是**系统进行资源分配和调度的一个独立单位**

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

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


2、进程的特征

进程是由多道程序的并发执行而引出的,它和程序是两个截然不同的概念

进程的基本特征:

  1. 动态性。进程是程序的一次执行,它有着创建、活动、暂停、终止等过程,具有一定的生命周期,是动态地产生、变化和消亡的。动态性是进程墩基本的特征
  2. 并发性。指多个进程实体同存于内存中,能在一段时间内同时运行。引入进程的目的就是使进程能和其他进程并发执行。并发性是进程的重要特征,也是操作系统的重要特征
  3. 独立性。指进程实体是一个能独立运行、独立获得资源和独立接受调度的基本单位。凡未建立PCB的程序,都不能作为一个独立的单位参与运行
  4. 异步性。由于进程的相互制约,使得进程按各自独立的、不可预知的速度向前推进。异步性会导致执行结果的不可再现性,为此在操作系统中必须配置相应的进程同步机制

进程的状态与转换

通常进程有以下5种状态,前3种是进程的基本状态

  • 运行态。进程正在处理机上运行。在单处理机中,每个时刻只有一个进程处于运行态
  • 就绪态。进程获得了除处理机外的一切所需资源,一旦得到处理机,便可立即运行。系统中处于就绪状态的进程可能有多个,通常将它们排成一个队列,称为就绪队列
  • 阻塞态,又称等待态。进程**正在等待某一事件而暂停运行,如等待某资源为可用(不包括处理机)或等待输入/输出完成**。即使处理机空闲,该进程也不能运行。系统通常将处于阻塞态的进程也排成一个队列,甚至根据阻塞原因的不同,设置多个阻塞队列
  • 创建态。进程正在被创建,尚未转到就绪态
    创建进程需要多个步骤:首先申请一个空白PCB,并向PCB中填写用于控制和管理进程的信息;然后为该进程分配运行时所必须的资源;最后把该进程转入就绪态并插入就绪队列
    但是,如果进程所需的资源尚不能得到满足,如内存不足,则创建工作尚未完成,进程此时所处的状态称为创建态
  • 终止态。进程正从系统中消失,可能是进程正常结束或其他原因退出运行。进程需要结束运行时,系统首先将该进程置为终止态,然后进一步处理资源释放和回收等工作

注意区别就绪态和等待态:

就绪态是指进程仅缺少处理器,只要获得处理机资源就立即运行
而等待态是指进程需要其他资源(除了处理机)或等待某一事件

之所以把处理机和其他资源划分开,是因为在分时系统的时间片轮转机制中,每个进程分到的时间片是若干毫秒
进程得到处理机的时间很短且非常频繁,进程在运行过程中实际上是频繁地转换到就绪态的
而其他资源(如外设)的使用和分配或某一事件的发生(如I/O操作的完成)对应的时间相对来说很⻓,进程转换到等待态的次数也相对较少

**3种基本状态之间的转换**如下:

  • 就绪态->运行态:处于就绪态的进程被调度后,获得处理机资源(分派处理机时间片),于是进程由就绪态转换为运行态
  • 运行态->就绪态:处于运行态的进程在**时间片用完**后,不得不让出处理机,从而进程由运行态转换为就绪态。此外,在可剥夺的操作系统中,当有更高优先级的进程就绪时,调度程序将正在执行的进程转换为就绪态,让更高优先级的进程执行
  • 运行态->阻塞态:进程请求某一资源(如外设)的使用和分配或等待某一事件的发生(如I/O操作的完成)时,它就从运行态转换为阻塞态。进程以系统调用的形式请求操作系统提供服务,这是一种特殊的、由运行用户态程序调用操作系统内核过程的形式
  • 阻塞态->就緒态:进程等待的事件到来时,如I/O操作结束或中断结束时,中断处理程序必须把相应进程的状态由阻塞态转换为就绪态

image-20240402151557218

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

进程的组成

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

  • 进程控制块

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

进程执行时,系统通过其PCB了解进程的现行状态信息,以便操作系统对其进行控制和管理

进程结束时,系统收回其PCB,该进程随之消亡

当操作系统欲调度某进程运行时,要从该进程的PCB中查出其现行状态及优先级
在调度到某进程后,要根据其PCB中所保存的处理机状态信息,设置该进程恢复运行的现场,并根据其PCB中的程序和数据的内存始址,找到其程序和数据
进程在运行过程中,当需要和与之合作的进程实现同步、通信或访问文件时,也需要访问PCB
当进程由于某种原因而暂停运行时,又需将其断点的处理机环境保存在PCB中

可⻅,在进程的整个生命期中,系统总是通过PCB对进程进行控制的,亦即系统唯有通过进程的PCB才能感知到该进程的存在

下表是一个PCB的实例,PCB主要包括进程描述信息、进程控制和管理信息、资源分配清单和处理机相关信息等

image-20240402153034219

  1. 进程描述信息
    进程标识符:标志各个进程,每个进程都有一个唯一的标识号
    用户标识符:进程归属的用户,用户标识符主要为共享和保护服务
  2. 进程控制和管理信息
    进程当前状态:描述进程的状态信息,作处理机分配调度的依据
    进程优先级:描述进程抢占处理机的优先级,优先级高的进程可优先获得处理机
  3. 资源分配消单
    用于说明有关内存地址空间或虚拟地址空间的状况,所打开文件的列表和所使用的输入/输出设备信息
  4. 处理机相关信息
    也称处理机的上下文,主要指处理机中各寄存器的值。当进程处于执行态时,处理机的许多信息都在寄存器中。当进程被切换时,处理机状态信息都必须保存在相应的PCB中,以便在该进程重新执行时,能从断点继续执行

在一个系统中,通常存在着许多进程的PCB,有的处于就绪态,有的处于阻塞态,而且阻塞的原因各不相同。为了方便进程的调度和管理,需要将各进程的PCB用适当的方法组织起来。目前,常用的组织方式有**链接方式和索引方式**两种

链接方式将同一状态的PCB**链接成一个队列,不同状态对应不同的队列,也可把处于阻塞态的进程的PCB,根据其阻塞原因的不同,排成多个阻塞队列
索引方式将同一状态的进程
组织在一个索引表**中,索引表的表项指向相应的PCB,不同状态对应不同的索引表,如就绪索引表和阻塞索引表等

  • 程序段

程序段就是能被进程调度程序调度到CPU执行的程序代码段

注:程序可被多个进程共享,即多个进程可以运行同一个程序

  • 数据段

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

进程控制

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

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

1、进程的创建

允许一个进程创建另一个进程,此时创建者称为父进程,被创建的进程称为子进程

子进程可以继承父进程所拥有的资源。当子进程被撤销时,应将其从父进程那里获得的资源归还给父进程。在撤销父进程时,通常也会同时撤销其所有的子进程

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

操作系统创建一个新进程的过程如下(创建原语):

  1. 为新进程分配一个唯一的进程标识号,并申请一个空白PCB(PCB是有限的)。若PCB申请失败,则创建失败
  2. 为进程分配其运行所需的资源,如内存、文件、I/O设备和CPU时间等(在PCB中体现)。这些资源或从操作系统获得,或仅从其父进程获得。如果资源不足(如内存),则并不是创建失败,而是处于创建态,等待内存资源
  3. 初始化PCB,主要包括初始化标志信息、初始化处理机状态信息和初始化处理机控制信息,以及设置进程的优先级等
  4. 若进程就绪队列能够接纳新进程,则将新进程插入就绪队列,等待被调度运行

2、进程的终止

引起进程终止的事件主要有以下:

  1. 正常结束,表示进程的任务已完成并准备退出运行
  2. 异常结束,表示进程在运行时,发生了某种异常事件,使程序无法继续运行,如存储区越界、保护错、非法指令、特权指令错、运行超时、算术运算错、1/O故障等
  3. 外界干预,指进程应外界的请求而终止运行,如操作员或操作系统干预、父进程请求和父进程终止

操作系统终止进程的过程如下(终止原语)

  1. 根据被终止进程的标识符,检索出该进程的PCB,从中读出该进程的状态
  2. 若被终止进程处于运行状态,立即终止该进程的执行,将处理机资源分配给其他进程
  3. 若该进程还有子孙进程,则应将其所有子孙进程终止
  4. 将该进程所拥有的全部资源,或归还给其父进程,或归还给操作系统
  5. 将该PCB从所在队列(链表)中删除

3、进程的阻塞和喚醒

正在执行的进程,由于期待的某些事件未发生,如请求系统资源失败、等待某种操作的完成、新数据尚未到达或无新任务可做等,进程便通过调用阻塞原语,使自己由运行态变为阻塞态

阻塞是进程自身的一种主动行为,也因此只有处于运行态的进程(获得CPU),才可能将其转为阻塞态

阻塞原语的执行过程如下:

  1. 找到将要被阻塞进程的标识号对应的PCB
  2. 若该进程为运行态,则保护其现场,将其状态转为阻塞态,停止运行
  3. 把该PCB插入相应事件的等待队列,将处理机资源调度给其他就绪进程

当被阻塞进程所期待的事件出现时,如它所期待的I/O操作已完成或其所期待的数据已到达,由有关进程调用唤醒原语,将等待该事件的进程唤醒

唤醒原语的执行过程如下:

  1. 在该事件的等待队列中找到相应进程的PCB
  2. 将其从等待队列中移出,并置其状态为就绪态
  3. 把该PCB插入就绪队列,等待调度程序调度

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

进程的通信

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

  • 共享存储

在通信的进程之间存在一块**可直接访问的共享空间,通过对这片共享空间进行写/读操作实现进程之间的信息交换**

image-20240402155053229

在对共享空间进行写/读操作时,需要使用**同步互斥工具(如P操作、V操作),对共享空间的写/读进行控制**

共享存储又分为两种:
1、低级方式的共享是**基于数据结构的共享**
2、高级方式的共享则是**基于存储区的共享**
操作系统只负责为通信进程提供可共享使用的存储空间和同步互斥工具,而数据交换则由用户自己安排读/写指令完成

注:进程空间一般都是独立的,进程运行期间一般不能访问其他进程的空间,想让两个进程共享空间,必须通过特殊的系统调用实现,而进程内的线程是自然共享进程空间的

  • 消息传递

在消息传递系统中,进程间的数据交换**以格式化的消息(Message)为单位**
若通信的进程之间**不存在可直接访问的共享空间,则必须利用操作系统提供的消息传递方法实现进程通信**

进程通过系统提供的发送消息和接收消息两个原语进行数据交换,该机制能很好地支持多处理机系统、分布式系统和计算机网络,因此也成为这些领域最主要的通信工具

  1. 直接通信方式。发送进程直接把消息发送给接收进程,并将它挂在接收进程的消息缓冲队列上,接收进程从消息缓冲队列中取得消息
    image-20240402155913949
  2. 间接通信方式。发送进程把消息发送到某个中间实体,接收进程从中间实体取得消息。这种中间实体一般称为信箱。该通信方式广泛应用于计算机网络中
  • 管道通信

管道通信**允许两个进程按生产者-消费者方式进行通信**。生产者向管道的一端写,消费者从管道的另一端读

数据在管道中是**先进先出**的

只要管道非空,读进程就能从管道中读出数据,若数据被读空,则读进程阻塞,直到写进程往管道中写入新的数据,再将读进程唤醒

只要管道不满,写进程就能往管道中写入数据,若管道写满,则写进程阻塞,直到读进程读出数据,再将写进程唤醒

为了协调双方的通信,管道机制必须提供三方面的协调能力:互斥、同步和确定对方的存在

在Linux中,管道是一种使用非常频繁的通信机制,管道可以克服使用文件进行通信的两个问题:

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

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

注:从管道读数据是一次性操作,数据一旦被读取,就释放空间以便写更多数据。普通管道只允许单向通信,若要实现父子进程双向通信,则需要定义两个管道

线程和多线程模型

1、线程的慕本概念

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

线程最直接的理解就是“轻量级进程”,它是一个**基本的CPU执行单元,也是程序执行流的最小单元,由线程ID、程序计数器、寄存器集合和堆栈组成**

线程是进程中的一个实体,是**被系统独立调度和分派的基本单位线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程所拥有的全部资源**

一个线程可以创建和撤销另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使**线程在运行中呈现出间断性**

线程也有就绪、阻塞和运行三种基本状态

进程只作为除CPU外的系统资源的分配单元,而线程则作为处理机的分配单元

由于一个进程内部有多个线程,若线程的切换发生在同一个进程内部,则只需要很少的时空开销

2、线程与进程的比较

  1. 调度
    在传统的操作系统中,拥有资源和独立调度的基本单位都是进程,每次调度都要进行上下文切换,开销较大
    在引入线程的操作系统中,线程是独立调度的基本单位,而线程切换的代价远低于进程
    在**同一进程中,线程的切换不会引起进程切换**
    但**从一个进程中的线程切换到另一个进程中的线程时,会引起进程切换**
  2. 并发性
    在引入线程的操作系统中,不仅进程之间可以并发执行,而且一个进程中的多个线程之间亦可并发执行,甚至不同进程中的线程也能并发执行,从而**使操作系统具有更好的并发性,提高了系统资源的利用率和系统的吞吐量**
  3. 拥有资源
    进程是系统中拥有资源的基本单位,而**线程不拥有系统资源(仅有一点必不可少、能保证独立运行的资源),但线程可以访问其隶属进程的系统资源,这主要表现在属于同一进程的所有线程都具有相同的地址空间**
  4. 独立性
    每个进程都拥有独立的地址空间和资源,除了共享全局变量,不允许其他进程访问。某进程中的线程对其他进程不可⻅。同一进程中的不同线程是为了提高并发性及进行相互之间的合作而创建的,它们共享进程的地址空间和资源
  5. 系统开销
    在创建或撤销进程时,系统都要为之分配或回收进程控制块PCB及其他资源。操作系统为此所付出的开销,明显大于创建或撤销线程时的开销
    类似地,在进程切换时涉及进程上下文的切换,而线程切换时只需保存和设置少量寄存器内容,开销很小
    此外,由于同一进程内的多个线程共享进程的地址空间,因此这些线程之间的同步与通信非常容易实现,甚至无须操作系统的干预
  6. 支持多处理机系统
    对于传统单线程进程,不管有多少处理机,进程只能运行在一个处理机上
    对于多线程进程,可以将进程中的多个线程分配到多个处理机上执行

3、线程的属性

线程的主要属性如下:

  1. 线程是一个轻型实体,它不拥有系统资源,但**每个线程都应有一个唯一的标识符和一个线程控制块**,线程控制块记录了线程执行的寄存器和栈等现场状态
  2. 不同的线程可以执行相同的程序,即同一个服务程序被不同的用户调用时,操作系统把它们创建成不同的线程
  3. 同一进程中的各个线程共享该进程所拥有的资源
  4. 线程是处理机的独立调度单位,多个线程是可以并发执行的。在单CPU的计算机系统中,各线程可交替地占用CPU;在多CPU的计算机系统中,各线程可同时占用不同的CPU,若各个CPU同时为一个进程内的各线程服务,则可缩短进程的处理时间
  5. 一个线程被创建后,便开始了它的生命周期,直至终止。线程在生命周期内会经历阻塞态、就绪态和运行态等各种状态变化

4、线程的状态与转换

与进程一样,各线程之间也存在共享资源和相互合作的制约关系,致使线程在运行时也具有间断性。相应地,线程在运行时也具有下面三种基本状态:

  • 执行状态:线程已获得处理机而正在运行
  • 就绪状态:线程已具备各种执行条件,只需再获得CPU便可立即执行
  • 阻塞状态:线程在执行中因某事件受阻而处于暂停状态

线程这三种基本状态之间的转换和进程基本状态之间的转换是一样的

5、线程的组织与拉制

  • 线程控制块(TCB)

与进程类似,系统也为每个线程配置一个线程控制块TCB,用于记录控制和管理线程的信息

线程控制块通常包括:1、线程标识符;2、一组寄存器,包括程序计数器、状态寄存器和通用寄存器;3、线程运行状态,用于描述线程正处于何种状态;4、优先级;5、线程专有存储区,线程切换时用于保存现场等;6、堆栈指针,用于过程调用时保存局部变量及返回地址等

同一进程中的所有线程都完全共享进程的地址空间和全局变量。各个线程都可以访问进程地址空间的每个单元,所以一个线程可以读、写或甚至清除另一个线程的堆栈

  • 线程的创建

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

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

  • 线程的终止

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

被终止但尚未释放资源的线程仍可被其他线程调用,以使被终止线程重新恢复运行

6、线程的实现方式

线程的实现可以分为两类:用户级线程(ULT)和内核级线程(KLT)。内核级线程又称内核支持的线程

  • 用户级线程(ULT)

在用户级线程中,有关线程管理(创建、撤销和切换等)的所有工作都由应用程序在用户空间中完成,内核意识不到线程的存在
应用程序可以通过使用线程库设计成多线程程序。通常,应用程序从单线程开始,在该线程中开始运行,在其运行的任何时刻,可以通过调用线程库中的派生例程创建一个在相同进程中运行的新线程

image-20240402164823379

对于设置了用户级线程的系统,其调度仍是以进程为单位进行的,各个进程轮流执行一个时间片

优点:

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

缺点:

  1. 系统调用的阻塞问题,当线程执行一个系统调用时,不仅该线程被阻塞,而且进程内的所有线程都被阻塞
  2. 不能发挥多处理机的优势,内核每次分配给一个进程的仅有一个-CPU;因此进程中仅有一个线程能执行
  • 内核级线程(KLT)

在操作系统中,无论是系统进程还是用户进程,都是在操作系统内核的支持下运行的,与内核紧密相关

内核级线程同样也是在内核的支持下运行的,线程管理的所有工作也是在内核空间内实现的

优点:

  1. 能发挥多处理机的优势,内核能同时调度同一进程中的多个线程并行执行
  2. 如果进程中的一个线程被阻塞,内核可以调度该进程中的其他线程占用处理机,也可运行其他进程中的线程
  3. 内核支持线程具有很小的数据结构和堆栈,线程切换比较快、开销小
  4. 内核本身也可采用多线程技术,可以提高系统的执行速度和效率

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

  • 组合方式

在组合实现方式中,内核支持多个内核级线程的建立、调度和管理,同时允许用户程序建立、调度和管理用户级线程

一些内核级线程对应多个用户级线程,这是用户级线程通过时分多路复用内核级线程实现的

同一进程中的多个线程可以同时在多处理机上并行执行,且在阻塞一个线程时不需要将整个进程阻塞。线程库是为程序员提供创建和管理线程的API,实现线程库主要的方法有如下两种:

  1. 在用户空间中提供一个没有内核支持的库。这种库的所有代码和数据结构都位于用户空间中。这意味着,调用库内的一个函数只导致用户空间中的一个本地函数的调用
  2. 实现由操作系统直接支持的内核级的一个库。对于这种情况,库内的代码和数据结构位于内核空间。调用库中的一个API函数通常会导致对内核的系统调用

目前使用的三种主要线程库是:POSIXPthreads、WindowsAPI、Java
Java线程API允许线程在Java程序中直接创建和管理

  • 多线程模型

有些系统同时支持用户线程和内核线程,由于用户级线程和内核级线程连接方式的不同,从而形成了下面三种不同的多线程模型:

  1. 多对一模型。将**多个用户级线程映射到一个内核级线程。这些用户线程一般属于一个进程线程的调度和管理在用户空间完成**。仅当用户线程需要访问内核时,才将其映射到一个内核级线程上,但是每次只允许一个线程进行映射
    优点:线程管理是在用户空间进行的,因而效率比较高
    缺点:如果一个线程在访问内核时发生阻塞,则整个进程都会被阻塞;在任何时刻,只有一个线程能够访问内核,多个线程不能同时在多个处理机上运行
  2. 一对一模型。将**每个用户级线程映射到一个内核级线程**。
    优点:当一个线程被阻塞后,允许调度另一个线程运行,所以并发能力较强
    缺点:每创建一个用户线程,相应地就要创建一个内核线程,开销较大
  3. 多对多模型。将**n个用户线程映射到m个内核级线程上,要求n ≥ m**
    特点:既克服了多对一模型并发度不高的缺点,又克服了一对一模型的一个用户进程占用太多内核级线程而开销太大的缺点。此外,还拥有上述两种模型各自的优点

image-20240402170604851

本节小结

为什么要引入进程?

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

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

进程是一个具有独立功能的程序关于某个数据集合的一次运行活动。它可以申请和拥有系统资源,是一个动态的概念,是一个活动的实体。它不只是程序的代码本身,还包括当前的活动,通过程序计数器的值和处理寄存器的内容来表示
一个进程实体由程序段、相关数据段和PCB三部分构成,其中PCB是标志一个进程存在的唯一标识,程序段是进程运行的程序的代码,数据段则存储程序运行过程中相关的一些数据

提醒:

线程是处理机调度的基本单位,可独立执行程序
线程共享一个线程地址空间
与进程之间线程的通信可以直接通过它们共享的存储空间
PCB是进程存在的唯一标志,线程共享同一进程的地址空间
进程由阻塞态变为就绪态不会使进程的状态发生变换
释放打印机后,另一个打印机进程就可能由阻塞态变为就绪态
内核级线程使处理器调度和分派的单位,线程本身不具有资源,可以共享所属进程的全部资源
多对一的线程模型中只有一个内核级线程,当被阻塞后整个进程都将被阻塞
进程实体由正文段(代码和赋值数据段)、数据堆段、数据栈段组成
二进制代码和常量存放在正文段、动态分配的存储区再数据堆段、临时使用的变量再数据栈段、优先级只能再PCB中
同一个系统的进程/线程可由系统调用的方法被不同的进程/线程多次调用
单处理器系统中实现并发技术后,各进程再某一时间段内并发运行,CPU与I/O设备间并行工作
进程自身决定从运行态到阻塞态,从运行态到就绪态是因为时间片用完,由调度程序决定,从就绪态到运行态由调度程序决定,从阻塞态到就绪态由协作进程决定
只有一个进程中的线程切换到另一个进程中的线程时才会引起进程的切换
人为操纵的键盘速度较慢,完全可以使用一个线程来处理整个系统的键盘输入
用户级线程中,调度的对象时进程,跨进程的用户级线程调度需要内核参与
同一进程内的线程切换需要从用户态转到核心态,系统开销大
一个线程阻塞会导致整个进程都被阻塞
设备分配是通过在系统中设置响应的数据结构实现的,不需要创建进程
线程是调度的基本单位,进程是资源分配的基本单位
读磁盘操作需要等待I/O会把自身阻塞,阻塞完成后会变为就绪态
管道的通量不受磁盘容量大小限制
等待某资源可用/等待输入/输出均会进入阻塞态
父进程会为子进程分配虚拟地址空间

处理机调度

调度的概念

1、调度的基本概念

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

处理机调度是多道程序操作系统的基础,是操作系统设计的核心问题

2、调度的层次

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

image-20240403204148462

  • 高级调度(作业调度)

按照一定的原则从外存上处于后备队列的作业中挑选一个(或多个),给它(们)分配内存、输入/输出设备等必要的资源,并建立相应的进程,以使它(们)获得竞争处理机的权利
简言之,作业调度就是内存与辅存之间的调度每个作业只调入一次、调出一次

多道批处理系统中大多配有作业调度,而其他系统中通常不需要配置作业调度

  • 中级调度(内存调度)

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

中级调度实际上是**存储器管理中的对换功能**

  • 低级调度(进程调度)

按照某种算法从就绪队列中选取一个进程,将处理机分配给它

进程调度是最基本的一种调度,在各种操作系统中都必须配置这级调度
进程调度的频率很高,一般几十毫秒一次

3、三級调度的联系

作业调度从外存的后备队列中选择一批作业进入内存,为它们建立进程,这些进程被送入就绪队列,进程调度从就绪队列中选出一个进程,并把其状态改为运行态,把CPU分配给它
中级调度是为了提高内存的利用率,系统将那些暂时不能运行的进程挂起来

  1. 作业调度为进程活动做准备,进程调度使进程正常活动起来
  2. 中级调度将暂时不能运行的进程挂起,中级调度处于作业调度和进程调度之间
  3. 作业调度次数少,中级调度次数略多,进程调度频率最高
  4. 进程调度是最基本的,不可或缺

调度的目标

不同的调度算法具有不同的特性,在选择调度算法时,必须考虑算法的特性

  • CPU利用率

CPU是计算机系统中最重要和昂贵的资源之一,所以应尽可能使CPU保持“忙”状态,使这一资源利用率最高

C P U 的利用率 = C P U 有效工作时间 C P U 有效工作时间 + C P U 空闲等待时间 CPU的利用率 = \frac{CPU有效工作时间}{CPU有效工作时间 + CPU空闲等待时间} CPU的利用率=CPU有效工作时间+CPU空闲等待时间CPU有效工作时间

  • 系统吞吐量

表示**单位时间内CPU完成作业的数量**

⻓作业需要消耗较⻓的处理机时间,因此会降低系统的吞吐量。而对于短作业,需要消耗的处理机时间较短,因此能提高系统的吞吐量。调度算法和方式的不同,也会对系统的吞吐量产生较大的影响

  • 周转时间

指**从作业提交到作业完成所经历的时间**,是作业等待、在就绪队列中排队、在处理机上运行及输入/输出操作所花费时间的总和
周转时间 = 作业完成时间 − 作业提交时间 周转时间 = 作业完成时间 - 作业提交时间 周转时间=作业完成时间作业提交时间
平均周转时间是指多个作业周转时间的平均值
平均周转时间 = ( 作业 1 的周转时间 + . . . + 作业 n 的周转时间 ) / n 平均周转时间 = (作业1的周转时间 + ... + 作业n的周转时间) / n 平均周转时间=(作业1的周转时间+...+作业n的周转时间)/n
带权周转时间是指多个作业带权周转时间的平均值
带权周转时间 = 作业周转时间 作业实际运行时间 带权周转时间 = \frac{作业周转时间}{作业实际运行时间} 带权周转时间=作业实际运行时间作业周转时间
平均带权周转时间是指多个作业带权周转时间的平均值
平均带权周转时间 = ( 作业 1 的带权周转时间 + . . . + 作业 n 的带权周转时间 ) / n 平均带权周转时间 = (作业1的带权周转时间 + ... + 作业n的带权周转时间) / n 平均带权周转时间=(作业1的带权周转时间+...+作业n的带权周转时间)/n

  • 等待时间

指**进程处于等处理机的时间之和**,等待时间越⻓,用户满意度越低

处理机调度算法实际上并不影响作业执行或输入/输出操作的时间,只影响作业在就绪队列中等待所花的时间。因此,衡量一个调度算法的优劣,常常只简单地考察等待时间

  • 响应时间

指**从用户提交请求到系统首次产生响应所用的时间**

在交互式系统中,周转时间不是最好的评价准则,一般采用响应时间作为衡量调度算法的重要准则之一。从用户⻆度来看,调度策略应尽量降低响应时间,使响应时间处在用户能接受的范围之内

调度的实现

1、调度程序(调度器)

用于调度和分派CPU的组件称为调度程序,它通常由三部分组成

image-20240403210849538

  1. 排队器。将系统中的**所有就绪进程按照一定的策略排成一个或多个队列,以便于调度程序选择**。每当有一个进程转变为就绪态时,排队器便将它插入到相应的就绪队列中
  2. 分派器依据调度程序所选的进程,将其从就绪队列中取出,将CPU分配给新进程
  3. 上下文切换器在对处理机进行切换时,会发生两对上下文的切换操作:第一对,将当前进程的上下文保存到其PCB中,再装入分派程序的上下文,以便分派程序运行;第二对,移出分派程序的上下文,将新选进程的CPU现场信息装入处理机的各个相应寄存器

在上下文切换时,需要执行大量load和store指令,以保存寄存器的内容,因此会花费较多时间
现在已有硬件实现的方法来减少上下文切换时间。通常采用两组寄存器,其中一组供内核使用,一组供用户使用。这样,上下文切换时,只需改变指针,让其指向当前寄存器组即可

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

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

不能进行进程的调度与切换的情况有以下几种:

  1. 在处理中断的过程中。中断处理过程复杂,在实现上很难做到进程切换,而且中断处理是系统工作的一部分,逻辑上不属于某一进程,不应被剥夺处理机资源
  2. 进程在操作系统内核临界区中。进入界区后,需要独占式地访问,理论上必须加锁,以防止其他并行进程进入,在解锁前不应切换到其他进程,以加快临界区的释放
  3. 其他需要完全屏蔽中断的原子操作过程中。如加锁、解锁、中断现场保护、恢复等原子操作。在原子过程中,连中断都要屏蔽,更不应该进行进程调度与切换

若在上述过程中发生了引起调度的条件,则不能⻢上进行调度和切换,应置系统的请求调度标志,直到上述过程结束后才进行相应的谓度与切换

应该进行进程调度与切换的情况如下:

  1. 发生引起调度条件且当前进程无法继续运行下去时,可以⻢上进行调度与切换。若操作系统只在这种情况下进行进程调度,则是**非剥夺调度**
  2. 中断处理结束或自陷处理结束后,返回被中断进程的用户态程序执行现场前,若置上请求调度标志,即可⻢上进行进程调度与切换。若操作系统支持这种情况下的运行调度程序,则实现了**剥夺方式的调度**

进程切换往往在调度完成后立刻发生,它**要求保存原进程当前断点的现场信息,恢复被调度进程的现场信息**
现场切换时,操作系统内核将原进程的现场信息推入当前进程的内核堆栈来保存它们,并更新堆栈指针。内核完成从新进程的内核栈中装入新进程的现场信息、更新当前运行进程空间指针、重设PC寄存器等相关工作之后,开始运行新的进程

3、进程调度方式

进程调度方式是指当某个进程正在处理机上执行时,若有某个更为重要或紧迫的进程需要处理,即有优先权更高的进程进入就绪队列,此时应如何分配处理机

通常有以下两种进程调度方式:

  1. 非抢占调度方式,又称**非剥夺方式**
    是指当一个进程正在处理机上执行时,即使有某个更为重要或紧迫的进程进入就绪队列,仍然让正在执行的进程继续执行,直到该进程运行完成或发生某种事件而进入阻塞态时,才把处理机分配给其他进程
    优点:实现简单、系统开销小,适用于大多数的批处理系统
    缺点:不能用于分时系统和大多数的实时系统
  2. 抢占调度方式,又称**剥夺方式**
    是指当一个进程正在处理机上执行时,若有某个更为重要或紧迫的进程需要使用处理机,则允许调度程序根据某种原则去暂停正在执行的进程;将处理机分配给这个更为重要或紧迫的进程
    优点:对提高系统吞吐率和响应效率都有明显的好处。但“抢占”不是一种任意性行为,必须遵循一定的原则,主要有优先权、短进程优先和时间片原则等

4、闲逛进程

在进程切换时,如果系统中没有就绪进程,就会调度闲逐进程运行,如果没有其他进程就绪,该进程就一直运行,并在执行过程中测试中断。闲逛进程的优先级最低,没有就绪进程时才会运行闲逛进程,只要有进程就绪,就会立即让出处理机

闲逛进程不需要CPU之外的资源,它不会被阻塞

5、两种线程的调度

  1. 用户级线程调度。由于内核并不知道线程的存在,所以内核还是和以前一样,选择一个进程,并给予时间控制。由进程中的调度程序决定哪个线程运行
  2. 内核级线程调度。内核选择一个特定线程运行,通常不用考虑该线程属于哪个进程。对被选择的线程赋予一个时间片,如果超过了时间片,就会强制挂起该线程

用户级线程的线程切换在同一进程中进行,仅需少量的机器指令
内核级线程的线程切换需要完整的上下文切换、修改内存映像、使高速缓存失效,这就导致了若干数量级的延迟

典型的调度算法

操作系统中存在多种调度算法,有的调度算法适用于作业调度,有的调度算法适用于进程调度,有的调度算法两者都适用。下面介绍几种常用的调度算法

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

FCFS调度算法是一种最简单的调度算法,它**既可用于作业调度,又可用于进程调度**。在作业调度中,算法每次从后备作业队列中选择最先进入该队列的一个或几个作业,将它们调入内存,分配必要的资源,创建进程并放入就绪队列

在进程调度中,FCFS调度算法**每次从就绪队列中选择最先进入该队列的进程,将处理机分配给它,使之投入运行,直到运行完成或因某种原因而阻塞时才释放处理机**

FCFS调度算法举例:

FCFS调度算法属于**不可剥夺算法**

它**对所有作业都是公平的,但若一个⻓作业先到达系统,就会使后面的许多短作业等待很⻓时间,因此它不能作为分时系统和实时系统的主要调度策略**

FCFS调度算法的特点是**算法简单,但效率低**
对⻓作业比较有利,但对短作业不利(相对SJF和高响应比)
有利于CPU繁忙型作业,而不利于I/O繁忙型作业

短作业优先(SJF)調度算法

短作业优先(SJF)调度算法从**后备队列中选择一个或若干估计运行时间最短的作业,将它们调入内存运行
短进程优先(SPF)调度算法
就绪队列中选择一个估计运行时间最短的进程**,将处理机分配给它,使之立即执行,直到完成或发生某事件而阻塞时,才释放处理机

SJF调度算法举例:

image-20240404102607951

缺点:

  1. 该算法**对⻓作业不利**。SJF调度算法中⻓作业的周转时间会增加。更严重的是,若有一⻓作业进入系统的后备队列,由于调度程序总是优先调度那些(即使是后进来的)短作业,将导致⻓作业⻓期不被调度
    (“饥饿”现象,注意区分“死锁”,后者是系统环形等待,前者是调度策略问题)
  2. 该算法**完全未考虑作业的紧迫程度,因而不能保证紧迫性作业会被及时处理**
  3. 由于作业的⻓短是根据用户所提供的估计执行时间而定的,而用户又可能会有意或无意地缩短其作业的估计运行时间,致使该算法不一定能真正做到短作业优先调度

注:SJF调度算法的平均等待时间、平均周转时间最少

优先级调度算法

优先级调度算法**既可用于作业调度,又可用于进程调度**。算法中的优先级用于描述作业的紧迫程度

作业调度中,优先级调度算法每次从后备作业队列中选择优先级量高的一个或几个作业,将它们调入内存,分配必要的资源,创建进程并放入就绪队列
进程调度中,优先级调度算法每次从就绪队列中选择优先级最高的进程,将处理机分配给它,使之投入运行

该调度算法分为如下两种:

  1. 非抢占式优先级调度算法
    当一个进程正在处理机上运行时,即使有某个优先级更高的进程进入就绪队列,仍让正在运行的进程继续运行,直到由于其自身的原因而让出处理机时(任务完成或等待事件),才把处理机分配给就绪队列中优先级最高的进程
  2. 抢占式优先级调度算法
    当一个进程正在处理机上运行时,若有某个优先级更高的进程进入就绪队列,则立即暂停正在运行的进程,将处理机分配给优先级更高的进程

根据进程创建后其优先级是否可以改变,可以将进程优先级分为以下两种:

  1. 静态优先级优先级是在创建进程时确定的,且在进程的整个运行期间保持不变。确定静态优先级的主要依据有进程类型、进程对资源的要求、用户要求
  2. 动态优先级在进程运行过程中,根据进程情况的变化动态调整优先级。动态调整优先级的主要依据有进程占有CPU时间的⻓短、就绪进程等待CPU时间的⻓短

进程优先级的设置可以参照以下原则:

  1. 系统进程>用户进程。系统进程作为系统的管理者,理应拥有更高的优先级
  2. 交互型进程>非交互型进程(或前台进程>后台进程)。平时在使用手机时,在前台运行的正在交互的进程应该更快速地响应,因此自然需要被优先处理
  3. I/O型进程>计算型进程。I/O型进程指那些会频繁使用I/O设备的进程,而计算型进程是那些频繁使用CPU的进程(很少使用I/O设备)。I/O设备(如打印机)的处理速度要比CPU慢得多,因此若将I/O型进程的优先级设置得更高,就更有可能让I/O设备尽早开始工作,进而提升系统的整体效率
高响应比优先调度算法

高响应比优先谓度算法**主要用于作业调度,是对FCFS调度算法和SJF调度算法的一种综合平衡,同时考虑了每个作业的等待时间和估计的运行时间。在每次进行作业调度时,先计算后备作业队列中每个作业的响应比,从中选出响应比最高的作业投入运行**
响应比 R   p   = 等待时间 + 要求服务时间 要求服务时间 响应比R~p~ = \frac{等待时间 + 要求服务时间}{要求服务时间} 响应比R p =要求服务时间等待时间+要求服务时间

  • 作业的等待时间相同时,要求服务时间越短,响应比越高,有利于短作业,因而类似子SJF
  • 要求服务时间相同时,作业的响应比由其等待时间决定,等待时间越⻓,其响应比越高,因而类似于FCFS
  • 对于⻓作业,作业的响应比可以随等待时间的增加而提高,当其等待时间足够⻓时,也可获得处理机,克服了“饥饿”现象
时间片轮转调度算法

时间片轮转调度算法**主要适用于分时系统**

系统将所有就绪进程按FCFS策略排成一个就绪队列,调度程序总是选择就绪队列中的第一个进程执行,但**仅能运行一个时间片,如50ms。在使用完一个时间片后,即使进程并未运行完成,它也必须释放出(被剥夺)处理机给下一个就绪进程,而被剥夺的进程返回到就绪队列的末尾新排队,等候再次运行**

在时间片轮转调度算法中,时间片的大小对系统性能的影响很大:

  • 若**时间片足够大,以至于所有进程都能在一个时间片内执行完毕,则时间片轮转调度算法退化为先来先服务调度算法**
  • 若**时间片很小,则处理机将在进程间过于频繁地切换,使处理机的开销增大**,而真正用于运行用户进程的时间将减少

时间片的大小应选择适当,时间片的⻓短通常由以下因素确定:系统的响应时间就绪队列中的进程数目系统的处理能力

多级队列调度算法

算法在系统中**设置多个就绪队列,将不同类型或性质的进程固定分配到不同的就绪队列每个队列可实施不同的调度算法,因此,系统针对不同用户进程的需求,很容易提供多种调度策略。同一队列中的进程可以设置不同的优先级,不同的队列本身也可以设置不同的优先级**

在多处理机系统中,可以很方便为每个处理机设置一个单独的就绪队列,每个处理机可实施各自不同的调度策略,这样就能根据用户需求将多个线程分配到一个或多个处理机上运行

多级反馈队列调度算法

多级反馈队列调度算法是**时间片轮转调度算法和优先级调度算法的综合与发展**

image-20240404104841255

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

实现思想:

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

优点:

  1. 终端型作业用户:短作业优先
  2. 短批处理作业用户:周转时间较短
  3. ⻓批处理作业用户:经过前面几个队列得到部分执行,不会⻓期得不到处理

各常见进程调度算法的特点:

image-20240404105703466

进程切换

对于通常的进程而言,其创建、撤销及要求由系统设备完成的I/O操作,都是利用系统调用而进入内核,再由内核中的相应处理程序予以完成的
进程切换同样是在内核的支持下实现的,因此可以说:任何进程都是在操作系统内核的支持下运行的,是与内核紧密相关的

  • 上下文切换

切换CPU到另一个进程需要保存当前进程状态并恢复另一个进程的状态,这个任务称为上下文切换
上下文是指**某一时刻CPU寄存器和程序计数器的内容。进行上下文切换时,内核会将旧进程状态保存在其PCB中,然后加载经调度而要执行的新进程的上下文**

上下文切换实质上是指处理机从一个进程的运行转到另一个进程上运行,在这个过程中,进程的运行环境产生了实质性的变化

上下文切换的流程如下:

  1. 挂起一个进程,保存CPU上下文,包括程序计数器和其他寄存器
  2. 更新PCB信息
  3. 把进程的PCB移入相应的队列,如就绪、在某事件阻塞等队列
  4. 选择另一个进程执行,并更新其PCB
  5. 跳转到新进程PCB中的程序计数器所指向的位置执行
  6. 恢复处理机上下文
  • 上下文切换的消耗

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

  • 上下文切换与模式切换

模式切换与上下文切换是不同的,模式切换时,CPU逻辑上可能还在执行同一进程。用户进程最开始都运行在用户态,若进程因中断或异常进入核心态运行,执行完后又回到用户态刚被中断的进程运行

用户态和内核态之间的切换称为模式切换,而不是上下文切换,因为**没有改变当前的进程上下文切换只能发生在内核态**,它是多任务操作系统中的一个必需的特性

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

本节小结

为什么要进行处理机调度?

若没有处理机调度,意味着要等到当前运行的进程执行完毕后,下一个进程才能执行,而实情况中,进程时常需要等待一些外部设备的输入,而外部设备的速度与处理机相比是非常缓慢的,若让处理机总是等待外部设备,则对处理机的资源是极大的浪费

而引进处理机调度后,可在运行进程等待外部设备时,把处理机调度给其他进程,从而提高处理机的利用率
用一句简单的话说,就是为了合理地处理计算机的软/硬件资源

调度算法有哪几种?有没有哪种调度算法比较适合分时操作系统和实时操作系统?

本节介绍的调度算法有先来先服务调度算法、短作业优先调度算法、优先级调度算法、高响应比优先调度算法、时间片轮转调度算法、多级反馈队列调度算法6种

先来先服务算法和短作业优先算法无法保证及时地接收和处理问题,因此无法保证在规定的时间间隔内响应每个用户的需求,也同样无法达到实时操作系统的及时性需求
优先级调度算法按照任务的优先级进行调度,对于更紧急的任务给予更高的优先级,适合实时操作系统
高响应比优先调度算法、时间片轮转调度算法、多级反馈队列调度算法都能保证每个任务在一定时间内分配到时间片,并轮流占用CPU,适合分时操作系统

提醒:

时间片轮转调度算法是为了多个用户能及时干预系统
时间片轮转增加了系统开销但其较快的响应时间使得用户能与计算机交互,从而满足用户需求
CPU繁忙型的作业是指该类型作业需要占用很长的CPU时间,类似于长作业
时间片一到,不论是否完成都必须被下一个进程抢占,因此时间片轮转算法是绝对可抢占的
中断向量本身是用于存放中断服务例行程序的入口地址,中断向量地址就应是该入口地址的地址
中断发生,由硬件保护并更新程序计数器的主要目的是为保证系统运行且可靠正确,而不是为了提高处理速度
当进程处于临界区是,说明进程正在占用处理机,只要不破坏临界资源的使用规则,就不会影响处理机的调度
一般I/O型作业的优先级高于计算型作业的优先级,因为I/O操作需要及时完成,没办法长时间保存所要输入/输出的数据,所以考虑到系统资源利用率,要选择I/O繁忙型作业有更高的优先级
就绪队列的数量会印象进程最终完成时间

同步与互斥

同步与互斥的基本概念

在多道程序环境下,进程是并发执行的,不同进程之间存在着不同的相互制约关系
为了协调进程之间的相互制约关系,引入了进程同步的概念

例如,让系统计算1+2×3,假设系统产生两个进程:一个是加法进程,一个是乘法进程。要让计算结果是正确的,一定要让加法进程发生在乘法进程之后,但实际上操作系统具有异步性,若不加以制约,加法进程发生在乘法进程之前是绝对有可能的,因此要制定一定的机制去约束加法进程,让它在乘法进程完成之后才发生

1、临界资源

虽然多个进程可以共享系统中的各种资源,但其中许多资源一次只能为一个进程所用。将**一次仅允许一个进程使用的资源称为临界资源**

许多物理设备都属于临界资源,如打印机等。此外,还有许多变量、数据等都可以被若干进程共享,也属于临界资源

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

  1. 进入区。为了进入临界区使用临界资源,在进入区要检查可否进入临界区,若能进入临界区,则应设置正在访问临界区的标志,以阻止其他进程同时进入临界区
  2. 临界区。进程中访问临界资源的那段代码,又称临界段
  3. 退出区。将正在访问临界区的标志清除
  4. 剩余区。代码中的其余部分

以下为临界区的伪代码:

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

2、同步

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

例如,输入进程A通过单缓冲向进程B提供数据。当该缓冲区空时,进程B不能获得所需数据而阻塞,一旦进程A将数据送入缓冲区,进程B就被唤醒。反之,当缓冲区满时,进程A被阻塞,仅当进程B取走缓冲数据时,才唤醒进程A

3、互斥

互斥也称**间接制约关系。当一个进程进入临界区使用临界资源时,另一个进程必须等待,当占用临界资源的进程退出临界区后,另一进程才允许去访问此临界资源**

例如,在仅有一台打印机的系统中,有两个进程A和进程B,若进程A需要打印时,系统已将打印机分配给进程B,则进程A必须阻塞。一旦进程B将打印机释放,系统便将进程A唤醒,并将其由阻塞态变为就绪态

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

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

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

1、软件实现方法

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

  • 单标志法

算法设置一个公用整型变量turn,用于指示被允许进入临界区的进程编号,即若turn=0,则允许 P0 进程进入临界区。该算法**可确保每次只允许一个进程进入临界区。但两个进程必须交替进入临界区,若某个进程不再进入临界区,则另一个进程也将无法进入临界区(违背“空闲让进”)。这样很容易造成资源利用不充分**

若 P0 顺利进入临界区并从临界区离开,则此时临界区是空闲的,但 P1 并没有进入临界区的打算,turn=1一直成立, P0 就无法再次进入临界区(一直被while死循环困住)

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

P1进程:
while(turn != 1);		// 进入区
critical section;		// 临界区
turn=0;					// 退出区
remainder section;		// 剩余区
  • 双标志法先检查

算法的基本思想是**在每个进程访问临界区资源之前,先查看临界资源是否正被访问,若正被访问,该进程需等待;否则,进程才进入自己的临界区**

为此,设置一布尔型数组flag[],如弟i个元素flag[i]FALSE,表示 Pi 进程未进入临界区,如为TRUE,表示 P1 进程进入临界区

Pi进程:
while(flag[j]);			// 进入区(1)
flag[i] = TRUE			// 进入区(3)
critical section;		// 临界区
flag[i] = FLASE;		// 退出区
remainder section;		// 剩余区

Pj进程:
while(flag[i]);			// 进入区(2)
flag[j] = TRUE			// 进入区(4)
critical section;		// 临界区
flag[j] = FLASE;		// 退出区
remainder section;		// 剩余区

优点:不用交替进入,可连续使用
缺点: Pi 和 Pj 可能同时进入临界区

按序列 (1)(2)(3)(4) 执行时,会同时进入临界区(违背“忙则等待”)。即在检查对方的flag后和切换自己的flag前有一段时间,结果都检查通过
这里的问题出在**检查和修改操作不能一次进行**

  • 双标志法后检查

先将自己的标志设置为TRUE,再检测对方的状态标志,若**对方标志为TRUE,则进程等待否则进入临界区**

Pi进程:
flag[i] = TRUE			// 进入区
while(flag[j]);			// 进入区
critical section;		// 临界区
flag[i] = FLASE;		// 退出区
remainder section;		// 剩余区

Pj进程:
flag[j] = TRUE			// 进入区
while(flag[i]);			// 进入区
critical section;		// 临界区
flag[j] = FLASE;		// 退出区
remainder section;		// 剩余区

两个进程几乎同时都想进入临界区时,它们分别将自己的标志值flag设置成TRUE,并且同时检测对方的状态(执行while语句),发现对方也要进入临界区时,双方互相谦让,结果谁也进不了临界区,从而导致“饥饿”现象

  • Peterson’s Algorithm

为了**防止两个进程为进入临界区而无限期等待**,又设置了变量turn,每个进程在先设置自己的标志后再设置turn标志。这时,再同时检测另一个进程状态标志和允许进入标志,以便保证两个进程同时要求进入临界区时,只允许一个进程进入临界区

Pi进程
flag[i] = TRUE; turn = j		// 进入区
while(flag[j] && turn == j);	// 进入区
critical section;		// 临界区
flag[i] = FLASE;		// 退出区
remainder section;		// 剩余区

Pj进程
flag[j] = TRUE; turn = i		// 进入区
while(flag[i] && turn == i);	// 进入区
critical section;		// 临界区
flag[j] = FLASE;		// 退出区
remainder section;		// 剩余区

具体如下:考虑进程 Pi ,一旦设置flag[i]=true,就表示它想要进入临界区,同时turn=j,此时若进程 Pj 已在临界区中,符合进程 Pi 中的while循环条件,则 Pi 不能进入临界区
若 Pj 不想要进入临界区,即flag[j]=false,循环条件不符合,则 Pi 可以顺利进入,反之亦然

算法的基本思想是单标志法和双标志法后检查的结合。利用flag解决临界资源的互斥访问,而利用turn解决“饥饿”现象

2、硬件实现方法

计算机提供了特殊的硬件指令,允许对一个字中的内容进行检测和修正,或对两个字的内容进行交换等。通过硬件支持实现临界段问题的方法称为低级方法,或称元方法

  • 中断屏蔽方法

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

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

这种方法**限制了处理机交替执行程序的能力,因此执行的效率会明显降低**
对内核来说,在它执行更新变量或列表的几条指令期间,关中断是很方便的,但将关中断的权力交给用户则很不明智,若一个进程关中断后不再开中断,则系统可能会因此终止

  • 硬件指令方法

TestAndSet指令:这条指令是原子操作,即执行该代码时不允许被中断。其功能是**读出指定标志后把该标志设置为真**

boolean TestAndSet(boolean *lock) {
	boolean old;
	old = *lock;
	*lock = true;
	return old;
}

可以为每个临界资源设置一个共享布尔变量lock,表示资源的两种状态:true表示正被占用,初值为false
进程在进入临界区之前,利用TestAndSet检查标志lock,若无进程在临界区,则其值为false,可以进入,关闭临界资源,把lock置为true,使任何进程都不能进入临界区;若有进程在临界区,则循环检查,直到进程退出

利用该指令实现互斥的过程描述如下:

while TestAndSet(&lock);
进程的临界区代码段;
lock = false;
进程其他代码

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

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

注:以上对TestAndSetSwap指令的描述仅是功能实现,而并非软件实现的定义。事实上,它们是由硬件逻辑直接实现的,不会被中断

Swap指令可以简单有效地实现互斥,为每个临界资源设置一个共享布尔变量lock,初值为false
在每个进程中再设置一个局部布尔变量key,用于与lock交换信息

在进入临界区前,先利用Swap指令交换lockkey的内容,然后检查key的状态
有进程在临界区时,重复交换和检查过程,直到进程退出

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

硬件方法的优点:

  1. 适用于任意数目的进程,而不管是单处理机还是多处理机
  2. 简单、容易验证其正确性
  3. 可以支持进程内有多个临界区,只需为每个临界区设立一个布尔变量

硬件方法的缺点:

  1. 进程等待进入临界区时要耗费处理机时间,不能实现让权等待
  2. 从等待进程中随机选择一个进入临界区,有的进程可能一直选不上,从而导致“饥饿”现象

互斥锁

解决临界区最简单的工具就是互斥锁

一个进程在**进入临界区时应获得锁;在退出临界区时释放锁**
函数acquire()获得锁,而函数release()释放锁

每个互斥锁有一个布尔变量available,表示锁是否可用。如果锁是可用的,调用acquire()会成功,且锁不再可用。当一个进程试图获取不可用的锁时,会被阻塞,直到锁被释放

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

acquire()release()的执行**必须是原子操作,因此互斥锁通常采用硬件机制来实现**

缺点:忙等待
当有一个进程在临界区中,任何其他进程在进入临界区时必须连续循环调用acquire()。当多个进程共享同一个CPU时,就浪费了CPU周期

因此,互斥锁通常用于多处理器系统,一个线程可以在一个处理器上等待,不影响其他线程的执行

信号量

信号量机制是一种功能较强的机制,可用来解决互斥与同步问题
它只能被两个标准的原wait(S)signal(S)访问,也可记为**P操作V操作**

原语是指完成某种功能且不被分割、不被中断执行的操作序列,通常**可由硬件来实现**
原语功能的**不被中断执行特性在单处理机上可由软件通过屏蔽中断方法实现**
原语之所以不能被中断执行,是因为原语对变量的操作过程若被打断,可能会去运行另一个对同一变量的操作过程,从而出现临界段问题

1、整型信等量

整型信号量被定义为一个**用于表示资源数目的整型量**S,waitsignal操作可描述为:

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(S)signal(S)的操作如下:

void wait(semaphore S){
	S.value--;
	if(S.value < 0){
		add this process to S.L;
		block(S.L);
	}
}

S.value--表示进程请求一个该类资源,当S.value < 0时,表示该类资源已分配完毕,因此进程应调用block原语,进行自我阻塞,放弃处理机,并插入该类资源的等待队列S.L,可⻅该机制遵循了“让权等待”的准则

void signal(semaphore S){
	S.value++;
	if(S.value <= 0){
		remove this process from S.L;
		wakeup(P);
	}
}

S.value++表示进程释放一个资源,使系统中可供分配的该类资源数增1。若加1后仍是S.value ≤ 0,则表示在S.L中仍有等待该资源的进程被阻塞,因此还应调用wakeup原语,将S.L中的第一个等待进程唤醒

3、利用信号量实现同步

信号量机制能**用于解决进程间的各种同步问题**
设S为实现进程P1,P2同步的公共信号量,初值为0。进程P2中的语句y要使用进程P1中语句x的运行结果,所以只有当语句x执行完成之后语句y才可以执行

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

若P2先执行到P(S)时,S为0,执行P操作会把进程P2阻塞,并放入阻塞队列
当进程P1中的x执行完后,执行V操作,把P2从阻塞队列中放回就绪队列,当P2得到处理机时,就得以继续执行

4、利用信号量实现进程互斥

信号量机制也能很方便地解决进程互斥问题
设S为实现进程P1,P2互斥的信号量,由于每次只允许一个进程进入临界区,所以S的初值应为1(即可用资源数为1)。只需把临界区置于P(S)V(S)之间,即可实现两个进程对临界资源的互斥访问

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

当没有进程在临界区时,任意一个进程要进入临界区,就要执行P操作,把S的值减为0,然后进入临界区
当有进程存在于临界区时,S的值为0,再有进程要进入临界区,执行P操作时将会被阻塞,直至在临界区中的进程退出,这样便实现了临界区的互斥

互斥是不同进程对同一信号量进行P,V操作实现的,一个进程成功对信号量执行了P操作后进入临界区,并在退出临界区后,由该进程本身对该信号量执行V操作,表示当前没有进程进入临界区,可以让其他进程进入


在同步问题中:
若某个行为**要用到某种资源,则在这个行为前面P这种资源一下
若某个行为
会提供某种资源,则在这个行为后面V**这种资源一下

在互斥问题中:
P,V操作要**紧夹使用互斥资源的那个行为**,中间不能有其他冗余代码

简单说明一下:

对于**同步问题,P,V操作一般是用在不同的进程里面的**
对于**互斥问题,P,V操作一般是用在同一个进程里面的,并且是夹住所要进行互斥的动作**

在同步问题里面,P操作是用来阻塞进程的,一般是用于阻塞本身,先让其他进程完成;V操作是用来释放进程的,让前面对应阻塞的进程得以释放进行运行
在互斥问题里面,P操作是用来给进程加锁的,给自身加锁让其他进程不能得到资源运行;V操作时用来解锁的,给自身解锁让其他进程可以获得资源从而运行


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

信号量也可用来描述程序之间或语句之间的前驱关系

简单来说:
进程要开始时,要检查前驱是否已完成(即P前驱进程);自身进程完成后,要释放资源来开放后继进程(即V后继进程)

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

  1. 关系分析。找出问题中的进程数,并分析它们之间的同步和互斥关系。同步、互斥、前驱关系直接按照上面例子中的经典范式改写
  2. 整理思路。找出解决问题的关键点,并根据做过的题目找出求解的思路。根据进程的操作流程确定P操作、V操作的大致顺序
  3. 设置信号量。根据上面的两步,设置需要的信号量,确定初值,完善整理

管程

在信号量机制中,每个要访问临界资源的进程都必须自备同步的PV操作,大量分散的同步操作给系统管理带来了麻烦,且**容易因同步操作不当而导致系统死锁**

管程的特性**保证了进程互斥,无须程序员自己实现互斥,从而降低了死锁发生的可能性。同时管程提供了条件变量**,可以让程序员灵活地实现进程同步

1、管程的定义

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

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

管程定义了**一个数据结构和能并发进程所执行(在该数据结构上)的一组操作,这组操作能同步进程和改变管程中的数据**

管程由4部分组成:

  1. 管程的名称
  2. 局部于管程内部的**共享数据结构说明**
  3. 对该数据结构进行操作的一组过程(或函数)
  4. 对局部于管程内部的**共享数据设置初始值的语句**

管程定义描述举例,其中(1)(2)(3)(4)对应上面组成部分的1234:

monitor Demo{		// (1)定义一个名称为Demo的管程
	共享数据结构S;	// (2)定义共享数据结构,对应系统中的某种资源
	init_code(){	// (4)对共享数据结构初始化的数据
		S = 5;		// 初始资源数=5
	}
	take_away(){	// (3)过程1,申请一个资源
		对共享数据结构x的一系列处理;
		S--;		// 可用资源-1
		...
	}
	give_back(){	// (3)过程2,归还一个资源
		对共享数据结构x的一系列处理;
		S++;		// 可用资源+1
		...
	}
}
  • 管程把对共享资源的操作封装起来,可以发现管程实际很像一个类。管程内的共享数据结构只能被管程内的过程所访问。一个**进程只有通过调用管程内的过程才能进入管程访问共享资源**
    在例子中,外部进程只能通过调用take_away()过程来申请一个资源;归还资源也一样
  • 每次仅允许一个进程进入管程,从而实现进程互斥。若多个进程同时调用take_away()give_back(),则**只有某个进程运行完它调用执行管程内的过程后,下一个进程才能开始运行它调用的过程**

2、条件变量

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

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

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


条件变量和信号量的比较

相似点:条件变量的wait/signal操作类似于信号量的P/V操作,可以**实现进程的阻塞/唤醒**
不同点:条件变量是**没有值的,仅实现了“排队等待”功能;而信号量是有值的,信号量的值反映了剩余资源数,而在管程中,剩余资源数用共享数据结构记录**

经典同步问题

王道书上的例子感觉还行,但是放在这里有些冗长了,所以就去网上找了一些博客来看

生产者-消费者问题

传送门

读者-写者问题

传送门

哲学家进餐问题

传送门

吸烟者问题

传送门

本节小结

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

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

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

进程之间存在同步与互斥关系
同步是指为完成某种任务而进阿里的两个或多个进程,这些进程因为需要在某些位置上协调它们的工作次序而等待、传递信息所产生的制约关系
互斥是指当一个进程进入临界区使用临界资源是,另一个进程必须等待,当占用临界资源的进程退出临界区后,拎一个进程才允许区访问临界资源

提醒:

信号量机制是一种有效实现进程同步和互斥的工具,进程执行的前驱关系实质上是指进程的同步工具
-------------------------------------------------------
信号量是一个特殊的整型变量,只有初始化和PV操作才能改变其值

通常,信号量分为互斥量和资源量

互斥量的初值一般为1,表示临界区只允许一个进程进入,从而实现互斥(互斥量的值没有特别说明的话定死为1)
当互斥量等于0时,表示临界区已有一个进程进入,临界区外尚无进程等待
当互斥量小于0时,表示临界区中有一个进程,互斥量的绝对值表示在临界区外等待的进程数

资源信号量的初值可以时任意整数,表示可用的资源数(资源量初值是可以由用户决定的)
当资源量小于0时,表示所有资源已全部用完,而且还有进程正在等待使用该资源,等待的进程数就是资源量的绝对值
-------------------------------------------------------
临界资源为互斥共享资源,非共享数据不属于临界资源。像打印机、共享变量、共享缓冲区这些都属于共享资源
可重入程序代码一次可以供多个进程使用
P、V操作时一种低级进程通信原语,不能被中断
P操作即wait()操作,等待某种资源知道可用,若这种资源暂时不可用,则进程进入阻塞态。要注意的时,执行P操作时的进程处于运行态。因此P操作可能导致进程阻塞
V操作唤醒一个等待晋城市,被唤醒进程变为就绪态。只有就绪进程能获得处理器资源,被唤醒的进程并不能直接转换为运行态
可以被多个进程在任意时刻共享的代代码必须是不允许任何修改的代码。代码求任意一个进程在调用此段代码是都以同样的方式运行,且在运行过程中被中断后再继续执行结果不受影响
共享程序段可能同时被多个进程使用,必须用可重入编码编写,否则无法实现共享的功能
管程由局限于管程的共享变量说明、对管程内的数据结构进行操作的一组过程、对局限于管程的数据设置初始值的语句组成
信号量机制中的V操作一定会改变信号量的值(S=S+1),而管程中的signal操作是针对某个条件变量的,若不存在因该条件而阻塞的进程,则signal不会产生任何影响
系统中有n个进程,阻塞队列中进程的个数最多有n个,这时进程被死锁,n个进程都再阻塞队列
管程中只有一个进程再执行,若进程执行了x.wait(),则该进程会被阻塞,并挂到条件变量x对应的阻塞队列上;若有进程执行了x.singal()这才会唤醒上述进程
两个进程不能同时进入临界区,体现了忙则等待准则
允许进程访问空闲的临界资源,体现了空闲让进准则
进程等待进入临界区的时间是有限的,体现了有限等待准则
不能进入临界区的执行态进程立即放弃CPU,体现了让权等待准则
让权等待准则不一定要实现

死锁

死锁的概念

1、死锁的定义

死锁:多个进程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进

2、死锁产生的原因

  • 系统资源的竞争

通常系统中拥有的不可剥夺资源,其数量不足以满足多个进程运行的需要,使得进程再运行过程中,会因争夺资源而陷入僵局,如磁带机、打印机等。
只有对不可剥夺资源的竞争才可能产生死锁,对可剥夺资源的进程是不对引起死锁的

  • 进程推进顺序非法

进程在运行过程中,请求和释放资源的顺序不当,也同样会导致死锁
例如:并发进程P1、P2分别保持了资源R1、R2,而进程P1申请资源R2、进程P2申请资源R1时,两者都会因为**所需资源被占用而阻塞,于是导致死锁**

信号量使用不当也会造成死锁。进程间彼此相互等待对方发来的消息,也会使得这些进程间无法向前
例如:进程A等待进程B发的消息,进程B又在等待进程A发的消息,可以看出进程A和B不是因为竞争同一资源,而是在等待对方的资源导致死锁

3、死锁产生的必要条件

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

  1. 互斥条件
    进程要求所分配的资源(如打印机)进行排他性使用,即**在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待**
  2. 不剥夺条件
    进程所获得的资源在未使用完之前,不能被其他进程强行夺走,即**只能由获得该资源的进程自己来释放**(只能时主动释放)
  3. 请求并保持条件
    进程**已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放**
  4. 循环等待条件
    存在一个进程资源的循环等待链,链中每个进程已获得的资源同时被链中下一个进程所请求。即存在一个处于等待态的进程集合{P1,P2,...,Pn},其中Pi等待的资源被Pi+1(i=0,1,...,n-1)占有,Pn等待的资源P0占有

image-20240406211040694

要注意的是,有环且形成了循环等待条件了,不一定会造成进程死锁,循环等待只是死锁的必要条件

例如:系统中有两台输出设备,P0占有一台,Pk占有另一台,且k不属于集合{P1,P2,...,Pn}Pn等待一台输出设备,它可从P0获得,也可能从Pk获得。因此,虽然PnP0和其他一些进程形成了循环等待圈,但Pk不在图内,若Pk释放了输出设备,则可打破循环等待

image-20240406211815302

资源分配含圈而系统又不一定有死锁的原因是,同类资源数大于1
但若**系统中每类资源都只有一个资源,则资源分配图含圈就变成了系统出现死锁的充分必要条件**

要注意区分不剥夺条件与请求并保持条件

4、死锁的处理策略

为使系统不发生死锁,必须**设法破坏产生死锁的4个必要条件之一;或允许死锁产生,但当死锁发生时能检测出死锁,并有能力实现恢复**

  1. 死锁预防。设置某些限制条件,破坏产生死锁的4个必要条件中的一个或几个
  2. 避免死锁。在资源的动态分配过程中,用某种方法防止系统进入不安全状态
  3. 死锁的检测及解除。无须采取任何限制性措施,允许进程在运行过程中发生死锁。通过系统的检测机构及时地检测出死锁的发生,然后采取某种措施解除死锁

预防死锁和避免死锁都属于事先预防策略

预防死锁的限制条件比较严格,实现起来较为简单,但往往导致**系统的效率低,资源利用率低**
避免死锁的限制条件相对宽松,资源分配后需要通过算法来判断是否进入不安全状态,实现起来较为复杂

image-20240406212456058

死锁预防

防止死锁的发生只**破坏死锁产生的4个必要条件之一**即可

1、破坏互斥条件

若**允许系统资源都能共享使用**,则系统不会进入死锁状态

但有些资源根本不能同时访问,如打印机等临界资源只能互斥使用
所以,破坏互斥条件而预防死锁的方法不太可行,而且在有的场合应该保护这种互斥性

2、破坏不剥夺条件

当一个已**保持了某些不可剥夺资源的进程请求新的资源而得不到满足时,它必须释放已经保持的所有资源,待以后需要时再重新申请**。这意味着,一个进程已占有的资源会被暂时释放,或者说是被剥夺,或从而破坏了不剥夺条件

该策略实现起来比较复杂,释放已获得的资源可能造成前一阶段工作的失效,反复地申请和释放资源会增加系统开销,降低系统吞吐量。这种方法**常用于状态易于保存和恢复的资源**,如CPU的寄存器及内存资源,一般不能用于打印机之类的资源

3、破坏请求并保持条件

采用预先静态分配方法,即**进程在运行前一次申请完它所需要的全部资源,在它的资源未满足前,不把它投入运行一旦投入运行,这些资源就一直归它所有,不再提出其他资源请求**,这样就可以保证系统不会发生死锁

这种方式实现简单,但缺点也显而易⻅,系统资源被严重浪费,其中有些资源可能仅在运行初期或运行快结束时才使用,甚至根本不使用。而且还**会导致“饥饿”现象**,由于个别资源⻓期被其他进程占用时,将致使等待该资源的进程迟迟不能开始运行。

4、破坏循环等待条件

为了破坏循环等待条件,可采用**顺序资源分配法。首先给系统中的资源编号,规定每个进程必须按编号递增的顾序请求资源,同类资源一次申请完**。也就是说,只要进程提出申请分配资源Ri,则该进程在以后的资源申请中就只能申请编号大于Ri的资源

这种方法存在的问题是,编号必须相对稳定,这就**限制了新类型设备的增加;尽管在为资源编号时已考虑到大多数作业实际使用这些资源的顺序,但也经常会发生作业使用资源的顺序与系统规定顺序不同的情况,造成资源的浪费**;此外,这种技规定次序申请资源的方法,也必然会给用户的编程带来麻烦

避免死锁

避免死锁同样属于实现预防策略,但并不是实现采取某种限制措施破坏死锁的必要条件,而是**在资源动态分配过程中,防止系统进入不安全状态,以避免发生死锁**

这种方法所**施加的限制条件较弱,可以获得较好的系统性能**

1、系统安全状态

避免死锁的方法中,允许进程动态地申请资源,但**系统在进行资源分配之前,应先计算此次分配的安全性。若此次分配不会导致系统进入不安全状态,则允许分配;否则让进程等待**

所谓安全状态,是指系统能按某种进程推进顺序(P1,P2,...,Pn)为每个进程Pi分配其所需的资源,直至满足每个进程对资源的最大需求,使每个进程都可顺序完成。此时称P1,P2,...,Pn为安全序列。若系统无法找到一个安全序列,则称系统处于不安全状态

image-20240408102618984

并非所有的不安全状态都是死锁状态,但**当系统进入不安全状态后,便可能进入死锁状态;反之,只要系统处于安全状态,系统便可避免进入死锁状态**

2、银行家算法

进程**运行之前先声明对各种资源的量大求量,当进程在执行中继续申请资源时,先测试该进程已占用的资源数与本次申请的资源数之和是否超过该进程声明的最大需求量**

若**超过拒绝分配资源**
若**未超过则再测试系统现存的资源能否满足该进程尚需的最大资源量**
若**能满足按当前的申请量分配资源**
否则也要推迟分配

对于算法的具体说明,王道书上写得太冗长了,懒得敲了,放几篇博客在这里

传送门1传送门2

书上的例子写得很详细,可以仔细看看,篇幅原因这里就不放出来了


这里大概讲一下解题流程:

安全算法

一般题目会给你一个表格,里面包含了Max项(最大需求矩阵)Allocation项(分配矩阵)Availabe项(可利用资源向量)
若题目给出的为资源总数,则有:资源总数 - 分配矩阵和 = 可利用资源向量Avaliable

  1. 可以利用MaxAllocation来**求得Need项(需求矩阵)Need = Max - Allocation**
    在开始的时候有一个工作向量Work,表示系统中的剩余可用资源数目,初始时Work向量等于Available向量
  2. 将 Work 向量与 Need 矩阵的每一行进行比较,找出比Work小的行
    若比Work小的行只有一个则直接加入安全序列;若有两个以上则随机选一个加入安全序列
  3. 释放那一行的资源,即**把该行对应的 Allocation 项与 Work 向量相加**,把新值赋给Work向量,更新Work向量,这样就代表加入安全序列的进程释放资源后剩下的可用资源数量
  4. 重复这个步骤直到找到一个安全序列,或者如果有一项不满足Need>Work,则表示找不到安全序列

银行家算法

银行家算法中还另外设置了一个请求向量Request,一般同样会给出

  1. 拿** Request 向量与进程对应的 Need 向量与 Available 进行比较**
    若二者**都大于Request向量,则假定可以为该进程分配资源**
    若二者**有小于Request向量,则该进程应等待**
  2. 修改:
    Available = Available - Request
    Allocation = Allocation + Request
    Need = Need - Request
  3. Work向量初始等于Available向量,并**进行安全算法检查**,若可以找到一个安全序列,则系统时安全的,立即把资源分配给该进程
    若找不到安全序列,则拒绝该进程的请求,让该进程等待

死锁检测和解除

前面介绍的死锁预防和避免算法,都是在为进程分配资源时时间限制条件或进行检测,若系统为进程分配资源时不采取任何措施,则应该提供死锁检测和解除的手段

传送门

本节小结

为什么会产生死锁?产生死锁有什么条件?

由于系统中存在一些不可剥夺资源,当两个或两个以上的进程占有自身的资源并请求对方的资源时,会导致每个进程都无法向前推进,这就是死锁。死锁产生的必要条件有4个,分别是互斥条件、不剥夺条件、请求并保持条件和循环等待条件
互斥条件是指进程要求分配的资源是排他性的,即最多只能同时供一个进程使用
不剥夺条件是指进程在使用完资源之前,资源不能被强制夺走
请求并保持条件是指进程占有自身本来拥有的资源并要求其他资源
循环等待条件是指存在一种进程资源的循环等待链

有什么办法可以解决死锁问题?

死锁的处理策略可以分为预防死锁、避免死锁及死锁的检测与解除
死锁预防是指通过设立一些限制条件,破坏死锁的一些必要条件,让死锁无法发生
死锁避免指在动态分配资源的过程中,用一些算法防止系统进入不安全状态,从而避免死锁
死锁的检测和解除是指在死锁产生前不采取任何措施,只检测当前系统有没有发生死锁,若有,则采取一些措施解除死锁

提醒:

一次分配所有资源的方法可以预防死锁的发生,当进程需要资源时,一次性提出所有请求,若请求的所有资源均满足则分配,只要有一项不满足则不分配,进程阻塞,知道所有资源空闲后满足进程所需时再分配
死锁的避免是根据防止系统进入不安全状态采取措施实现的
死锁的四个必要条件中,无法被破坏的是互斥使用资源
出现环路只是满足了循环等待条件,而满足必要条件不一定会导致死锁
没有环路破坏了循环等待条件,一定不会发生死锁
出现环路的同时,每一种资源只有一个,可以判断一定有死锁
若资源足够多,则不会发生死锁;若资源不足,则有发生死锁的可能
死锁定理是用于处理死锁的检测死锁的方法
限制用户申请资源的顺序属于死锁预防
银行家算法可以避免系统进入死锁状态

疑难点

1、进程与程序的区别与联系

1、
进程是程序及其数据再计算机上的一次运行活动,是一个动态的概念
程序是一组有序的指令集合,是一种静态的概念
进程的运行实体是程序,离开程序的进程没有存在的意义
2、
进程是程序的一次执行过程,它是动态的创建和消亡的,具有一定的生命周期,是暂时存在的
程序是一组代码集合,是永久存在的,可长期保存
3、
一个进程可以执行一个或几个程序,一个程序也可构成多个进程
进程可创建进程,而程序不可能形成新的程序
4、
进程与程序的组成不同。进程的组成包括程序、数据和PCB

2、死锁与饥饿

一组进程处于死锁状态是指组内的每个进程都在等待一个事件,而该事件只可能由组内的另一个进程产生。这里所关心的主要是事件是资源的获取和释放。与死锁相关的另一个问题是无限期阻塞或饥饿,即进程在信号量内无穷等待的情况
产生饥饿的主要原因是:在一个动态系统中,对于每类系统资源,操作系统要确定一个分配策略,当多个进程同时申请某类资源时,由分配策略确定资源分配给进程的次序。有时资源分配策略可能是不公平的,即不能保证等待时间上界的存在。在这种情况下,即使系统没有发生死锁,某些进程也可能会⻓时间等待。当等待时间给进程推进和响应带来明显影响时,称发生了进程“饥饿”

饥饿与死锁的主要差别如下:
1、进入饥饿状态的进程可以只有一个,而因循环等待条件而进入死锁状态的进程却必须大于或等于两个
2、发生饥饿的进程的状态可能是就绪态(⻓期得不到处理机),也可能是阻塞态(如⻓期得不到所的I/O设备),而发生死锁的进程的状态则必定是阻塞态

3、银行家算法的工作原理

银行家算法的主要思想是避免系统进入不安全状态
在每次进行资源分配时,它首先检查系统是否有足够的资源满足要求,若有则先进行试分配,并对分配后的新状态进行安全性检查。若新状态安全,则正式分配上述资源,否则拒绝分配上述资源
保证系统始终处于安全状态,从而避免了死锁现象的发生

4、进程同步、互斥的区别和联系

并发进程的执行会产生相互制约的关系:
一种是进程之间竞争使用临界资源,只能让它们逐个使用,这种现象称为互斥,是一种竞争关系
另一种是进程之间协同完成任务,在关键点上等待另一个进程发来的消息,以便协同一致,是一种协作关系
  • 25
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值