第二章 进程与线程(含死锁)

文章目录

第二章 进程与线程(含死锁)

1.并发和并行

并发

并发(Concurrent),在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行。

举个栗子:打游戏和听音乐两件事情在同一个时间段内都是在同一台电脑上完成了从开始到结束的动作。那么,就可以说听音乐和打游戏是并发的。

并行

并行(Parallel),当系统有一个以上CPU时,当一个CPU执行一个进程时,另一个CPU可以执行另一个进程,两个进程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。

重点:系统要有多个CPU才会出现并行。在有多个CPU的情况下,才会出现真正意义上的同时进行

并发和并行的区别

并发和并行我们这里通过一个栗子来说明:一个餐桌上有两个人、四盘菜(麻婆豆腐、回锅肉、毛血旺、蒜泥白肉)。

一个人在吃饭过程中,吃了麻婆豆腐、吃了回锅肉、吃了毛血旺、吃了蒜泥白肉。吃上述四个菜就是并发执行的。上述过程看似是同时完成,其实是在吃不同东西中来回切换。

在上述栗子中,两个人之间的吃饭就是并行的,两个人之间可以在同一时间点一起吃麻婆豆腐、或者一个吃蒜泥白肉、一个吃回锅肉。两个人之间互不影响。

并发是指在一段时间内宏观上多个程序同时运行。并行指的是同一个时刻,多个任务确实真的在同时运行。
在这里插入图片描述

区别

  • 并发,指的是多个事情,在同一时间段内同时发生了。
  • 并行,指的是多个事情,在同一时间点上同时发生了。
  • 并发的多个任务之间是互相抢占资源的。
  • 并行的多个任务之间是不互相抢占资源的
  • 只有在多CPU的情况中,才会发生并行。否则,看似同时发生的事情,其实都是并发执行的
    在这里插入图片描述

2.进程的概念、进程的基本特征、组成结构

进程定义

进程是程序的一次执行过程;进程是一个程序及其数据在处理机上顺序执行时所发生的活动;

进程是具有独立功能的程序在一个数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单元。

进程就是一个程序,比如我们打开的QQ,后台其实就是一个进程。这个概念的引入就是为了实现多道程序并发执行,以提高 CPU 的利用率。

进程的基本特征

  • 动态性:进程是程序的一次执行,它有着创建、活动、暂停、终止等过程,具有一定的生命周期,是动态地产生、变化和消亡的。动态性是进程最基本的特征。
  • 并发性:指多个进程实体同时存于内存中,能在一段时间内同时运行。并发性是进程的重要特征,同时也是操作系统的重要特征。引入进程的目的就是为了使程序能与其他进程的程序并发执行,以提高资源利用率。
  • 独立性:指进程实体是一个能独立运行、独立获得资源和独立接受调度的基本单元。凡未建立PCB的程序都不能作为一个独立的单元参与运行。
  • 异步性:由于进程的相互制约,使得进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进。异步性会导致执行结果的不可再现性,为此在操作系统中必须配置相应的进程同步机制。
  • **结构性:**每个进程都配置一个PCB对其进行描述。从结构上看,进程实体是由程序段、数据段和进程控制块三部分组成的。

进程的组成结构

进程的组成主要包括三大部分:PCB程序段与数据段。程序段就是当前正在执行的程序代码,而数据段则是在运行时动态产生的数据,比如全局变量等。用于存放进程的管理和控制信息的数据结构就是 PCB(Process Control Block)。

PCB是一种数据结构,存放的是一些管理信息。主要信息如下:

  • 进程描述信息:PID 即唯一标识进程的整形数据,每个进程对应一个 PID;UID 表示进程对应的用户ID。在 Linux 环境下,使用 ps -ef 命令,第一列展示的就是 UID,第二列则是 PID,第三列是 PPID,即父进程的 PID
  • 进程控制信息:进程的状态,包括运行、就绪、阻塞等,表示进程当前的运行情况;进程的优化级等
  • 资源需求相关:资源分配、控制信息等
  • 其他信息:与处理机相关,各种寄存器,如程序计数器、指令寄存器等,涉及CPU上下文切换,文件描述符记录打开文件信息等

3.进程与程序的区别与联系

  • 进程是动态的,程序是静态的
  • 进程可以并发,而程序没有
  • 进程是资源竞争的基本单位
  • 进程是由程序和数据两部分组成
  • 进程有生命周期,

4.进程的状态及其相互转换

进程的状态有:运行态就绪态阻塞态

  • 运行态:进程正在处理机上运行。在单机处理机环境下,每个时刻最多只有一个进程处于运行态。
  • 就绪态:进程获得了除处理机外的一切所需资源,一旦得到处理机,便可立即运行。系统中处于就绪状态的进程可能有多个,通常将他们排成一个队列,成为就绪队列。
  • 阻塞态:又称等待态。进程正在等待某一事件而暂定运行,如等待某资源为可用或等待输入/输出完成。即使处理机空闲,该进程也不能运行。

进程之间的相互转换关系如下:

在这里插入图片描述

5.线程的定义以及和进程的区别和联系

线程的定义

线程是轻量级的进程,它是一个进程内的基本调度单位,有自己的程序计数器、寄存器及堆栈。

线程是进程中执行运算的最小单位,是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。

线程和进程的区别和联系

联系

(1)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。

(2)资源分配给进程,同一进程的所有线程共享该进程的所有资源。

(3)处理机分给线程,即真正在处理机上运行的是线程。

(4)线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。线程是指进程内的一个执行单元,也是进程内的可调度实体.

区别

(1)调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位

(2)并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行

(3)拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源.

(4)系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。

6.进程间制约关系、进程通信概念

进程间制约关系

详细介绍在8.进程同步和互斥的概念、临界资源和临界区的概念

直接制约关系:进程同步

间接制约关系:进程互斥

进程通信概念

(来自百度百科的概念)进程通信是指在进程间传输数据(交换信息)。进程通信根据交换信息量的多少和效率的高低,分为低级通信(只能传递状态和整数值)和高级通信(提高信号通信的效率,传递大量数据,减轻程序编制的复杂度)。其中高级进程通信分为三种方式:共享内存模式、消息传递模式、共享文件模式。

进程通信(InterProcess Communication,IPC)就是指的进程间的信息交换。实际上,上面讲的同步和互斥也是一种进程通信,只不过它传输的仅仅是信号量,通过修改信号量,使得进程之间建立联系,相互协调和协同工作,但是它缺乏传递数据的能力

当进程间传输的信息量很少(比如某个状态信息),这样同步和互斥就可以完全承担,但是在大多数情况之下,进程之间需要交换大批数据,这样就需要一种新的机制,那就是进程通信

a.共享存储

在通信的进程之间存在一块可直接访问的共享空间,通过对这片共享空间进行写/读操作实现进程之间的信息交换,如图所示,在对共享空间进行写/读操作时,需要使用同步互斥工具,对共享空间的写/读进行控制。
在这里插入图片描述

b.消息传递

进程通过系统提供的发送消息和接收消息两个原语进行数据交换。

  • 直接通信方式

发送进程直接把消息发送给接收进程,并将它挂在接收进程的消息缓存队列上,接收进程从消息缓冲队列中取得消息,如图所示。
在这里插入图片描述

  • 间接通信方式

发送进程把消息发送到某个中间实体,接收进程从中间实体取得消息。这种中间实体一般称为信箱,这种通信方式又称信箱通信方式。该通信方式广泛运用于计算机网络中,相应的通信系统称为电子邮件系统。

  • 管道通信

管道通信是消息传递的一种特殊方式。所谓“管道”,是指用于连接一个读进程和一个写进程以实现他们之间的通信的一个共享文件,又称pipe文件。向管道提供输入的发送进程,以字符流形式将大量的数据送入管道;而接收管道输出的接收进程则从管道中接收数据。管道机制必须提供以下三方面的协调能力:互斥同步确定对方的存在
在这里插入图片描述

管道也是一种文件,但它又和一般的文件有所不同,管道可以克服使用文件进行通信的两个问题,具体表现如下:

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

7.原语概念、进程控制

概念

原语是由若干条指令组成的,用于完成特定功能的,具有原子性(不可分割)的子程序。它与一般过程的区别:它们是原子操作(Action Operation)为保证操作的正确性,原语在执行期间是不可被中断的。因此,规定在执行原语操作时要屏蔽中断,以保证原语操作的不可分割性。

用于进程控制的原语

  • 创建原语(Create)、撤消原语(Termination)
  • 阻塞原语(Block)、唤醒原语(Wakeup)
  • 挂起原语(Suspend)、激活原语(Active)

进程控制

进程的创建

导致进程的创建有以下四种典型的原因/事件

  • 用户登录:用户输入登录命令,系统建立一个进程并把它插入就绪队列。
  • 作业调度:调度作业后将该作业装入内存,为之分配资源,并创建进程。
  • 提供服务:用户提出请求后,系统专门提供一个进程供用户使用。
  • 应用请求:进程为自己那些能并发执行的操作创建新的子进程,使其能并发执行,进而使整个进程能快速完成。

其中前三根据需要由系统创建新的进程,最后一条由进程自己创建的新进程。

进程的创建过程

  • 申请空白PCB
    • 分配唯一标识符
    • 从PCB集合中取走一个空白的PCB
  • 分配资源
    • 分配程序和数据以及用户栈所需要的内存空间
  • 初始化PCB
    • 将进程的相关信息填入PCB中
  • 插入就绪队列
进程的终止

导致进程终止的典型原因/事件

  • 正常结束(结束标志)
  • 异常结束
    • 越界错误
    • 保护错
    • 非法指令
    • 特权指令错
    • 运行超时
    • 等待超时
    • 算术运算错
    • I/O故障

如果进程在执行的过程中,发生了以上异常现象中的任何一种,系统都会及时停止这个进程的运行,以防止出现严重的后果。

  • 外界干预
    • 操作员或操作系统干预
    • 父进程请求
    • 父进程终止

进程终止的过程

  • 根据进程的标识符,从PCB集合中检索该进程的PCB,并读出进程状态
  • 若处于执行状态,应立即终止该进程的执行,并置调度标志为真,指示该进程被终止后应重新进行调度;
  • 若有子孙进程,应将其所有子孙进程予以终止,以防它们成为无法控制,或者是系统所不能识别的进程;
  • 回收资源并归还(父进程或系统);
  • 从所在的队列中移出被终止进程。(移到空闲队列中
进程的阻塞

处于运行状态的进程,在其运行过程中期待某一事件发生,如等待键盘输入、等待磁盘数据传输完成、等待其它进程发送消息,当被等待的事件未发生时,由进程自己执行阻塞原语block使自己由运行态变为阻塞态。可见,进程的阻塞是进程自身的一种主动行为。

阻塞过程如下

在这里插入图片描述

进程的唤醒

当被阻塞进程所期待的事件出现时,例如,当进程提出I/O请求时,进程会进入到阻塞状态,但是不能让这个进程一直处于阻塞状态,等到其I/O操作完成时,那么系统就要采用唤醒原语wakeup唤醒这个处于阻塞的进程,以使它继续执行。

唤醒过程如下

在这里插入图片描述

进程的挂起和激活

当有引起进程挂起的事件,系统利用挂起原语suspend()将指定进程或者处于阻塞状态的进程挂起。
当有发生激活进程的事件发生,若该进程在外存中已有足够的空间时,可将在外存上处于静止就绪的进程从外存调入内存,系统利用激活原语active()将指定进程激活。

进程控制原语与进程状态转换的对应,如下所示:
在这里插入图片描述

8.进程同步与互斥概念、临界资源及临界区概念

进程同步

多个相互合作的进程在一些关键点上可能需要互相等待或互相交换消息。一个进程运行到某一点时,除非合作进程已经完成了某种操作或发来了消息,否则就必须暂时等待那些操作的完成或信息的到来,进程间的这种关系称为同步

暂停等待已取得同步的那一点,称为同步点;需要等待的由其他进程完成的操作或发送的信息,称为同步条件

同步关系是一种直接制约关系

特点

(1)进程之间要在某些点上协调工作,到达的先后顺序是有要求的。

(2)进程之间互相了解对方的工作,任何一方单独运行会出现差错。

(3)一方或双方的运行会直接地依赖于对方所产生的信息或发出的消息。

进程互斥

当一个进程正在使用某独占型资源时,其他希望使用该资源的进程必须等待,当该进程用完资源并释放后,才允许其他进程去访问此资源,进程之间的这种相互制约关系称为互斥

互斥关系时一种间接制约关系

临界资源和临界区的概念

临界资源:一次仅允许一个进程使用的资源。(打印机、扫描仪、绘图机)

临界区: 进程中访问临界资源的程序段

使用临界资源的进程必须遵守一种约定: 进入临界区之前必须先发出请求,准许后才能进入; 退出临界区时必须声明,以便让其他进程进入。

对于临界区的操作要遵循以下准则: 空闲让进、忙则等待、有限等待、让权等待。

9.进程互斥的准则及实现方式

进程互斥的准则

  • 不假设各并发进程的相对执行速度
  • 出于临界区外的进程不能阻止其他进程进入临界区
  • 任何时刻只允许一个进程处于临界区中
  • 不能使进程在临界区外永远等待

互斥的实现

  • 关中断法:每个进程进入临界区后先关中断,离开前开中断
  • 加锁法:用锁变量来表示临界区是否可用
  • 严格的轮转法:用标志严格控制轮流使用临界区
  • 信号量法:信号量是是OS中表示资源的物理实体,是一个与队列相关的整型变量,其值仅由down,up原语改变

10.管程的基本组成结构和运行过程

为什么提出管程

由于采用信号量及P、V同步机制来编写并发程序,对于共享变量及信号量变量的操作将被 分散于各个进程中,有以下缺点。

  • 程序易读性差,因为要了解对于一组共享变量及信号量的操作是否正确,则必须通读整个系统或者并发程序。

  • 程序不利于修改和维护,因为程序的局部性很差,所以任一组变量或一段代码的修改都可能影响全局。

  • 正确性难以保证,因为操作系统或并发程序通常很大,要保证这样一个复杂的系统没有逻辑错误是很难的。

  • 同步操作使用不当可能导致死锁

为了更易于编写正确的程序,Brinch Hanson和Hoare提出了一种高级同步机制,称为管程(Monitor)

管程的定义

管程是一种更高级的同步原语,更便于使用,管程的互斥由编译器负责,使用者只需将所有临界区转换为管程即可。管程代表共享资源的数据结构和对该共享数据结构实施操作的一组过程所组成的资源管理程序。

管程的基本组成结构

一个管程是由过程条件变量数据结构等组成的特殊模块或软件包。进程仅能通过管程访问其中的数据结构。

详细点,管程由四部分组成

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

管程特性

管程有一个很重要的特性,即任一时刻管程中只能有一个活跃进程(这一特性使得管程能够有效地完成互斥

管程的定义描述举例

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

学过面向对象的就知道,其实管程就像是一个类。

  • 管程把对共享资源的操作封装起来,管程内的共享数据结构只能被管程内的过程所访问。一个进程只有通过调用管程内的过程才能够进入管程访问共享资源。对于上例,外部进程只能通过take_away()过程来申请一个资源;归还资源也一样。
  • 每次仅允许一个进程进入管程,从而实现管程互斥。若多个进程同时调用take_away(),give_back()。则只有某个进程运行完它调用的过程后,下一个进程才能开始运行调用的过程。也就是说,各个进程只能串行执行管程内的过程,这一特性保证了进程“互斥”访问共享数据结构S。

条件变量

当一个进程进入管程后被阻塞,直到阻塞的原因接触时,在此期间,如果该进程不释放管程, 那么其他进程无法进入管程。为此,将阻塞原因定义为条件变量condition。通常,一个进程被阻塞的原因可以有多个,因此在管程中设置了多个条件变量。每个条件变量只能进行两种操作,即wait和signal。

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

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

11.进程同步与互斥的信号量实现

信号量

信号量其实就是一个变量(可以是一个整数,也可以是更复杂的记录型变量),可以用一个信号量来表示系统中某种资源的数量。如系统中只有一台打印机,就可以设置一个初值为1的信号量。如所有的临界资源在同一时刻只能被一个进程访问,所以临界资源的信号量为1。

信号量的实质:具有一个等待队列的计数器,也属于临界资源的一种,要获取信号量资源,则对信号量进行-1操作,要释放信号量资源,则对信号量资源进行+1操作。
注:信号量操作是原子操作

信号量实现同步

原理:进程获取临界资源之前,要先获取信号量资源;
若无信号量资源,则该进程阻塞等待,进入等待队列。
若有信号量资源,则对信号量进行P(-1)操作,再获取临界资源。
当临界资源+1时,对应的信号量资源则执行V(+1)操作,然后唤醒在等待队列中等待获取临界资源的进程。

  • 分析需要同步的两个操作,即必须保证一前一后的执行顺序的操作。
  • 设置同步信号量S,初始为0。
  • 在前操作之后执行V(S)
  • 在后操作之前执行P(S)

信号量实现互斥

原理:一个进程获取了该临界资源之后,另一个进程无法再访问该临界资源。
实现互斥,采用一元信号量,即:该信号量的计数器,只能为0或1。
一个进程要获取临界资源时,先获取对应的信号量资源;
当无信号量资源时,则该进程阻塞等待,进入等待队列。
当有信号量资源时,则对该信号量资源进行P(-1)操作,然后获取该临界资源。
当该进程使用完临界资源时,将释放信号量资源(对信号量资源进行V(+1)操作),然后唤醒等待队列中的进程。

(1) 确定临界区(如对临界资源打印机的访问就应放在临界区)
(2) 设置互斥信号量(mutex)
(3) 在临界区之前执行P(mutex)
(4) 在临界区之后执行V(mutex)

12.经典的IPC问题

IPC,Inter-Process Communication的缩写,含义是进程间通信,是指两个进程间交换数据的过程。

哲学家进餐问题

哲学家的生活包括两个不同的阶段:吃饭和思考

当一个哲学家觉得饿时,他就试图去取他左边和右边的叉子,每次拿一把,但是部分次序,如果成功地获得了两把叉子,他就吃一会儿,然后放下叉子继续思考。

一种不正确的解法

#define N 5		//哲学家的数目
void philosopher(int i) {		//i是哲学家的编号,从0到4
    while (true) {
        think();				//哲学家正在思考
        take_fork(i);			//取左边的叉子
        take_fork((i + 1) % N);	//取右面叉子:%表示取模运算
        eat();					//空心粉味道不错
        put_fork(i);			//把左面叉子放回桌子
        put_fork((i + 1) % N)	//把右面叉子放回桌子
    }
}

但是当所有哲学家同时拿起左边的叉子,无法得到右边的叉子——死锁

程序稍作修改:所有的哲学家都同时拿起左叉,看到右叉不可用,又都放下左叉,等一会儿,又同时拿起左叉,如此这般,永远重复。对于这种情况,即所有的程序都在无限期地运行,但是都无法取得任何进展,就成为饥饿(starvation)。

对上例算法可做一点改进,它既不会死锁也不会饥饿:即,使用一个二进制信号量对五个think之后的语句进行保护。在开始拿叉子之前,哲学家先对信号量mutex执行down操作。在放回叉子后,他要对mutex执行up操作。

从理论上讲,该解法是可行的。但是从实力角度来看,有性能上的缺陷:任意时刻只能有一个哲学家进餐。而五把叉子实际上允许有两位哲学将同时进餐。

正确的解法

#define N  5                        /* 哲学家数目 */
#define LEFT  (i+N-1)%N             /* i的左邻编号 */
#define RIGHT  (i+1)%N              /* i的右邻编号 */
#define THINKING  0                 /* 哲学家在思考 */
#define HUNGRY  1                   /* 哲学家试图拿起叉子 */
#define EATING  2                   /* 哲学家进餐 */
typedef int semaphore;              /* 信号量 */
int state[N];                       /* 记录每位哲学家状态 */
semaphore mutex = 1;                /* 临界区的互斥 */
semaphore s[N];                     /* 每位哲学家一个信号量 */

/* i: 哲学家编号,从0到N-1 */
void philosopher(int i) {
    while (TRUE) {                  /* 无限循环 */
        think();                    /* 哲学家思考 */
        take_forks(i);              /* 需要两个叉子, */
        eat();                      /* 哲学家进餐 */
        put_forks(i);               /* 将叉子放回到桌子上 */
    }
}

void take_forks(int i) {
    down(&mutex);                   /* 进入临界区 */
    state[i] = HUNGRY;              /* 记录哲学家i处于饥饿状态 */
    test(i);                        /* 尝试获取两把叉子 */
    up(&mutex);                     /* 退出临界区 */
    down(&s[i]);                    /* 如果得不到需要的叉子则阻塞 */
}

void put_forks(i) {
    down(&mutex);                   /* 进入临界区 */
    state[i] = THINKING;            /* 哲学家进餐完毕 */
    test(LEFT);                     /* 测试左邻是否可以吃 */
    test(RIGHT);                    /* 测试右邻是否可以吃 */
    up(&mutex);                     /* 离开临界区 */
}

void test(i) {
    //我是饥饿的但是左右都不再吃的时候
    if (state[i] == HUNGRY && state[LEFT] != EATING && state[RIGHT] != EATING) {
        state[i] = EATING;
        up(&s[i]);
    }
}

上面给出的解法是没有死锁的,而且对于任意多位哲学家的情况都能获得最大的并行度。它使用一个数组state来记录哲学家是在吃饭、思考还是饿了。一个哲学家只有在两个邻座都不在进餐时,才允许转换到进餐状态。哲学家i的邻居是由宏LEFTRIGHT定义。

该程序使用了一个信号量数组,每个信号量对应于一位哲学家,这样,所需的叉子被占用时,饥饿的哲学家就可以被阻塞。注意每个进程将历程philosopher作为代码运行,而其他例程,如take_forksput_forkstest都只是普通的例程,而不是单独的进程。

读者/写者问题

另一个著名的问题是读者—写者问题,它建模了对数据库的访问。

例如,设想一个飞机定票系统,其中有许多竞争的进程试图读写其中的数据。多个进程同时读是可以接受的,但如果一个进程正在更新数据库,则所有其他进程都不能访问数据库,即使读操作也不行。这里的问题是:如何对读者和写者进行编程?

进程A操作进程B操作是否允许
允许
互斥
互斥
typedef int semaphore;
semaphore mutex = 1;    	/* 控制对RC的访问 */
semaphore db = 1;       	/* 控制对数据库的访问 */
int rc = 0;        			/* 正在读或想要读的进程数 */

void reader(void) {
    while (TRUE) {        	/* 无限循环 */
        down(&mutex);    	/* 排斥对RC的访问*/
        rc = rc + 1;    	/* 又多了一个读者 */
        /*如果这是第一个读者,那么......*/
        if (rc == 1)
            //只要有一个读者在读书编者就不能编书
            //当前有进程在读取数据库
            down(&db);
        up(&mutex);    		/*恢复对RC的访问*/
        read_data_base(); 	/*访问数据*/
        down(&mutex);    	/*排斥对RC的访问*/
        rc = rc - 1;    	/*读者又少了一个*/
        /*如果这是最后一个读者,那么......*/
        if (rc == 0)
            up(&db);
        use_data_read();    /*非临界区操作*/
    }
}

void writer(void) {
    while (TRUE) {
        think_up_data();    /*非临界区操作*/
        down(&db);    		/*排斥访问*/
        write_data_base();  /*修改数据*/
        up(&db);        	/*恢复访问*/
    }
}

第一个读者对信号量db执行DOWN。随后的读者给计数器rc加1。当读者离开时,它们递减这个计数器,而最后一个读者则对db执行UP这样就允许一个阻塞的写者可以访问数据库

设想当一个读者在使用数据库时,另一个读者也来访问数据库,由于同时允许多个读者同时进行读操作,所以第二个读者也被允许进入,同理第三个及随后更多的读者都被允许进入。

13.调度的层次及不同系统的调度目标

调度的层次

  • 作业调度(高级调度)

决定参与CPU与其他系统资源的竞争,即作业由后备到运行态。

  • 交换调度(中级调度)

决定参与CPU竞争的进程,即进程由运行到阻塞态,或由阻塞态到就绪态。

  • 进程调度(低级调度)

决定获得CPU的进程,即进程由就绪态到运行态。

不同系统调度的目标

  • 作业调度
    • 功能
      • 记录系统中各作业的状况
      • 从后备队列中选一部分作业投入运行
      • 为被选中的作业做好执行前的准备工作
      • 在作业执行结束时做善后处理工作
    • 目标
      • 公平
      • 高效
      • 大吞吐量
      • 短的响应时间和周转时间
  • 进程调度
    • 功能
      • 记录系统中所有进程的执行情况
      • 选择占有处理机的进程
      • 进程上下文切换

14.调度的时间、切换过程

进程调度的时机

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

在这里插入图片描述

进程调度的方式

进程调度的方式可以分为两种:非剥夺调度方式剥夺调度方式

非剥夺调度方式

非剥夺调度方式,也称为非抢占方式。即只允许进程主动放弃处理机。在运行过程中即便有更紧迫的任务到达,当前进程依然会继续使用处理机,直到该进程终止或主动要求进入阻塞态。

这种方式实现简单,系统开销小但是无法及时处理紧急任务,适合早期的批处理系统。

剥夺调度方式

剥夺调度方式,又称为抢占方式。当一个进程正在处理机上执行时,如果有一个更重要或更紧迫的进程需要使用处理机,则立即暂停正在执行的进程,将处理机分配给更重要紧迫的那个进程。

这种方式可以优先处理更紧急的过程,也可以实现让各进程按时间片轮流执行的功能(通过时钟中断)。适合于分时操作系统、实时操作系统。

进程的切换和过程

这里需要知道的是两个每次的区别:狭义进程调度进程切换的区别。

狭义的进程调度指的是从就绪队列中选中一个要运行的进程。这个进程可以是刚刚被暂停执行的进程,也可能是另一个进程。

进程切换是指一个进程让出处理机,由另一个进程占用处理机的过程。

广义的进程调度包含了选择一个进程进程切换两个步骤。

进程切换的过程主要完成了:

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

如:程序计数器、程序状态字、各种数据寄存器等处理机现场信息,这些信息一般保存在进程控制块。

注意:进程切换是有代价的,因此如果过于频繁地进行进程调度、切换,比如会使整个系统的效率降低,使系统大部分时间都花在了进程切换上,而真正用于执行过程的时间减少。

15.各种常用的进程及作业调度算法

进程及作业调度的性能评价指标

平均周转时间
T i = t i e − t i s ( t i e : 作 业 完 成 时 刻 , t i s : 作 业 提 交 时 刻 ) T i = t i e − t i r ( t i e : 进 程 完 成 本 次 C P U 周 期 时 刻 , t i r : 进 程 进 入 就 绪 队 列 时 刻 ) T = 1 / n ( ∑ i = 1 n T i ) Ti=tie-tis ( tie:作业完成时刻, tis:作业提交时刻)\\Ti=tie-tir ( tie:进程完成本次CPU周期时刻,tir:进程进入就绪队列时刻)\\T=1/n(\sum_{i=1}^nTi) itietistietisitietirtieCPUtir1/ni=1ni

平均带权周转时间

设ti表示进程或作业的实际运行时间,则有:
T i ’ = T i / t i         T ’ = 1 / n ( ∑ i = 1 n T i ’ ) Ti’=Ti/ti\\     T’ =1/n(\sum_{i=1}^nTi ’ ) iiti    1/ni=1ni
平均等待时间:(主要针对进程而言)
W i = t i p − t i r ( t i p : 指 进 程 获 得 C P U 的 时 刻 ) W = 1 / n ( ∑ i = 1 n W i ) ( t i r 指 进 程 进 入 就 绪 队 列 的 时 刻 ) Wi= tip-tir (tip:指进程获得CPU的时刻) \\ W= 1/n(\sum_{i=1}^nWi)( tir 指进程进入就绪队列的时刻) itiptir(tipCPU1/ni=1nitir

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

补充:时间片大小的确定

  • 时间片过小:有利于短作业,因为它能在该时间片内完成,但是,意味着频繁的执行进程调度和进程上下文的切换,增加系统的开销。即:退化为短作业优先算法 。
  • 时间片过大:每个进程都能在一个时间片内完成,即退化为FCFS算法。

响应比高优先调度算法(HRN)

在这里插入图片描述

优先级调度算法

在这里插入图片描述

补充:
1、优先级是利用某一范围内的整数来表示的,又把该整数称为优先数(优先数的大小并不一定和优先级成正比)。确定优先级大小的依据如下;

  • 进程类型
  • 进程对资源的需求
  • 用户要求

2、优先级的类型有两种:

  • 静态优先级:在创建进程时确定的,在进程的整个运行期间保持不变。虽然静态优先级简单易行,系统开销小,但是不够精确,可能会出现优先级低的进程长期没有被调度的情况。
  • 动态优先级:在创建程序之初,先赋予其一个优先级,然后其值随进程的推进或者等待时间的增加而改变,以获得更好的调度性能。

多级反馈队列调度算法

在这里插入图片描述

16.死锁的基本概念、死锁产生的原因及四个必要条件、死锁建模

死锁的概念

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

举个栗子,在某个计算机系统中只有一台打印机和一台输入 设备,进程P1正占用输入设备,同时又提出使用打印机的请求,但此时打印机正被进程P2 所占用,而P2在未释放打印机之前,又提出请求使用正被P1占用着的输入设备。这样两个进程相互无休止地等待下去,均无法继续执行,此时两个进程陷入死锁状态。

死锁产生的原因

  • 系统资源的竞争

    • 当系统中供多个进程共享的资源如打印机、公用队列的等,其数目不足以满足诸进程的需要时,会引起诸进程对资源的竞争而产生死锁。
  • 进程运行推进顺序不当引起死锁

    • 进程推进顺序合法

    当进程P1和P2并发执行时,如果按照下述顺序推进:P1:Request(R1); P1:Request(R2); P1: Relese(R1);P1: Relese(R2); P2:Request(R2); P2:Request(R1); P2: Relese(R2);P2: Relese(R1);这两个进程便可顺利完成,这种不会引起进程死锁的推进顺序是合法的。

    • 进程推进顺序非法

    若P1保持了资源R1,P2保持了资源R2,系统处于不安全状态,因为这两个进程再向前推进,便可能发生死锁。例如,当P1运行到P1:Request(R2)时,将因R2已被P2占用而阻塞;当P2运行到P2:Request(R1)时,也将因R1已被P1占用而阻塞,于是发生进程死锁。

死锁产生的四个必要条件

  • 互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
  • 请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
  • 不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。
  • 循环等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。

这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生

死锁建模

采用有向图建立资源分配图

在这里插入图片描述

  • 进程A占有一个资源
  • 进程B请求一个资源
  • 死锁
一个死锁是如何产生的例子

在这里插入图片描述

一个死锁是如何避免的例子

在这里插入图片描述

四种处理死锁的策略
  • 忽略该问题
  • 检测死锁并恢复
  • 仔细对资源进行分配,动态地避免死锁
  • 通过破坏引起死锁的四个必要条件之一,防止死锁的产生

17.死锁的预防、避免(银行家算法)、检查和解除死锁的原理与方法

死锁的预防

在程序运行之前防止发生死锁。

前面说了死锁产生的条件有四个,分别是:互斥条件、占有和等待条件、不剥夺条件、循环等待条件。

而死锁防止的策略就是至少破坏这四个条件其中一项。

破坏互斥条件
  • 使资源同时访问而非互斥使用,就没有进程会阻塞在资源上,从而不发生死锁。

只读数据文件、磁盘等软硬件资源均可采用这种办法管理;
但是许多资源是独占性资源,如可写文件、键盘等只能互斥的占有;
所以这种做法在许多场合是不适用的。

破坏占有和等待条件
  • 采用静态分配的方式,静态分配的方式是指进程必须在执行之前就申请需要的全部资源,且直至所要的资源全部得到满足后才开始执行。

实现简单,但是严重的减低了资源利用率。
因为在每个进程占有的资源中,有些资源在运行后期使用,有些资源在例外情况下才被使用,可能会造成进程占有一些几乎用不到的资源,而使其他想使用这些资源的进程等待。

破坏不剥夺条件
  • 剥夺调度能够防止死锁,但是只适用于内存和处理器资源。

方法一:占有资源的进程若要申请新资源,必须主动释放已占有资源,若需要此资源,应该向系统重新申请。

方法二:资源分配管理程序为进程分配新资源时,若有则分配;否则将剥夺此进程已占有的全部资源,并让进程进入等待资源状态,资源充足后再唤醒它重新申请所有所需资源。

不破坏循环等待条件
  • 给系统的所有资源编号,规定进程请求所需资源的顺序必须按照资源的编号依次进行。

    采用层次分配策略,将系统中所有的资源排列到不同层次中

    • 一个进程得到某层的一个资源后,只能申请较高一层的资源
    • 当进程释放某层的一个资源时,必须先释放所占有的较高层的资源
    • 当进程获得某层的一个资源时,如果想申请同层的另一个资源,必须先释放此层中已占有的资源

死锁的避免

各种死锁防止方法能够防止发生死锁,但必然会降低系统并发性,导致低效的资源利用率。

在程序运行时避免发生死锁。

安全状态

在这里插入图片描述

图 a 的第二列 Has 表示已拥有的资源数,第三列 Max 表示总共需要的资源数,Free 表示还有可以使用的资源数。从图 a 开始出发,先让 B 拥有所需的所有资源(图 b),运行结束后释放 B,此时 Free 变为 5(图 c);接着以同样的方式运行 C 和 A,使得所有进程都能成功运行,因此可以称图 a 所示的状态时安全的。

定义:如果没有死锁发生,并且即使所有进程突然请求对资源的最大需求,也仍然存在某种调度次序能够使得每一个进程运行完毕,则称该状态是安全的。

安全状态的检测与死锁的检测类似,因为安全状态必须要求不能发生死锁。下面的银行家算法与死锁检测算法非常类似,可以结合着做参考对比。

单个资源的银行家算法

一个小城镇的银行家,他向一群客户分别承诺了一定的贷款额度,算法要做的是判断对请求的满足是否会进入不安全状态,如果是,就拒绝请求;否则予以分配。

在这里插入图片描述

上图 c 为不安全状态,因此算法会拒绝之前的请求,从而避免进入图 c 中的状态。

多个资源的银行家算法

在这里插入图片描述

上图中有五个进程,四个资源。左边的图表示已经分配的资源,右边的图表示还需要分配的资源。最右边的 E、P 以及 A 分别表示:总资源、已分配资源以及可用资源,注意这三个为向量,而不是具体数值,例如 A=(1020),表示 4 个资源分别还剩下 1/0/2/0。

检查一个状态是否安全的算法如下:

  • 查找右边的矩阵是否存在一行小于等于向量 A。如果不存在这样的行,那么系统将会发生死锁,状态是不安全的。
  • 假若找到这样一行,将该进程标记为终止,并将其已分配资源加到 A 中。
  • 重复以上两步,直到所有进程都标记为终止,则状态时安全的。

如果一个状态不是安全的,需要拒绝进入这个状态。

死锁的检查和恢复

对资源的分配加以适当限制可防止或避免死锁发生,但不利于进程对系统资源的充分共享。

不试图阻止死锁,而是当检测到死锁发生时,采取措施进行恢复。

如果进程 - 资源分配图中无环路,此时系统没有发生死锁。
如果进程 - 资源分配图中有环路,则可分为以下两种情况:

  • 每种资源类中仅有一个资源,则系统发生了死锁。此时,环路是系统发生死锁的充分必要条件,环路中的进程就是死锁进程。
  • 每种资源类中有多个资源,则环路的存在只是产生死锁的必要不充分条件,系统未必会发生死锁。
每种资源类中仅有一个资源的死锁检测

在这里插入图片描述

上图为资源分配图,其中方框表示资源,圆圈表示进程。资源指向进程表示该资源已经分配给该进程,进程指向资源表示进程请求获取该资源。

图 a 可以抽取出环,如图 b,它满足了环路等待条件,因此会发生死锁。

比如:进程D需要的资源是U、T;进程G需要的资源是V、U;进程E需要的资源是T、V
此时进程D占有资源U,进程E占有资源T,进程G占有资源V
所以此时导致进程D、E、G所申请的资源不能得到全部满足,陷入死锁。

死锁检测算法:

每种类型一个资源的死锁检测算法是通过检测有向图是否存在环来实现,从一个节点出发进行深度优先搜索,对访问过的节点进行标记,如果访问了已经标记的节点,就表示有向图存在环,也就是检测到死锁的发生。

每种资源类中有多个资源的死锁检测

在这里插入图片描述

每个资源类用一个方框表示,方框中的原点表示此资源类中的各个资源;

每个进程用一个圆圈来表示,用有向边表示进程申请资源和资源分配情况。

约定方框→圆圈表示资源分配,圆圈→方框表示申请资源。

这种情况下,图3-6 发生了死锁,而图3-7没有发生死锁。

死锁定理:当且仅当此状态的进程-资源分配图是不可完全简化的,这一充分条件称为死锁定理。

死锁检测算法

在这里插入图片描述

上图中,有三个进程四个资源,每个数据代表的含义如下:

  • E 向量:资源总量
  • A 向量:资源剩余量
  • C 矩阵:每个进程所拥有的资源数量,每一行都代表一个进程拥有资源的数量
  • R 矩阵:每个进程请求的资源数量

进程 P1 和 P2 所请求的资源都得不到满足,只有进程 P3 可以,让 P3 执行,之后释放 P3 拥有的资源,此时 A = (2 2 2 0)。P2 可以执行,执行后释放 P2 拥有的资源,A = (4 2 2 1) 。P1 也可以执行。所有进程都可以顺利执行,没有死锁。

算法总结如下:

每个进程最开始时都不被标记,执行过程有可能被标记。当算法结束时,任何没有被标记的进程都是死锁进程。

  1. 寻找一个没有标记的进程 Pi,它所请求的资源小于等于 A。
  2. 如果找到了这样一个进程,那么将 C 矩阵的第 i 行向量加到 A 中,标记该进程,并转回 1。
  3. 如果没有这样一个进程,算法终止。
死锁恢复
  • 资源剥夺法
    剥夺陷于死锁的进程所占用的资源,但并不撤销此进程,直至死锁解除。

  • 进程回退法
    根据系统保存的检查点让所有的进程回退,直到足以解除死锁,这种措施要求系统建立保存检查点、回退及重启机制。

  • 进程撤销法

    • 撤销陷入死锁的所有进程,解除死锁,继续运行。
    • 逐个撤销陷入死锁的进程,回收其资源并重新分配,直至死锁解除。

可选择符合下面条件之一的先撤销:

1.CPU消耗时间最少者

2.产生的输出量最小者

3.预计剩余执行时间最长者

4.分得的资源数量最少者后优先级最低者

  • 系统重启法
    结束所有进程的执行并重新启动操作系统。这种方法很简单,但先前的工作全部作废,损失很大。

参考资料

面试必考的:并发和并行有什么区别? - 腾讯云开发者社区-腾讯云 (tencent.com)

操作系统之进程详解(一) - 知乎 (zhihu.com)

进程线程剖析(二)-进程组成、状态与特点 - 知乎 (zhihu.com)

进程与线程的联系和区别? - 腾讯云开发者社区-腾讯云 (tencent.com)

进程间的制约关系 - huoxin7480 - 博客园 (cnblogs.com)

一文搞懂六大进程通信机制原理(全网最详细) - 知乎 (zhihu.com)

操作系统(七)进程管理——进程控制 - 魏亚林 - 博客园 (cnblogs.com)

信号量机制及实现进程的互斥、同步 - 简书 (jianshu.com)

经典的IPC问题 - 我係死肥宅 - 博客园 (cnblogs.com)

王道操作系统考研笔记——2.1.7 进程调度的时机、切换与过程、方式-云社区-华为云 (huaweicloud.com)

什么是死锁?死锁发生的四个必要条件是什么?如何避免和预防死锁产生? - Kevin.ZhangCG - 博客园 (cnblogs.com)

操作系统之进程详解(一) - 知乎 (zhihu.com)

进程线程剖析(二)-进程组成、状态与特点 - 知乎 (zhihu.com)

进程与线程的联系和区别? - 腾讯云开发者社区-腾讯云 (tencent.com)

进程间的制约关系 - huoxin7480 - 博客园 (cnblogs.com)

一文搞懂六大进程通信机制原理(全网最详细) - 知乎 (zhihu.com)

操作系统(七)进程管理——进程控制 - 魏亚林 - 博客园 (cnblogs.com)

信号量机制及实现进程的互斥、同步 - 简书 (jianshu.com)

经典的IPC问题 - 我係死肥宅 - 博客园 (cnblogs.com)

王道操作系统考研笔记——2.1.7 进程调度的时机、切换与过程、方式-云社区-华为云 (huaweicloud.com)

什么是死锁?死锁发生的四个必要条件是什么?如何避免和预防死锁产生? - Kevin.ZhangCG - 博客园 (cnblogs.com)

死锁的产生、防止、避免、检测和解除 - 知乎 (zhihu.com)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值