#pic_center
R 1 R_1 R1
R 2 R^2 R2
目录
- 知识框架
- No.0 引言
- No.1 进程的概念、组成、特征
- No.2 进程的状态转换和组织
- No.3 进程控制
- No.4 进程通信
- No.5 线程的概念与特点
- No.6 线程的实现方式和多线程模型
- No.7 线程的状态与转换
- No.8 调度的概念、层次
- No.9 进程调度的时机、切换与过程、方式
- No.10 调度器和闲逛进程
- No.11 调度算法的评价指标
- No.12 调度算法
- No.13 调度算法
- No.14 进程同步进程互斥
- No.15 进程互斥的软件实现方法
- No.16 进程互斥的硬件实现方法
- No.17 互斥锁
- No.18 信号量机制
- No.19 用信号量机制实现进程互斥、同步、前驱关系
- No.20 生产者消费者问题
- No.21 多生产者-多消费者
- No.22 吸烟者问题
- No.23 读者写者问题
- No.24 哲学家进餐问题
- No.25 管程
- No.26 死锁
- No.27 死锁的处理策略—预防死锁
- No.28 死锁的处理策略—避免死锁
- No.29 死锁的处理策略—死锁的检测与解除
知识框架
No.0 引言
No.1 进程的概念、组成、特征
- 从这一小节开始,我们将正式进入第二章,涉及处理和管理相关的内容。
- 在这个小节中,我们首先要了解一个非常重要的概念,即"进程"。在第一章的讲解中,我们经常提到系统中正在运行的程序,但偶尔也会提到"进程"这个词汇。
- 对于许多初学者来说,"进程"和"程序"这两个概念可能容易混淆,不容易理解。在接下来的内容中,我们将介绍进程的定义以及它的组成部分和重要特征。首先,让我们来看一下进程的第一个部分。
一、进程的概念
- 这个地方如果直接抛出一些文字性的描述,很多非计算机专业的同学可能会比较难理解。因此,我们这次采用一个更贴近大家生活和经验的方式来进行讲解。首先,让我们来看一下,在我自己的Windows电脑里,打开了任务管理器。任务管理器中可以看到当前系统中正在运行的进程。有很多进程在同时运行。
- 如果此时我想要使用QQ,我打开QQ程序,然后QQ程序开始运行。在任务管理器的进程栏中,我会看到一个与QQ相关的进程条目。如果我想同时登录两个或者甚至三个QQ账号,我可以再次打开QQ程序。此时,你会发现在进程栏中,有3个与QQ相关的进程条目出现。尽管我多次打开的都是QQ.exe这个程序,但这个程序的每次执行都对应着不同的进程。如果你的电脑在身边,你可以自己动手尝试一下,看看是否出现了这种情况。
- 因此,所谓的程序是一种静态的,存储在硬盘中的可执行文件,比如在Windows电脑中就是QQ.exe这个程序。这个可执行文件实际上包含了一系列的指令。指令的概念我们在第一章已经讲解过。而所谓的进程则是动态的,它是程序的一次执行过程。这也就意味着,即使是同一个程序,它多次执行时会对应多个不同的进程,就像我们在这个界面中所看到的一样。因此,通过这个界面,大家应该能够很直观地理解进程和程序之间的区别。
- 接下来,一个问题出现了:既然这三个进程都在执行同一个程序,那么操作系统在背后是如何区分这三个进程的呢?操作系统不可能将它们都标记为"腾讯QQ进程",对吧?所以为了解决这个问题,
- 所以,所谓的程序实际上是一种静态的存在于磁盘中的可执行文件,例如在Windows电脑上,就是QQ.exe。这个可执行文件实际上包含了一系列的指令集合,而指令的概念我们在第一章已经讲解过。
- 而所谓的进程则是动态的,它代表了程序的一次执行过程。也就是说,即使是同一个程序,每次执行都会对应一个不同的进程,就像我们在这个界面中所看到的那样。通过这个界面,大家应该能够很直观地理解进程和程序之间的区别。
二、进程的组成
-
那么接下来问题产生了,既然这三个进程执行的都是同一个程序,那么操作系统在背后要怎么区分这三个进程呢?不能把它们都叫做腾讯QQ进程吧。所以其实为了解决这个问题,操作系统在创建一个进程的时候,会给这个进程分配一个唯一的、不重复的ID,叫做p ID,也就是进程 ID。它就相当于我们人类世界的身份证号,我们每个人的身份证号都是唯一的、不重复的,而 p ID 就是进程的身份证号。那还是让大家看一个直观的例子,我现在正在录制视频,使用的是苹果的电脑,那苹果的电脑有一个叫做活动监视器的一个小工具,它和 Windows 的那个任务管理器其实是一样的,可以用这个小工具来看一下现在在我的系统上正在运行的进程有哪些。在这一栏可以看一下有一个叫做 PID,也就是进程 ID 的一个属性。我们让这些进程根据 PID 的递减的次序来排列,那可以看到各个进程的 PID,它们都是不重复的。那此时我来打开一个叫做 Tapro 的一个应用程序,当我打开这个应用程序之后,大家注意观察这个地方,出现了和它相对应的一个进程的信息,它的 PID 是 2641。那此时我再把这个应用程序给退出,然后再一次打开,有没有发现它的 PID 变成了 2642?刚才是 2641,现在变成了 2642。所以这就能很好的说明一个问题,我们每一次新建一个进程,都会给它分配一个不重复的唯一的 ID,在很多操作系统当中,PID 的分配都是每一次加一这样的一个很简单的策略。可以看到现在系统在背后,他又自己创建了一个新的进程,虽然我不知道这个进程是干嘛的,但是他的 PID 依然是递增的。所以其实回到刚才的这个图,虽然说这些进程,他们的进程的名称都叫腾讯 QQ 32 位,但是他们在背后都有各自的 PID,他们的 PID 肯定是不重复的。
-
那么接下来问题产生了,既然这三个进程执行的都是同一个程序,那么操作系统在背后要怎么区分这三个进程呢?不能把它们都叫做腾讯QQ进程吧。所以其实为了解决这个问题,操作系统在创建一个进程的时候,会给这个进程分配一个唯一的、不重复的ID,叫做p ID,也就是进程 ID。它就相当于我们人类世界的身份证号,我们每个人的身份证号都是唯一的、不重复的,而 p ID 就是进程的身份证号。那还是让大家看一个直观的例子,我现在正在录制视频,使用的是苹果的电脑,那苹果的电脑有一个叫做活动监视器的一个小工具,它和 Windows 的那个任务管理器其实是一样的,可以用这个小工具来看一下现在在我的系统上正在运行的进程有哪些。在这一栏可以看一下有一个叫做 PID,也就是进程 ID 的一个属性。我们让这些进程根据 PID 的递减的次序来排列,那可以看到各个进程的 PID,它们都是不重复的。那此时我来打开一个叫做 Tapro 的一个应用程序,当我打开这个应用程序之后,大家注意观察这个地方,出现了和它相对应的一个进程的信息,它的 PID 是 2641。那此时我再把这个应用程序给退出,然后再一次打开,有没有发现它的 PID 变成了 2642?刚才是 2641,现在变成了 2642。所以这就能很好的说明一个问题,我们每一次新建一个进程,都会给它分配一个不重复的唯一的 ID,在很多操作系统当中,PID 的分配都是每一次加一这样的一个很简单的策略。可以看到现在系统在背后,他又自己创建了一个新的进程,虽然我不知道这个进程是干嘛的,但是他的 PID 依然是递增的。所以其实回到刚才的这个图,虽然说这些进程,他们的进程的名称都叫腾讯 QQ 32 位,但是他们在背后都有各自的 PID,他们的 PID 肯定是不重复的。
-
那还有像最后的这一列,他是列出了各个进程的一个对网络流量的使用情况。所以其实操作系统在背后,他不只是记录了各个进程的 PID,除了 PID 之外,他还记录了各个进程的其他的一些信息,比如说刚才我们所看到的,他所属的用户 ID,或者简称 UID。操作系统可以根据 PID、UID 这些基本的进程描述信息,来区分各个进程。还有刚才我们看到的,分配了多少内存,正在使用哪些 IO 设备,正在使用哪些文件,这些信息的记录,可以帮助操作系统实现对系统资源的一个管理工作。另外呢还有刚才我们看到的,CPU 使用时间、磁盘使用情况、网络流量使用情况等等,这些信息可以帮助操作系统实现对进程的控制和调度,等等一系列的管理策略。
-
那既然操作系统在背后要记录这么多的信息,那么这些信息都会被统一的放在一个叫做 PCB 的一个数据结构当中。它的英文缩写是 Process Control Block,就是进程控制块。总之呢,操作系统他需要对各个并发运行的进程进行管理,而但凡他在管理这些进程的时候,所需要用到的信息,都会放在这个叫做 PCB 的数据结构当中。所以 PCB 是一个很重要的数据结构,它是进程存在的唯一标志。当一个进程被创建的时候,操作系统也会为他创建相应的 PCB。当然这个 PCB 当中又包含了进程的啊 p ID、UID 等等一系列的信息。而当一个进程结束的时候,操作系统就会回收他的 PCB。
1、PCB进程控制块
- 那在 PCB 当中需要保存的信息,大致上可以分为这样的四类:进程描述信息、进程控制和管理相关的信息、还有资源分配的情况。这三种类型的信息刚才我们都已经介绍过啊。除此之外,还会在 PCB 当中保留啊处理机相关的信息。那只有在 PCB 当中保存了处理机相关的信息,才可以实现进程的切换工作,那这个坑我们先留着之后再来填。
- 接下来让大家看一个令人脑壳疼的东西啊。我在网上找到了 Linux 的内核源码,然后在一个叫做 sched.h 这样的文件当中,我们就可以看到啊,在 Linux 操作系统当中,它定义的 PCB 长什么样子。在 Linux 当中它的 PCB 的名字叫做 task_struct。有兴趣的同学也可以大家去 Linus 的官网,下载一下它的源码,来看一下这个文件。那在这个 task_struct 里边,它会记录各种各样的信息,比如说这个字段啊 state,这个字段,它其实就是记录了进程当前的状态,而处于就绪态呢、运行态呢还是阻塞态呢等等。那进程的状态,这是我们下一个小节当中会展开介绍的内容。再来看这个字段 fields,这个字段,它记录的是当前进程,它打开了哪些文件,然后还有一个叫做 IO context 的字段,这个字段,它记录的是 IO 管理当中所需要使用的信息。当然这个数据结构里面定义了好多好多字段,大家可以看一下,光是这个数据结构的定义,它就写了 1,900 多行的代码。这么多代码是让人很抓狂的一个事情。
- 总之呢,我们想要认识 PCB 当中的所有字段,那是不可能的。我们只需要知道 PCB 当中存放的都是操作系统在对进程进行管理的时候所需要的那些信息就可以了。那除了 PCB 之外,进程还有两个很重要的组成部分,一个叫做程序段,一个叫做数据段。刚才我们说过,PCB 它是给操作系统用的一个数据结构,而程序段和数据段,它其实是给进程自己用的。
2、程序段/数据段
- 那除了 PCB 之外,进程还有两个很重要的组成部分,一个叫做程序段,一个叫做数据段。刚才我们说过,PCB 它是给操作系统用的一个数据结构,而程序段和数据段,它其实是给进程自己用的。
三、程序是如何运行的?
- 我们具体来看一个例子,在之前的第一章节当中,我们学到了啊,这个程序在运行之前需要编译成二进制的机器指令,而这个程序执行的过程,其实就是 CPU 执行这些一条一条的指令的一个过程。那接下来我们把程序运行的过程再进一步的细化一下,其实我们写完一个程序之后,经过编译、链接等一系列的步骤,最终会形成一个可执行文件,像大家熟悉的 Windows 电脑里,就是 .exe 的文件。那这个可执行文件,平时是存放在硬盘当中的,这个可执行文件当中保存的其实就是我们刚才说的那一系列的指令序列。
- 而当这个程序要运行之前,需要把它从硬盘读入到内存当中,并且操作系统会建立一个与它相对应的进程。那根据刚才我们的分析,我们知道它会建立相对应的 PCB。那除了 PCB 之外,这个程序的那一系列指令序列,也需要读到内存当中,那这一系列的指令序列,我们把它称作为程序段。那其实这个程序执行的过程,或者说这个进程它执行的过程,就是 CPU 从内存当中读入这些一条一条的指令,然后来执行这些指令。
- 除了执行这些指令之外,其实在执行指令的过程当中,会有一些中间的数据,比如说我们这定义了一个变量叫做 x,那么这些变量的内容,其实也需要放在内存当中。所以还会有另外一个叫做数据段的区域,用来存放这个程序运行过程当中所产生所需要使用的各种数据,就比如说我们定义了哪些变量,这些信息就是放在数据段里的。所以一个进程的实体,它由 PCB、程序段和数据段这么三个部分组成。
-
我们之前一直在说进程有哪些部分组成,但其实更严格的来说,应该是说进程实体有哪些部分组成。进程实体,或者说进程印象,它是动态的,而进程实体、进程印象,它是静态的。我们可以把进程实体理解为是这个进程在动态执行过程当中某一时刻的一个快照、一个照片。进程实体能够反映这个进程在某一个时刻的状态,比如说这个进程运行的过程当中,x 的值这个变量的值本来是一,但是在进行了加加这个操作之后,x 的值就会变成 2。所以在进程的运行过程当中,进程实体它是在不断变化的。所以准确的说,我们应该说进程实体由 PCB、程序段和数据段这样三个部分组成。
-
不过除非题目特别考察进程和进程实体的区别,不然大家也可以认为所谓的进程就是进程实体,没必要去钻这个牛角尖扣这个字眼。那 PCB 是给管理者,也就是给操作系统使用的,而程序段和数据段里面的内容,是给进程自己使用的,和进程自己的运行逻辑有关。所以在引入了进程实体的概念之后,我们可以把进程的定义为这样:进程它是进程实体的一个运行过程,是系统进行资源分配和调度的独立单位。进程是资源分配的独立单位,这一点很好理解,从刚才活动监视器这我们也可以看到,操作系统是以进程为单位,给各个进程来分配这些资源的,比如说内存。所以进程是资源分配的独立单位。
-
那这个地方还涉及到另一个概念,叫做进程的调度。其实所谓的调度,就是指操作系统决定让哪个进程上 CPU 运行。进程的调度,相关的内容,我们会在之后的小节当中有更进一步的学习。这先不展开。最后进程还拥有结构性,就是指每个进程都会有一个 PCB、一个程序段和一个数据段。那所有的这些特性大家都只需要理解,不需要啊死记硬背。
四、进程的特征
- 我们介绍了进程这个很重要的概念,进程或者说进程实体,由 PCB、程序段和数据段这三个部分组成。PCB 是一个很重要的数据结构,它是进程存在的唯一标志。操作系统就是通过 PCB 里边记录的这些各种各样的信息,来对各个进程进行管理的,所以但凡是在操作系统管理进程,所需要的数据肯定都是放在 PCB 当中。
- 而进程它自己所需要的数据,是放在程序段和数据段当中的。另外需要注意的是,进程的动态性是它最基本的特性,并且还需要注意,进程它是独立获得资源、独立接受调度的一个基本单位。让大家注意这一点的原因是,在引入了线程之后,进程就不再是接受调度的基本单位了,但是进程依然是获得资源的基本单位,这点还会经过后续的小节进行进一步的讲解。这暂时先不展开。
五、总结
- 介绍了进程这个很重要的概念,进程或者说进程实体由PCB,程序段和数据段这样三个部分组成,PCB是一个很重要很重要的数据结构,它是进程存在的唯一标志,操作系统就是通过,PCB里边记录的这些各种各样的信息,来对各个进程进行,管理的,所以但凡是在操作系统管理进程,所需要的数据,肯定都是放在PCB当中,而进程他自己所需要的数据,是放在程序段和数据段当中的,
- 另外呢需要注意的是,进程的动态性是他最基本的特性,并且还需要注意,进程他是独立获得资源,独立接受调度的一个基本单位,让大家注意这一点的原因是啊,在引入了县城之后,晋城就不再是接受调度的基本单位了,但是晋城依然是获得资源的基本单位,这点还会经过后续的小节,进行进一步的讲解,这暂时先不展开,好的,那么以上就是这个小节的全部内容,
No.2 进程的状态转换和组织
- 这个小节中,我们会学习进程的状态和状态的转换相关的知识点。接下来,我们会介绍进程所拥有的各种各样的状态,以及它们之间在什么情况下需要转换。
- 另外,我们还会介绍进程的组织方式的问题,也就是各个进程的 PCB 之间要,呃,用什么样的方式把它们组织起来。这样的一个问题。
一、进程的状态
1、创建态、就绪态
- 首先,我们来看一下进程有哪些状态。在上个小节当中,我们提到过,其实我们的程序也就是可执行文件,平时是存放在硬盘里的。而当这个程序想要执行的时候,需要把这个可执行文件把它调入内存。同时,操作系统会为它建立相应的 PCB,也就是建立一个相应的进程。那,当一个进程正在被创建的这个期间,啊,这个进程的状态就是处于创建态。
- 在这个阶段,操作系统会给这个进程分配相应的系统资源,比如说给他分配一些内存空间。另外呢,在这个阶段,操作系统也会完成对 PCB 的一个初始化的工作。而当一个进程完成了创建工作之后,他就会进入一个新的状态,叫做就绪态。处于就绪态的进程其实是已经具备了,啊,运行的条件,只不过此时 CPU 比较忙,他还没有空闲,所以 CPU 暂时不能为这个进程服务。
2、运行态
- 那一个系统当中,可能会有很多很多个处于就绪态的进程。那么,当 CPU 空闲的时候,操作系统就会从这些,呃,处于就绪态的进程当中,选择其中的一个,让他上 CPU 运行。而如果一个进程,此时正在 CPU 上运行的话,那么这个进程就处于运行态。
- 那么经过之前的学习,我们知道一个进程它正在运行,意味着此时 CPU 呃,正在处理这个进程背后的那个程序,也就是呃,CPU 正在执行这个进程相应的那些指令序列,比如说 CPU 执行了进程 2 的指令一,指令 2,指令 3。
3、阻塞态
- 那我们假设此时进程 2 的指令 3 是发出了一个系统调用,而这个系统调用是请求操作系统给他分配打印机资源。而此时打印机设备,他很忙,他正在为别的进程服务,所以这个打印机资源暂时不能分配给进程 2。所以这个进程 2 接下来的这个指令,也就是要往打印机输出数据,这条指令就没办法往下执行。
- 那既然这个进程接下来的这些指令暂时不能往下执行的话,那么显然,我们不应该让这个进程一直占用着 CPU 资源,所以类似于刚才我们所说的这种情况,很多时候,进程在运行的过程当中,有可能会请求等待某个事件的发生,比如说像刚才我们所说的,他会等待系统给他分配某一种系统资源,或者他需要等待其他进程的响应等等。
- 总之,这个进程在运行的过程当中,有可能会主动地请求等待某个事件的发生,而当这个事件发生之前,这个进程是没有办法继续往下执行的,所以在这个时候操作系统就会呃,剥夺这个进程对 CPU 的使用权,让这个进程下 CPU,并且让它进入到一个新的状态,叫做阻塞态。
- 也就是说,这个进程因为等待某个事件而被阻塞了。那当这个 CPU 再次空闲之后,操作系统又会选择呃一道处于就绪态的进程,让他上 CPU 运行。
4、终止态
- 下来的故事是这样的,这个打印机设备之前不是正在为别的进程服务吗?那如果说这个打印机的服务已经结束,那这个打印机就会空闲下来。所以当打印机空闲下来的时候,它就可以分配给刚才请求打印机的那个进程,也就是进程 2。所以当操作系统把这个打印机资源分配给进程 2 的时候,这个进程 2 等待的事件其实就已经发生了。此时操作系统会让这个进程 2 从阻塞态再次回到就绪态。
- 也就是说,当他等待的这个事件发生了之后,这个进程就再次啊拥有了上处理机运行的条件。那我们让这个故事继续下去,假设此时正在 CPU 上运行的这个进程,进程 1,他已经运行结束了。
- 那在他运行结束的时候,他会发出一个叫做 exit 的系统调用。那这个系统调用,其实就是要请求操作系统终止这个进程。那此时,这个进程的状态就会变成终止态。然后操作系统会让这个进程下 CPU,并且做一系列的善后的工作,他会回收这个进程所占有的呃各种资源,包括什么内存空间啊,什么打印机设备啊等等。总之所有的资源都需要回收,并且最后他还会呃回收这个进程的 PCB。而当终止进程的这些工作完成了之后,这个进程就从这个系统当中彻底消失了。
二、进程的状态间的转换
- 那么我们再用一个图,把刚才所提到的这些进程的状态和他们的转换再给穿一下。一个进程在运行之前需要被创建。在创建的过程当中啊,系统会完成一系列相应的工作,包括啊新建 PCB,还有给这个进程分配一系列的资源等等。那如果一个进程正在被创建的话,那这个进程此时就是处于创建态的。
- 当一个进程被创建完毕之后,他就拥有了可以上处理机上 CPU 运行的这种条件。那这个时候进程就进入了就绪态,也就是说处于就绪态的进程,他其实只差处理机这种资源,其他他所需要的资源,他已经呃都具备了。那如果处于就绪态的一个进程被操作系统调度,那这个进程就可以上处理机运行。
- 当他在处理机上运行的时候,他就处于运行态,也就是说,正在处理机上运行的进程其实是啊既拥有了他所需要的其他所有的那些条件和资源,同时他也拥有了处理机这种资源。而有的时候,正在运行的进程可能会请求呃等待某些事件的发生。
- 在这个事件发生之前,这个进程是没有办法继续往下执行的,所以在这种情况下,进程不应该一直占用着处理机资源。所以,此时这个进程应该被剥夺处理机资源,同时除了处理机资源之外,他还在等待其他的某一种资源,或者说等待其他的某一种事件的发生。那如果说处于阻塞态的进程,他等待的事件发生了,那这个进程就可以从阻塞态又回到就绪态。
- 也就是说,当他回到就绪态,就说明这个进程他已经拥有了除了处理机之外的所有的他想要的那些资源。所以从刚才的这个讲解过程当中,我们会发现,运行态到阻塞态的这个转换,其实是进程自身主动的一种选择,主动的行为。
- 一般来说,都是进程通过系统调用的方式来申请某一种系统资源,或者请求等待某个事件的发生。所以这个转换的过程,是进程主动选择的。而阻塞态到就绪态的转变,它并不是进程自身能够控制的。比如说一个进程,他正在等待打印机资源,那么这个打印机资源什么时候分配给他,这并不是这个进程能够说了算的。所以阻塞态到就绪态的转换,他是一种被动的行为,他并不是进程自己可以控制的。
- 所以大家需要注意的是,一个进程不可能直接从阻塞态转换为运行态,也不可能直接从就绪态转换为阻塞态,因为进程要变成阻塞态,那肯定是需要进程主动请求,进程要发出这种主动请求,那就意味着这个进程肯定是正在 CPU 上运行才可能发出这种主动请求。所以说只可能从运行态转换成阻塞态,而不可能从就绪态转换成阻塞态。
- 那在之前的讲解中,我们也提到过,处于运行态的进程,他可以主动地请求啊运行结束,或者说如果一个进程,他在运行的过程当中,遇到了一些不可修复的错误,比如说整数除以 0 这样的错误,那在这种情况下,这个进程也应该被终止,那在操作系统啊,对这个进程做相应的终止工作的时候,这个进程就处于终止态。
- 此时操作系统会回收呃进程所拥有的各种资源,并且会撤销它的 PCB。那最后,我们还要强调一个刚才没有提到的状态的转换。有的时候,进程可以直接从运行态转换成就绪态,比如说操作系统给进程分配的时间片用完了的时候,进程就会从运行态转换成就绪态。
- 什么叫时间片用完呢,还记不记得我们在第一章当中啊提到过时钟中断?一个进程本来正在处理机上运行的好好的,但是此时如果 CPU 收到一个时钟中断的信号,然后发现这个进程已经运行了很长时间了,呃不应该让他继续往下执行了,那这种情况就是所谓的时间片用完的一个状态。
- 这个进程就应该被剥夺 CPU 的使用权,让它从运行态回到就绪态。因为在这种情况下,实际上进程仍然具备继续执行的条件,只是被剥夺了处理器的执行权,无需等待除了处理器之外的其他事件发生。因此,进程从运行态回到了就绪态。
- 这涉及到了经典的进程五状态模型,也被称为“丁字模型”。其中,运行态、就绪态和阻塞态是基本状态,因为进程的大部分生命周期都处于这三种状态中。值得强调的是,在单核 CPU 环境下,同一时刻最多只能有一个进程处于运行态,而在多核 CPU 环境下,多个进程可以并行运行。
- 另外,需要注意的是,阻塞态有时也被称为等待态,创建态也叫新建态,终止态也叫结束态。这些都是相同概念的不同称呼,大家要留意这些术语。
- 为了记录进程的状态,操作系统会在进程的 PCB 中有一个名为 “state” 的变量,用来表示当前状态。例如,可以使用数字 1 表示创建态,数字 2 表示就绪态,以此类推。
三、进程的组织方式
1、链式方式
- 操作系统会以有组织的方式管理各个状态的进程 PCB。在链式方式中,操作系统管理一系列队列,每个队列代表一种状态,其中存放相应状态的进程 PCB。例如,有一个执行队列指针,它指向正在运行的进程的 PCB;就绪队列指针指向处于就绪态的进程的 PCB 队列。操作系统可能将优先级较高的进程的 PCB 放在队列的前端。同样,阻塞队列也是类似的,可以根据不同的阻塞原因划分多个子队列,例如等待打印机资源的队列和等待磁盘资源的队列。
2、索引方式
- 此外,操作系统也可以使用索引方式来组织进程的 PCB。对于每种状态,操作系统会建立相应的索引表,每个索引项指向相应状态的一个进程的 PCB。
四、总结
- 总而言之,进程状态之间的转换和进程的组织方式是操作系统中的重要概念。进程的状态转换是由进程主动请求以及操作系统调度等因素共同影响的结果。而进程的组织方式通过链式或索引方式,将不同状态的进程 PCB 有组织地管理起来,以便操作系统进行调度和管理。希望通过这些内容的解释,能够让大家对进程状态和状态转换有更加清晰的理解。
No.3 进程控制
一、什么是进程控制
-
在这个小节中,我们会学习进程控制相关的知识点。那么,什么是进程控制呢?我们的王道书上给出了这样的一些描述:进程控制的主要功能是对系统当中的所有进程实施有效的管理。它具有创建新进程、撤销有进程实现状态、进程状态转换等功能。那其实说白了,进程控制它就是比如说创建一个新进程,那不就是让一个进程从无到有啊,从创建态再到就绪态,这是创建新进程所需要干的事情。那撤销一个已有进程,不就是让进程进入终止态,然后最终把这个进程干掉的一个过程吗?所以其实所谓的进程控制,就是要实现这些进程的状态转换。
-
那么在进程的状态转换的时候,操作系统需要做一些什么事情呢?这就是这个小节当中,我们要讨论的内容。
- 刚才我们简要的了解了什么是进程控制。接下来我们会介绍怎么实现进程控制。啊,需要用原子性来实现。那这个一会会展开之后,我们会介绍几个进程控制相关的原语。他们分别需要实现哪些功能。
二、如何实现进程控制
- 刚才提到进程控制,也就是进程的状态转换,相应的处理是需要,用原语来实现的,而原语这个概念我们在第一章当中,提到过,操作系统的内核中有一些特殊的程序,它叫原语,原语这种程序,它的执行是具有原则性的,也就是说这个程序,运行是必须一气呵成的,中间不可以被中断,也就是说,实现进程的状态转换这个事情,中间的一系列操作必须一气呵成,
- 那么接下来,我们来考虑一下这样的问题,为什么进程控制,或者说进程的状态转换,这个过程需要一气呵成呢,我们结合上一小节
- 在学习中,我们知道在进程控制块(PCB)中有一个变量,用于表示进程当前的状态。例如,当这个状态变量等于1时,我们认为进程处于就绪状态,而当它等于2时,进程处于阻塞状态。如果一个进程处于就绪状态(state等于1),那么它的PCB应该被挂在就绪队列中。而如果state等于2,那么该进程的PCB应该被挂在阻塞队列中。
- 接下来,让我们考虑一个情景:我们知道处于阻塞队列中的进程正在等待某个事件的发生。假设现在,与进程2(对应PCB 2)相关的事件已经发生。在这种情况下,操作系统的内核程序需要将该进程的状态从阻塞状态转换为就绪状态。在执行状态转换的过程中,至少需要执行两个步骤。首先,需要将PCB中的state变量从2更改为1。其次,需要将PCB 2从阻塞队列中移除,并挂载到就绪队列中。因此,在实现状态转换的过程中,操作系统至少需要完成这两个步骤。
- 接下来,让我们考虑另一种情况:假设在state被设置为1之后,突然检测到一个中断信号。这种情况下,系统需要处理中断。此时,PCB 2的状态为1(就绪状态),但从所处的队列来看,PCB 2仍然在阻塞队列中。这导致了PCB 2中所表示的状态和其所在的队列信息不一致。因此,如果操作系统在进程状态转换或进程控制的过程中不能一气呵成,可能会出现关键数据结构的不一致,这些数据结构对系统非常重要,因为这可能会影响操作系统后续的工作,甚至导致系统错误。
- 综上所述,进程的状态转换或进程控制过程需要一气呵成,正好原语具有这种特殊性质,它是不可中断的内核程序。因此,我们可以使用原语这种特殊的程序来实现一气呵成的操作。
三、如何实现原语的原子性
我们要探讨的问题是,为什么源于这种特殊的程序,它可以一气呵成不被中断呢,其实它的这种原子性,是用两个特权指令,关中断和开中断这两个指令来实现的,我们来看一下这两个指令的作用,那假设这是一个正在运行的内核程序,那CPU会依次执行这些指令,并且根据第一章的讲解我们知道,CPU每执行完一条指令之后,他都会例行的检查,是否有中断信号需要处理,那如果说他在执行了指令2之后,CPU发现此时有一个中断信号,那在这种情况下,CPU就会暂停执行当前的这个程序,转而执行一个处理中断的程序,那等这个中断处理完成之后,他才会再回到原来这个程序,继续往下执行,那这是我们之前认识到的情况,就是呃CPU每执行完一条指令,他都会检查,是否有外部中断信号需要处理,那接下来我们再来看一下,如果执行了关中断指令,会发生什么情况,假设此时CPU,正在依次的执行这些指令,然后当他执行了关中断,这条特权指令之后,CPU就不再例行检查中断信号了,所以接下来CPU会继续往下执行,那如果说此时,他执行了指令a的这个过程当中,呃有一个外部中断信号到来了,但是此时他并不会像之前一样,例行的检查是否有中断信号,而是会继续往下处理,一直到CPU执行了开中断指令之后,他才会恢复以前的那种习惯,也就是每执行完一条指令,那就会检查一下此时是否有嗯,外部中断信号需要处理,所以当他执行了开中断指令之后,他会发现哎,之前有一个中断信号我还没有处理,所以在这个时候,CPU才会转转向执行中断处理程序,所以从刚才这个例子当中,我们就可以看到,在关中段和开中段这两条指令中间的,这一系列的指令序列,他们的执行肯定是不可被中断的,这样的话,就实现了我们,开篇提到的所谓的原子性,这段指令序列的,执行肯定是一气呵成的,他中间不可能再被中断,所以,这是关中段指令和开中段指令的一个,特殊的作用,那显然,这两个指令他们肯定是特权指令,那接下来我们来思考一个问题,如果这两个特权指令,允许普通的用户程序使用的话,会发生什么情况呢,那是不是就意味着,我可以在我的程序开头,就植入一个关中断指令,然后一直到我的程序末尾才,再执行开中断指令,这样的话,只要我的程序上CPU运行了,那我的程序肯定会一直霸占着CPU,而不会被中断,那显然这种情况是不应该让它发生的,所以关中段指令和开中段指令,他们是特权指令,只能让内核程序使用,而不能让普通的用户程序使用,好的,那么到目前为止我们知道了两个事情,第一,进程控制或者说进程的状态转换,这个事情必须一气呵成,而想要做到一气呵成,我们可以用原语这种特殊的,程序来实现,而原语的实现,需要由开中段指令和关中段指令来,配合着完成,那接下来我们要讨论的问题是
四、进程相关的原语
1、实现进程的创建的原语
进程控制相关的这些原语,或者说相关的这些特殊的程序,他们在背后需要完成一些什么事情呢,首先来看第一个原语啊,这个原语是用于实现进程的创建的,如果操作系统要创建一个进程,那么他就必须使用创建原语,那么这个创建原语,他会干这样的几件事情,首先是要申请一个空白的PCB,因为PCB是进程存在的唯一标志嘛,所以要创建一个进程,当然是需要创建一个和他相对应的PCB,另外呢,还会给这个进程分配他所需要的资源,比如说像内存空间等等,然后还会,把这个PCB的内容,进行一些初始化的工作,比如说分配p i d,设置u i d等等,最后他还会把这个PCB,啊插入到就绪队列,所以说创建原语,让一个进程从创建态进入到了就绪态,把它放到了就绪队列里,那有些比较典型的事件会引起啊,操作系统使用创建源语创建一个进程,比如说当一个用户登录的时候,操作系统会给这个用户,建立一个啊与他对应的用户管理进程,或者用户通信进程等等,或者发生作业调度的,时候也会创建一个进程,根据去年的反馈,很多同学不知道作业到底是什么,其实作业就是,此时还放在呃外存里的那些,还没有投入运行的程序,所以所谓的作业调度就是指,从外存当中挑选一个程序,让他把它放入内存,让他开始运行,我们知道,当一个程序要开始运行的时候,肯定是需要创建和他相对应的进程的,所以当发生作业调度的时候,就需要使用到这个创建原语,另外呢有的时候,一个进程可能向,操作系统提出某些请求,然后操作系统会,专门建立一个进程来处理这个请求,还有的时候,一个进程也可以主动的请求,创建一个紫进程,总之发生这些事件的时候,都会引起啊系统创建一个新的进程,也就是说他会使用到这个创建原语,那接下来要看的,
2、撤销原语
那是撤销原语,撤销原语是呃,要终止一个进程的时候使用的,使用了撤销原语之后,就可以让一个进程从某一种状态,转向终止态,最终这个进程从系统中彻底消失,那撤销原语需要做这样的一些事情,首先既然要撤销一个进程,那肯定需要找到这个进程相应的PCB,那如果说这个进程此时正在运行的话,那就需要立即剥夺他的CPU使用权,然后把CPU分配给其他进程,同时操作系统在杀死一个进城的时候,还会杀死所有他的子进城,并且这个进城被撤销之后,他之前所占有的那些资源应该,归还给他的父进城,最后的最后,还需要把这个进城的PCB,从系统中删除,那这样的话这个进城就彻底的完蛋了,有的同学可能不理解子进程,复进程这样的概念,我们来看一个很实际的例子,这是我现在正在录视频的时候,使用的电脑,这个界面显示的是此时我的电脑当中,正在运行的各种进程,这个界面中的这种显示方式,其实就反映了这些,呃进程之间的父子关系,可以看到这儿有一个呃PID为0的进程,它是最祖先的一个祖先进程,然后这个祖先进程,它创建了一个PID为一的进程,这个进程叫做launch,其实它就是我们在第三章会学习到的,所谓的装入程序,或者叫装入进程,在开机了之后,我们启动的所有的别的那些进程,其实都是这个装入进程来启动的,所以别的那些进程,都是这个装入进程的紫进程,那除了他之外,其他的那些进程,也可以创建自己的紫禁程,来完成相应的一系列工作,比如说访达这个呃进程,他就创建了这几个他自己的紫禁程,那这样的设计方法有什么优点呢,我们可以想一下,刚开始系统中几乎所有的资源,都是这个进程所拥有的,比如说我的电脑里8 g b的内存,全部是他所拥有的,那之后,当他在建立自己的这些紫禁城的时候,他可以按照这些紫禁城的需要,把自己手里的那些资源,再分配给他手底下的这些进程,比如说他本来手里有8GB的内存,那他把其中的50,兆字节分配给了这个进程,把54.5兆字节分配给了这个进程,而当他的这些子进程终止了之后,那当然是需要把自己手里的这些资源,还给他们的,附进程也就是上面的这个进程,所以回到刚才我们提到的这个地方,相信大家就更容易理解了,其实我们的操作系统当中的各个进程,他们之间的关系是一种竖形的结构,系统中的0号进程和1号进程,是最祖先的两个进程,然后这两个进程,又依次创建了他们的紫进程,各个进程又可以再创建各自的紫进程,所以这些进程之间的关系,其实是一种竖形的结构,不过这个并不是我们操作系统,这一门课要考察的重点,只是为了让大家能够更深入的理解,而这提到的这两个特性,希望没把大家绕晕,那我们回到正题继续往下,很多事件,有可能会引起一个进程的终止,比如说一个进程自己请求终止,也就是他呃使用了exit这个系统调用,那这种情况下,操作系统在背后,就会需要使用到这个撤销原语,来把这个进程撤销掉,另外呢,如果一个进程做了一些非法的事件,比如说整数除以0,或者非法使用特权指令,这种情况也会被操作系统强行的撤销,强行把它干掉,还有一种情况,有时候是用户会选择杀掉一个进程,比如说我们在使用Windows电脑的时候,经常出现卡死的情况,那这种情况下,很多同学喜欢用这样的方式,打开任务管理器,然后结束,掉呃某一个卡死的进程,那这种就是外界干预的情况,那接下来我们要看的,
3、阻塞原语和唤醒原语
那是阻塞元语和唤醒元语啊,这个小节的内容确实比较多,可能很多同学听到这就已经,比较疲惫了,但是其实我们这些元语,他到底要干什么,这些事情我们并不需要死记硬背,我们只需要理解他背后的过程就可以,嗯考试的时候,不可能让你默写这些东西的,所以我们着重以理解为主,大家不需要刻意的记忆,那回到这,有的时候,一个进程可能会从运行态,进入到阻塞态,那在这种情况下,操作系统就会,在背后执行一个阻塞言语,来实现这个状态的转换,阻塞一个进程需要做的事情啊,比较简单,首先是找到这个进程对应的PCB,然后需要保护啊进程运行的现场,什么叫保护进程运行的现场,这个我们一会再解释,这又是一个比较庞大的话题,另外呢系统还需要把,PCB当中的状态信息设置为阻色态,然后然后让这个进程下处里积,并且把它插入到相应的等待对列当中,那经过上个小节的学习我们知道,一个进程需要被阻塞,那肯定是因为他主动请求,要等待某个事件的发生,而如果这个进程,他所等待的事件发生了之后,这个进程就会被唤醒,也就是说,操作系统会让这个进程的状态,从阻塞他,又回到就绪他,那在这个时候就会需要使用到,唤醒原语,唤醒原语需要做这样几个事情,首先要找到他的PCB,然后把PCB从等待对列当中移除,然后把它设置为就绪态,并且把PCB插入到就绪对列当中,等待被调度,这两个原语做的这些事情啊,相信都不难理解,那需要注意的是啊,一个进程因为什么事情被阻塞,就应该被什么事情给唤醒,所以啊唤醒原语和阻塞原语,他们必须是成对使用的,那接下来我们再来认识最后,
4、切换原语
后一个原语叫做切换原语,切换原语会让此时,正在处于运行态的进程,下处理机,让他回到就绪队列,并且从就绪队列当中,选择一个处于就绪态的进程,让他上处理机运行,所以切换原语会让两个进程的,状态发生改变,那切换原语需要做这样的一些事情,首先是需要把进程的运行环境信息,存到PCB当中,什么叫进程的运行环境信息呢,这点涉及到一些硬件的知识,我们一会再展开细聊,另外呢他还会把,呃进程的PCB移到相应的对列,比如说让这个下处理机的进程的PCB,回到继续对列当中,另外呢他还会挑选了一个进程,让他上处立即运行,并且更新他的PCB的内容,同时他还会从这个进程的PCB当中,恢复这个进程所需要的运行环境,那什么叫保存运行环境,什么叫恢复运行环境,这是啊比较难理解的地方,接下来我们得深入探讨一下这个问题,那在之前的学习中我们认识到了一个
五、程序是如何运行的?
程序的运行,需要经历这样一系列的流程,程序运行之前,需要把它相应的这些呃指令,把它放入到内存当中,然后CPU,从内存中读取这些一条一条的指令,并且执行,但是接下来我们要拓展一个,呃更深层的细节,CPU在执行这些指令的过程中啊,需要进行一系列的运算,那么CPU当中会设置很多的寄存器,来存放这些指令,这些程序在运行过程当中,啊所需要的某些数据,总之寄存器,就是CPU里边用于存放数据的一些,呃地方,那CPU当中会有各种各样的寄存器,比如说我们之前提到过PSW,就是程序状态字寄存器,CPU的状态内核态还是用户态啊,这个状态信息就是保存在PSW,啊这个寄存器当中的,当然除了CPU状态信息之外,PSW中还会保存别的一些信息,那这我们就不展开,这是计算机组成原理里边,需要学习的地方,另外CPU中还会有一个比较关键的啊,寄存器叫做PC,也就是程序计数器寄存器,这个寄存器里边存放的是,接下来需要执行的指令,它的地址是多少,那这点我们一会结合实力就,很好理解了,另外CPU当中还会有一个指令寄存器,这个寄存器当中存放的是当前CPU,正在执行的那条指令,还有呢CPU中还会有一些其他的,呃通用的寄存器,可以用来存放一些别的必要的信息,等等等等,总之CPU当中会有一系列的寄存器,我们这只列举了几个,操作系统这门课当中啊,大家需要稍微的了解一下的啊计算器,那接下来我们来分析一下,这样的一些指令序列的执行,在背后发生了什么样的事情,我这自己胡乱写了4条指令,这4条指令所,完成的事情就是定义了一个叫做,x的变量,并且实现了x加加的操作,那假设此时CPU正在执行的是指令一,那么他会把指令一的内容读到IR,也就是指令寄存器当中,并且程序计数器这个寄存器当中,会存放,接下来他应该执行的那一条指令,也就是指令2的地址,那此时CPU执行指令一,他发现,指令一是让他往内存的某一个地方,写入一个呃,变量x的值,那CPU执行指令一的时候,就会往内存的某一个地方,写入变量x的这个值,也就是一,那执行完指令一之后,CPU就会开始执行下一条指令,而从PC这个计算器当中,他就知道,下一条要执行的指令应该是指令2,所以接下来他会取出指令2,把指令2的内容放在呃IR,也就是指令计存器当中,同时PC的内容也更新为再下一条指令,那指令二是让CPU把变量x的值,把它放到某一个呃通用寄存器当中,所以CPU会从内存中取出这个,x变量的值,把它放到通用寄存器当中,于是这个计存器的内容就变成了一,那这样的话就执行完了指令2,那再接下来,CPU又要执行再下一条指令,所以他会取出指令3,然后PC的内容同样的也会更新,那指令3,是让他把计存器当中的这个数据,进行加一的操作,所以这个通用计存器中的值,就会从一变成2,再接下来CPU又会执行再加一条指令,那么此时执行的这条指令,指令4,是让他把这个通用计存器当中的内容,把它写回到变量x,所存放的这个位置当中,所以执行了指令4之后,就会把内存当中x的值从一变成了2,所以可以看到,我们执行x加加这样的操作,其实在背后,CPU是执行了一系列的,更基本的那些指令,才完成了这个事情,并且从刚才讲的这个过程当中,我们会发现,这些指令顺序执行的过程当中,有很多中间结果,是放在这些寄存器当中的,比如说x加加这个操作,刚开始,其实只是把它放在了通用寄存器里,而并没有写回内存,但是需要注意的是,这些寄存器并不是这个进程所独属的,如果其他进程上CPU运行的话,那么这些寄存器也会被,其他进程所使用,那这会发生什么情况呢,我们再把这个故事从头捋一遍,现在这个CPU,它要依次的执行这些指令,那刚开始执行指令一指令2 指令3,当他执行了指令3之后,呃寄存器里的这个值变成了2,而此时,如果说他要转向执行另一个进程,会发生什么情况呢,刚才我们说到如果另一个进程上CPU,运行的话,那么,另一个进程也会使用到这些寄存器,所以另一个进程在上CPU运行的时候,有可能会把,前一个进程在寄存器当中,保留的这些中间结果给覆盖掉,比如说它覆盖成了这个鬼样子,那我们之前的这个进程不是呃,执行到了指令3吗,因为他的前三条指令,执行的那些中间结果都已经被覆盖了,所以,这个进程已经没有办法再往下执行了,所以为了解决这个问题,可以采取这样的策略,当一个进程他要下处理机的时候,可以把他之前,呃运行的这个运行环境的信息,把它保存在自己的PCB当中,当然这个PCB当中,并不需要把所有的寄存器信息都保存,下来,只需要保存一些必要的信息就可以了,比如说PSW PC还有这个通用寄存器,那这个进程执行了前三条指令之后,它的运行环境是这个样子的,我们把它放到了PCB当中,接下来才可以切换成别的进程,那接下来别的进程在使用CPU的时候,可能会往这些计存器当中,写各种各样的数据,总之之前那个进程的数据,有可能会被覆盖,但是当之前的这个进程需要重新回到,呃CPU运行的时候,操作系统就可以根据之前,保存下来的这些信息,来恢复它的运行环境了,那把它的运行环境恢复之后,CPU就知道接下来他要执行的是指令4,并且此时通用计存器,当中存放的数值是2,所以既然接下来要执行的是指令4,那CPU就会根据PC的这个指向,把指令4的内容,取到呃IR这个计算器当中,然后让PC指向下调指令,同时CPU开始解析这条指令时,到底是要干什么,他发现指令4是让他,把寄存器当中的内容,写回到x存放的位置,所以接下来,他就可以把2这个内容写回到,x的这个位置,于是,x加加这个操作就真正的被完成了,总之呢这个地方讲了这么多的内容,想让大家知道的就是,什么叫做进程的运行环境,其实所谓的运行环境,或者说进程上下文,它就是进程在运行过程当中,寄存器里存储的那些中间结果,当一个进程需要下处理机的时候,需要把它的这个运行环境,把它存到自己的PCB当中,而当一个进程,需要重新回到CPU运行的时候,就可以从PCB当中,恢复他之前的这个运行环境,让他继续往下执行了,所以保存进程的运行环境,和恢复进程的运行环境,这是实现进程,并发执行的一个很关键的一个技术,那这个小节的干货
六、总结
比较多我们讲了一些,涉及底层硬件的一些知识,可能学过祭祖的同学觉得这些,其实都很好理解,但是我们又不得不照顾到一些,跨考的同学,和一些不考祭祖的同学,那在操作系统这门课当中,硬件相关的知识我们不需要去深究,只是为了让大家理解其中的某一些,很关键的操作系统概念而,又不得不提一些硬件的知识,毕竟,操作系统是最接近硬件的一层软件嘛,所以中间扯这么多,也是为了让大家能够更好,更深入的理解,那大家需要注意一下原语这个概念,它使用关中段和开中段来实现,它的执行必须一气呵成不可中断,之后我们介绍了一些,进程控制相关的原语,各个原语它中间做了各自的事情,但是这些都不需要死记硬背,其实无论是哪一个控制言语,他所要做的无非就是这么三件事,第一就是更新PCB当中的一些信息,第二是把PCB插入到合适的对列,第三向进程创建和进程终止的时候,有可能还需要分配和回收,这个进程的资源,那更新PCB的信息,主要是修改这个进程的状态,也就是state那个变量,或者就是呃,往PCB当中保存进程的运行环境,或者从PCB恢复进程的运行环境,总之我们了解了这些控制言语,背后做的事情,能够帮助我们更好的理解进程管理啊,处理及管理这一系列的知识,但是这些内容确实不需要死记硬背,所以虽然这个小节看起来内容很多,但是更多的都是理解性的东西,大家不需要花时间去记忆,希望大家不要惊慌,好的那么这就是这个小节的全部内容,同学们辛苦了
No.4 进程通信
各位同学大家好,在这个小节中,我们会学习进程间通信的几种方式,分别,是共享存储消息传递还有管道通信
一、什么是进程通信
进程之间的通信,指的就是两个或者多个进程之间,产生了数据交互,在一个系统当中,可能会同时存在多个呃进程,多个进程都在运行,那这些进程之间难免需要,相互配合着工作,在这种情况下,进程和进程之间的这种通信,数据通信就显得很有必要了,比如说当我们在玩微博的时候,可能你会看到一篇嗯,某明星的八卦对吧,一个吃瓜的文章,你想把这个文章分享给你的微信好友,那你是不是可以直接使用微博这个呃,呃这个应用里面内置的一个分享功能,然后把这个吃瓜文,分享给你的微信好友,那可以看到,在这个过程当中,其实就已经发生了,进程之间的通信了,本来这个吃瓜文的链接是在微博这的,然后你直接用微博的这个分享功能,直接就把这个呃吃瓜文的链接,分享到了微信这,所以这个过程显然是进程和进程之间,呃发生了这种数据交互,也就发生了通信,好说到这,大家也可以关注一下我的微博,王道咸鱼老师啊,当然也可以,关注一下我的这个微博小号,就是平时我我,我开那个变声器给大家讲啊,计算机网络的时候啊,我是以楼楼老师的这个,这个身份来示人的,好所以上面这个是我的大号,下面这个是我的小号啊,大家可以关注一下,好那既然我们进程之间的通信,是很有必要的,那么进程之间的通信应该如何实现呢,这个需要操作系统的支持,需要操作系统的支持,好那为了,
二、为什么进程通信需要OS支持
呃探究这个问题,我们首先要聊清楚,为什么这个进程之间的通信,一定要有操作系统内核的支持,原因是这样的,我们系统当中,给各个进程分配啊,内存地址空间的时候啊,各个进程的这个内,内存地址空间是相互独立的,比如进程p它可以访问自己的空间,进程q可以访问自己的空间,但是进程p,它不可以访问进程q的地址空间,啊这么规定是出于安全的考虑,因为你想一下,如果一个进程,可以随意的访问其他,进程的这个内存地址空间,那么呃,一个进程是不是就可以随意修改,其他进程的数据了,或者随意的读取其他进程的数据,那这样的话你想想你的手机里边呃,不知道什么时候,安装了一个一个什么垃圾,软件对吧,然后这个垃圾软件这个垃圾软件,如果他可以随意的访问你的呃,其他进程的地址空间,那有可能他直接,把你微信里的一些私密的聊天数据,或者一些私密的呃,什么照片之类的直接就给你读走了,对吧,那这显然是很很危险的不安全的,因此呃出于这种安全的考虑,各个进程,只能访问自己的这一篇内存地址空间,而不能访问其他进程的内存地址空间,无论是读或者写数据都不行,好因此如果两个进程p和q,他们之间需要进行数据交互,需要进行进程之间的通信,那么显然,进程p是不可能,直接把这个数据写到,q的这片空间里边的,好所以,由于进程,不可以直接访问其他进程的这个空间,因此就必须要有操作系统的支持,才可以完成进程之间的通信,才可以完成进程之间的通信,好那嗯,我们接下来会介绍,三种进程之间的通信方式,分别是共享存储,消息传递还有管道通信,首先我们来看共享存储这种啊进,
三、共享存储
进程间通信的机制,共享存储的原理是这样的,各个进程只能访问自己的这片空间,但是如果,操作系统支持共享存储的这种,啊这种功能的话,那么一个进程,它可以申请一片共享存储区,而这片共享存储区,也可以被其他进程所共享,这样的话,一个进程p,如果他要给q传送数据的话,那么p是不是就可以先把数据写到,这一篇共享存储区里面,因为他对这片区域是有访问权限的,然后接下来,进程q再从这片区域里边读出数据,读数据是不是就OK了,好所以,由于这个共享存储区可以被,多个进程所共享,因此这些进程之间的数据交换,就可以通过这一片,被共享的区域来进行,那这就是共享存储的,呃进程间通信方式,比如像Linux操作系统当中,大家可以去呃上网搜一下,呃如何实现共享存储呢,首先呃一个进程比如说进程p,和q他们想要通信,那么发起通信的这个进程p,他可以使用Sham memory open,使用这个系统调用,来申请一片共享内存区,也就黄色的这片区,好接下来,进程p和进程q都需要使用m map,使用这个系统调用,把这一片存储区域,映射到自己的虚拟地址空间当中,现在,大家还不知道什么叫虚拟地址空间,等大家学到第3章的时候,再回头看这个部分,可能会更清晰一些,如果用第三张,我们要学习的内容来解释的话,相当于调用了m map这个函数之后,进程的页表项或者段表项,就会增加一项,然后这个页面或者这个段,就可以映射到,就可以映射到,刚才申请的这一篇共享存储区,那这样的话,各个进程的虚拟地址空间当中,都包含了这个共享存储区,他们都可以访问这一篇共享存储区,那这个部分大家学到第三章之后,再回来看,可能会更清晰一些,总之只需要简单的加一个页表像,或者段表像,就可以完成这种共享内存区的映射,这个事情了,另外还需要注意一个问题,如果多个进程都往这片区域,写数据的话,是不是有可能会导致写冲突啊,比如进程p他正在往这片区域写,那进程q也往这一片写,那是不是就有可能导致,数据覆盖的问题啊,好所以,各个进程,他们之间,如果使用共享存储的方式,来进行通信的话,那么需要保证,各个进程对这个共享存储区,的访问是互斥的,应该保证他们对这片区域的访问,是互制的,也就是,当进城p正在访问这片区域的时候,那其他进城就不能访问这片区域,那怎么实现这个互斥的功能呢,操作系统内核会提供一些同步互斥,工具比如说我们在2.3那个部分,2.3那个部分会学习PV操作,PV操作就是操作系统,会提供的这种同步互斥的工具,所以呃各个进程,各个进程,他们可以用类似于PV操作这种机制,来实现,对呃共享存储区的一个互斥的访问,那具体的,大家可以学了2.3那个小节之后,再回头来理解这句话,好总之,各个进程对共享存储区的访问,应该是互斥的,进行的好,这是共享存储,刚才我们说的这种共享存储的方案,是基于存储区的共享,就是操作系统给你划定,
了这么大的一片区域,比如说这一这整片是4KB,4KB这么大,好那这么大的区域当中,几个进程,你到底要往这个地方写,还是要往这个地方写,或者读的进程你要从这个地方读,还是从另一个地方读,这些都是很很自由的很自由的,操作系统只负责把这片区域划给你,但是并不管你怎么使用这片区域,这是基于存储区的功效,操作系统划分出这一片共享区域之后,数据你到底要怎么存,到底要存在什么位置,都是由呃要通信的这些进程,他们之间来来决定的,而不是由操作系统来决定,所以这种共享方式他灵活性非常高,是一种高级通信方式,就是传送数据的速度会很快,好相比之下,还有一种方式,叫做基于数据结构的共享,比如说操作系统给你们两个进程,划定的这篇共享区域,它就规定,只能存放一个长度为10的数组啊,大家可以理解为是一个特,殊的全局变量,就是这个特殊的全局变量,可以被各个进程所共享,为什么叫特殊的呢,因为如果我们写代码的时候,定义的全局变量,其实是局部于一个进程的啊,而这个地方我们所谓特殊的全局变量,是可以被各个进程所共享的,但是当我们定义这个全局变量的时候,比如说,我们是定义了一个int型的数组,int a长度为3,好那各个进程来共享这个嗯,这个这个数据结构,这个长度为3的呃数组的时候,各个进程对这一片共享区域的访问,读和写是不是就只能按照,这个数据结构所规定的,这种格式来进行了,只有三个int型的变量,每一次要么读,要么写一个int型的变量,好所以可以看到,如果是基于数据结构的这种共享的话,那这个进程之间,他们之间通信是不是自由度就是呃,没有那么高,并且这个传送数据的数,呃速度也会比较慢,所以基于数据结构的这种共享方式,是一种低级的通行方式,低级的通行方式好,那这就是进程之间通信的第一种方法,叫做共享存储,共享存储又可以进一步划分为,基于数据结构,的共享和基于存储区的共享,上面这种共享方式灵活性差速度慢,下面这种共享方式灵活性高速度快,好接下来看第二,
四、消息传递
第二种进程之间的通信方式,叫做消息传递,如果采用这种方式的话,那么进程之间的数据交换,会以格式化的消息为单位,通过操作系统提供的发送和接收,这样的两个原语来进行数据交换,一会我们会用,动画的方式带大家来理解这个过程,而什么叫格式化的消息呢,先来看这个,所以格式化的消息由两个部分组成,一个是消息头,一个是消息体,那消息头要写明要注明,这个消息这一枕头消息,到底是由谁发送的,到底要发送给谁,然后整个消息的长度是多少等等,就是一些概要的信息,这是消息头,然后消息体里边呢就是啊具体的,一个进程,要传送给另一个进程的这个数据,好那么呃,这种消息传递的进程之间通信方式,又可以进一步的划分为,直接通信方式和,间接通信方式,其中直接通信方式就是呃,发送进程要指明接收进程的ID,我们系统里的每一个进程都会有一个,进程的id,p i d嘛那发送的进程需要指明,到底是谁来接受,所以直接通信方式相当于,我发送进程直接点名,我就是要他接受,就这个意思,而间接通信方式呢,会通过一个叫做信箱的啊,一个中间实体,来进行通信,所以间接通信方式又称为信箱通信,方式好,我们来看一下这两种啊,消息传递的这种通信方式的区别,首先来看什么是直接通信方式
1、直接通信方式
进程p现在要给进程q发送一个消息,那么在操作系统的内核区域,操作系统的内核区域,是不是管理着各个进程的PCB啊,进程控制快,这是由操作系统来管理的呃数据结构,好那在各个进程的这个PCB当中,有一个呃包含了一个队列,叫做消息队列,消息队列,比如说进程q的PCB当中,其中就包含了一个进程q的消息队列,也就是呃其他进程,其他进程要发送给进程q,应该被进程q接收的这些消息,这些消息,都挂在啊进城q的这个消息对列里面,好那来看一下现在故事是这样的,进城p要给进城q发一个消息,那首先进城p需要在自己的地盘,自己的这片地址空间这,来啊来完善这个消息的信息,包括消息头消息体,接下来进程p,进程p它会,使用到刚才我们说的这个发送源语,操作系统提供的发送源语啊send,用这个原语来指明我的这个message,这个消息是要发送给q,这个进程指明了这个消息的接收者,指明了消息的接收者,好,那这个发送原语会导致操作系统内核,接收到这个消息,并且会把刚才的这个消息,把它挂到进城q的消息堆列里边,也就是说这个消息,体这个消息体,是从进城p的这个空间进城p的啊,用户空间被复制到了内核空间,被复制到了内核空间,好接下来,接下来如果进程q啊开始运行,那进程q,它可以使用接收源语receive来指明,我现在要接收一个message,接收一个消息,那接收呃谁发来的message呢,是接收p进程发来的message,所以这个接收源语这儿,它需要指明我要接收的这个,呃这个这个消息是来自于哪个进程,好所以进程q执行这个接收源语之后,操作系统内核,会检查进程q的这一系这些消息,对列看一下哎,这几个消息,到底哪个消息是由p发过来的,好现在找到了邮p发过来的消息,那么,操作系统内核会把这个消息体的数据,又从呃操作系统的内核区,给复制到,给复制到这个进程q的这个用户区,这个地址空间这儿,好所以所谓的直接通信方式,直接通信方式就是,要点名道姓的来进行这个消息传递,我一个进程到底是要传给谁,然后接收的这个进程啊,他接收的时候,我要接收的是来自于谁的那个消息,需要点名道姓的进行这个消息传递,好这是直接通信方式,接下来我们再来看什么叫间
2、间接通信方式
接通信方式,刚才我们说间接通信方式呃,他需要通过一个中间实体,所谓的信相来进行消息的传递,所以又称之为呃信相通信方式,好那这种通信方式是这么来实现的,比如进程p和进程q他们之间要通信,那么进程p可以通过系统调用,可以通过系统调用申请一个邮箱,一个所谓的邮箱,比如说,他可以向操作系统申请一个新的邮箱,这个邮箱的名字叫做a,叫做a当然也可以申请多个邮箱啊,比如说另一个邮箱叫做b,好现在这两个进程怎么通信呢,进程p,首先它在自己的这个地址空间这,嗯来来完善这个消息体的内容,在自己的地址空间内来,来填充这个消息的内容,呃这个过程大家理解吗,其实就相当于你写这进程p的代码啊,你定义了一个变量叫message,然后message到底要等于多少,你给它付一个值,这就是所谓的在自己的地址空间内,在自己的地址空间内,完善这个消息题的信息,其实就就是一个复值,就是一个复值这个意思,好现在,进程p,他已经给啊他要发送的这个消息,这个消息体已经付好值了,那么进程p可以用发送元语send,来指明我要发送到哪个信箱,指明我要发送到的是a信箱,然后发送的是message这个消息题,好注意哈,这个间接通信方式,是指明了我要发送到哪个信箱,是要发送到a信箱,并没有指明我要发送给哪个京城,刚才我们说的直接通信方式,直接通信方式是指明了,点名道姓的,指明了我是要发给q这个进程的,这是直接通信方式,但现在我们的这间接通信方式,也就是信箱通信方式,我并没有指名道姓的说我要发给q,我只是,指明了我要发送到a这个邮箱这儿,好那进程q,进程q它使用接收原语的时候,它可以指明我是要从a这个信箱这儿,接收一个message,接收一个消息体,那这样的话,信箱a的这个这个这个消息message,就会被操作系统复制给复制到这个,呃进程q的,自己的这个空间这了,好所以,这就是,使用信箱来完成消息传递的一个过程,那通常来说,操作系统是可以允许,多个进程往同一个信箱里,就是发消息,也允许多个进程从同一个信箱里啊,就receive就是接收消息,好,那这是消息传递里边的间接通信方式,又叫信箱通信方式,注意体会,和之前直接通信方式的一个区别,直接通信方式需要点名道姓的指名,我是谁我要发给谁,好接下来我们再来看最后一种
五、管道通信
进程之间的通信方式叫做管道通信,那管道这个词还是很形象的,它就像我们的一个水管,水管的管道一样,就是呃写进程,写进程可以从水管的一边写入数据,读进程,从水管的另一边就是取走数据,这个数据的流动只能是单向的,就从左到右或者从右到左,只能是单向的,就像水管当中的水流一样,这个水流的方向只可能是单向的对吧,要么从左到右,要么从右,到左大家有没有见过一个水管里边,那个水流既往右同时又往左的水流吧,没有见过这样的水管对吧,好所以管道通信的,这个管道和水管是一样的啊,我们可以从一端写入数据,从另一端读出数据,这个数据的流向指呢,是单向的而不可以是双向同时进行的,好那站在操作系统的层面,这我们提到的管道,它是一种特殊的共享文件,又叫pipe文件,pipe就是英文的水管的意思嘛,管道的意思,也就是说如果两个进程,他们之间,要用管道通信的方式,进行这个进程间通信,那么首先我们需要通过这个呃,系统调用的方式,就某一个进程通过系统调用的方式,来申请一个管道文件,操作系统会新建这个管道文件,这个文件的本质,其实就是在内存当中,开辟了一个大小固定的内存缓冲区,然后两个进程,可以往这个内存缓冲区里边,写数据和读数据,但是这个数据的读写是呃先进先出的,先进先出有这样的特性呃,去年有很多同学,在这个地方是有疑问的哈,就是一个管道,一个管道文件,本质上是一个大小固定的内存,呃内存区域,那这不和我们那个共享内存的方式,就没有什么区别了吗,刚才我们说,就是之前我们说共享内存的那种呃,通信方式,也是操作系统给两个进程,给两个进程p和q,给他们分配了一片可以共享,可以被共享访问的内存区,对吧而现在我们的这个管道,我们这个管道也,是一个大小固定的内存区域,也是可以被两个进程p和q来访问的,那和我们共享内存有什么区别呢,有什么区别呢,好区别在这啊,在共享内存的这种通信方式当中,我们刚才说过,这一片内存区域p和q你想怎么,你想怎么玩就怎么玩,你想把数据写在这写在这写在这,都OK没有问题,然后你想从这读数据,想从这读数据都OK,没问题没有任何的限制,很自由这是共享存储的通信方式啊,没有任何的限制,但是管道通信的这种方式哈,管道通信的这种方式,嗯相当于是这样的,这操作系统,给两个进程分配了一个内存缓冲区,也就是一个管道文件,也就是一个管道文件,好现在p,可以往这个管道文件里边写数据,然后q,是要从这个管道文件里边读数据,那呃,什么叫先进先出的特性呢,可以这么理解,这个区域他大小肯定也是有限的对吧,好现在p往里边写,那么如果他的头部这头部这是空的,那么他先往这写,写写写一个字节,比如说一个字节的数据,然后这写了之后再再往这写,然后再往这写,再往这写,好对于q来说他要取走数据的时候,并不是说他想读哪,他想读哪就读哪,他想取哪就取哪,他只能先把头部,头部的这些数据给取了,直接取了,依次的往后取,明白我意思吧,就是在共享存储的那个通信方式里边,我可以我可以把我的数据写在中间,这直接就写在中间,这虽然前面有空的但我直接写在中间,这或者我直接写在尾部这都是OK的,没有任何限制,然后我这个读进程,读数据的时候也是一样,我可以直接从中间取数据,或者直接从尾部取数据都是OK的,但是在这个管道通信里边,在管道通信里边,它是一个数据流的形式,我这个数据如果前边还有空位的话,这个数据肯定是先放在前面这个位置,放在前面这个位置只有前面填满了,只有前面填满了,我才会往继续往后面写这个数据,而读数据的时候也是一样的,只能先把前边的这些数据读空了,然后才可以读后续的这些数据,才可以读获取的这些数据,好所以管道通信和这个呃,和这个共享内存的通信嗯,区别还是很大的,管道通信就要求这个数据的就是读写,一定是先进先出的,一定是先进先出的,就是一个对列嘛,或者更准确的讲,我们应该把这个管道管道的这个呃,内存缓冲区这一小片内存缓冲区,把它理解为一个循环对列,一个循环对列,现在大家应该都学过呃数据结构了,循环对列的原理我们知道对吧,就循环对列的话呃如果说如果说这个,它嗯整片区域后半部分满了,后半部分就是p,嗯它写入数据,然后后边的这些位置都写满了,但是前边这些位置如果空出来的话,如果空出来的话,那是不是p,就可以往这个,循环对列的另外这个空的这一边,继续写入数,据啊去写数据,好所以,这个固定大小的内存缓冲区,本质上就是一个循环对列,而写进程往里边写数据,或者读进程从里面读数据,都需要,遵循这个对列的先进先出的一个规则,这就是管道的这个内存缓冲区,和我们刚才提到的,共享存储的这个共享内存区,的一个区别,一个是没有限制的,一个是先进先出这种限制的,好那刚才我们还提过一个点呃,用管道通信的这种方式,一个管道的数据流向一定是单向的,而不可以是双向的,所以一个管道它只能支持半双公通信,也就是在某一段时间内,只能实现这种单向的传输,那如果说两个进程我需要给你传,同时你也需要给我传,就是两个方向的数据传输,都需要同时进行的话,嗯我们称这种,双向同时进行的数据传输为双宫,全双宫通信,全双公通性,这些是既往的一个概念哈,半双公通性指的就是,同一时刻我只能支持单向的传输,但是这个方向的传输结束之后,我也可以把这个传输的方向改过来,但是两个方向的传输不可以同时进行,这是半双公通信,而全双公通信,指的是,两个方向的数据传输都要同时进行,好所以,如果,需要两个方向的传输都同时进行的话,那么这种情况下,我们就需要申请,向操作系统申请两个管道文件,一个管道文件呃,负责从左往右的这个数据传输,另一个管道文件,负责从右往左的这个数据传输好,另外各个进程对管道,的访问也应该是互斥的进行的,应该是互斥的进行的,但是呢对管道的互斥访问,是由操作系统来保证的,不需要由这个进程自己来保证,好第三点需要注意的是,刚才我们说,这个管道,它是一个大小固定的内存缓冲区,既然大小固定,那么这片缓冲区肯定会被写满对吧,那如果写满的话,这个进程是不是就不能,继续往里面写数据了,好所以如果管道被写满的话,那么写进程写进程应该被阻塞,他写数据的这个这个动作应该被阻塞,等待,直到读进程把管道里边的数据取走,等这个管道有空位了,我们再把这个写进程给唤醒,好另一个方面,读进程是从这个,这个呃内存缓冲区里面读数据的,那同样的这个数据有可能会被读空,对吧所以如果管道被读空的话,那么读进程的读read这个动作,就应该被阻塞,直到写进程往这个管道里边写入数据,才可以把这个读进程给唤醒,所以管道通信的这种方式,不管是读进程还是写进程,都有可能进入阻塞的状态,好接下来第5点,也是我们管道通信这个部分,很多教材最有争议的一个点,管道通信的实现方式,决定了,管道当中的这些数据一旦被读出,就彻底的消失了,就彻底的消失了,所以如果如果说有多个读进程,有多个读进程同时读一个管道的时候,就有可能会导致错乱,导致混乱,因为我们管道里边的这些数据,并没有指明说我到底是要给进程q,的还是要给进程进程m的,没有说明对吧,所以如果多个进程,如果多个进程,嗯都从同一个管道这读数据的话,那么有可能有可能呃,这个数据的读取,这个动作就是就是会比较乱的,第一块数据可能被m读走了,然后第二块数据可能被q读走了,那针对于这个问题,不同的操作系统会有不同的解决方案,比如说像早期的system five,这种操作系统,它在实现管道的时候就规定一个管道,一个管道允许多个写进程,多个进程同时往这个呃管道里面写,写就完了,但是呢读进程只允许有一个,只允许有一个,不允许多个读进程同时读这个管道,那我们2014年的有个408真题当中,高等教育出版社的那个官方答案,也是这么说的,就是他说一个管道可以有多个写进程,一个读进程,这是高等教育出版社啊官方给的答案,但是在有的操作系统当中,比如说Linux里边,Linux里边,呃又允许有多个写进程和多个读进程,只不过系统会让各个读进程,轮流的从管道当中读数据,比如说有两个进程q和m,进程q和进程m,都需要从这个管道里面读数据啊,那么q和m就是轮流着读的,你读一次我读一次你读一次我读一次,由操作,系统来控制这个轮流读数据的过程,所以在很多操作系统的教材里面,大家又有可能会看到说,一个管道可以被多个进程写,多个进程读,而在有的操作系统教材里面,他又会说一个管道允许多个进程写,但是只允许一个进程读,因此这个地方也是去年和前年,争议非常非常大,很多同学都不知道该信谁的呃,一个一个地方,好因此这个地方我们要知道,在实际应用当中,一个管道是可以被允许多个写进程,多个读进程的,但是我们毕竟最终是要面对考试,而考试呢,嗯这种应试类的这种考试,肯定都得有一个所谓的标准答案对吧,那以后以后大家在呃,讨论这个管道通信的时候,我们都和这个高教社呃,14年的这个真题统一就说,可以有多个写进程,然后只能有一个读进程,我们以这种说法为准,同时大家在做题的时候,如果看到有的题目说,一个管道可以有多个写进程,多个读进程,也不要觉得奇怪,从现实应用的角度来看,这种说法并没有什么错误,好,那这就是最后一种进程间通信的方式,管道通信那在
六、总结
在这个小节中,我们介绍了共享存储,消息传递和管道通信,这三种常见的进程间通信方式,那这三种呃通信的功能,都是需要操作系统的底层来支持的,共享存储的这种通信方式意味着啊,操作系统会分配一个共享内存去,并且把这个共享内存去映射到,各个进程的虚拟地址空间当中,那这个地方现在大家还暂时看不懂,等我们学到第3章,知道什么是断表,什么是页表之后再回来看这块,你就会恍然大悟了,要把一片内存区域映射到进程的,虚拟地址空间当中,只需要加一个段表象就可以做到,非常简单,另外呢呃,采用共享存储的时候,需要注意各个进程呃,我们需要保证,他们是互斥的访问这个共享,共享空间的,而这个互斥的过程需要有呃通信进程,就是各个通信的进程,他们自己来负责实现,比如说,使用操作系统提供的PV操作等等,使用操作系统的呃同步互斥的机制,来确保,各个进程是互斥的访问这片区域的,其实本质上对于共享存储区域的访问,是一个读者携手问题,这个是我们在2.3,2.3那个小节里边会学的,一个经典的同步互助问题,读者写着问题,也就是说多个进程啊,可以同时从这片区域里读,但是不可以同时往这片区域里边写,等大家学到读者写这个问题的时候,会知道我这说的是什么,怎么回事,好接下来第二种进程间通信的方式,叫做消息传递,消息传递又可以进一步分为,直接通信和,间接通信方式,直接通信方式就是呃,要指名道姓的说明,我要把这个消息发送给哪个进程,然后呃,操作系统会把这个消息直接挂到,接收进程的消息堆列里面,这是直接通信方式,间接通信方式又叫信箱通信啊,消息会被,操作系统先放到一个指定的信箱当中,而消息的接收者,也需要指明自己要从哪个信箱当中,取走一个消息,那这个小节最后,我们又介绍了管道通信,对于操作系,统而言管道通信的这个管道,其实是一个特殊的共享文件啊,你在Linux里边采用呃管道通信的话,你是可以在自己的系统里面,找到这个文件的,但本质上,这个文件它就是一个内存缓冲区,如果结合,我们数据结构的知识来看的话,这个内存缓冲区,其实就是一个循环对列,一个循环对列,写进程可以往这个循环对列里边呃写,写一些数据,然后当这个内存缓冲区,这个循环对列满了之后,写进程需要阻塞,那读进程读的时候啊也是一样的,如果说整个内存缓冲区,或者说这个这个循环队列被读空了,那么读进程就需要阻塞等待,那需要注意的是,一个管道文件只能实现半双弓的通行,这就类似于我们现实生活中的水管,一根水管啊我可以让水从左往右流,然后也可以让水从右往左流,但是我不可能让这个水管里面的水,既往左流也往右流,所以如果我想要,从左往右和从右往左的这个水流,同时都存在的话,那么我就需要建立两个管道,两个水管才可以实现,好的,那么这就是进程间通信的三种方式啊,那最后,再跟大家说一个王道书上的小问题,这个小问题也是,去年和前年让无数人抓狂啊,不知道他说的对不对的一个地方啊,今年我查阅了大量的资料,并且自己也在Linux操作系统上,就是做了广道通信的一个实验,那么,现在我有一个很笃定的一个答案,可以告诉大家,呃王道书里大家看一,下哈你们王道书里有这样的一句话,就是说,呃,写进程会先把这个管道缓冲区给写满,然后才让读进程读,当缓冲区中还有数据时,写进程不会往缓冲区里边写数据,这句话是错误的,这句话是错误的,这句话读起来就像是说,你一个管道必须全部写满了之后,读进程才可以从,才可以把这个管道里的数据取走,但事实上不是这样的,我在Linux上面呃做了这个实验,验证了这个问题,当我们的写进程,往管道里边写数据的时候,即便管道没有被写满,也可以被读进程给读,呃给读走,就是读进程要读这些数据,只有一个条件,就是管道别空了,只要管道没空,那么读进程就可以,继续的从这个管道里面读数据,对于写进程也是一样的,我不管你这个管道有没有被堵空,反正只要管道没满,只要管道没满还有空间可以让我写,那么我就可以继续往管道里面写数据,这是我做实验验证过的东西,好所以大家把那个呃,王道书上的这句话给修改一下,这段话是说的有问题的,好的那么这就是这个,,
No.5 线程的概念与特点
各位同学大家好,在这个小节中,我们会学习现成,相关的一系列的知识点,首先我们会用一个例子来介绍,什么是现成,为什么要引入现成机制呢,在引入现成机制之后,呃比起传统的进程机制来说,带来了哪些变化,之后我们会介绍县城,有哪些重要的属性,首先来看一下什么是县城,
一、什么是线程为什么要引入线程呢
为什么要引入县城呢,在很久很久以前啊,在没有引入进程之前,系统之间的各个程序,是只能创行执行的,所以在那个时候我比如说像我,我们想一边运行啊音乐播放程序,一边运行QQ这个程序,那么啊显然是不可以实现的,在那个时候我们不可能边聊QQ,然后边听音乐,但之后引入了进程的机制之后,就可以实现,QQ边聊QQ边听音乐这样的事情,但是大家再来呃深入的思考一下,QQ可以做一些什么事情呢,我们可以用QQ进行视频聊天,同时还可以和其他的人进行文字聊天,然后再同时还在传送呃文件,那么这些事情,是怎么在进程当中完成的呢,很显然在传统的进程定义当中,进程它是程序的一次执行,但是这些功能,视频聊天,文字聊天,传送文件这些功能,显然是不可能由一个程序,顺序执行来处理的,如果只能顺序的来处理那么就不可,能呃在我们用户看来,这几件事情是可以同时发生的,就不可能有这样的现象,所以,有的进程,其实他是需要同时处理很多事情的,就像刚才咱们说的QQ那样,但是传统的进程,他只能串行的执行一系列的程序代码,就是这个样子,在传统的进程呃机制当中,CPU会轮流的为各个进程进行服务,那么这些进程就可以并发的执行,并且每一个进程,会有他自己相应的一系列程序代码,然后被CPU服务的时候,这些代码就可以一句一句,开始往下执行,所以在传统的进程机制当中,进程是执行由的最小单位,这句话什么意思呢,听了后面的呃这个讲解,大家应该就可以理解,之后为了,满足,像咱刚才咱们说的一个进程当中,同时就宏观上同时做很多事情,人们又引入了现成机制,用来增加系统的并发度,引入了现成之后,系统的呃CPU的这个,调度服务对象就不再是进程,而是进程当中的线程,每一个进程当中可能会包含多个线程,然后CPU会轮流的为用一定的算法呃,轮流的为这些线程进行服务,就像这个样子,为各个县城服务,那么这样的话,同一个进程当中被分为了多个县城,像刚才咱们说的QQ,视频聊天和传送文件,这些这两件事情,如果想要并发的执行的话,那么我们就可以把,这两件事情对应的处理程序,放到两个呃,不同的县城下,那么这两个县城可以并发的执行,自然这两件,事就可以并发的完成,所以在引入了县城机制之后,县城就成了程序执行流的最小单位,在没有引入县城之前,一个进城其实就对应一份代码,这些代码只能顺序的依次往下执行,但是在引入了县城之后,每每一个进程可以有多个线程,并且,这些线程它可以有各自不同的代码,也可以是每个进也可以是不同的进,线程运行的是同一份代码,但是这些代码都会并发的被CPU处理,然后并发的依次执行下去,所以这就是呃,所谓程序执行流的最小单位的意思,所谓的县城其实我们可以把它理解,
也为是一种轻量级的进程,以前CPU调度的单位是进程,但是现在CPU的服务对象不是进程,而是以线程为单位,所以线程它是基本的CPU执行单元,也是程序执行流的最小单位,这个经过刚才的讲解,相信大家已经可以理解,在引入县城之后,进城之间可以并发的执行,并且进城之间的各个呃,县城也可以并发的执行,所以引入县城机制,进一步的提高了系统的并发度,可以使得一个进城内,也可以并发的处理各种各样的任务,就像刚才咱们聊到的QQ聊天,呃什么传文件这样这些事情,然后在隐若县城之后,晋城不再是CPU调度的基本单位,晋城它只作为,除了CPU之外的系统资源的分配单元,什么意思呢,假如系统当中,那假如说这个计算机系统,当中有各种各样的系统资源,那么这些资源是被分配给晋城的,而不是分配给县城,就像这个样子,那么在引入了现成机制之后比
二、引入线程后的变化
起传统的呃进程机制来说,有了哪些变化呢,首先我们来看一下资源分配,和处理机调度方向,这个刚才已经讲过,传统的晋城机制当中,晋城他既是资源分配的基本单位,也是处理机调度的基本单位,但是在引入县城之后,晋城他只是资源分配的基本单位,而县城变成了调度的基本单位,这是区别,在并发性角度来讲,传统的进程机制中,进只能是进程之间并发的执行,但是在引入县城之后,各个县城间也可以并发执行,所以进一步提升了系统的并发度,在实现并发带来的系统开销方面,传统的进程间并发,需要切换进程的运行环境,切换进程的运行环境,其实系统开销是比较大的,但是呃引入了县城机制之后,如果我们是切换,同一个进城内的不同县城,那么我们不需要切换呃,进城的运行环境,这样的话,呃并发所带来的系统开销,就会进一步的降低,怎么理解,切换进程的运行环境,所带来的系统开销呢,我们来用一个,呃去图书馆看书的例子来来理解,假如说,你在使用图书馆当中的某一张桌子,但是突然有一个人,你不认识的人也要用这个桌子,那么你需要把你的运行环境,你的书给收走,然后他要把自己的运行环境把他,自己的书放到桌子上,所以这就是呃,进程切换所带来的运行环境的切换,这个切换过程,其实需要付出一定代价的,你需要把书搬走他需要把书放放上去,但是如果说呃,这个时候是你的舍友,想要用这张书桌的话,那么既然你们认识,就相当于你们俩是属于同一个顶层的,这种情况下就可以不把你自己的书,桌子呃不把你自己的书收走,把这个书依然放在桌子上,这就类似于统一进程内的现程的切换,统一进程内的现程切换,不需要切换进程的运行环境,所以由于你们不需要把书挪来挪去,因此这个开销也会降低很多,接下来我们再来看一下静止现场,
三、线程的属性
程有哪些属性,线程是处理机调度的单位,这个刚才已经强调过很多次了,然后多个CPU计算,多CPU的计算机当中,就是多核CPU的计算机当中,每个线程它可以占用不同的CPU,比如说现在的呃,CPU一般都是什么双核4核8核的,那各个线程可以分配到不同的,核心里去,另外呢,每个线程它其实像呃会有一个线程ID,和线程控制快,TCB这样的数据结构,线程控制快,其实呃有点类似于我们之前学过的PCB,进程控制快,线程控制快也是用于管理线程,所创建的一个数据结构,那么和进程类似,线程他也会有就色,就绪阻塞运行这样的三种基本状态,因为隐若县城机制之后,县城他只是处理机调度的基本单位,而系统资源分配是分配给晋城的,所以县城几乎不拥有系统资源,系统资源都是在晋城那里,那么县城,肯定也需要使用一系列的系统资源,这些系统资源从哪里来呢,其实同一个进程当中的不同县城,他们是可以共享,使用这个进程所拥有的,系统资源的,这些系统资源包括像什么IO设备,还有内存地址空间这样的资源,由于呃同一个进程当中的不同县城,他们可以共享内存地址空间,所以同一个进程中的县城,他们之间的通信,就可以不需要呃操作系统干预,可以直接通过共享的内存地址空间,就可以完成他们之间的信息传递,另外呢我们还需要注意,同一个进程中的线程切换,其实并不会引起进程切换,但是不同的进程中的线程切换,会引起进程切换,如果我们切换的是同进程内的线程,那么由于不需要切换进程的运行环境,所以系统开销是比较小的,如果我们切换的是不同进程间的线程,那么它会导致呃进程的切换,相应的也需要切换进程的运行环境,所以系统开销就比较大,这个就是刚才咱们讲到的图书,馆那个例子,好的那么这就是线程的相应的一系列
No.6 线程的实现方式和多线程模型
各位同学大家好,在上个小节中,我们学习了现成相关的一些基本概念,基础的知识,那这个小节中我们会来看一,下有哪几种啊,现成的实现方式,并且会学习几种多现成模型,那现成的实现方式分为用户级现成,和内合级现成,另外还有的系统当中,会把这两种实现方式都混合起来使用,那这个大家一会会看到实际的例子,那首先我们来看一下,第一种现成的实现方式叫
一、用户级线程
叫做用户级现成,其实这种实现方式,是在早期的操作系统,也就是只支持进程,还暂时没有支持,现成的那些操作系统当中,来使用的,当时所谓的线程,是由程序员们写的线程库来实现的,也就是说在这个时代,操作系统的视角,看到的其实依然是只有进程,但是程序员们写的这些应用程序当中,可以使用线程库,来实现多个线程并发的运行,这样的事情,我们还是来结合,上,小节当中提到的这个例子来进行理解,上个小节中我们提到过,我们的QQ可以一边视频聊天,一边文字聊天,一边实现文文件传输,那上个小节中我们提出了这样的方案,如果要让这三个事情并发的运行的话,那么在不支持现成的系统当中,我们可以分别建立三个进程,这三个进程分别是处理其中的某一个,呃事情进程一的代码,是不断不断的来处理视频聊天,这个事情,进程2是不断不断的来处理,这个文字聊天,而进程3是处理这个文件传输,那我们可以看到,处理视频聊天的这个代码,呃是在是用这个循环来一遍一遍,一遍的呃不断的执行的,另外的两个代码也一样,所以其实我们可以用这样的方式,来实现让这三段代码并发的运行,我们用一个外物循环,让它一直不断的循环,然后这个i的值会012012,这样循环的变化,当i等于0的时候,我们可以让这个进程来处理视频聊天,当i等于一的时候让他处理文字聊天,当i等于2的时候让他处理文件传输,那由于我们这个程序啊,进行外循环的速度是非常快的,那我们其实也可以把这3段代码,看作是3段呃,并发的运行的代码,他们分别处理了不同的事情,所以其实如果我们从,单纯代码的角度来看的话,那么一个线程,其实我们可以把它理解为,是一段代码逻辑,那这个地方提到的这三段代码逻辑,我们就可以把它看作是三个线程,另外,我们这写的外物循环的这个处理逻辑,其实我们就可以把它看成一个,最简陋最弱智的一个线程库,这个县城库,完成了对各个县城的一个管理,调度的工作,那这个弱,那这个弱智县城库,对我们的这些县城的调度规则很简单,就是第一次先处理视频,第二次处理文字,第三次处理文件,第四次要处理视频,第五次处理文字等等等等,这个地方,我们用一个简单的wild循环,和几个if语句,就实现了一个最简单,最简单最简单的现成库,那我们很多的编程语言都会提供啊,用于管理现成的现成库不,过他们提供的现成库,要比我们的wild循环复杂多了,程序员可以利用这个现成库,来实现这个用户及现成的创建,销毁调度等等一系列的功能,那接下来问题来了,在刚才我们所说的这个例子当中,操作系统其实他只看得到进程,而这个进程上处理机运行的时候,其实是程序员自己写了一个线程库,来创建了逻辑上的线程,也就是这所谓的用户级现成,那我们来思考这样的几个问题,第一这些用户级现成的管理工作,是由谁来完成的,其实我们刚才写的这个wild循环,就是简单的实现了对这三个,现成的管理,让他们交替的运行,所以用户级现成的管理工作,是由应用程序通过现成库来完成的,并不是操作系统负责的,第二个问题,现成切换是否需要CPU,从用户态转换为内核态,那经过刚才的分析,这个问题其实也不难回答,我们的现成切换,其实是由我们这个外物循环来控制的,这并不需要呃,涉及到请求操作系统服务之类的事情,所以现成切换的管理,是由我们的现成库,应用程序自己完成的,在用户太下就可以完成,现成的切换工作,并不需要操作系统的干涉,第三个问题,操作系统是否能意识到用户,及现成的存在,那显然,操作系统他只能看到这个进程的存在,他只知道这个进程他是一坨代码,而在这坨代码里面,又分别被分为了几个现成,操作系统是,意识不到这些现成的存在的,所以,这也是为什么这种现成的实现方式,叫做用户及现成的原因,只有用户,才能感知到这个用户及现成的存在,而操作系统其实感知不到这些用户及,现成的存在,那最后我们要思考的问题是,这种实现方式它有什么优点和缺点呢,首先来看优点,刚才我们提到过,用户级现成的管理工作,包括切换创建等等,这些工作都不需要请求操作,系统的服务,只需要在用户态下就可以完成,也就是说对用户级现成的管理,并不需要涉及到CPU变态这个事情,而之前我们说过,CPU变态是有开销有成本的,所以那既然用户及现成的管理工作,不需要呃,CPU切换到内核态,所以对于他们的管理工作,肯定开销是比较小,效率是比较高的,那接下来来看一下,用户级现成这种实现方式有什么缺点,那我们回到我们自己实现的,这3个最简单的,用户级现成,我们来看一下,假设此时这个QQ进程上处理机运行,而这次运行的时候爱的值是等于0的,也就是说,视频聊天的,呃这一段代码会上处理机运行,但是假设,视频聊天的这段代码在运行的过程中,发生了阻塞,比如说他想要申请摄像头那个资源,但是申请失败,那么由于他,想要的这个系统资源得不到满足,因此这段代码的执行就会被阻塞,那我们想一下,既然,这个代码的执行被阻塞在了这个地方,那么这个外恶循环还能继续下去吗,肯定不行了对吧,只有这个阻塞被解除之后,这个外恶循环才可以继续啊执行下去,所以这种用户及现成的实现方式,有一个很明显的缺点,那就是如果其中的某一个现成被阻塞,那么其他的这些现成也会被阻塞,也没办法执行下去,那从这段尾代码当中,相信不难理解这一点,所以,这就是用户及现成这种实现方式,最大的一个缺点,只要其中一个被阻塞,那整个进程都会被阻塞,所以这种方式的并发度并不高,另外虽然上个小节中我们提到过,引入县城之后,县城成为了呃CPU调度的基本单位,但是如果这个县城是用用户级县城,这样的方式来实现的话,那么在这种情况下,其实CPU的调度单位依然是进程,操作系统是给进程分配CPU时间呢,因此即便我们电脑是多核处理机,但是由于进程,才是呃CPU调度的基本单位,因此这个进程只能被分配一个核心,所以这些线程并不能并行的运行,那这是早期的操作系统当中,人们实现现成的方式,在这个阶段操作系统还只支持进程,并不支持现成,那之后随着,
二、内核级线程
操作系统的发展,呃越来越多操作系统开始支持线程,那操作系统支持的一种线程,就叫做内合集的线程,那这种内合集现成,就是操作系统视角,也可以看得到的现成,那现代的操作系统大多都,支持内合集现成,比如说我们很熟悉的Windows Linux等等,那接下来我们,还是要思考同样的3个问题,在引入了内合集现成之后,这个现成的管理工作到底是谁来做呢,那由于这个内合集现成,是在操作系统层面实现的现成,因此这个内合集现成的管理工作,当然是需要由操作系统来完成,第二个问题,现成的切换是否需要CPU状态的转换,那既然这些内合集现成,由操作系统负责管理,那他们的切换,他们的管理工作,肯定是需要操作系统介入的,因此在进行现成切换的时候,当然是需要从用户态转变为内合态,第三个问题,操作系统,是否能够意识到内合集现场的存在,这个不用说了,最后我们根据刚才认知到的这些信息,来分析一下,这种实现方式有什么优点和缺点,首先来看优点,如果某一个操作系统,它支持内核及线程的话,那么在这种操作系统当中,内核及线程,它是处理机调度的基本单位,而进程只作为分配资源的基本单位,因此在多核CPU的环境下,这几个线程,可以分别分派到不同的核心下,呃并行的执行,另外呢不同的内合集线程中,可以跑不同的呃代码逻辑,比如说这个代码逻辑是实现视频聊天,这个是实现文字聊天,这个是实现文件传输,那么由于内合集线程,它是处理及分配的基本单位,那在这种情况下,即便其中的某一个线程啊被阻塞,那其他的两个线程,依然可以继续执行下去,所以采用这种方式有一个优点,那就是线程之间的并发能力强,那再来看一下这种方式的缺点,当引入了内合集现成之后,那一个进程有可能会对应,多个内合集现成,那操作系统需要对这些现成进行管理,所以内合集现成之间的切换,是需要CPU从用户台变为内合态的,当切换完成之后,还需要从内合态转转回用户态,而之前我们提到过很多次,CPU变态是有成本有开销的,所以这种实现方式,会导致现成的管理成本要更高,开销更大,那刚才我们学习了用户级,现成和内合集,现成这两种,现成的实现方式,那这两种方式都有各自的优点和缺点,那有没有可能,把这两种方式结合起来呢,
三、多线程模型
1、一对一模型
那在支持内合集现成的系统当中,如果在引入现成库的话,那么我们就可以实现,把若干个用户及现成,映射到某一个内合集现成这样的事情,那根据用户及现成和内合集,现成的这种映射,关系啊,就引出了3种多现成模型,像刚才我们一直在讲的这种模型,一个用户级现成对应一个内合级现成,这个是一对一模型,那如果采用这种映射方式的话,一个进程他有多少个用户级现成,就会有多少个内合级现成,他们都是一对应的,那这种方式的优点呢,就是刚才我们提到过的,一个县城被阻塞之后,别的县城还可以继续执行,因为内合集县城,是处理及分配的基本单位,另外这些代码逻辑,这些线程可以分派到多核处理机上,并行的执,行这是它的优点,而缺点呢,和刚才我们所说的一样,就是管理的成本高,开销大因为线程的管理工作,肯定需要切换到内核态,那只要涉及到CPU变态,就会使开销变大,那再来看第二种多线程,
2、多对一模型
模型叫做多对一,也就是多个用户级现成,映射到一个内合集现成,那如果是这种映射关系的话,其实它就退化成了我们之前提到的,纯粹的用户级现成那种实现方式,那由于一个进,程只被分配到了一个内合集的县城,而在这个内合集县城上面,通过县城库,又实现了3个用户级的县城,因此这些县城的管理工作,只需要在用户台下就可以完成,所以县城的管理开销小效率高,但是缺点呢,就是其中的一个用户及线程阻塞之后,会导致,其他的用户及线程也跟着被阻塞,并发性不高,并且这些用户及线程是不可能,并行的运行的,因为只有内合集,线程才是处理机的分配单位,如果一个进程,它只对应一个内合集线程的话,那么在同一时刻,这个进程,肯定只能被分配一个CPU的核心,当然如果给这个进程分配,多个内合集线程的话,那么在多核CPU环境下,这些内合集线,程肯定是可以并行的运行的,不过在我们的考试当中,如果提到这种多对一的线程模型的话,那么我们啊,默认一个进程,只被分配了一个内科技的线程,那最后我们要认识的是多对多,
3、多对多模型
多模型就是把,n个用户级线程映射到,m格内合集线程上,呃n的数量要大于等于m,那在这个模型当中,由于一个进程它有两个内合肌线程,因此其中一个内合肌线程被阻塞的话,另一个内合肌,线程是可以继续运行下去的,因此它克服了多对一模型,并发度不高的缺点,另方面,这种多对多的模型n是大于等于m的,也就是说内合集现成的数量,它要比用户集现成的数量要更小,因此,操作系统对这些现成的管理开销,也相应的会更小,而在一对一模型当中,有多少个用户级现成,就需要给他创建多少个,对应的内合集现成,那内合集现成太多的话,操作系统的管理开销就会更大,所以这种方式,他又克服了一对一模型当中,现成管理开销太大的一个缺点,那我们再来总结一下,用户级县城和内河级县城的,啊一个区别和联系,我们可以这么来理解,所谓用户级县城,我们可以把它理解为是,一个代码逻辑的载体,比如说这个用户级现成,它承载的是文字聊天相关的代码逻辑,这个用户级现成,它承载的是文件传输相关的代码逻辑,而内合级现成,可以理解为是运行机会的一个载体,因为操作系统在分配处理及,CPU资源的时候,是以内核及现成为单位进行分配的,所以在这边这个模型当中,虽然有3个用户及现成,但是啊这个进程最多只可,能被分配两个CPU的核心,我们的一段代码逻辑,只有获得了运行机会的时候,他才可以被CPU执行,那这可以让我们的县城管理,有更多的灵活性,比如说在这边这个例子当中,如果我们的QQ,视频聊天需要耗费比较多的CPU,资源的话,那么我们可以呃让,左边这个内合集线程,让它专门来,执行视频聊天相关的这个代码逻辑,而右边这个内合集线程,我们可以让它,并发的执行文件传输和,文字聊天这两个部分的逻辑,那如果某一个时刻,文件传输又要耗费很多的,CPU资源的话,那么我们可以把文字聊天这块的逻辑,把它映射到这边,让这个内合机线程来进行处理,那需要注意的是,在引入了内合集线程之后,一个进程可能会对应多个内合集线程,而只有所有的这些内合集线程,都被阻塞的时候,我们才说这个进程进入了阻塞状态,那根据去年同学们的反馈
四、总结
这个小节的内容也是不太容易理解的,特别是对于跨考的,没有自己写过程序的同学来说,呃可能理解起来比较吃力,对于跨考的同学来说,在学完了数据结构,有了一些这种呃写代码的思想之后,再回来理解这个部分的内容,可能也会更容易一些,我们需要再次强调的是,用户及现成是在用户视角能看到的,现成由现成库实现,就像那个很简单粗暴的y循环,可以认为是一个最,简单的现成库,那内合集现成,才是操作系统视角能看得到的,现成由操作系统来负责管理,所以,内合集现成才是处理及分配的单位,那,对于我们介绍这几种多现成模型来说,大家主要是要理解他们各自的优缺点,另外这个部分的内容比较容易呃考察,关于阻塞的问题,这个大家在课后习题当中会有体会,好的,那么以上就是这个小节的全部内容
No.7 线程的状态与转换
好现成的状态与转换,其实现成的状态与转换,和进程的状态与转换,呃几乎是一模一样的,并且现成的状态,我们通常只关注呃最核心
一、线程的状态和转换
呃最主要的这三个状态,也就是运行态就绪态和阻塞态,他们之间的转换,和进程之间的转换完全一致,如果一个系统他支持现程的话,那么一个运行态的现程,他被分配的时间用完了,那么他就会下处理机进入就绪态,而一个就绪态的线程,如果它被调度程序选中,那么就可以从就绪态回到运行态上,处理即运行,而一个正在运行的线程,如果它发出了呃某种请求,等待某个事件的发生,比如说等待IO完成,那么它就会从运行态转变为阻色态,而如果一个阻塞的线程,他等待的事件发生了,那么他就会从阻塞态回到就绪态,好所以这就是线程的状态与转换,我们着重关注这种三状态的模,型好接下来线
二、线程组织与控制
成的组织与控制,那其实现成的组织与控制,和进程的组织与控制也是非常类似的,在组织与控制进程的时候,操作系统会对进程的呃PCB,也就是进程控制快进行一个管理,其中包含了进程的各种各样的信息,那对于县城来说,要管理县城,我首先得给各个县城,建立一个与之对应的,呃一个数据结构,对吧那县城对应的数据结构就是TCB,也就是县城控制块,那每个县城控制块里边,会包含这样的一些内容,可以简称为TID,这和进程的PID是很类似的,其次每一个县城,他们执行的代码可能是各不相同的,因此每个县城,他执行到代码的哪个位置,是不是得把它记录下来,好,所以TCB当中还会包含呃程序计数器PC,用于指明线程现在执行到哪了,那如果一个线程它切换下处理机,那此时就需要把这个处理机上的PC,PC的值给它保存到TCB当中,而如果一个线程,它上处理机运行从旧绪态回到运行态,那这个线程的运行现场,运行环境是不是得被恢复,那就可以从这个TCB当中取出PC的值,把它放回PC计存器里面,这是属于现程运行现场的一个信息,那除了程序技术GPC之外,还需要保留其他的通用计存器,学过机组我们都知道,代码运行的中间结果,会被保存到各种各样的计存器里边,因此当我们切换现程的时候,是不是也需要保存这个现程的呃,各种计存器的一个值啊,那除了寄存器之外,还有一个重要的信息叫做堆站,那我们知道,堆站是用于保存函数调用的信息,a调用了b b调用了c,并且也会记录每一次的函数调用,它的返回地址是什么,a调用了b,那么b这个函数啊,调用结束之后,应该返回到a的哪一句代码,对吧,需要把这个函数的返回位置也给它,在堆站里边记录下来,同时啊每一层函数的局部变量,也会放在堆站里面,那现成的堆占啊,它可能是比较大的,是内存里的一大片区域,如果把这一整片区域都全部把它,放到TCB里,显然没必要,对吧,我们只需要保存堆占指针就可以,保存一个指针,那么我们可以通过这个指针,来找到这个线程,它的堆占在内存里的哪个位置,哪片区域就可以了,好所以还需要保存堆占指针,好所以这个地方,我把这三个部分把它圈起来了,程序计数器PC,以及其他各种计存器的值,还有堆占的信息,这些是现成,切换的时候我们需要保存和恢复的,一些现成的运行环境,下处理机的线程需要把这些信息,保存到TCB当中,而上处理机的线程,需要从TCB当中取出这些信息,然后把这些信息填回到相应的位置,那堆占指针通常是把它恢复到SP,堆占寄存器里面的啊,那除了上面这个信息之外TCB,当中还需要保存运行状态,运行态就绪态而阻色态,甚至如果是阻色态的话,呃还可以把阻色的原因什么的,就是更详细的给它进行一个记录好,最后还需要保存现成的优先级,那通常在现成调度或者,系统在分配资源的时候,可以根据现成的优先级,来进行一个呃分配,或者制定一个策略,好那这就是TCB当中,大致需要包含的一些内容,那有了TCB之后,每一个TCB这种数据结构,就可以表示一个现成,那我们把多个现成的TCB,给它组织起来,就可以形成一个现成表,组织的方式有很多种,比如说每个进程给他设置一张,呃现成表,或者系统当中所有的现成,组成一张现成表,当然也可以按照现成的状态不同,组织成不同的现成表,不同的系统可以采取不同的策略,总之所谓县城的组织,无非就是按照你的需求,把各个TCB啊,有规律的分门别类的把他们组织起来,分类管理,这就是县城的组织,那所谓县城的控制,就是要让县城在各种状态之间,来回切换,这就是县城的控制,所以这两个部分新增的内容,无非是把进城变成了县城而已,而县城往往比进城还要更简单,
No.8 调度的概念、层次
各位同学大家好,在这个小节中,我们会学习处理机调度的基本概念,和几个调度的层次,分别为高级调度中级调度和低级调度,其中由中级调度也就是内存调度,我们会引出补充一个,呃课本上没有太多提及的知识点,就是进程的挂起态,并且会介绍,一个进程状态的基状态模型,之后我们还会介绍,三个调度层次的联系和对比,那么首先来看
一、调度的概念
一下什么是调度,其实调度这个概念,和我们的生活离得并不遥远,比如说在我们去银行的时候,这个银行,他可能只有几个窗口可以为客户服务,那么这些客户到底应该先为谁服务呢,银行一般采用的就是先,到先服务的这种,这种原则,那如果说此时有一个VIP客户啊,这个客户在这个银行里存了几个,小目标就是存了几个亿,那么这个,VIP客户可能就会被银行优先的服务,他的优先级更高,再看另外一个场景,早上咱们起床的时候,可能每个宿舍只有一个卫生间,但是大家,都想成为这个坐在王座上的男人,那么每个人都想使用,但是有的人说我我想要使用3分钟,有的人要10分钟有的人要1分钟,还有一个人他也需要使用3分钟,那大家经过商量之后就决定了一种啊,使用这个,资源的一个原则,就是时间使用的短的可以让他先使用,而时间长的就后使用,如果说时间长度相同的,那么就先进入这个队列,先排队的就可以先使用,所以大家经过商量之后就决定用一,234这样的顺序来使用卫生间这个资源,所以其实所谓的调度他就是指当,我们有一堆东西,一堆任务要处理的时候,由于啊当前的资源有限,那么这些事情没办法同时的被处理,那这个时候我们就需要按照某种规则,比如说先到先服务,或者说时间短的优先,各种这样的规则来决定,我们要用什么样的顺序,来处理这些任务,这就是所谓的调度研究的问题,好的那来看一下在我们程序运行的
二、调度的三个层次
1、高级调度
整个生命周期内,什么时候会发生调度的情况,第一种调度叫做高级调度,又叫做作业调度,所以我们这需要补充作业的概念,那作业的概念其实在之前的讲解中,或多或少是提过的,那所谓的作业,其实指的就是某一个具体的任务,大家在书里面会看到这样的描述,就是说,用户向操作系统提交了一个作业,那这句话其实你可以理解为,就是用户让操作系统,帮他启动某一个特定的程序,然后这个程序,是来处理某一个具体的任务的,所以这就是作业的概念,好那我们知道我们要启动一个程序,那这个程序相关的数据,肯定需要从外存放到内存里边,但是我们的内存资源又是有限的,所以有时候如果内存已经满了,内存资源不足的话,那么我们给操作系统提交的这些作业,或者说我们想要,系统帮我们启动的这些个程序,有可能没办法马上把它们放到内存,马上启动,所以在这个时候,操作系统就会进行所谓的高级调度,或者也可以称之为作,业调度,操作系统会按照作业调度相关的规则,从这个所谓的作业后背对列里边,选择一个作业,先把它调入内存,并且会为这个作业,建立与它相对应的进程,也就是建立一个PCB,所以这就是所谓高级调度,或者说作业调度要做的事情,如果说当前用户提交了很多作业,那这个时候需要由操作系统来决定,到底要先调入哪个作业,那每个作业在整个生命周期内,只会掉入一次,掉入的时候会建立PCB,当作业完成或者说这个任务完成之后,作业会被调出,这个时候才会撤销与之对应的PCB,好那这是高级调度的概念,接下来看
2、低级调度
看低级调度,又可以叫进程调度或者叫处理机调度,因为我们内存里面其实同时会,存在很多很多个进程,但是我们系统当中的CPU,资源又是有限的,所以操作系统也需要制定某一种策略,从我们的,进程就绪队列当中挑选出一个进程,把处理机资源分配给他,那多道程序并发执行,这件事肯定需要用到进程调度,所以进程调度是操作系统当中最,基本的一种调度,并且进程调度的频率是很高的,因为只有高频率的进程调度,才可以让各个进程,呃很快速的轮流的上CPU执行,这样才可以让用户在宏观上看,好像各个进程是同时执行那样,好那只是低级调度,那最后再来看中级调度
3、中级调度
刚才我们说过,计算机当中,有可能会出现,这种内存资源不足的情况,内存里边同时会存在多个进程的数据,那如果说内存不足的话,其实我们可以让某一些不太紧急,不太重要的进程,先把这些进程的数据,把它从内存调出外存,那如果说一个进程的数据,从内存放到了外存里边,那这个进程此时就处于挂起状态,操作系统会把这些,处于挂起状态的进程,他们的PCB组织成一个队列,叫做挂起队列,其实就类似于我们之前学习过的,就绪队列,组色队列,那此时,如果说已经有空闲的内存资源了,那操作系统是不是又需要,通过某一种调度策略,来决定到底要先把哪个进程的数据,先把它调回内存,那这个就是所谓的终极调度管的事情,又叫做内存调度,不知道大家在平时用手机的时候,有没有这样的体验,就是呃有时候你切换程序,或者说切换进程的时候,有的时候你切换你会发现那个进程,切换的很快,而有的时候那个进程切换的又很慢,那有一种可能的原因就是,当你的这个进程切换的很快的时候,那这个进程的数据有可能是,放在内存里边的,而你发现切换进程很卡很慢的时候,有可能是因为你那个进程的数据,他之前已经不在你的手机内存里了,而是被系统调到了外存当中,所以当你切换这个进程的时候,哎,系统发现这个进程现在非运行不可了,那他会临时的,把这个进程相关的数据从,外存再读回内存,然后只有他读回内存之后,这个进程才可以顺利的运行,因此你会感受到有那么一丝丝的卡顿,其实卡顿的过程就是,系统在进行终极调度,他选中了你的那个进程,让他回内存来运行,那想要在进程运行的生命周期内,有可能会多次调出多次调入,所以中级调度发生的频率,肯定要比高级调度要更高,好的那这就是调度的3个层次,高级调度中级调度和低级调度,那么既然提到了挂期状态,我们再来补充一个和挂期状态,相关的期状态模型,
三、七状态模型
其实挂起状态,又可以进一步的细分为,就绪挂起和阻塞挂起两种状态,咱们之前已经学了进程的5状态模型,这也是408里,呃要求掌握的一个,一个进程的状态模型,但是对于一些自主命题的学校来说,有可能会考察7状态模型,所以大家也不要掉以轻心,那么在引入了旧绪挂,起和阻塞挂起两种状态之后,一个处于就绪态的进程,如果说此时这个系统的负载比较高,内存空间已经不够用了,那么,他有可能会把一个处于就绪态的进,程把它,放到把它暂时调到外存当中,然后这个进程,就进入了一个就绪挂起的状态,一直到内存空间空闲,或者说这个进程又需要继续执行,那这个进程又会被激活,把它的数据,相应的数据又挪回内存当中,这样的话,一个旧绪挂起的进程又回到了旧绪态,除此之外,一个处于阻色态的进程也可以被挂起,相应的啊也可以再重新的被调入内存,然后进行激活重新回到呃阻色态,而有的操作系统,有可能会使一个处于阻塞挂起的进程,当他等待的阻塞事件发生的时候,这个进程就会直接,进入到一个就绪挂起的状态,然后之后,当他再被重新被调回内存的时候,是直接回到就绪态而不是回到阻色态,而有的时候,一个进程当他处于运行,他运行结束之后,可能这个进程下处理机的时候,就会被直接放到外存当中,让他进入就绪化急的状态,而有的时候,一个处于创建态的进程,当他创建结束之后,创建完PCB之后,有可能出现内存空间不够的情况,那这种情况下,有可能处于创建态的进程之后,会先进入到一个就绪挂起的一个状态,那么这就是所谓的7状态模型,那大家需要注意的是,挂起和阻塞的区别,这两种状态是呃,都是,暂时不能获得CPU服务的两种状态,但是区别在于处于挂起态的进程,进程印象是放在外存里的,而处于阻塞态的进程,他的进程印象其实还在内存当中,而有的操作系统,有可能会把这些,处于就绪挂起和阻塞挂起的这些进程,分为两个不同的挂起对列啊,当然也有的操作系统,还会根据这个阻塞的原因不同,再把阻塞挂起的这些进程,再细分为多个对列,那么这就是漆状态模型,大家也需要注意一下,我们再来整理一下3层调度的联系和,
四、三种对比
对比这三层调度分别要做什么,相信刚才的讲解应该已经比较细了,这就不再展开,那么高级调度和中级调度,这两层调度,是发生在外存和内存之间的调度,区别在于高级调度,它是面向作业的调度,一个作业在刚开始会被调入一次,被调出一次,并且作业调入的时候,会会为这个作业建立相应的PCB,也就是建立它相应的进程,而中级调度内存调度,它是面向进程的一种调度,它是把暂时不会运行的进程,相关的进程印象相关的一些数据,把它调到外存里,然后之后通过中级调度,再,再把这些进程的数据从外存调回内存,而低级调度,它是内存和CPU之间的一个调度,对于这三层调度的发生频率来说,他们的发生频率依次是从低到高的,而这三种调度对进程状态的影响是,高级调度,他有可能会使一个进程从无到创建态,最后当他创建完了,PCB创建完毕之后,还会把这个进程放入到就绪队列里,让他进入就绪态,所以他对进程状态影响是这个样子,而内存调度,它会使一个处于挂起态的进程,重新回到就绪态,而如果说挂起态又细分为,阻色挂起和就绪挂起的话,那么也可以说,它可以使一个处于阻色挂起的进程,重新回到阻色态,而低级调度也就是进程调度,他是选择一个处于就绪态的进程,让他进入运行态,投入处理机开始运行,所以这是这三,种调度对进程状态的影响,那么我们再来简单,
五、总结
回顾一下这个小节,我们介绍了处理机调度的基本概念,和三个调度的层次,我们需要注意的是,这三种调度的后面一种名称,这三个名称才是在咱们的考题当中,最高频最容易出现的一种名称,所以这个大家需要注意,另外呢,我们需要理解三层调度的联系和对比,大家在脑子里再回忆一下对比一下,那么我们还呃,通过中级调度引出了一个咱们,书里没有,具体介绍的一个知识点,就是所谓的挂起态,并且介绍了和挂起态相关的,七状态模型,这两个知识点,其实在考试当中也是有可能被考到的,特别是自主命题的一些学校,那么最后我们需要注意的是,咱们在介绍这几种呃3层调度的时候,都是说他们都是按照某种规则,那么呃这个课当中,我们主要学习的是作业调度,和进程调度相关的这些所谓的规则,而这个就是咱们之后,要研究的调度算法的问题,好的那么这就是这个小节的全部内容,
No.9 进程调度的时机、切换与过程、方式
各位同学大家好,在这个小节中,我们会继续学习进程调度,相关的一系列知识点,首先我们会来回答下,进程调度的时机是什么,什么时候需要进行进程调度,而什么时候又不能进行进程调度,并且在聊这个进程调度实际的问题,的时候,会引出一个进程调度的方式的问题,分为非抢占式和抢占式两种,另外呢,进程调度的目的其实是为了进程切换,那么,进程切换和进程调度有什么区别呢,进程切换的过程当中,我们又需要做一些什么事情呢,这些都是这个小节的内容,好了那么我们,
一、进程调度的时机
先来看一下进程调度的时机,之前我们已经介绍了进程调度的概念,呃进程调度也叫做低级调度,就是指按照一定的算法,从就绪队列当中,选择一个进程为他分配处理机,那么,什么时候需要进行进程调度和切换呢,主要可以分为两个大类,第一种就是当前运行的这个进程,他主动的放弃了处理机,第二种是当前运行的进程,被动的被迫的放弃了处理机,比如说如果一个进程他正常的终止,或者因为运行的过程当中,发生了某种异常,而不得不终止,再或者一个进程他发出了一个IO请求,然后主动的请求进入阻塞状态,然后用来等待IO完成,这些情况,都是进程主动放弃,处理机的一个具体的例子,那被动放弃处理机就比如说呃,给一个进,给这个进程分配的时间片用完了,或者说有一个更紧急的事情需要处理,再或者当前有一个,优先级更高的进程进入了就绪队列,那么这些情况下又有可能需要呃,把这个当前运行的进程,强行的剥夺他的处理机使用权,然后他就不得不被动的放弃处理机,而进程调度,并不是什么时候都可以进行的,有的时候不能进行进程调度和切换,比如说在我们处理中断的过程当中,由于中断处理它的过程是很复杂的,并且和硬件是息息相关的,因此在中断处理的过程中是很难做到,呃就是中断处理处理到一半,去进行进程调度和切换的,而第二种情况是进程在,操作系统内和程序临界区中,这种情况也不能进行进程调度,这点不太容易理解,之后咱们还会继续展开细聊,第三种情况是在原子操作的过程中,也就是之前咱们介绍过的一些原语,比如说,咱们之前讲原语的时候举过一个例子,在一个进程他的状态发生改变,比如说从阻塞态变为就绪态的时候,那么我们继续要修改进程PCB当中,这个进程状态的标志位,同时也需要把PCB,放到一个新的相应的就绪队列里,那么如果说我们只做了前面这一件事,中间就开始进行进程切换进程调度了,那么就有可能会导致,PCB当中记录的这种状态标志,和PCB实际放的啊这个就绪队列,还是阻塞队列,实际放在这个位置,不相匹配的这种情况,而这种数据的不匹配,就有可能会为系统造成一些安全隐患,所以说,这种原语或者说原子操作中间的过程,肯定是不能允许进行进程切换的,那么接下来我们再来细聊一下,呃第二个问题进程,
在操作系统内和程序临界,区中不能进行调度与切换,这是正确的一种表述,但是,大家在做一个课后习题的时候会发现,12年的一个题当中有个选项是说,进程在处于临界区时不能进行,处理及调度,这个表述是错误的,在了解内核程序临界区,和普通的临界区的区别之前,我们需要先,聊一个现在暂时还没有接触的概念,叫做临界资源,临界资源就是指一个时间段内,只允许一个进程使用的资源,各个进程需要互斥的来访问临界资源,你访问的时候我不能访问,我访问的时候你不能访问,这是互斥的意思,那么临界区就是指,访问临界资源的那段代码,因此呃,各个进程肯定也只能,互斥的进入临界区,互斥的执行啊临这这一段,访问临界资源的代码,而内核程序的临界区也就是前者,一般就是用来访问,某一种内核数据结构的,比如说进程的就绪对列,那么当一个进程他,此时处于一个内核程序临界区,并且这个临界区,他是要访问就绪队列的话,那么在访问之前,他会把这个就绪队列上锁,而如果说这个进程,当前还没有推出内核程序临界区的话,也就意味着这个临界资源,并没有被解锁,那么在没有解锁的过程中,如果说我们要发生进程调度的话,那么进程调度相关的程序,肯定是需要访问就绪队列,这个临界资源的,因为他要从就绪队列当中,挑选一个进程,为他分配处理机,那么由于这个就绪队列,这个临界资源他此时还是上,锁的一个状态,所以如果说在这种情况下,去进行进程调度的话,那么这个进程调度肯定是没办法,顺利的进行下去的,因为此时这个还没有解锁,所以可以看到,对于内核程序临界区,访问的这些临界资源,也就是这些内核数据结构而言,如果这些内核数据结构啊,这些临界资源,被上锁了,并且没有被尽快的释放的话,那么有可能,会影响到操作系统内,和其他的管理工作,就比如说刚才咱们聊到的进程切换,所以说,我们在访问这些内核程序临界区的呃,时呃这些期间内,我们不能进行进程的调度和切换,我们必须让这个进程迅速的呃,尽快的执行完,内核程序临界区的那些代码,然后完成对这个临界资源的访问,之后就尽快的把这个,对临界资源的锁给解除,只有这样,其他的操作系统内核才可以继续呃,有序的进行管理工作,而另外一种情况,假如此时这个进程,访问的是一种普通的临界资源,比如说是一个打印机的话,那么他在访问他的时候,会开会先进行上锁,那打印机在完成打印完成之前,这个进程其实是一直在临界区内的,他还一直保持着对打印机的访问,但是由于他还没有推出临界区,所以这个临界资源并不会被解锁,但是打印机他又是一种慢速的IO设备,如果这个情况下,呃不允许进程调度进程切换的话,那么就会导致这个进程,他需要一直空,等着这个打印机的打印结束,所以在这个进程空等的过程当中,他同时还霸占着CPU,所以CPU可以说一直是在空闲的状态,他一直没有做一些有意义的事情,所以如果说呃,这个进程在访问一个普通的,临界资源,在一种普通的临界区当中的话,这个情况下,其实是应该进行进程调度的,因为普通的临界区,访问的这些普通的临界资源,并不会呃像之前所说的这个例子一样,直接的影响到操作系统,内核的管理工作,所以说为了增加系统的并发度,增加CPU的利用率,那么在访问这些普通的临界区的时候,是可以进行进程调度和切换的,那么讲到这里刚开始的这两个问题,为什么前者对为什么后者错,相信大家已经有所理解了,那么接下来我们再来看下
二、进程调度的方式
个问题在有的操作系统当中,他只允许进程主动的放弃处理机,而不允许这个进程在运行的过程中,被迫的被剥夺处理机资源,但是又有的操作系统他是允许啊,当有更紧急的人物处呃,需要处理的时候,他是会强行的,剥夺这个当前运行进程的,处理及资源的,所以由这个问题就是,当前运行的进程,是否可以被强行的剥夺处理及资源,这个问题我们引出了下一个知识点,就是进程调度的方式,分为非剥夺调度方式和剥夺调度方式,前者又可以称作非抢占式,后者又可以称作抢占式,那么,非剥夺调度方式就是咱们刚才聊到的,只允许啊,进程主动的放弃处理机这样一种方式,除非这个进程他正常或者异常的终止,或者他主动的要求进入阻塞他,不然这个处理机,是会一直为当前运行的这个进程,所呃为他服务的,那么抢占式或者说剥夺调度方式的话,如果说此时有一个更重要,更紧急的任务需要处理的话,那么,这个当前执行的进程就会被暂停执行,剥夺处理机资源,然后把处理机分配给更重要,更紧急的那个进程进行处理,那显然前面这种方式,它的这个规则会相对来说要简单一些,所以这种方式实现起来要简单,并且系统管理的开销也要更小,但是缺点就在于,他没有办法及时的处理紧急任务,所以呃这种非抢占式,只适合于早期的一些批处理系统,而后者抢占式,它可以优先的处理更紧急的进程,并且可以让各个进程按照,时间片轮转的这种方式来执行,当时间片到的时候,就可以强行的把当前运行的进程,把它的处理及资源给剥夺,所以说后面这种方式,它就比较适合于,后面,出现的分时操作系统和实时操作系统,这就是进程调度的方式的问题,分为这样两种,那么既然我们选,
三、进程的切换和过程
选择了一个进程,要为他分配处理机的话,在进程与进程切换的过程当中,又发生了一些什么事情呢,首先我们来聊一聊呃进程,狭义的,进程调度和进程切换有什么区别,狭义的进程调度指的是从就,绪队列中选中一个要运行的进程,这样一个过程,而这个要运行的进程,他可以是刚刚才暂停执行的进程,也可以是另一个进程,如果选中的是另一个进程的话,那么我们就需要进行进程切换,而进程切换指的就是,呃之前的那个进程让出处理机,然后之后的进程占用处理机,这样的一个过程,而有的题目当中出现的进程调度,它指的是广义的进程调度,广义的进程调度包含了进程切换和,选择一个进程,这样,这前面的就是前面所说的这两个步骤,所以在读题的时候,如果出现了两种调度的含义,大家不要觉得奇怪,自己能够分辨,它是狭义的还是广义的就可以了,进程切换的过程,主要完成了什么事情呢,其实我们可以把这个过程,简化为两件事情,一件事就是把,对原来正在运行的那个进程,他的各种数据,包括处理机的运行环境,这些数据把它保存下来,第二件事,就是把新的进程的各种各样的,运行环境之类的数据,把它恢复过来,而运行环境运行现场的一些信息,比如说程序计数器,程序状态自然各种数据寄存器,这些信息一般是存在,进程控制快PCB当中的,所以呃对这个数据进行保存,其实保存到了PCB当中,而对这些数据进行恢复,其实从PCB当中读出这些数据,并且把这些数据,放到相应的寄存器当中,那么经过刚才的分析我们会发现,进程的切换,其实其实并不是啊一瞬间就可以,完成的,它是需要付出一定的时间代价的,所以我们不能简单的认为,进程切换越频繁,进程的并发度就越高,如果进程的切换调度过于频繁的话,其实反而会导致整个系统的效率,降低因为系统会把大部分的时间,都花在这个进程,切换的这个开销上,而真正用于执行进程,推进进程的时间反而会减少,所以进程切换是有代价的,了解这一点,对于理解咱们之后讲时间片的,一个调度算法呃,是至关重要的,这个地方大家稍微注意一下,好的那么我们再来快速
四、总结
就回顾一下这个小节的内容,这个小节的内容并不多,并且也不算是考试的重点,不过大家也需要理解,首先我们介绍了进程调度的时机,包括什么时候需要进程调度啊,分为进程主动放气处理机和进程,被动放气处理机,这两种情况,并且我们举了一系列的例子,之后我们介绍什么时候,不能进行进程调度,重点介绍了,进程在操作系统内核程序临界区当中,为什么不能进行进程啊调度,因为呃这个时候进程进行进程调度,有可能会影响到,操作系统内核的一系列管理工作,而如果进程处于一个普通的程,序临界区当中,他访问的是一个普通的临界资源的话,这种情况下是可以进行,进行进程调度的,这点是不太容易理解的难点,但是也不算重点,那么之后我们要介绍进程切换的过程,其中大家最需要,注意最需要理解的是,进程的调度和切换是有代价的,只需要知道这一点就可以了,其他的这些,要有一个大体的印象就可以,另外呢,我们还介绍了进程调度的两种方式,分为强战士和非强战士,而这两种调度方式,会在之后咱们讲调度算法的时候,大量的出现,所以继续学习后面的内容,会对这两个部分的理解会更加深入,好的那么这就是这个小节的全部内容
No.10 调度器和闲逛进程
接下来调度器或者叫调度程序,很简单的一个概念,调度程序是操作系,同内核的一个呃,非常非常重要的呃一个程序模块,我们说一个进程,会在就绪运行阻塞之间来回横跳,那2和3这两个呃状态的转换,就是由调度程序来负责完成的,操作系统的调度程序,要决定这样的两个事,首先让谁去运行,这就涉及到这个调度程序,它采用的调度算法是什么,先来先服务,短作业优先还是时间片轮转等等,那除了让谁运行之外,还需要决定运行多长时间,不同的进程,可以给他分配不同的时间片大小,好所以这就是操作系统的调度程序,反正就是用来管调度的,当前运行的进程要不要让他下处理机,如果他下了处理机,那么接下来就去对列里的这些进程,要让谁上处理机运行,这就调查程序要管的一个事情,那之前我们讲过这样的一个知识点,叫做进程的调,度时机,那现在我们也可以换一种说法,就是,什么样的事件会触发这个调度程序啊,他开始工作,首先创建一个新进程的时候,这旧区队列是不是会发生改变,那旧区队列一变,是不是就有可能让这个呃新进程抢占,当前正在运行的这个进程的一个CPU,所以创建新进程的时候,调度程序哎会出来工作一下,检查一下要不要让这家伙上处理机啊,除此之外,进程退出也会触发调度程序的工作,一个正在运行的进程,他此时决定自己终结自己对吧,那他终结了自己处理机不就空闲了吗,因此调度程序就得出来看一下,接下来让谁上处理机工作好,除此之外,一个正在运行的进程阻塞,显然也需要调度程序出来看一下对吧,接下来让谁上处理机,再者发生i o中断的时候,有可能使得某些阻塞进程回到角序态,那同样的原理,只要就绪队列一改变,那么调度程序就需要啊出来检查一下,这新旧绪的进程,应不应该让他上处理机运行,应不应该让他抢占等等好,另一点值得注意的是,如果我们采用的是非,抢占式的调度策略,那么只有运行的进程,阻塞或者退出的时候,才会触发这个调度程序的工作,而如果采用的是抢占式的策略,那么每个时钟中段,或者每k个时钟中段,都会触发调度程序出来呃检查工作,这个很好理解吧,如果是抢占式的调度策略,那就意味着只要旧绪队列一改变,那就必须,检查一下新旧绪的进程,有没有可能抢占,当前正在运行的这些进程,那这个检查的动作,就是由时钟中断来触发的,每过一个时钟周期,或者每过k个时钟周期,都例行的唤醒一下这个调度程序,让他来检查,此时就绪队列有没有新进程到达,如果有那么,需不需要让他抢占,当前正在运行的进程,而如果是非抢占式的调度策略,那只有当前运行的进程,他主动放弃处理机的时候,才有必要唤醒这个调度程序,让他检查一下,对吧平时只要这个进程还在运行,那我们就没有必要频繁的唤醒,这个调度程序,因此非抢占式的这个调度策略,不会由时钟中断来唤醒调度程序,而这就是所谓的调度程序,你只需要知道他是干嘛的,以及什么时候,这家伙会出来工作就可以了,那刚才我们说调度的时候,我们说的是进程的调度,如果一个系统他支持的不只是进程,还支持现程,那么调度程,
No.11 调度算法的评价指标
各位同学大家好,在这个小节中,我们会学习,一系列用于评,价一个调度算法好坏的一些评价指标,包括CPU利用率系统吞吐量,周转时间等待时间和响应时间,那在学习的过程中,大家要,注意理解各个指标为什么这么设计,并且要会计算每一个指标,首先来看
一、CPU利用率
下什么是CPU利用率,其实在早,期的呃计算机当中,计算机的造价是很昂贵的,特别是CPU,这个部件的造价占了很大一部分,它这这个东西,基本上就是用钱堆出来的,一个一个很奢侈的东西,并且在现代的这些计算机当中,其实CPU也不便宜,所以因为CPU这么贵,那么人们就会希望让CPU,尽可能多的为人们工作,所以就设计了一个叫做CPU,利用率的这样一个指标,这个指标就是用来呃,就是用来表示CPU处于忙碌的时间,占总时间的比例,那么这个利用率就可以啊,用忙碌的时间比上总时间,就可以算出这个利用率啊,但是有的题目,它不仅仅是会让我们算CPU利用率,还会让我们算某种,比如说某种IO设备的利用率,比如说如果呃一个计算机当中,他只支持单道程序,然后有个作业,刚开始的时候需要在CPU运行5秒,然后打印输出5秒之后再执行5秒,然后就结束,那么在这个过程中CPU利用率就是呃,就是先刚开始运行了5秒,然后之后又运行了5秒,总共运行了10秒,CPU处于忙碌的时间是10秒,然后整个过程处理的时间是5+5+5也就15,那么就是66.66%将近是这样子,然后打印机的利用率的话,就是打印机处于忙碌的时间,也就是5秒,在比上总时间那么就是33.33%,大概是这样,但是在真正的考研题目当中,通常会考察多道程序并发执行的情况,这种情况下,大家可以用班特图来辅助计算这,个地方先不展开,在课后习题会有遇到,并且也会有相应的讲解,那么第二个评,
二、系统吞吐量
评价的指标就是叫做系统吞吐量,那么对于计算机来说,呃计算机他肯定是希望,用尽可能少的时间,可以处理完尽可能多的作业,所以就呃,设计了一个叫做系统吞吐量的指标,用来表示,单位时间内完成了多少道作业,所以系统吞吐量可以用,总共完成了多少道作业,再除以总共花了多少时间,就可以算得系统吞吐量,比如说有个计算机,处理完10道作业花了100秒,那么吞吐量就是10/100,那么就是0.1道每秒,也就是平均每秒可以完成0.1道作业,这就是单位时间内完成的作业的数量,那这就是系统吞吐量,第三个指标是周转时
三、周转时间
对于计算机的用户来说,这个用户肯定是很关心,自己的作业从提交到完成,总共花了多少时间,一般来说,这个时间当然是花的越少越好,所以周转时间就是用来反映这样一个,呃所花费时间的一个指标,就是指从作业被提交给系统开始,到作业完成为止,这段时间到底有多长,那么它总共包作业,这个周转时间总共包括4个部分,就是作业在后外存的后备堆列上,等待被作业调度的时间,然后进程在就绪堆列上的时间,还有进程处于运行态的时间,还有进程处于阻塞态的时间,后面的这3项就是就绪态运行态,和进程和阻塞态,这三项,在整个作业的这处理过程当中,是会发生多次的,整个过程中只会有一次作业调度,这个咱们在之前的小节当中,也有介绍过,所以周转一个作业的周转时间,我们可以用作业的完成时间,减掉作业被提交给系统的时间,这样就可以简单的计算出来啊,另外呢对于操作系统来说,他肯定是会更关心整体,系统的整体表现,所以他会更关心所有的作业,周转时间的一个平均值,所以,就有另外一个指标叫做平均周转时间,就是用各个作业的周转时间之和,再除以再再除以作业的数量,那么我们再来呃思考一个这样的问题,对于各个用户提交的这些作业来说,有的作业它的运行时间是短的,有的作业运行时间是长的,所以说如果在周转时间相同的情况下,其实运行时间,啊更长的那些那些那些作业,对于用户来说,肯定感受会稍微更好一些,举一个很直观的一个有味道的例子,比如说我们去排队等厕所,那么你本来只需要呃使用1分钟,但是你要排队排10分钟,就是总共整个周转过程,你总共花了11分钟,这种这种感受肯定是很糟糕的,本来自己只需要用一会,但是又需要等待那么长的时间,不过对于另外一个人来说,他总共需要使用十分钟,但是他只需要等一分钟,所以另外这个人,他的整个周转时间其实也是11分钟,只不过呃,这11分钟当中,只有一分钟是用来等待的,所以对于第二个人来说,这一分钟的等待其实对他来说,感受没有那么糟糕,所以这就是,呃在周转时间相同的情况下,啊作业的这个实际运行时间长短不同,所导致的对于用户的感受的这种区别,因此,人们又提出了另外一个指标叫做待权,啊啊周转时间,就是指作业的周转时间,在比上作业实际运行的时间,因此可以看到,对于周转时间相同的两个作业来说,他的如果哪个作业的实际运行时间,更长那么这个作业啊,相应的被服务的这个时间,所占的比例也就更多,那么这个待全的周转时间就会更小,用户满意度相应的也会更高,而对于实际运行时间,相同的两个作业来说,肯定是周转时间短的那个作业,对于用户来说他的满意度会更高,那么周转时间短的话相应的待全,周转时间也会更小,所以通过这个式子我们会发现,待全周转时间他肯定是大于等于一的,因为呃,周转时间包含了作业的实际运行时间,他肯定比实际运行时间要更大,那么待全周转时间和周转时间都,呃都是一样的,他们肯定都是呃越小越越小,对于用户的体验来说就会越好,相应的和平均周转时间一样,也会有一个所谓的平均待全周转时间,这个就是系统会比较关心的一个指标,呃,这个就是把各个作业的待全周转时间,给加合起来,最后再除以一个作业数,那么这就是周转时间相关的4个指标,接下来我们再来看下一个
四、等待时间
指标叫等待时间,对于计算机的用户来说,肯定是希望自己的作业啊,尽可能少的等待处理机,那么等待时间就是用来,度量这个用户的作业,等待处理机啊,被等待被服务的这个时间之和,到底是多少,等待越长肯定用户的啊满意度就越低,那么我们先来看一下,当一个作业刚开始被提交的时候,它是被放到了外存中的,作业后备对列当中,作业在后备队列当中需要等待被服务,也就是被作业调度,当他被调度以后,这个作业就会呃放到内存当中,并且建立起相应的进程,当这个进程建立了之后,他会被CPU服务也会被IO设备服务,当然也会有,等待被服务的这样一些时间,一直到最后整个进程结束,然后把作业移出内存,那么对于进程来说,一个进程的等待时间,其实指的就是这个进程被建立起之后,开始呃开,开始累计,他等待被服务的时间总和是多少,但是需要注意的是,他在等待IO完成的这个期间,其实是正在啊被正在被IO设备服务的,所以,这种时间是不能算到等待时间里的,另外呢对于作业来说,我们不仅要考虑他呃,建立了相应的进程,之后的这一系列的等待时间,我们还要加上,他在外存的后备队列当中,等待被调度的这一段时间,所以呃,作业的等待时间和进程的等待时间,计算起来是有一些不同的,这个稍微注意一下,通过之后的课后习,题大家会发现一个现象,就是一般来说一个作业,他总共需要被CPU服务多久,被IO设备服务多久,这些,这个总时间一般来说都是确定不变的,所以调度算法其实只会影响作业,或者说进程的,等待时间,当然和之前的那些指标一样,等待时间也有一个对应的,与他对应的指标,叫做平均等待时间,那么平均等待时间,就是把所有进程或者作业的等待时间,做一个加和,再除以作业的数量就可以了,最后我们再来讲一个
五、响应时间
呃叫做响应时间的一个指标,对计算机用户来说,如果说他提交了一个请求,比如说,就是在键盘里输入了一个调试命令,那么他肯定是希望尽早的被,呃系统服务,被回应他提出的这个请求,所以响应时间,指的就是这个用户从提出请求,到首次产生响应所用的时间,这就是响应时间,好的那么我们再来回顾一下这个小节,我们介绍了5种
六、总结
用来评价调度算法的指标,那么大家要理解,各个指标为什么这么设计,并且还会还要会计算各个指标,其中CPU利用率和系统吞吐量,咱们举了一两个比较简单的例子,后面的其他的这些指标,我们会在之后的,对于算法的讲解当中在,不断的进行实践,所以这个地方暂时没给出具体的例子,那这个小节当中比较容易忘记的是,待全周转时间和平均待全周,转时间这两个指标,那么大家需要结合,咱们举的那个比较有味道的例子,来理解这个指标为什么这么设计,只要理解了其实记住它就不难了,好的那么这就是这个小节的全部内容,,
No.12 调度算法
各位同学大家好,在这个小节中,我们会学习几种调度算法,分别是先来先服务,短最优先和高响硬币优先这三种算法,那么在学习这个小节的过程中,我们会按照一定的框架思路,来依次分析各个算法,首先我们会介绍每一种算法的提出,他是呃想解决什么问题,出于什么目的而提出的这个算法,然后出于这个目的,他又采用了什么样的算法规则,之后这些算法到底是用于作业调度,还是用于进程调度,用于作业调度和进程调度的时候,有没有什么区别,这个也是我们需要关注的一个问题,之后呢各个算法可能会,呃有抢占式的版本和非抢占式的版本,这个就是咱们之前介绍的调度方式,呃强调的两个知识点,抢占式和非抢占式,另外我们在考试的选择题当中,还经常会考察各个算法的优点和缺点,所以这个也很重要,第六个我们会呃,重点关注一下,各个算法有没有可能导致饥饿的问题,饥饿这个概念可能之前还没有提过,其实很简单,就是指如果一个进程或者一个作业,长期得不到服务,那么这个进程或者说作业,它就是处于饥饿的状态,那么我们,会按照这样的框架,来依次分析各个算法,首先来看先来先服务算法,
一、FCFS
这个算法的思想,其实和我们日常生活中排队买,东西特别类似,主要是从公平的角度来考虑啊,和他的名字一样,也就是说,先到达呃,就绪队列,或者先到达后背队列的这个进程,或者作业,就先为他提供服务,那么当用于作业调度的时候,考虑的其实是,哪一个作业先到达后背队列,这个后备队列是在外存中的,咱们在上一个小节,也强调过这个知识点,呃如果说是用于进程调度的话,那么考虑的是哪一个进程,先到达的就绪队列,就绪队列是在内存中的,这是呃当他作为作业调度,或者进程调度的时候,有的一个小小的区别,另外呢先来先服务算法,一般是非抢占式的算法,也就是说,对于当前正在占用处理机的那个进程,或者说作业,那么只有这个进程,他主动的放弃处理机的时候,才会进行调度,才会用这个调度算法的规则,来选择下一个应该得到服务的进程,那么我们直接来看一个具体的例子,可能会更容易理解,有这样几个进程P1到P4四个进程他
二、FCFS例子
他们的到达时间和运行时间分别是,呃这样的一个一个数据,那这个时间大家认为是秒,或者毫秒都可以,这个无所谓,那么如果我们采用的是,先来先服务的标读算法,我们呃一会会计算一下,这些进程的等待时间,平均等待时间周转时间平均周转时间,还要待全周转时间这些啊,我们在之前的小节当中介绍过的,评价一个调度算法的指标,先来先服务,调度算法啊它的规则很简单,其实就是按照到达的先后顺序,来依次进行调度,所以啊因为他是到达的先后顺序,所以事实上,他考虑的是哪一个进程,他到了之后等待的时间更长,等待时间越久的进程,就会越优先的得到服务,那么这是先来先服务,调度算法考虑的一个点,也就是考虑的是,各个进程或者作业的等待时间,所以这个调度顺序,其实很容易就看出来,就是根据调到达顺序0245,他们P1 P2 P3 P4是依次到达的,所以会按这样的顺序进行调度,那么P1从0时刻到达就开始运行,然后运行了7个时间呃之后,那么P2是第二个被调度的,它会运行4个时间,之后是P3运行一个时间,然后P4运行四个时间,就是这样的顺序,那么,我们来计算一下他们的这几个指标,周转时间的话,其实就是用,各个进程的完成时间减掉到达时间,那P1是在7这个时刻完成的,而他到达的时间是0,所以P1的呃周转时间就是7减0也就是7,而P2是11这个时刻完成的,但是他到达的时间是2,所以P2的周转时间就是11-2也就是9,那么P3和P4这些也是类似的,而待权周转时间,和周转时间是不一样的,待权周转时间,我们要用周转时间除以运行的时间,那么,P1就是周转时间是7它运行的时间也是7,所以P1的待权周转时间是1,P2 P3 P4这些啊大家再具体思考一下然后,这儿就不展开了,等待时间的话,其实呃在这个题当中比较简单,我们可以直接用周转时间,减掉运行时间,其实就是各个进程的等待时间,所以呃把上面的这些式子依次的,除号变成减号,然后一计算,就可以得到各个进程的等待时间,分别是多少,但是大家需要注意的一点是,在这个题目当中,我们给出的这个例子当中的这些进程,其实都是纯计算型的进程,也就是说,这些进程其实只需要CPU为他们服务,呃所以说一个进程在他到达以后,肯定只会有两两个状态,要么他就是在等待被CPU处理,呃被被调度,要么他就是处于运行的状态,所以对于这个题目,这种纯计算型的进程来说,我们只需要用它的这整个周转时间,来减掉它的运行时间,那么剩下的肯定就是,呃等待时间这个部分,但是有的题目可能会给出的是呃一些,既有计算,还有IO操作的这些进程,那么,在计算这些进程的等待时间的时候,我们就不能简单的用周转时间,减去运行时间,这样来计算,我们呃需要用周转时间,也就是他呃,整个完成时间到到达时间,这这个总的时间段的长度,减掉他在CPU上运行的时间,还要再减掉,IO设备为它服务的这个时间,这样得到的才是这种啊,又有计算,又有IO操作的进程的等待时间,这个地方大家稍微注意一下,所以这个公式不要,认为它是一成不变的,要具体问题具体分析那,这种例子我们暂时不展开,如果在课后习题遇到的话,大家自己再巩固一下,那平均周转时间,平均在全周转时间和平均等待时间,计算起来就很简单了,其实就是把他们这些数据全部加起来,然后除以进程的个数4,那么分别得到的是这样的数据,现在我们先不用关心,一会这些数据还会再列出来,所以这就是先来先服务算法啊,他具体的一个调度的规则很简单,就是按照到达的时间顺序,或者说呃等待的时间越久的进程,他就越发排在前面,就是按照这样的规则来进行调度的,那么,在这个地方大家可能会注意到一个点,就是对于P3这个进程来说,我们在计算它的待全周转时间的时候,这个全值我们计算到了8,是非常大的一个值,因为待全周转时间其实表示的是,这个进程的整个周转时间,比运行时间要大多少倍,这样一个这样一个呃指标,所以说债权周转时间这么大,也就意味着,这个进程本来只需要很少的时间,为他服务,但是他需要等很长的时间,才可以被处理完,因此对于P3这个进程的用户来说,可能他的体验就是特别糟糕的,那么啊我们接下来就再来讨,继续讨论一下他的优缺点,优点的话其实就很公平,因为类似于我们平时排队买东西,先到先先到的先被服务,而且这个算法其实很简单,实现起来呃也不复杂,那缺点呢,就是刚才我们提到的P3那个进程,待权周转时间算出,来会特别的大,也就是说,对于一个排在,长作业后面的短作业,或者排在长进程后面的短进程来说,他需要等待很长的时间才可以被服务,那么,对于短进程或者短作业的用户来说,就体验会特别糟糕,所以呃,先来先服务算法其实是对长作业有利,对短作业不利的,那么这个地方,为了让大家能够有更感性的体验,我们来举一个排队买奶茶的例子,假如说你去某一个奶茶店排队买奶茶,那么一般来说,他们采用的规则就是先来先服务,那先到的肯定先先给你做奶茶,但是如果这个时候你的面前,突然出现了一个呃,要买20杯奶茶,那么这家店,可能做这20杯奶,茶需要半个小时的时间,虽然说你只买一杯奶茶,做你的奶茶的时间只需要一分钟,但是由于,前面你的前面突然出现了一个长作业,或者说长进程,所以,对于你这个短作业或者短进程来说,你的体验就会特别糟糕,你可以算一下你的代权周转时间,就会特别特别大,所以它是对长作业有利,对短作业不利的啊,这样讲,大家应该就可以体体会到什么叫有利,什么叫不利了,那么对于先来先服务算法来说,其实啊是不会导致饥饿的,因为不管是哪个作业,他只只要一直等着,反正他前面的那些作业或者进程,总会被处理完,所以他是不会导致饥饿的,这就是先来先服务算法,那么从刚才这个例子我们已经知道,先来先服务算法对于啊大权周转时间,还,有平均等待时间这些指标其实是啊,不太优秀的,所以短则有优先算法的提出,其实就是为了追求更少的啊,平均等待时间
三、短作业优先
l平均周转时间平均带权周转时间,那短作业优先算法它的规则也很简单,就是最短的作业或者进程,优先的得到服务,而这个地方所谓的最短,其实是指这个作业或者说进程,它要求服务的,要求被服务的时间最短的那个,优先得到服务,那么,短作业优先算法呃是用于作业调度的,当然也可以,这种规则就是最短的优先被服务,这种规则也可以用于进程调度,但是用于进程调度的时候,一般是称为短进程优先算法,也就是SPF这个呃这个j指的是job,然后这个用作进程调度的话,这个p指的是process就是进程,那短作业优先算法,默认一般来说是非强占式的,但是也有短作业优先,算法的强占式版本,称为最短剩余时间优先算法,SRTN这是它的英文缩写,那么我们具体来看一下,用一个例子来看一下,这两种算法到底有什么区别,
四、短作业优先例子
还是刚才的那个呃题目,只不过我们把它改成了,使用非强战士的短作业优先算法,也就是SGF这个算法,那这个例题当中给出的是,用于进程调度的,呃这种场景,所以这个地方严格来说我们应该是说,使用非强战士的短进程优先调度算法,而不是短作业优先,不过很多,题目中也不会太在意这个细节,大家只需要知道,它们的规则其实是一样的,就是根据呃,要求服务时间,也就是运行时间,来做优先级的排列就可以了,那这个调度算法就是每次调度的时候,他会选择当前已经到达的,注意是当前已经到达了,并且运行时间最短的一个作业,或者进程,为他进行服务分配处理机,所以这个调度顺序也很好确定,在0这个时刻,只有P1这个进程是已经到达的,所以这个时候只能为P1这个进程服务,只能调度P1,所以第一个进程肯定是P1,当P1运行完了之后,也就是到了时刻7这个时刻,其他的这些所有的进程,全部都已经到达了,但是,P3的要求运行的这个时间是最短的,所以P1之后会调度P3,当P3运行完了之后,还剩P2和P4两个进程,但是由于P2它是先到达就绪队列的,所以虽然它俩的运行时间是一样的,但是会优先调度P2这个进程,因此整个过程的运行情况是这个样子,那这个地方我们可以用这个图,结合各个进程的到达时间,来很快速的算出每个进程的周转时间,待权周转还有等待时间分别是多少,并且把这些指标加加和,再除以4之后就可以得到平均周转,平均待权周转和平均等待时间,这个地方大家也最好自己动手算一下,我们和之前先来先服务算法,得到的这三个指标来进行一个对比,会发现采用,了非抢占式的短作业优先算法之后,平均周转时间从8.75降为了8,平均待权周转从3.5降为了2.56,而平均等待时间从4.75降为了4,所以从这个这三个指标来看,短作业优先算法在呃这些方面的表现,它是要优于先来先服务算法的,我们如果采用的是,抢占式的短作业优先调度算法,那这个抢占式的短作业优先
先刚才我们也已经强调过,它也有另外一个名字,叫做最短剩余时间优先算法,英文缩写是SRTN,那用这个算法的话由于它是抢占式的,所以每当有一个新的进程,进入到就绪队列的时候,会引发就绪队列改变,当旧序队列改变的时候,我们就需要看一下,他到底这个新来的进程,到底会不会抢占处理机,所以当这个旧序队列改变的时候,我们就需要,计算一下当前新到达的那个进程,他的剩余时间,就剩余的运行时间,比起当前正在运行的这个进程,剩余的时间到底是不是更短,如果更短的话,我们就会把新来的这个进程,让他抢占处理机,所以这就是抢占式造成的一个结果,那么嗯当前这个正在运行的进程,如果被抢占处理机之后,他就会回到旧绪队列当中,另外呢如果说当前运行的进程,主动放弃处理机的时候,也就是一个进程他正常的完成的时候,也需要用这样的调度算法,来进行一个呃调度,所以这是这种算法当中,我们需要注意的两个时,间点一个是就绪队列改变的时候,另外一个是一个进程完成的时候,那么我们按照这样的规则,来依次分析一下各个时刻,首先在0这个时刻只有P1到达,那么P1当前的剩余时间,也就剩余的运行时间,就是整个也就是7个时间,那么呃就是这个样子,所以此时因为只有P1在就绪队列当中,当然是为P1分配处理机,P1上处理机运行,但是当P1运行了两个时间,呃两个单位的时间的时候,在2这个时刻P2到达了就绪队列,因此就发生了,就绪队列发生了改变这样的事情,所以这个时候,系统会重新计算一下,当前正在运行的这个进程,P1剩余5个时间5个单位的运行时间,而新到达的进程P2,它只剩于啊总共4个单位的时间,所以P2的剩余时间更短,因此会选择P2这个进程,让它抢占处理机,然后P1 重新回到就绪队列,同样的,到4这个时刻虽然P2还没有完成,但是P3这个进程到达,由于此时P3的运行剩余运行时间,比起前两个,比起其他进程来说是最少的,所以这是这个时候P3,呃正常的呃被抢占处理机,到第5这个时刻P3正常完成了,但同时这个P4进程,他也在5这个时刻到达,所以此时就绪队列中剩余P1,P2 P4这样四个进程,但是,P2之前已经运行了两个单位的时间,他在运行两个单位的时间就可以了,所以P2又会啊被分配处理机P2运行,在P2运行了剩余的两个时间之后啊,又剩下P1和P4,再一对比又是P4的剩余时间最短,所以P4又会上处理机运行,当P4运行完了之后,才是最后再把P1上处理机,把剩下的这五个单位的时间运行完,所以整个过程就是这个样子,大家再结合我们刚才的分析,也可以动手画一下,那这个地方,我们可以结合这个图,和各个进程的到达时间,就可以很方便的算出,咱们之前提到过很多次的,这一系列的指标,这个地方我们需要注意的是啊,抢占式的短最优先算法中,这些进程的执行可能是断断续续的,比如说P1执行了这样一段,还有这样一段,P2也是执行了这样两个呃两段,那么这和咱们之前,介绍的那两种算法是不太一样的,所以对于这个算法的这些指标的计算,大家也啊要自己动手算一下,看看能不能得到正确的结果,那我们再把之前,非抢占式的短最优先,得到的这三个指标,把它们放在一块,我们会发现,采用了抢占式的短最优先,或者说最短剩余时间优先之后,得到的平均周转,平均待全周转,还要平均等待时间这三个指标,比呃非抢占式的还要更小,也就意味着,采用抢占式的这种算法之后,他在这些方面的表现,要优于非抢占式的短最优先算法,所以这个地方大家学,除了了解这两种不同的,抢战士和非抢战士,在具体的做题的过程当中,有什么区别之外,我们还需要注意几个小细节
如果说在题目当中提到,短作业优先或者短进程优先算法的话,那么默认的其实是非强占式的,但是在很多书上,包括咱们的王道书上,大家会看到这样的一句话,就是短作业优先算法啊,他的,这种算法得到的平均等待时间和平均,周转时间是最少的,但是根根据刚才咱们那个计算的结果,可以看到,其实啊最短剩余时间优先算法,所得到的平均等待时间和平均周转,时间还要更少,所以这句话,其实严格来说这个表述是错误的,不严谨的,如果要让这句话变得更严谨一点,我们应该加一个条件,在所有的进程同时可运行的时候,采用短最优先调度算法,所得到的平均时间或者平,平均周转时间是最少的,或者我们还可以改一种说法,如果说所有的进程,几乎都是同时到达的,那么采用这种算法,得到的平均等待时间和平均周转时间,是最少的,那这个地方为什么要强调,要用一个几乎这样的表述呢,因为,进程的到达肯定还是有先后顺序的,只不过在宏观上看我们可以认为就是,呃连续到达的那些很快速的到达,那些进程几乎是同时到达的,然而他们事实上也会有先后顺序,只不过是微观上看有先后顺序,宏观上看这些进程几乎同时到达,还有另外一种说法,如果说我们不要这个,所有的进程都几乎同时到达,这个条件的话,我们可以说抢占式的就是,刚才所提到的那个最短剩余时间优先,这个算法,所得到的,平均等待时间和平均周转时间,是最少的,这个是没有问题的啊,如果数学基础好的同学啊,可以试试看能不能证明这个算法,为什么是平均等待时间,平均周转时间最少,第三点虽然短作业优先算法,它的平均等待时间,平均周转时间不一定是最少的,这个刚才咱们已经说过,但是相比于像先来先服务,还要最高享一笔优先,相比于这样的算法来说,短作业优先算法其实,仍然是可以获得,比较少的平均等待时间,平均周转时间这两个指标的,所以啊在有的题目当中有可能会遇到,就是,刚才咱们说的这个这句话这样的选项,那如果说遇到这样的选项的话,大家一定要再判断一下,其他的,哪三个选项是不是有很明显的错误,如果别的那些选项都错的很明显的话,那我们也可以选择这个选项,认为它是正确的,因为我们操作系统这门课,其实它和啊像物理啊数学啊这些啊,基础理学的这些学科不太一样,它并不是一个,对于很多概念,很多算法的这一些说法定义,可能不同版本的教材也会有所不同,所以他并没有一个很严格的说法,所以大家在做课后习题的时候,可能也会,发现有一些我们在之前讲的,还有课后习题当中说法不一致,前后有那么一点点矛盾的那种现象,但是大家也需要学会适应这种情况,然后在考试的时候最好就是判断一,下所有的选项,然后选一个错误更的,更少的更合适的选项,那经过刚才,排两个例题,相信大家对这个算法,强战士和非强战士到底应该怎么做,应该已经有了一个比较直观的理解,所以短作业优先算法,它的优点就是最短,可以得到最短的平均等待时间,和平均周转时间,这个最短为什么打双引号呢,这个大家应该已经知道了,大家在遇到的时候呢稍微注意一下,知道一个,这句话不严谨,这样的一个细节就可以了,而缺点的话其实也很明显,就是对于短作业有利长作业不利,如果说这个,就绪队列中,源源不断的有更短的作业到来的话,那么就有可能会产生,长作业饥饿的现象,另外呢,像这个作业或者进程的运行时间,其实是由用户提供的一个数据,所以这个用户,可以把自己本来应该是长作业,但是把,自己提交的这个数据把它写的很短,所以事实上,短作业优先这种算法并不一定真正的,能够做到短作业优先这件事,那是否会导致饥饿呢,刚才我们已经说了是会导致饥饿的,呃并且如果一个进程或者一个呃,作业他一直得不到服务的话,是一直得不到服务的话,那么这种饥饿更加严重的饥饿现象,就可以称作进程饿死或者作业饿死,那这就是短作业优先算法,那么经过,
五、两种对比思考
通过刚才的讲解我们会发现,对于呃先来先服务,和短作业优先这两种算法来说,先来先服务算法他每次调度的时候,其实考虑的是每一个作业或者进程,他的等待时间哪一个最长,他考虑的是等待时间,但是对于每个作业啊,他的运行时间到底是长是短,这些他并不关心,所以啊这就导致了,这个先来先服务算法对长作业友好,对短作业不友好的问题,相反的短作业优先这个算法,他又是只考虑了一个,作业或者进程的执行时间,他每次都是给估计的执行时间,估计运行时间最短的那个作业,或者进程,为他来调度他的,所以他并不考虑每一个进程,到底等待了多长时间,所以这就会导致另外一个相反的问题,就是对常作业不友好,甚至还会导致常作业饥饿的这种现象,那么我们能不能设计一种算法,既考虑到每一个作业的等待时间,同时还能兼顾,每一个作业或者说进程的运行时间呢,因此人们就用这样的想法,就提出了高响硬币优先算法那
六、高响应比优先
那高显硬笔优先算法,就是刚才咱们所说的这样的一个思想,既要考虑它的运行时间,还要考虑到要求服务的时间,也就是估计的运行时间,那这个算法的规则,就相对来说要复杂一些,在每一次调度的时候,会计算各个呃,当前已经到达的这些进程或者作业的,响应笔然后选择一个响应比最大的,最高的进程为他们服务,那响应比的计算方式是这样的,等待时间加上要求服务时间,或者说运行时间再,除以要求服务时间,那从这个式子当中我们也会发现,响应比肯定是一个大于等于一的数,因为呃因为等待时间它是一个正的嘛,然后要求服务时间上下都有,所以分子肯定要比分母更大,那这个算法既可以用于作业调度,也可以用于进程调度,那这个算法呢一般来说是非抢占式的,所以只有当一个作业或者一个进程,他主动的放弃处理机的时候,我们才需要使用这个调度算法,来计算各个,进程的响应笔,那为了让大家更直观的理解,还是用刚才的这个例题,这些进程的到达时间,和运行时间还是一样的然后是
七、高响应比优先例子
用的是高响应比欧先算法,那这个算法呢,我们就是当一个进程,他主动的放弃CPU的时候,也就是当他正常或者异常的完成终止,或者说,当这个进程主动的要求阻塞的时候,我们才需要使用这个调度算法,来计算各个进程的响应比,但是这个题目当中,由于它是纯计算型的一些进程,所以啊并不会有IO操作,也就是并不会主动的要求阻塞,因此这个情况咱们暂时不需要考虑,响应笔的,计算公式是刚才说的这个样子,所以在0这个时刻,由于整个系统的就绪队列当中,只有P1是到达的,所以管他P1的响应比到底是多少,肯定是P1上处理机,然后由于他是非抢占式的算法,所以只有P1主动的放弃CPU,也就是P1运行了7个,单位的时间,他当他结束的时候,才会进行第二次调度,那第二次7这个时刻,会发现,就绪队列当中有P2 P3 P4这样三个进程,那P2他是从2这个时间点到达的,然后一直到了7这个时间点,那这个时候到这个时候,他总共等待了7-2,也就是5个单位的时间,然后他要求服务的时间是4,然后再除以要求服务时间是4,就可以得到2.25,P3 P4这些也是一样的,大家在自己分析一下,那经过计算会发现,P3这个进程的响应比他是最高的,所以当然让P3上处理机运行,然后,P3的运行时间只有一个单位的时间,所以当他主动放弃处理机的时刻,就是8这个时刻,还剩下P2和P4两个进程,那么还是用刚才,相相同的这种规则再来计算,发现P2的相应比要更高,所以P2右上处以及运行,然后P2执行了四个时刻之后,12这个时间,呃P2完成就序,队列里就只剩下P4这个进程了,所以当然也不用计算它的响应笔,P4肯定是下一个上处理机运行的进程,所以整个过程就是P1 P3 P2 P4,这个地方,大家可能会发现响应比这个公式,它是等待时间加上要求服务时间,再处于要求服务时间,所以P2和P4这两个进程,它们的要求服务时间,也就是运行时间其实是相等的,那既然P2先到达就绪队列的话,那么就意味着P2的等待时间,是要更大的,而另外的要求服务时间他们都是,相等的所以P2的等待时间更大的话,当当然,P2的响应比计算出来肯定也更大,看这个时刻P22.25 P4是1.5,然后这个时刻P22.5 P4是1.75,它肯定都是大于P4的,所以这是响应笔优先,呃计算响应笔的一个特点,那具体的运行就是这个样子,就不再展开了,所以响高响应比优先这个算法,他综合考虑了一个进程或者说作业,的等待时间和要求服务时间,在两个进程等待时间相同的情况下,要求服务时间更短的那个进程,计算出来的响应比会更大,所以要求服务时间更短的会优先,这,也是我们短作业优先算法的一个优点,另一方面如果要求服务时间一样的话,那么就像刚才咱们,聊到的P2和P4两个竞争一样,等待时间更长的会优先,所以,这就是先来先服务那个算法的优点,所以这个算法其实是综合了,前两个算法的一个优点,然后做了一个折中的一个处理,对于一个长作业来说,随着使等待时间越来越大,那么他的响应比肯定也会越来越大,所以他被调度的机会也会越来越高,所以这个算法,也避免了,短作业优先这个算法造成的这个,长作业饥饿,的问题,所以这个算法其实是不会导致饥饿的,那么这个地方
八、总结
嗯对这几个算法做了一个简要的总结,嗯对于这些算法的思想,还有这些算法具体怎么算,怎么怎么做题,这个规则大家需要自己回忆,并且一定要结合课后习题来进行巩固,那这个地方需要注意的是,短最优先算法,他得到了最短的,平均等待和平均周转时间,这个最短的为什么打双引号,大家再思考一下,回忆一下,其实这几这三种算法,他主要关心的是对用户作业,或者用户进程的一种公平性的问题,就像先来先服务这种,他要求绝对的公平,另外呢,这几种算法还追求平均周转时间,平均等待时间这些,这些其实都是用来评价啊,系统的整体性能,整体整体表现的指标,所以啊这几种算法,大家会发现他其实并不关心响应时间,并且也不会区分各种任务的紧急程度,所以啊,这3种算法对于用户来说其实是,基本没有交互性的,交互性特别糟糕,那么这3种算法,它一般就是,使用在早期的批处理系统当中,因为在早期的这个批处理阶段,其实计算机也很昂贵,所以人们更追求系统的整体表现,而不是对于各个用户,对于各个用户来说的一种用户体验,其实也是情有可原的,那这几种算法当中,先来先服务这个算法,现在,也经常会结合其他的一些算法来使用,所以先来先服务算法,在现在的计算机当中,也扮演了很重要的角色,除了批处理系统使用的调度算法之外,下面一个小节我们还会继续介绍,教适合,适用于交互式系统的一些调度算法,那这个地方强调一下,在学习这个小节,还有下一个小节的这些,调度算法的时候,大家一定一定要尝,试动手输出一下,好的那么这就是这个小节的全部内容,
No.13 调度算法
各位同学大家好,在这个小节中,我们会学习剩下的几种标读算法,包括时间篇轮转,优先级调度多级反馈对列标读算法,和上个小节一样,大家需要注意各种算法,他们的算法思想是什么,这些算法的提出,主要是为了解决什么问题,第二点呃,最需要关注的,当然是这些算法到底是怎么运行的,他们的规则是什么,另外呢,这些算法到底适用于作业调度,还是竞争调度,分别有什么区别,这还有这些算法到底是强占式的,还是非抢占式的,这些都需要呃大家稍微注意一下,另外呢优点和缺点,一定是这个小节和上个小节,啊最最常作为选择题考察的知识点,最后我们还需要关注,这些算法到底有没有可能导致,饥饿现象,那么,我们会按从上至下的顺序依次讲解,首先我们来看一下时间片轮转
一、时间片轮转调度算法
调度算法,这种算法的提出其实是为了公平,轮流的为各个进程服务,然后可以让各个进程在啊一定的,一定长度的时间间,隔内都可以得到响应,其实时间片轮转调度算法啊,是伴随着,分时操作系统的诞生而诞生的,那么这种算法的规则,其实也相对来说也比较简单,就是啊操作系统会按照各个进程,他们到达就绪队列的顺序,轮流的让各个进程执行一个时间片,而这个时间片的长短是不一定的,就是有的操作系统,可能会设置的长一点,有的可能会设置的短一点,而有的甚至有可能会动态的调整,那么当一个进程,啊他的时间片用完了之后,这个进程会被强行的剥夺处理机,然后把这个正在运行的进程重新放回,就绪队列里,再重新排队等待调度,而时间片轮转调度算法,它一般是用于进程调度的,因为呃所谓时间片,其实指的是处理机的时间片,而一个作业只有当它放入了内存,并且建立了相应的进程之后,只有作为进程,他才有可能被分配处理机的时间片,因为进程肯定是执行的基本单位嘛,所以说,这个算法一定是用于进程调度的,而通过刚才的这个描述我们会发现,当一个进程他的时间片用完之后,虽然说他还没有结束,但是有可能会被强行的剥夺处理机,这个资源,所以说,这种算法肯定是一种抢占式的算法,并不一定要等到这个进程,主动的放弃处理机,才会发生调度,而这种抢占或者说这种时间片的切换,是由时钟时钟装置,也就是某一种计时的硬件,来发出时钟中段,来通知CPU时间片已到的,而时钟中段还有时钟装置,这些具体的内容,会在,计算机组成原理那门课里进行学习,如果不考这门课的同学,只需要有个了解,能有个印象就可以了,那么我们用一个例题来,
二、时间片轮转调度算法-2
来具体看一下,这个算法到底是怎么运转的,这些,进程他们的到达次序依次是这样的,那么我们分别分析一下,如果我们我们把时间片大小分别设为,2 和5,分别是什么情况,这个地方大家可能会注意到,在这个题目当中,我们并不像上一小节,啊那几个调度算法一样,就是还还来计算他的呃,什么平均周转时间,平均等待时间这些指标,原因在于,时间片轮转调度算法,它一般是用于分时操作系统的,比起之前所说的那些,什么平均周转时间那些那些东西来说,这个这种类型的操作系统会更关心,进程的响应时间,所以在这个地方我们也不再计算,进程的什么等待时间,周转时间这些指标,当然如果说题目中要求大家计算的话,也需要啊自己要学会分析,不过我相信经过上一小节的讲解,大家应该已经学会怎么计算了,那么如果说我们按照,时间片大小为2来进行呃来运行的话,刚开始就序对列是空的,然后在0这个时刻P1进程到达了,并且此时就绪,队列当中只有P1这个进程,所以呃肯定是让,P1上处理机运行一个时间片,而一个时间片的大小是2,两个单位的呃时间长度,所以P1,上处理机运行了两个单位的时间长,度之后这个时间片就用完了,然后会进行下一次调度,也就是在时刻2的时候,会进行一次调度,而这个时候,进程二P2刚好也在第二个时刻到达,并且P1它呃由运行态,重新回到就绪态,然后又会重新插入到就绪队列的队尾,所以这个时刻,P2到达就绪队列,然后P1也会重新回到队位,虽然P1也是在2这个时刻下处理机,然后回到就绪队列里的,而且P2这个进程也是在2这个时刻,同时几乎是同时到达的,但是如果我们啊,在题目当中遇到这种情况的话,我们一般是默认新到达的这个进程,也就是P2这个进程,他先插入就绪队列,然后呃之后呢,这个下处理机的进程再紧随其后,再回到就绪队列,所以我们把P2排在前P1排在后,但是如果题目中有特别的说明,那大家需要随机应变,根据题目给出的那个规则来进行分析,所以2这个时刻,由于啊此时,P2是处于这个就绪队列的对头的,啊元素所以我们会让P2上处理机运行,运行一个时间片,也就是两个单位的时间,那么在4这个时刻,P3到达,P3到达并且和刚才一样P2也会呃,紧接着下处理机,加入到这个就绪队列的队位,然后4这个时刻发生的调度就是,选择对头的元素P1,让他上处理机在执行两个呃,两个单位的时间,也就是一个时间片的大小,那么P1在执行了一个单位的时间,也就是到了5这个时刻的时候,P4这个进程到达,所以P4会被插入到就绪队列的队尾,但是需要注意的是,此时P4到达,但是由于P1的时间片还没有用完,它的时间片大小为2,但是此时只用了一个单位,的时间所以暂时不会发生调度,另外呢为什么,P1没有出现在这个就绪队列里呢,因为此时P1还处于运行态,所以它肯定是在CPU上执行的,所以P1不会,呃现在暂时不会出现在就绪队列当中,所以是这个样子,那么接下来,P1又会继续执行一个单位的时间,把他自己的这个时间片给用完,那么在6这个时刻,呃由于他时间片用完了,所以又会发生一次调度,P1下处理机重新放回就绪对列对尾,然后P3就是对头元素上处理机运行,然后需要注意的是,P3的运行时间,总共只需要一个单位的时间,所以虽然说就时间片的大小是2,但是,由于P3只需要运行一个单位的时间,所以在7这个时刻,P3就会主动的放弃处理机,所以虽然此时分配给P3的时间片,他还没有用完,但是由于P3的主动放弃,所以我们也需要发生一次调度选择,选择下一个进程上处理机,那么P3放弃处理机之后,P2在对头所以P2上处理机运行,并且P2是在继续运行两个单位的时间,刚好就是他剩余的这个时间,那么P2在运行两个单位之后,9这个时刻又会发生调度,然后P4上处理机运行,接下来,就和之前讲的那些情况很类似了,P4运行一个时间片之后调度,然后P1再上处理机运行,P1由于他只剩下一个单位的时间,所以当他运行了一个单位的时间,也就到了12,这个时刻,也会像p他,也会像P3一样主动的放弃处理机,于是,P4这个进程又会被调度上处理机执行,在P4执行了一个时间片之后,操作系统会再次发生调度,但是由于此时就绪队列已经为空了,所以,他会继续让P4接着运行一个时间片,那么到16这个时刻,P4把,他剩余的这些运行时间全部运行完,所以P4就结束了,那么16这个时刻,也就意味着所有的进程已经完成,那这就是时间片大小为2的情况,接下来我们再来看一下,时间片大小为5的时候会发生什么,呃什么
三、时间片轮转调度算法-5
情况在刚开始0这个时刻只有P1到达,所以P1上去处理及运行,而由于他的运行时间,只有5个单位的时间,并且给他分配的时间片大小也为5,所以给他分配的这个时间片,足够他运行结束,所以P1在0到5这段时间呢,P1都会一直运行,而期间呢,2这个时刻4这个时刻还有5这个时刻,P2 P3 P4会依次到达,而在5这个时刻P1刚好运行完成它,呃,就是下处理机之后会发生第二次调度,这个时候,P2这个进程由于他是先到达的,所以他排在对头就绪队列的对头,因此会选择让P2上处理机运行,同样的嗯,P2的运行时间小于给他分配的,这个时间片的大小,所以啊到9这个时刻,P2的时间片没有没有用完,但是他会主动的放弃处理机,然后进行第三次调度,此时就绪队列里只剩P3和P4,然后P3是排在对头的,所以P3上处理机运行,运行了一个单位的时间之后,P3有主动的放弃处理机,然后P4上处理机运行到15这个时刻,P4运行了一个时间片大小,呃这这么长的时间之后啊,操作系统此时本来应该发生调度的,但是他发现此时就序队列已经为空了,所以他会让P4继续执行一个时间片,所以一直到16这个时刻,P4的所有的运行时间都已经运行了,那么他会放弃处理机,然后这所有的进程就执行结束,所以这就时间片大小为5的情况,接下来我们再来看看一个就是假如说,
我们按照先来先服务调度算法来,调度这些程序这些进程的话,那么它的调度顺序是这个样子,有没有发现,他和我们刚才时间片为5,这种情况是很类似的,除了这个地方啊,操作系统还需要做一个,小小的检查之外,其他的这些啊就是发生调度的时机,还有甚至调度的顺序都是一模一样的,所以我们会有这样的一个结论,如果说我们的时间片太大,导致每个进程,几乎每个进程,都可以在一个时间片内完成的话,那么时间片轮转调度算法,就会退化为先来先服务,调度算法,在刚才咱们举的这个例子当中,假如我们把时间片的大小设置为6,或是或者是比6更更大的值,那么这个时间片轮转调度算法,就会导致每一个进程都可以在给自己,分配的时间片之内就执行结束,所有的调度都只会发生在这些进程,主动放弃处理机的时候,并且这些调度的顺序也会按照,各个进程到达,就绪队列的先后顺序来依次调度,那么这个算法就完全退化为了,先来先服务的调度算法,所以如果时间片太大的话啊,当然也会增大这些进程的响应时间,就失去了,时间片轮转调度算法最大的一个优点,所以时间片是不能选择的太大的,那么呃,怎么理解它会,增大各个进程的响应时间呢,比如说我们这个系统当中,有10个进程正在并发的执行,如果说,我们把时间片大小设置为1秒的话,那么如果呃一个进程被响应,有可能需要等待9秒,如果一个用户,在自己的时间片刚好用完的时候,发出了通过键盘发出了一个调试命令,那么这个调试命令,就需要等待9秒之后,才有可能被系统响应,所以这就大大的增加了,进程的响应时间,而第二点,如果我们把时间片设置的太小的话,又会导致进程的切换过于频繁,而我们在之前的讲解当中,我们已经知道,进程调度和切换,由于它是由于需要保存恢复运行环境,然后中间处理一系列的事情,所以调度和切换,其实是需要付出一定的时间代价的,所以如果说进程切换过于频繁,那么系统会花大量的大部分的时间,来处理进程,切换中间的这些这些这些事情,从而导致我们实际进行,进程运行进程执行的时间比例反而,会减少所以可以看到,时间片的大小如果选择太小的话,其实也是不利的,因此,我们一定要选择一个时间片大小,适中的呃这种这种这种情况,而什么怎么定义这个时间片太小呢,一般来说,在设计时间片的大小的时候,可以让就是切换进程所造成的开销,呃比例占比不超过1%,那么在这种情况下,我们就认为啊,这样的时间片不是太小,就是可以接受的,而这个一般来说不会考察,只是作为一个拓展,让大家啊能有更进一步的了解,那么这就是时间片轮转调度算法的,一系列的规则,还有一些细节,优点呢,就很明显就是对各个进程都是公平的,会轮流的为他们服务,并且响应很快,只要我们设置的时间片大小是合理的,那么就会在,一定的时间间隔内就可以给各个进程,各个用户都有一个响应,所以这种,这种调度算法,就比较适合于分时操作系统,可以为各个用户,分配一个大小的时间片,而缺点呢就是呃刚才咱们说的,用于进程切换的时候会有一定的开销,另外呢,这个算法他并不区分任务的紧急程度,而通过刚才对算法规则的了解,我们会发现,这种算法其实是不可能导致饥饿的,因为他肯定是轮流的,会为各个进程服务,最后我们需要注意的是,刚才强调的问题,就是时间片带太大或者太小,分别会有什么影响,这个在选择题当中经常会作为考察,那以上就是时间片轮转调度算法,
四、优先级调度算法
是优先级调度算法,这种算法其实是随着呃计算机的发展,然后越来越多的应用场景,需要根据任务的紧急程度,重要程度来决定处理这些任务,处理这些进程的顺序,所以就提出了优先级调度算法,会为每一个作业或者进程,设置一个优先级,然后在调度的时候,会选择优先级最高的一个进程,或者作业,进行调度,那么这个这个算法的规则并不复杂,这个算法既可以用于作业调度,也可以用于进程调度啊,唯一的区别在于,作用于作业调度的时候就是把一个,处于外存当中的,外存后备对列当中的作业让他选,选择他然后进入内存,然后用于进程调度的时候是,选择一个在内存的就绪队列,当中的一个进程,为它分配处理机,这一点咱们在之前的讲解当中,也已经强调过很多遍,那么啊优先级调度算法,甚至还可以用于,我们之后会学到的IO调度,那这个算法呢,它既有抢占式的也有非抢占式的版本,在做题的时候我们需要注意的是,非抢占式的我们只需要在,一个进程主动放弃处理机的时候啊,来检查这个时候进行调度就可以,而如果说,题目告诉我们,采用的是,抢占式的优先级调度算法的话,那么当一个就绪队列发生改变的时候,我们也需要检查是否会发生抢占,那具体我们用一个例题来进行说明,
五、例子
各个,进程的到达时间运行时间还有优先数,是像这个样子,这个地方优先数越大优先级越高,大家需要注意的是,优先数和优先级的概念并不并不一样,有的题目当中有可能是优先数越小,优先级越高,所以具体要看题目给出的信息和条件,那既然采用的是非强占式的,优先级调度算法,那么也就是说,我们只需要关注,各个进程主动放弃处理机的时候,这个时刻就可以了,所以啊整个调度的情况是这个样子,在0这个时刻,P1指整个系统当中,只有P1这个进程到达,所以P1理所应当让他上处理机运行,而只有P1主动放弃处理机,也就是他运行了7个单位的时间之后,我们才会进行第二次的调度,在这个时候,其他的这些进程都已经到达了,但是由于P3的优先数最高,也就是优先级最高,所以我们会在这次调度当中,选择优先级最高的P3进程,让它上处理机运行,而p 3-8这个时刻,也就运行了一个单位的时间之后,他会主动的放弃处理机,接下来就是下一次调度,那么P2和P4这两个进程,他们的优先级是一样的,但是不同的在于,不同点在于,P2他的到达时间是要比P4更早的,所以P2会优先上处理机运行,然后PR运行4个单位的时间之后,再调度再进行下次调度,此时只剩下P4,然后P4运行一直到它结束,就16这个时刻,那这就是非抢占式的优先级调度算法,而如果我们采用的是,抢占式的优先级调度算法的话
就是我们除了要关注各个进呃,各个进程主动放弃处理机,这样的一个时刻之外,我们还需要关注,这个就绪队列发生改变的时刻,我们需要检查一下,到底是不是会发生强占,是不是是不是当前到达的这个进程,优先级要比,此时,正在运行的进程优先级还要更高,那么在0这个时刻,P1到达只有P1系统当中只有P1这个进程,所以P1理所应当上处理机,但是当他运行到2这个时刻的时候,P2进程到达了,那P2的优先级要比P1更高,所以P2会抢占处理机,然后P1回到就绪队列,所以P2在抢占了处理机之后,它又会继续运行,直到3,直到4这个时刻P3进程到达就绪队列,也就是说就绪队列发生了改变,所以这个时候系统也会检查,到底是否会发生抢占,由于P3的优先级是更高的,所以当然P3会抢占处理机,P3上处理机运行,那么当呃P3运行到5这个时刻的时候,他会他运已经运行结束,因为他只需要运行一个单位的时间,然后他会主动的放弃处理机,接下来的调度,接下来这个5这个时刻,P4刚好也已经到达了就绪队列,然后插到了队尾,但是由于,P2进入这个就绪队列的时间更早一些,所以我们依然会优先选择P2这个进程,让P2上处理机运行,那接下来啊,就是P2刚才已,经运行了两个单位的时间,接下来他只需要在,运行两个单位的时间,也就是到7这个时刻,他就会主动的放弃处理机,再次发生调度,而P4的精准P4优先级又要比P1更高,所以P4先上处理机运行,最后才是让P1,接上处理机,运行完他剩下的那些时间单位,所以这就是,抢占式的优先级调度算法的一个,呃一个答题的过程,那么优先级调度算法呃
相关的知识,我们还需要补充这么一些点,首先就是就绪队列其实未必只有一个,有的操作系统,它是按照优先级来组织就绪队列的,另外呢,如果说设置一个就绪队列的话,也有的操作系统是啊,会按照优先级从高到低的顺序来,动态的就是排队,是调节这个就绪对联当中,各个进程的位置,可会让优先级更高的进程,排在更靠近对头的位置,这样的话,在调度的时候就只需要从对头,啊来选择一个进程,为他分配处理机就可以了,另外,根据优先级在确定了之后,是否可以动态的发生改变,我们又可以把优先级分为两种,静态优先级和动态优先级,静态优先级就是创建进程的时候,确定了之后就一直不变,而动态优先级是在,刚开始会给他一个初始值,但之后可能会根据具体运行的情况,在动态的调整啊优先级的大小,那么我们要怎么为一个进程,设置一个优先级的初始值,才会比较合理呢,一般来说有这样一些原则,系统进程的优先级要高于用户进程,这个很好理解,因为系统进程毕竟是作为管理者,所以管理者的,优先级自然是要比被管理者要更高的,另外呢,前台进程的优先级要高于后台进程,这个其实在咱们什么iPhone,安卓这些手机上,就是有这样一个原则,因为前台进程,毕竟是现在用户能够看到的进程,所以这些进程的流畅程度啊之类的,这些这些指标,肯定是要比后台进程要更重要,所以它的优先级,也理应要高于后台进程,第三个操作系统会更偏好IO型进程,IO型进程又称作IO繁忙型进程,与它相对应的,呃有个概念叫做计算型进程,也就是CPU繁忙型进程,那么为什么操作系统会更加偏好,更加优先的处理IO繁忙型进程呢,我们可以这样理解,IO设备它和CPU是可以并行的工作的,所以如果说我们让IO繁忙型进程的,优先级比,计算型的进程更高的话,那么也就意味着IO繁忙型的进程,它可以优先的运行,而它越优先的运行,就越有可能让IO设备尽早的投入工作,尽早的开始和CPU并行的工作,所以啊这就会直接的导致,系统资源的利用率会,上升也就是IO设备的利用率,并且系统的吞吐量,也会因为这样的策略,会有所提升,所以操作系统更偏好IO型进程,对整体的性能提升是有帮助的,这一点需要大家理解,在选择题当中也会出现,也会考察,那么啊接下来考虑的一个问题是,当我们采用动态优先级这种,这种策略的时候,我们什么时候应该调整一个进程的,优先级呢,我们可以从追求各个进程公平竞争,还有提升资源利用率,等等各种各样的呃,系统性能的角度来考虑,比如说如果一个进程,他在就绪队列当中等了太长时间,那么可以适当的提升他的优先级,如果一个进程他运行了很长时间,那么可以适当的降低他的优先级,那这就是从一个啊公平的角度来考虑,而而上面这一条就是,进程在就绪队列当中等了很长时间,适当提升他的优先级,这个有没有发现,其实咱们之前学过的,高响应比优先那个算法,所谓的响应比也是这样一个规则,就是当他等待的时间长了之后,响应比会增大,那么响应比其实就代表了这个,进程的优先级,所以他的优先级也得到了提高,所以其实我们也可以啊,只认为响高响应比优先算法,他就是一种,动态的优先级的一种调度算法,另外呢如果我们发现一个进程,他频繁的进行IO操作,那么可以适当提升他的优先级,因为他频繁进行IO操作,就说明,他很有可能是一种IO缓慢型的进程,那么我们提升他的优先级,经过刚才的分析我们也知道可以啊,这样可以提升系统资源的利用率,然后系统的吞吐量,这些指标也会得到提升,所以经过刚才的讲解,大家应该已经可以体会到优先几标,
六、两者思考
一个问题,像先来先服务算法,它的优点最大的优点就是公平,而短作业优先,这个算法的优点就是可以,让短作业尽早的处理完,然后就可以追求到一个比较短的啊,平均等待时间,平均周转时间,而时间片轮转调度算法呢,它的优点,又是可以让各个进程,可以及时的得到响应,优先级调度算法,又可以灵活的调整各个进程,被服务的机会,也就是通过优先级来进行控制,那么我们是否能设计出一种算法,能够综合上面这些算法的优点,取一个折中平衡呢,那基于这个想法,人们就提出了多级反馈对列标度算法,那这个算法就是对其他的,咱们之前学过的,那些调度算法的一个折中权衡
规则呃比较复杂这先不展开,咱们之后结合例题,来一点一点进行分析,然后,这算法一般来说是用于进程调度的,并且它是一种可抢占式的算法,而抢占的时候也需要注意,呃在抢占的过程中,这个规则也比较复杂啊,咱们会根据之后的立体再来展开讲解,那这个地方虽然说它是抢占式的算法,但是在实际的操作系统应用当中,也有可能是把它实现为非抢,占式的版本,但是考试的时候呢,咱们就以汤子莹的教材为准,就是认为他是抢占时的算法,并且注意一下他在抢占的时候,会发生什么事情,那我们直接,大家再自己暂停,来看一下刚才咱们开头提出的,这些算法规则,还有抢占的时候的一个规则,看看是不是已经能看得懂了,这就不再展开,那多级反馈对列它有很多优点,就是对,各种各样的进程都是相对公平的,每个进程刚开始进来的时候,肯定都是会被优先处理的,因为刚开始的时候,他的优先级是最高的,然而这就是啊线拉线服务的一个优点,而且每个新到达的进程,其实很快就可以得到响应,因为他优先级高,呃所以在低级队列的话,肯定很快就可以被调度,这又是时间片轮转调度,的一个一个优点,另外呢短进程只需要经历过,优先级比较高的那几个队列,就执行了几个呃较短的时间片之后,就可以完成了,所以这也会导致就是呃,这些进短进程的平均周转时间会比较,比较理想,而这就是,最短进程优先这个算法的一个优点,另外呢,短进程优先算法一般来说是要求用户,来提供就是,自己的进程大概需要运行多长时间,这样的数据的,但是有可能会有用户作假,就是本来是长进程,他把自己的时间说的很短,但是像多级反馈对列这种这种算法,他又不必要优先,事先估计这个进程的运行时间,从而就避免了用户作假这种,造成的一系列的后果,另外多级反馈对列也可以像,优先级调度算法那样,可以灵活的调整,对各种进程的偏好程度,比如说对CPU型呃,就是计算型还有IO型这两种进程来说,如果我们想偏呃偏好IO型进程,想优先的处理IO型进程的话,那么我们可以这么处理,当一个IO型进程,当他啊在运行的过程当中,因为发出IO请求而主动的阻塞的时候,我们可以把这个进程当他,就是再次被唤醒的时候,让他重新放回原来那一级的队列,而不是让他降到下一级队列,这种处理就会让导致,一个IO密集型的进程,它可以长期的保持一个较高的优先级,保持在较高优先级的队列里,所以这就是调整,对各类进程偏好程度的一种,方式一个例子,那么这个算法是有可能会导致饥饿的,因为如果说有源源不断的有进程,有短进程到达的话,那么他可能短进程在第一级队列,被分配一个较短的时间片之后,就可以被处理完,然后这种进程源源不断的到,来的话那么已经被降级为更低级,更低优先级的那些进程,就有可能会长期得不到服务,他就从而也会导致饥饿的现象,那么这是这个小节的呃一
七、总结
些简要的一些总结,特别对于多集反馈对列这个算法,它的规则运行的规则是比较复杂的,大家要注意结合刚才的那个立体,来进行理解,另外呢优先级调度算法当中,我们需要注意它是有抢占式的版本,也有非抢占式的版本,在做题的时候我们需要注意的是,非抢占式的优先级调度算法,我们只需要注意,各个进程主动的放弃处理机,这样的时刻,我们在这个时刻,检查是否需要调度就可以了,而对于抢占式的优先级调度算法来说,我们除除了刚才所说的情况之外,还需要注意一个就绪,队列发生改变的这种情况下,是否会发生抢占这样的事情,然后在这个时刻也需要进行检查,那各个算法有各自的优点和缺点,但是比起之前介绍的那三种算法来说,这三种算法的优缺点是,更不容易被考察的,那么他们是否会导致饥饿这个问题,大家也需要学会自己啊会分析,另外我们补充的呃,时间片轮转调度算法,我们需要注意,时间片太大或者太小会有什么影响,大家现在再回忆一下,然后优先级调度算法当中,我们也又补充了,所谓的动态优先级和静态优先级,他们有什么区别呢,另外我们在为各个进程设置优先,级的时候呃都有哪些原则呢,这些在之后课后习题当中也会遇到,大家在通过题目来进行巩固和总结,这个小杰介绍的这几种算法,是比较适合用于交互式系统的,因为比起早,期的这些批处理操作系统来说啊,这个计算机的造价其实是越来越低的,所以之后出现的这些交互式的系统,包括像分时操作系统,还有实时操作系统,这些操作系统,它会更注更注重这些啊,对于各个进程的响应时间,还有公平性平衡性这些指标,而不是一味的追求,批处理操作系统当中的什么平均,周转时间啊,这些这些很宏观的一些指标,所以啊,这几种算法刚好又是特别适用于这种,啊交互式系统的,这一系列的需求,可以提供比较快的响应时间,然后也可以,用一系列的,呃这种策略,来追求一个比较公平平衡的这种性能,所以他们是适合用于交互式系统的,比如说像呃Unix系统,使用的就是多级反馈对列调度算法,那么最后还是要强调一下,这些调组算法相关的知识点,大家一定要通过动手做课后习题,来再进一步的巩固总结,好的,那么以上就是这个小节的全部内容
No.14 进程同步进程互斥
一、什么是进程同步
各位同学大家好,在这个小节中,我们会介绍进城同步和进城互持,相关的概念,我们会结合一些具体的例子,让大家能够更形象的理解这两个概念,首先来看一下什么是进城同步,其实在聊进程同步之前,咱们已经,接触过一个和进程同步,息息相关的另外一个概念,叫做进程的异步性,那么异步性就是指各个并发的进程,他们会以各自独立的,不可预知的速度向前推进,在讲异步的时候咱们举过一个例子,就是老查,他需要并发的执行两个约会任务,就是分别和1号约和2号约,每一个约会的进程,他又会有一系列的要求,老查来执行的指令,那么由于这些并发执行的进程,有异步性,所以这些指令的推进顺序,是我们不可预知的,所以老扎有可能是这么约的刚,开始是执行1号的这个指令一,接下来执行1号的指令2,也就是让老扎把心给1号,于是,由于此时老扎的心还没有给任何人,所以他会顺利的把心交给1号,那么接下来如果说切换为呃,就是调度了,第二个约会进程的话,那么和2号的,这个约会就需要执行这样的两条指令,那么这个2号的指令一,就是会让老扎把心给2号,那此时由于老扎的心已经给了1号,所以2号的这一条指令,让老扎把心给他,就没办法顺利的执行下去,所以这是第一种情况,那么他还有可能是这么约的,刚开始他是和1号约,就是执行了1号的指令一就是和老扎,让老扎陪他吃吃饭,接下来他是和2号约,然后执行了2号的指令一,于是他会根据2号的这个要求,把心给2号,那接下来如果,切换为和1号的这个约会进程的话,那么他会接着执行1号的指令2,也就是让老扎把心给1号,但是由于此时心已经在2号这,所以,1号这个也没办法顺利的执行下去,除非2号把心归还给老扎,让老扎再把这个心再分配给1号,所以可以看到啊,由于异步性,导致了这些这这两个并发的进程,他们的推进顺序是我们不可预知的,可能是这样可能是这样,还有可能是其他的一些推进方式,但是假如说,女1号这个1号他只想做老渣的初恋,而2号只想交一个有恋爱经验的渣男,那么就意味着这个1号的指令二,一定是要在2号的指令一之前执行的,也就是说,老渣需要先把心分配给1号啊,这样的话1号才是老渣的初恋嘛,然后之后再把这个心回收了,再把这个心分配给2号,这样,才能保证2号交到的是一个有恋爱,经验的渣男,所以说在这种情况下,我们就必须保证,这两个并发执行的进程,他们之间的推进顺序,推进次序是我们可预知的,也就是要让这条指令,一定需要在这条指令之前执行,像刚才咱们举的这两个例子,第一种执行方式就是我们啊,就可以得到我们期待的结果,而第二种执行方式,就是得不到我们期待的结果,所以其实可以看到,有的时候,这种易步性,是我们必须要解决的一个问题,我们必须要保证,各个进程之间的推进次序,是按我们想要的那种顺序来,依次推进的,那么,操作系统就需要提供一个叫做同步,进程同步的机制,来实现刚才我们所说的这种需求,再看另外一个例子,
之前咱们在,聊管道通呃进程通信的时候,讲过一种通信方式叫做管道通信,也就是写进程先要往广道里写数据,然后当这个写数据写满之后,读进程,才才可以执行读数据这样的操作,把这些数据依次取走,那写进程和读进程这两个进程,他们是并发执行的,具有一步性,所以说写进程写数据和读进程读数据,这两个操作,谁发生在前,谁发生在后,其实啊按照他的异步性来说,是我们不可预知的,但是在这种应用场景当中,我们又必须保证写数据发生,在读数据之前也就是这个样子,所以这个就是所谓的进程同步的问题,进程同步讨论的就是怎么解决啊,进程异步的问题,所以同步我们又可以把它呃,称为直接制约关系,这种同步就是指两个或多个进程,他们有的时候可能会像这样,相互协调的来完成呃,来合作的完成某一种工作,但是在这个合作的过程当中,又需要,呃确定协调他们之间的这种工作次序,写进写数据在前读数据在后,就类似于这个样子,所以这个就是所谓的进程同步的问题,那么如何实现进程同步,是咱们之后的小节会展开细聊的啊,一个重要的知识点,在这个地方,大家只需要对进程同步是什么,能有个概念,能有个理解就可以了,那么接下来
二、进程互斥
咱们再聊一下什么是进程互制,在之前的学习当中我们知道啊,各个并发执行的进程,他们是需要共享这种特性来支持的,因为呃这些并发执行的,进程肯定不可避免的需要使,共享的使用一些系统资源,比如说像内存啊,像打印机摄像头这些l设备,那么咱们之前呃聊到过两种,操作系统的资源共享方式,互斥和同时共享这两种,其中互斥共享指的是在一个时间段内,只允许一个进程访问呃的这种资源,这种资源就只能采用互斥共享的方式,而同时共享方式就是指,允许在一个时间段内,由多个进程同时对他们,访问当然这个同时指的是宏观上的,同时微观上可能这些进程是交替的在,访问这些,共享资源的那么,我们把第一种,就是在一个时间段内,只允许一个进程使用的这种资源,称称作为共呃临界资源,对于这些临界资源的访问,我们就需要互斥的进行,所谓互斥就是指,当某一个进程在访问,这个临界资源的时候,另一个想要访问这个临界资源的进程,他必须等待,一直到,当前访问这个临界资源的那个进程,呃对这个资源的访问结束,把它释放了之后,那另一个进程才可以进去,访问这个临界资源,所以这个就是所谓的互斥的概念,那一般来说实现进程的,
互斥或者说对临界资源的互斥访问,可以在逻辑上分为这样的四个部分啊,四个部分的代码,进入区临界区退出区和剩余区,进入区其实是负责检查,此时到底能不能来访问这个临结资源,如果说可以的话,他就会对这个临结资源进行一个上锁,的这样一个操作,就是会设置一个,正在访问临界资源这样的一个标志,那么当他设置了这个标志之后,其他的进程在,想要访问临界资源的时候,在进入区进行检查就会发现,此时已经有一个进程,正在访问临界资源,那么其他的进程就会被阻止,进入这个临界区,访问临界资源,那这个临界区呢,就是,实际用来访问临界资源的那段代码,比如说,说咱们在打印机要通过打印机打印输,出的话那么对打印机执行写操作,呃这个代码就需要写在临界区里,那退出去呢,就是负责解除,刚才在进入区,设置的这个,正在访问临界资源的这个标志,我们可以把进入区和退出区,理解成是一对,一个是上锁,一个是解锁这样的一对操作,而临界区,是实际用于访问临界资源的那段代码,剩余区呢,就是可以做一些呃,在这个部分的代码当中,可以做一些其他的处理,这个地方,比较容易考察的是前三个部分,很多题目,他可能会设置一些干扰的选项,比如说他他他可能会这么说,就是说进入区和退出区,是用于访问临界资源的那段代码,那这个如果说没有注意到,注意到进入区和临界区的区别的话,那么很有可能会被误导,就错选了这样的一个错选项,所以这个地方大家稍微注意一下,另外呢,临界区还有另外一个名字叫做临界段,所以看到这个名字的时候也要知道啊,他说的其实就是临界区,那接下来我们再来思考一下,如果一个进程,此时暂时没办法访问这个临界区的话,那么这个进程应该是呃,我们应该对这个进程,做什么样的处理呢,他是应该放弃处理机还是继续保持,占用处理机,或者说,如果这个进程此时进不了临界区的话,他有没有可能会一直进不了临界区呢,那这些都是我们,在实现进程互斥的时候,需要考虑的问题,那么一般来说
说为了保证这些系统的整体性能,那在实现对于临界资源的互斥访问,或者说实现进程互斥的时候,我们需要遵循这样的几个原则,首先叫空闲让进,也就是说一个临界区如果此时空闲,没有被任何进程访问的话,那么应该允许一个,请求进入临界区的进程,立即进入临界区,这是空闲让进,第二个原则是盲责等待,当一个呃进城,此时已经在访问临街区的话,那么,另外的进城如果此时想要进入临街区,那么就就就必须,让另外的那些进城等待,第三个原则叫做有限等待,也就是说,如果一个进程此时暂时进不了临界区,那么我们应该要保能够保证,这个进程在有限的时间之后,就可以进入临界区,就是要保证这个进程不会发生进程,饥饿的这种现象,第四个原则叫让权等待,也就是说,当一个进程,此时暂时进不了临界区的话,那么这个进程应该立即释放处理机,不应该一直占用,就防止进程啊处于一种盲等待的状态,所谓盲等待,就是指这个进程他是在等待,已经暂时没办法继续往下推进了,但是这个进程还一直占用着处理机,使处理机一直处于一个忙碌的状态,没有办法啊给别的进程进行服务,所以,这就是进程互斥需要遵循的4个原则,在下一个小节,咱们讲进程互斥的实现方法的时候,这几个呃需要遵循的原则,我们还会在复习,然后会对这些原则进行一个应用,所以这个地方呃暂时不用担心,那么这就是这个小节的
三、总结
全部内容,我们介绍了晋城同步和晋城互斥的,基本概念,其中晋城,同步又可以称之为,晋城之间的直接制约关系,也就是说,这些晋城之间是有直接的合作的,而晋城互斥,又可以称作为晋城之间的间,接制约关系,因为晋城之间并没有直接的合作关系,他们之间只是因为想要互斥的,使使用某一种系统啊,临界资源,所以才会产生这种制约关系,那么晋城互斥啊的实现,在理论上我们可以,把它分为4个逻辑部分,进入区临结区退出区和剩余区,比较常考察的是上面这3个部分,另外呢,我们还介绍了进程互斥呃需要,遵循的以4个原则,那么对于这4个原则的使用,会在下一个小节的,讲解当中再继续出现,那以上就是这个小节的全部内容
No.15 进程互斥的软件实现方法
各位同学大家好,在这个小节中,我们会学习进程互斥的几种,软件实现方法,那我们会学习单标制法双标制先检查,双标制后检查和皮特森算法,这四种算法啊,这个小节的考察频率,总体来说还是很高的啊,经常会在选择题,甚至大题当中进行考察,所以这个小节的内容啊十分重要,那这个小节的学习当中,大家需要注意理解,各种算法的思想和原理,当然这个我一会会用一些,比较容易理解的例子来进行解释,另外我们还需要,结合上个小节当中学习到的,实现互斥的4个呃代码逻辑部分,来对每一种算法进行分析,也就是说进入区是哪一段代码,然后临界区是哪一段,退出区是哪一段,特别我们要关注的是,进入区和退出区,都分别做了什么样的一种处理,第三点大家需要关注的是啊,这些算法他们分别有哪些缺陷,那这个问题,大家可以结合上个小节当中学习到的,实现互持的4个原则来进行分析,也就是空闲让进,盲责等待啊那几个原则,那具体的,我们会在讲解当中带大家进行分析,那首先我们来体会一下
一、如果没有进程互斥
如果在一个系统当中,没有进程互斥的话,那么会发生什么情况,假设系统中有a,b这两个进程正在并发的运行,那么如果刚开始是a进程,上处理机运行,那a进程会执行他相应的代码啊,并且他会使用打印机这个资源,但是当他使用打印机的这个过程中,如果说a进程的时间片已经用完了,那么在这种情况下,a进程就会被剥夺处理机的使用权,接下来操作系统会调度b进程,让b进程上处理机运行,注意此时a进程其实还在使用打印机,只不过使用到一半,就被切换到另外的进程了,那此时如果b进程上处理机运行的话,呃他执行了一些代码之后,b进程也会开始使用打印机,所以由于a进程,他使用打印机使用到一半的时候,b进程也开始使用打印机了,因此a,b这两个进程的内容就会打印在一起,就会混在一起,那这种情况并不是我们想要看到的,所以从这个例子中,我们就可以看到互斥的一个必要性,那如果a,b这两个进程可以互斥的访问打印机,也就是说,只有当a把打印机使用完了之后,b才可以接着访问打印机,那在这种情况下,就不会出现我们这所说的这种问题了,因此我们接下来要探讨的问题就是,能不能用软件用代码的方式,实现让两个进程,互斥的访问某一个临界资源,那首先来看,
二、单标志法
第一种方法叫做单标制法,这个算法的思想是啊,两个进程在访问完临界区之后,会把,使用临界区的权限转交给另一个进程,那我们会设置一个变量叫turn,这个变量表示的是当前,允许哪个进程进入临界区,如果turn,等于0的话,说明此时是允许0号进程进临界区,如果turn等于一的话,说明此时是允许1号进程进临界区,那么两个进程,对临界区的访问代码就是这个样子,首先,每一个进程会在进入区这个位置,判断一下,此时是不是允许自己进入,也就是说,把turn的值和自己的这个编号,进行一个对比,如果此时,turn的值不等于自己的编号的话,那么说明此时临界区只允许,另一个进程来访问,那我们来分析一下这个过程,刚,开始turn的值是0,所以如果刚开始,操作系统是调度了,PE进程上处理及运行的话,那么PE进程在进入区这会检查,发现turn的值并不等于1,就是y循环的这个条件是满足的,于是P1进程它会被卡在y循环这,一直不断的轮循检查,因为turn的值一直是不等于一的,直到P1进程,用完了操作,系统给他分配的时间片之后,此时操作系统会发生调度,让P1下处理机而让P0上处理机运行,因此当P0上处理机的时候,他在进入区这进行了一个判断,发现此时turn的值是等于0的,也就是说外物循环的这个条件,得不到满足,因此P0进程可以跳过外物循环,直接进入第二步,也就是说,P0进程,他就可以开始访问这个零界区了,那接下来我们思考一下,和刚才打印机的例子相同的情况,假设P0这个进程此时正在访问临界区,而在他访问临界区的过程当中,又发生了处理机调度,然后又切换回了进程P1,那由于此时turn的值依然为0,因此即使PE进程再次上处理机运行,那PE进程的外物循环条件,依然是满足的,turn的值依然不等于一,所以PE进程还是会在这,不断的轮循轮循,轮循一直在检查这个turn的值,直到PE进程的时间片用完,那此时操作系统会再次啊要读P0进程,让他是继续上处理机运行,那P0 进程就会接,接着继续访问他的这个临界资源,然后访问完了之后,就可以往下继续执行,那直到P0进程在退出区这个地方,把turn的值改为一之后,PE进程才有可能,跳过这个进入区的外循环,然后访问临界资源,所以从刚才我们分析的过程当中,可以看到,这个算法是可以实现,同一时刻,最多只允许一个进程,访问临界区这个事情的,也就是说它可以实现互斥,那很多同学在学习这个算法的时候,很容易一头钻进这个代码里面,但是这些代码比较容易让人看得头晕,所以我尝试着,能不能利用大家的一些生活经验,让大家简化对这个算法的认识过程,那其实我们可以把turn这个变量,把它看作是一个谦让的过程,它背后的逻辑其实表示的就是谦让,P0进程再退出去把turn的值设为1,背后的意思是说P0进程,把,零界区的这个使用权谦让给了P1进程,同样的,P1进程在退出区把turn的值设为了0,那这个背后的逻辑其实是说P1进程,把零界区的这个使用权,谦让给了P0进程,那我们来尝试跳出代码,看一下生活当中谦让的例子,我们假设老渣和小渣啊,又是这两哥们,他们俩想要使用马桶,那马桶这个东西,一般来说都是护斥使用的,那么他们俩制定了一个规则,就是说啊,这个马桶只能让两个人轮流的使用,让小扎使用一次让老扎使用一次,再让小扎使用一次再让老扎使用一次,所以小扎每一次要使用马桶之前,他要做的事情是首先要进行一个检查,检查此次是否轮到啊让自己用了,如果轮到自己用了,那么他就可以开始访问这个临界资源,开始啊啦啦啦,那当小扎使用完这个临界资源之后,他就会开始做出一个谦让的动作,就是说我表示,下一次这个临界资源可以让给老扎用,然后接下来他开始做其他的事情,其实这些逻辑,和我们这的1234都是一一对应的,那老扎同学他也一样,刚开始他是要检查,是否轮到自己使用这个临界资源了,接下来啊如果轮到自己了,那么他就可以进入临界区,开始访问临界资源,当他用完这个临界资源之后,他会表示啊一个谦让的动作,下一次这个东西我让给小渣用,然后他就开始做其他的事情,那接下来我们要考虑一下这个算法,它虽然可以实现啊,各个进程对临界区的一个护持访问,但是它有一个最致命的缺点,就是,各个进程它只能轮流的使用临界区,每个进程在退出区这一块,都会表示一个谦让的动作,下一次是让对方使用,那如果说,对方一直不使用这个资源的话,那么当他,想要再次使用这个资源的时候,发现哎此时并没有轮到自己,所以虽然这个临界资源此时是空闲的,然而由于上一次是自己,表示了这个谦让的动作,所以此时自己还暂时不能,访问这个临界资源,因此他最大的一个问题是,他违反了空闲让,进的原则,那这是单标制法
三、双标志先检查法
接下来要学习的是双标志先检查法,这个算法的思想是啊,要设置一个布尔型的数组flag,用来标记各个进程,是否想要进入临界区,比如flag 0等于false的时候,说明0号进程此时并不想进入零界区,而当它等于two的时候,说明此时0号进程是,想要进入零界区的,那这个算法采取的策略是,每一个进程在进入临界区之前,都会先检查对方是否想要进入临界区,比如说此时PE进程先检查flag 0的值,也就是说,他要确定对方是否想要进入临界区,如果flag 0等于true,也就是对方想要进入临界区的话,那么PE进程就会一直被卡在这个y,循环这儿,而如果他发现,对方此时并不想进入临界区的话,那么他就会把自己的这个flag一,设为true,也就是向对方表明,我想要进入临界区的一个意愿,所以在这种算法当中,每一个进程在,进入区做了这样的两个事情,一个事情是检查,对方是否想要进入临界区,第二个事情,如果此时对方不想进入临界区的话,那么就,呃表达自己想要进入临界区的意愿,所以其实在进入据把flag设为处,它在背后的含义是说啊,要表达,自己想要进入临界区的一个意愿,那我们依然还是用小渣和老渣访问啊,临界资源的这个例子,如果采用的是双标志先检查法的,话那么它在背后的处理逻,辑其实是这样的,如果小渣此时想要使用马桶,那么他先要检查老渣,也就是对方他是否想要使用,如果对方此时是想使用的,那么他就需要循环等待,而如果此时对方不想使用,这个资源的话,那么他就可以啊,表达,自己想要使用这个资源的一个意愿,也就是把自己所对应的flag值设为true,我们也可以把这个,动作理解为是上锁的动作,因为把自己的flag值设为true,其实就相当于通知对方,这个资源现在被我锁住了,那之后,他就可以开始使用这个临界资源,当他使用完临界资源之后啊,在退出区,这又会把自己的flag值置为false,也就是要表达我自己啊,不再使用这个临界区了,所以其实在退出区,把自己的flag值设为false,这个动作,我们可以把它理解为呃,解锁就是通知对方,这个资源我现在已经不再锁住了啊,你想用的话你就继续用吧,那对于老查他的处理逻辑也是一样的,那看起来这个算法好像是没有问题的,但是,如果这两个进程是并发的运行的话,那么情况就有可能不同了,那我们来看一下,因为刚开始flag 0和flag一都为false,也就是说刚开始两个进程,他们都不想进入临界区,那如果说这两个进程并发运行的话,假设PE进程他先上处理就运行,那PE进程,呃会对这个wild条件进行判断,他发现flag一等于false于,是他会跳出这个Wil循环,然后进入第二句,而他还没有执行第二句的时候,如果此时就发生了一个切换,切换到了P1进程,那由于P0进程还没有执行第二句,因此此时P1进程的第五句判断,他会发现flag 0的值此时还是false,因此这个y循环也会被跳过,于是他就开始进入第6句,那接下来,P1进程他会把flag一的值设为true,然后开始进入临界区开始访问临界区,那如果说此时又发生了进程切换,又切换回了这个P0进程的话,那P0进程他会把自己的flag值设为true,同时它也会进入临界区,那可以看到,如果这两个进程他们并发的运行的话,那他们就啊同时进入了临界区,那大家也可以结合下面这个例子,来看一下,如果按1526这样的方式来执行的话,是不是小渣和老渣就同时啊,开始使用马桶啊,这是很奇怪的事情,所以双标志先检查法,它存在的主要问题是违反了,盲则等待的原则,虽然临界区此时是处于盲的状态,也就是说有一个进程正在访问临界区,但是在并发运行的环境下,依然有可能发生,两个进程同时进入临界区的情况,那导致这个问题的原因在于啊,这些进程他们在进入区干了两个事情,一个是检查,一个是上锁,而检查和上锁这两个处理的动作,他们并不能一气呵成,也就是说,处理了其中的一个动作之后,有可能发生进程切换进程调,度因此这就导致了啊我们,刚才所说的问题,导致他们同时进入临界区,那这是双标志先检查法,那接下来我们要看的是
四、双标志后检查法
双标值后检查法啊,这个算法和刚才的算法其实,思想上大致上是相同的,都是用一个flag数组来表示啊,此时每一个进程,想要进入临界区的一个意愿,但是和双标志先检查法的不同点在于,啊后检查法当中,他是先进行了上锁的动作,之后才进行检查,而先检查法当中是先检查后上锁,那在老扎小扎的这个场景当中,就相当于啊,当小扎想要使用马桶的时候,他会说我小扎现在想要使用马桶,就是向对方,表达这个自己想要使用的意愿,也就是进行了上锁的工作,之后,他会开始检查老查是否想要使用,如果老查此时不想使用的话,那么自己就可以开始进入临界区,开始访问这个临界资源,当他访问完临界资源之后,再把自己的这个意愿给撤销,也就表示我之后不再使用了,这是一个解锁的动作,那老扎也一样,那同样的我们还是考虑并发的情况,如果这两个进程并发运行的话,我们按1526来分析一下会发生什么情况,假设刚开始是小渣先进行啊这个处理,那么他会表达,自己想要使用临界资源的意愿,而此时发生的进程调度,那么接下来是老渣开始,执行他的这一系列指令,那老渣他,也会表达说,自己也想用这个临界资源的意,愿那接下来再切换回小扎的时候,他会发现,此时老扎是想要使用这个临界资源的,于是小扎就需要等待2这一步,而同样的如果再切换回老渣的话,那么他也会发现,此时小渣他也说过自己想要使用,所以他也不敢进入这个临界区,因此他就会卡在6这一步,所以这就导致了,小渣和老渣这两个并发的进程,他们都进不了临界区的情况,因此双标志后检查法他虽然简,解决了这个盲则等待的问题,也就是说不可能有,两个进程同时访问临界区,但是他又产生了新的问题,就是,违背了空闲让进和有限等待的原则,空闲让进的意思就是说呃,此时虽然这个临界区,没有任何一个进程在访问,但是,像刚才我们所分析的那种情况一样啊,在这种情况下,有可能,每一个进程都都进不了这个临界区,所以这是违背了空闲让进,另外如果两个进程都卡在了y,循环这个地方,那么这两个进程,他们就永远不可能往下推进,因此他们就变成了无限的等待这个,连接区所以啊,这个算法他违背了有限等待的原则,那这会导致啊,各个进程长期,无法访问这个临界区资源,从而产生饥饿的现象,所以这是双标志后检查法的一个缺点
五、Peterson算法
核心算法这种算法的思想,它就是结合了双标制法和单标制法的,那种核心,双标制法是啊每一个进程会主,动的表示自己想要进入临界区的意愿,而单标制法的term变量,则它的本质在于,每一个进程都会互相谦让着,让对方啊来使用这个临界区,也就类似于一个孔融让梨的一个过程,所以这个算法他会设置一个flag数组,这个数组就是用来表示每一个进程,他是否想要使用临界区,这个意愿是用来表达意愿的,而turn这个变量,是来表达谦让这个动作的,而每一个进程,他在进入区当中会做这样的几个事情,第一,他把自己所对应的那个flag值设为true,也就是说,表示自己此时想要进入临界区,第二个动作,他会把turn的值设为对方的这个编号,P0进程把turn设为1 P1进程会把turn设为0,那这个负值其实就是一个谦让的动作,也就是说他表示啊,自己可以优先让对方,使用这个临界区的资源,那再回到小渣和老渣的例子当中,小渣在使用马桶之前,他会先表示我小渣想要使用,这是表达自己的意愿,也就是把flag值设备true,第二步,他会把turn的值设为对方的这个标号,那这其实就是啊,表示我愿意优先让老扎先用,这是一个谦让的过程是一个客气化,第三步他会检查此时老查是不是想用,那这个其实就是检查,对方的flag值是否为true,如果对方也想用,并且最后是自己表达了这个,谦让的动作,那么这种情况下我就等待,那这个逻辑就对应了外物循环的这个,判断如果此时对方也想进入,并且turn的值等于一,也就是说,最后是我自己表达了那个谦让,而对方没有再让回来,那在这种情况下,自己就需要卡在这个外物循环这,循环等待那,直到小扎他发现啊老扎现在已经不,各位同学大家好,在这个小节中,那么P1进程就会一直被卡在这个,Wil循环这儿,已经不想用这个资源了,或者说当他发现老渣,呃表示了他愿意优先让小渣使用,也就是这两个条件中任何一个不满足,那他就不再等待,他就会开始使用这个临界资源,那当他访问完临界资源之后,他会把自己的flag值设为false,也就是表达我自己,啊已经不想再使用这个临界资源了,那老查的处理逻辑是一模一样的,那对于这个算法的分析,我们依然要考虑到,啊这两个进程并发运行的情况,所以同学们可以啊暂停来自己推一下,如果我们按照这样的一些顺序来,依次执行的话,那么会不会发生,两个进程同时进入临界区,或者两个都进不了临,界区这类的事情呢,那我们来一起分析一下,最后的这种执行方式,16278,我们用小扎和老扎的例子来进行分析,然后大家一会再用,代码的角度来进行分析,为什么要用啊他们俩的例子来分析呢,因为因为这个比较,符合我们正常人类的习惯,可以让大家更能看明白背后的逻辑,但是如果直接使用代码的话,可能这些什么值变来变去就,会容易让大家啊头晕,所以重要的其实是理解这个代码背后,想要表示的那种逻辑,那来看一下,刚开始是小渣先执行他的,这一系列动作,他执行第一个动作,他表示说小渣想要使用这个临界资源,表达了意愿,接下来呃切换到老渣,然后老渣他也说,我老渣也想要使用这个临界资源,也表达了自己的意愿,也就是说,他们俩此时都想要使用这个临界资源,那接下来是小渣呃做了一个动作,他表示他愿意优先让老渣先用,这是一个谦让的动作,再接下来老渣执行7这个语句,老渣表示他愿意优先让小渣先用,那在执行了1627之后,同学们想一想在这种情况下,接下来应该是谁使用这个资源呢,其实结合我们的生活经验并不难理解,由于7这一句是在2之后执行的,也就是说,老渣的谦让动作,在小渣的谦让动作之后,也就是说,刚开始本来是小渣说了一句客气话,啊没事让你先用吧,然后老渣又说了一句不不不,还是让你先用,那在这种情况下,其实啊继续往下执行的话,应该是让小渣,得到优先使用马桶的权利的,所以在执行了1627之后,接下来想要再执行8,那么老渣会发现,小渣此时他也想要使用这个临界资源,并且最后是自己表示了这个,谦让的动作,那既然最后一句客气话是我自己说的,那我就只能先等在这个地方了,于是他就会卡在循环的这个位置,直到再切换回小扎,小扎发现这个循环卡不住他,然后他就可以开始访问这个临界区了,所以其实皮特森算法虽然看起来复杂,但是他在,啊进入区做的无非就是这么几个事情,第一就是主动争取,就是表达自己想要进去的意愿,第二,嗯在争取的同时又进行了主动的谦让,表示啊我愿意让啊对方先进去,他是一个有礼貌的进程,然后第三个动作,是检查对方是否也想要使用,并且最后一次,是不是自己说了这个谦让的动作,自己说了这个客气话,那谁在最后说了客气话,谁就会失去行动的优先权,那这一点其实我们,啊在平时当中体会是很明显的,比如说过年了,有个阿姨想要给你发压岁钱,然后阿姨对你说啊乖,收下阿姨的心意,然后你说哎,不了不了阿姨你的心意我领了,钱我就不要了,然后阿姨跟你说,啊对阿姨来说你还是个孩子,你就收下吧,那在这个过程中,先是你说了一句客气话,之后又是阿姨说了一句客气话,那在这个时候你会说啊行吧行吧,那谢谢阿姨,所以结局就是你有压岁钱了对吧,因为最后的这一句客气话是阿姨说的,最后的谦让动作是阿姨来表示的,所以阿姨在说了这句话之后,他就失去了行动的优先权,然后这个压岁钱就真的被你领走了,那再来看这样一个场景,你跟阿姨说不了不了阿姨,你的心意我领了,钱我就不用了,阿姨说嗯你就收下吧,之后你又说了一句哎真的不用了,阿姨我已经成年了,而接下来阿姨不再说话,那在这种情况下,你是最后一个说了客气话的人,你是最后一个做出谦让动作的人,因此你在说完这句话之后,你就失去了行动的优先权,那在这个情况下,阿姨有可能就真的不给你压岁钱了,所以其实大家对这个算法的思想,应该是能够理解的很透彻的,只不过他用代码的方式呃表现,他穿了一层马甲,可能大家就又看不出来了,但是他在背后做的事情,和我们的这种谦让的动作是一模一样,尽管各个进程是并发运行的,但是他们对turn的值的一个设置,肯定也有一个先后顺序,最后是谁设置了turn的值,就说明最后是谁,做出了这个谦让的动作,那这个进程就会啊失去行动的优先权,他就会让对方优先进入这个临界区,那建议大家,还是把刚才我们提出的这几个问题,啊并发运行的时候,他们各自这些变量是怎么变化的,大家啊都暂停来推导一下,那理解了算法的思想之后,这个推导的过程相信并不难,那这个地方我们只抛出结论,皮特森算法,他用软件的方法解决了互斥的问题,可以实现空闲让进,盲则等待和有限等待这3个原则,那为什么可以满足这3个原则啊,大家需要通过这儿的推导,来自己进行体会,不过这个算法的一个明显的缺点是,他并没有遵循让权等待的原则,所谓的让权等待就是说,如果此时我这个进程进不了临界区,那么就应该立即释放处理及资源,而不应该继续在CPU上跑,但是这个算法的处理方式是,如果我这个进程此时进不了临界区,那么我会一直被卡在外外循环,这然后其实自己还一直在CPU上执行,不断的来检查这个外外循环的条件啊,是否得到满足,所以虽然这个进程此时进不了临界区,但是他依然会占有CPU资源,因此就没有满足让权等待的这个原则,所以皮特森算法,它相较于之前的那三种,解决方案来说,是最好的,但是依然不够好,那还有没有更好的办法呢,这个,我们会在之后的小节当中慢慢的介绍,好的那么在
六、总结
这个小节中,我们介绍了几种呃,互斥的软件实现方法,对于比较,缺乏代码经验的跨考的同学来说,呃不建议大家直接钻到代码里边,而是要理解每一个代码,他在背后想要表达的那个逻辑,那单标制法的一个精髓在于,他用一个turn的变量来表示,谦让这个动作,而双标制法的精髓在于,它用一个flag数组来表示啊,自己想要进入临界区的一个意愿,而皮特森算法,结合了双标制法和单标制法的一个,思想的精髓,每个进程在进入区当中,都会先主动的争取,主动的表达自己想要进入临界,区的意愿,同时又主动的做出一个谦让的动作,在很多题目当中,只要是用软件方式实现的呼斥,其实大多都离不了这两种思想,要么就是一个谦让,要么就是啊表示想要进,入的意愿,只不过在题目当中,有可能会换一个变量的名字,或者啊进行一些细微的变化,但是大家只要抓住,谦让和表达意愿这两个核心的思想,其实很多问题就都有思路来分析了,那具体的,大家还需要根据课后习题来进行,巩固,另外想要说的一点是双标志先检查法,他的这种思路其实是很好的,就是先检查这个临界区,此时是否有人想使用啊,如果没人使用的话,那我自己就给他上一个锁,但是导致这个算法问题的关键在于,检查和上锁这两个动作,并不能一气呵成,那么如果能让检查和,上所都一气呵成的话,其实这个算法是没有问题的,那在下个小节当中,我们会学到呃两种硬件的实行方法,用硬件的方式来保证,这两个动作一气呵成,那么就可以保证这个算法的正确性了,好的,那么以上就是这个小节的全部内容
No.16 进程互斥的硬件实现方法
各位同学大家好,在上一小节中,我们介绍了进程互斥的4种,软件实现方法,这个小节我们会介绍另外的3种,进程互斥的硬件实现方法,那么这个小节的学习过程当中,大家需要注意理解各个方法的原理,并且要稍微的了解各个方法,呃有什么优缺点,
一、中断屏蔽方法
那看第一种中断屏蔽方法,其实中断屏蔽这种方式,咱们在之前介绍原语的时候呃也介,绍过他无非就是使用开,和关中断这两个指令,来实现不可被中断,中断屏蔽这件事情,那么原语不可被中断的这个特性,其实也是用这样一组指令来实现的,在一个进程访问临界区之前,他会执行一个关中断指令,也就意味着执行了关中断之后,这个进程就不可能再被中断,也就意味着不可能会再发生,啊这个进程切换的事情,一直到这进程访问完临界区,执行开中段指令之后,才有可能会有别的进程,被切换上处理机运行,并访问这个临界区,所以这种方式很明显它的,优点就是实现起来很简单高效,并且这个逻辑其实很清晰,然后缺点呢,就是它不适用于多处理机的这种系统,因为关中断指令,只对执行关中断指令的那个处理机,有用所以,如果此时处理机a执行了关中段指令,那么就意味着,在这个处理机上面的进程,不会被切换,那么这个进程,就可以顺利的访问临界区,但是对于另一个处理机处理机b来说,他其实还是会正常的切换进程,如果说,此时另外的那个处理机上运行的进程,也需要访问这个临界区,也用这种方式的话,那么有可能会发生,两个处理机上的两个进程,同时对临界区进行访问的情况,所以这是,中段,屏蔽方法不适用于多处理机的原因,另外一个缺点呢就是呃,关中段和开中段这两个指令,它的权限特别大它属于特权指令,需要在内核态下才能运行,因此这种方式,只适用于操作系统内核进程,不适用于用户进程,只有操作系统内核进程,才有权限执行这两个指令,所以这是中段屏蔽方法,那么第二
二、testandset指令
第二个呃,硬件实现方式,是test and set这样一个指令,这个指令可以简称为TS指令,就是test and set,然后有的地方,有的书上,也可也会把它称为test and set lock,也就TSR指令,所以在咱们的408真题当中,也出现过TSL这样的一个表述方式,所以这两个名称大家都需要注意一下,那这个指令其实是用硬件来实现的,在执行的过程当中是不允许被中断,只能一气呵成,呃这个是用c语言描述的,past and set这个指令的一个逻辑,需要强调的是,这个只是为了表示一个逻辑,让大家理解,他在背后做了一些什么事情,但是这些事情其实都是由,硬件来完成的,并且他不可以被中断,系统会为临界区设置一个共享变量,布尔型的变量lock,lock是用来表示当前这个临界区,是否已经被枷锁,是否已经被上锁,如果它为处的话就表示已经有呃,某一个进程表示要对这个临界区上锁,如果为false的话就是不上锁,那么,test and set这个指令,他在背后做的事情就是,首先他会呃,用一个叫做old的,一个也是boy型的变量,用来记录以前lock,它的值到底是多少,也就是用来记录之前,这个临界区是否已经被上锁了,然后无论这个临界区是否被上锁,它在记录下来这个值之后,一定会让这个临界区进入一个枷锁,一个上锁的状态,也就是把lock这个值设为true之后,再呃用某种方式返回呃out这个值,那么在实际使用TSR,实实现互斥的过程当中,就是用这样的方式来实现的,在y这个循环当中,会不断的呃执行test and set这个指令,如果说lock呃本来就是true,也就是说,以前这个临界区就是被上锁的,那么它返回来的old这个值也会为true,那么while这个循环就会一直循环下去,循环条件会一直满足,一直到lock这个值,被当前访问临界区的那个进程,在退出区改成了false,所以就会变成test and set这个返回来的,old这个值变为了false,于是,之前一直被卡在这儿的那个进程,就可以跳出这个循环,然后正式的开始访问临界区的代码段,一直到访问结束之后,再把自己上的锁给解除,所以这是test and set这个指令的一个,呃实现的一个逻辑,这个地方虽然用了return,但实际硬件执行的过程当中,其实就是把lock这个值放到了某一个,物理寄存器里,然后再把lock这个值覆盖为处,呃是做了这样一个事情,所以啊,刚才咱们已经分析了lock为false,和lock为处的情况,那这个算法,和之前咱们介绍的软件实现的方法,相比其实啊在那个双标志先检查当中,导致最后出问题的原因是,在进入区当中进行上锁和检查,这两个操作,并不是一气呵成的,但是如果说用硬件的这样,TSL这个指令来执行的话,那么就可以保证,检查和上锁这两个事情,其实是一气呵成的,一边把它上锁一边进行检查,把以前的那个值给返回回来,所以这是呃,这是他能解决问题的一个原因,这种方式的优点呢,就是实现起来很简单,其实呃如果用,如果有这样一个指令的话,那么我们的代码不会特别复杂,可以很简洁,不需要像软件的实现方法那样,计算推算是否会有呃,异步带来的一些逻辑漏洞,所以这种方式其实是,比起软件解决方法来说是要好的多的,并且它也适用于多处理及环境,当然为什么适用于多处理及环境,这个有兴趣的同学大家可以去,查查一下,涉及到总线相关的一些一些一些特性,但这个方法也有一个呃缺点,就是他不满足让权等待的原则,通过刚才的分析我们知道,当这个呃lock此时是被上锁的情况下,那么此时,想要再进入临界区的另外一个进程,他会一直在这个wild循环里被卡住,一直不断地执行TSL这个指令,所以即使暂时没有办法进入临界区,但是他也会一直占用着CPU,然后执行这个指令,从而导致这种盲等的这种现象,所以这是他的一个缺点,那最后我们再来介绍swap指令,那大家也需要
三、swap指令
就要注意一下,另外这两个名称在考试中,如果不小心遇到的话,也需要能够知道,他说的就是swap之类,这个指令和刚才TSL一样,它也是用硬件实现,并且中间是不允许被中断的,那么swap指令做的事情,其实就是交换了两个变量的值,把a的值换到了b这儿,把b的值换到a这儿,这个逻辑并不复杂,那么它在具体的使用当中,实现互斥是这样实现的,其实表面上看起来,swipe和之前TSR是有很大不同,但是逻辑上来看,他们所做的事情其实也差不多,也就是刚开始,他会用一个o的这样的一个变量,来记录以前,lock这个值到底是是true还是false,lock这个值和TSR里面一样,就是用来表示当前临界区,是否已经被上锁,那么swipe,这个指令对lock号和o的这两个,呃变量进行交换之后,那么,呃以前的lock的值会放到o的里边,然后o的本来是true,那么它这个true,这个值又会被设置到lock这儿,所以其实它做的和TSR指令呃,可以说在逻辑上看是一模一样的,那么,在执行了这个指令之后,会例行检查以前lock这个值是否为处,如果说o的这个值为处的话,那么就说明,在之前这个临界区就已经被上锁了,那么这个循环会一直呃循环下去,一直到old为false,那么说明,之前这个临界区是没有被上锁的状态,那么就会跳出这个循环,然后可以顺利的呃进入临界区,访问这些代码段,当然SWAP指令和TSR指令在硬件,层次可能实现的方式会不太一样,但是我们可以看到,逻辑上看,他们俩做的事情,其实并没有太大的区别,所以他们的优点和缺点,也可以说是一样的,就是实现简单,但是也适合于多处理机的环境,不过同样和TSR指令一样,都不满足让全等待的原则,如果说此时暂时不能进临界区的话,他可能一直被卡在这个循环,这占用处理机,然后不断的循环检查进入盲等的状态,
四、总结
全部内容,我们介绍了三种硬件,进程护持的硬件实现方式,其中TSR和SWAP这两种指令,在逻辑上其实就是做了这样几个事情,大家再结合这个图再来回忆分析一下,那么对于中断屏蔽这种方法来说,它只适用于单处理机系统呃,并且只能由操作系统内核进程来使用,开关中断这两个特权指令,好的,那么以上就是这个小节的全部内容,,
No.17 互斥锁
接下来听考点锁,锁是用于实现互斥的一种方法,你可以简单理解为,所就是一个不而行的变量,只有to和force或者0和一两种状态,分别表示当前已上锁或者没有上锁,有这样的两个函数可以操作所,Aqual这个函数就是上锁获得锁,而release可以释放锁,好,那用锁实现互斥的主要缺点就是盲等,如果进不了临界区就会不断的外循环,被卡在这盲等,这就会导致CPU资源的浪费,违反了让权等待的原则,由于盲等的过程中,需要不断的循环检查,因此这类的锁通常把它称为自旋锁,自己在原地旋转跳跃闭着眼,像我们之前学过的TSL指令,SWAP指令以及单标制法,这些都属于自选所,那无论是哪种自选所,总之都是用于解决进程护斥的问题的,同时所有的这些自选所,包括这介绍的护斥所都有盲等的问题,都违反了让权等待的原则,那值得一提的是,
如果一个进程在盲等,暂时进不了临界区,那么他并不会一直占用呃处理机,如果这个进程他的时间片用完,那调度程序依然会让他下处理机,所以并不是盲等就会一直占用处理机,他的时间片还是会用完的,那这种盲等的特性其实也有它的优点,因为在等待锁的期间,不需要切换进程的上下文,切换进程的上下文其实,代价还是蛮大的,对吧所以在多处理器的系统当中,如果对临界区上锁的时间很短,那么这种自选等待,其实代价反而会很低,在多处理器的系统当中,如果某一个进程P1,此时正在自选等待,那么他只会吃掉这一个核的,啊一个计算能力,那如果此时,上锁的进程P2,也在某一个核心里面运行,P2是不是很有可能在接下,快速的使用好临界区,然后快速的解锁,解锁了之后,这个P1在自选等待的过程当中,是不是突然就会发现哦,解锁了,那它是不是就可以顺利的进入临界区,而这个自选盲等的过程,由于没有发生进程切换,因此盲等的代价反而是很低的,所以自选所的这种策略,通常会用于多处理器的系统当中,呃一个核,此时可能它的运算能力会被吃掉,在忙等但是其他核可以照常工作,并且快速的释放临界区,这种自选锁,就不太适用于单处理机系统,因为如果是单处理机系统的话,PE进程此时他吃掉了唯一一个核对吧,他在这个时间片内,有可能等到被解锁吗,不可能只有他这个时间片用完,并且上锁的那个进程啊,上处理机然后解锁之后,他才有可能获得这个锁,所以在单处理机系统当中,他不可能说这个自旋等着等着哎,突然就获得锁了,不会发生这种情况,因此这种自旋锁,更适用于多处理机的系统,好这就是新考点所用于实现进程互斥
No.18 信号量机制
各位同学大家好在,这个小节中,我们会学习信号量机制,这个极其重要的知识点,在考研当中我,我们需要掌握两种类型的信号量,一种是整形信号量,另一种是记录型信号量,我们会在后面呃分别展开讲解,那么在正式聊本节的内容之前,我们先用之前小节学过的内容来引出,呃两个问题,之前咱们学过,在近程护持当中啊有4种,软件实现方式和3种硬件实现方式,其中单标志法,双标志先检查和双标志后检查,这3种方法啊,都存在着比较严重的一些问题的隐患,大家尝试回忆一下还能不能想起来呢,那么在双标志先检查法当中,我们之前聊过,造成双标志先检查法的,问题的主要原因,在于,他在进入区的检查和上锁,这两个操作是无法一气呵成的,中间有可能,先执行了检查就进行进程切换,所以在这种情况下,如果两个进程并发执行的话,那么就有可能导致,啊两个进程同时进入临界区的问题,那么我们提出第一个问题,能不能用某种方法,能够让检查和,上锁这两个操作都可以一气呵成呢,从而避免啊,这这个双标志检查,先检查法的这个严重问题呢,第二个问题,除了这3种啊,问题比较大的算法之外,PS算法还有后面的三种硬件实现方式,其实问题都不大,但是这些方式,也都无法实现让权等待这个原则,那么第二个问题就是,我们能不能用某种方式,实现真正的让这些,呃并发执行的进程能够遵守让,权等待的原则呢,这是第二个问题,其实这两个问题在很早很早以前,1965年有一个荷兰学者叫做迪杰斯特拉,如果学过,数据结构的同学,对这个名字可能并不陌生,他提出了一个呃很好的解决方式,解决方案叫做信号量机制,那么信号量机制就可以比较好的解决,进程互斥和进程同步的问题,我们先来看一下,对信号量机制的一些文字性的描述,用户可以通过
一、信号量机制简介
用户可以通过啊操作,系统提供的一对原语,来对信号量进行操作,然后解实现进程互斥,进程同步这样的事情,这提到的信号量,其实我们可以把它理解为,简单的理解为它就是某一种变量,它可以是一个整数,也可以是比较复杂的,记录型的这种数据结构的变量,一个数据一个信号量,它可以用来表示,系统当中的某种资源的数量,比如说啊一个系统当中有一台打印机,那么我们就可以啊,为这个打印机,设置一个和他对应的信号量,让这个信号量的值把它初始值设为一,那么就表示这个系统当中,打印机这种资源的数量是有一个,所以这是信号量啊的用法,那么之后我们还会根据,更多的例子,来让大家加深对这这段话的理解,这提到的原语咱们在之前也介绍过,它其实就是一种特殊的程序段,就是由操作系统提供的一种啊执,在执行的过程当中不可以被中断,只能一气呵成的这种程序,而这个原语的具体实是怎么实现的,咱们之前也介绍过,那还记得刚开始开篇的时候,咱们提过的问题吗,双标志先检查法,呃的问题,主要在于他在进入区当中的,检查和操作,呃,检查和上锁这两个操作无法一气呵成,那么既然原语拥有这种一气呵成,不可被中断的特性,那如果说我们把,检查和上锁这两个操作,都放在一个原语当中完成,那么是不是就可以避免他,无法一气呵成的这种问题了呢,所以啊这就是为什么信号量机制,会想到用原语来解决问题的一个原因,那这提到的呃一对原语,指的是wet原语和signal原语,这两个原语啊,其实我们就把它理解为是一种呃,一种我们自己写的程序段,那这个wait其实就是函数名,或者一个函数名,然后这个s,括号里的这个s,我们就把它理解为是一种函数,调用时候的一个参数,那这个点这个地方,大家如果学过数据结构,或者自己有一些编程基础的话,应该不难理解,另外呢wait和signal这两个原语,或者说这两个程序这两个函数,也可以简称为PV操作,这个PV操作,我在自己复习的时候,一直不明白它的来历,然后之前查了一下,才知道它其实是来自两个荷兰语,具体什么意思,有兴趣的同学资大家可以去查一下,在我们的考研当中,经常会把wait和signal这两个函数,这两个原语,把它简写为p和v这样的一种简写,的形式说了这么多,其实这整块想要告诉大家的,无非就是两件事,第一信号量它是一种呃一种变量,可以用信号量来表,示我们系统当中某种资源的数量,第二,我们可以用系统提供的一对原语,wait原语和signal原语,来对信号量进行操作,那具体是怎么操作呢,我们之后的讲解还会展开,这我们注意啊这个地方的有一句话,信号量这种变量它可以是一个整数,也可以是一个更复杂的记录型的变量,那么根据这个问题,我们就衍生出了两种类型的信号量,一种叫整形信号量,一种叫记录型信号量,我们先来看第一种整形信号量
二、整形信号量
整形信号量其实就是用一个整数,来表示某一种系统资源的数量,那么,它和普通的整形变量有什么区别呢,普通的整形变量,我们可以对它进行加减乘除,各种各样的,操作运算,但是对于,作为信号量来说的这种整形变量来说,我们只能对这种信号量,进行啊三种操作,一种是初始化,另一种是p操作还有一种是v操作,所以呃按照他的定义,如果说一个计算机系统当中,有一台打印机,那么我们就可以定义一个和打印机,这种系统资源,对应的整形信号量,我们把它呃这个变量名设置为SS,等于一也就是说,呃这个系统当中,原本是有一台打印机这种系统资源的,那呃,我们可以通过wait和signal或者说PV,这两个操作,对s这个信号量进行一些更改,一些操作,那具体wait和signal,啊做了一些什么事情呢,我们直接来看一个例子,如果一个进程P0,他想要使用打印机这种资源,那么由于这种资源是有限的,他只有一个,并且我们需要互斥的访问这个打印机,所以在使用打印机资源之前,进程P0他必须要先执行一个wait言语,对信号两s进行操作,那wait原语当中做了两件事情,第一件事就是这句代码,他会检查当前,剩余的资源数量是否是还足够,如s小于等于0,就说明现在,系统当中已经没有这种资源了,那么,这个进程就会被一直卡在这个循环,下不去但是由于P0进程,执行wait原语的时候这个s的值是一,所以这个循环并不会卡住它,它会跳出这个循环然后执行下一句,下一句是把s的值减一,也就是说,这个打印机资源,已经被分配给P0进程了,因此系统当中打印机资源的数量,s要减一,所以这是这一步的意思,所以s就变为了0这个值,当P0在访问打印机资源的过程当中,假如说发生了进程切换,有另外的进程,比如说进程P1,他也想使用打印机这种资源,那么他在使用之前,先执行wait这个原语,不过由于此时s的值已经是0,也就是说,此时系统当中已经没有打印机资源了,所以P1这个进程在执行y o这个循,环的时候由于满足这个循环的条件,所以它会一直循环一直循环循环等待,直到P0这个进程把打印机资源释放了,那其他的进程也一样,即使他们都想真正使用打印机资源,但是由于这个循环是过不了的,所以他们会一直循环等待,那一直到P0使用完打印机资源之后,他又执行了signal原语,signal原语做的事情很简单,其实就是把这个s的值加一,也就是告,也就是告诉把这个打印机还给系统,并且啊,把打印机对应的这个信号量的值加一,也就表示打印机资源的数量变多了,那当打印当s等于一之后,P1 或者说其中的某一些,某一个正在等待打印机的进程,这个循环就可以被跳过,然后再继续执行下面的循下面的语句,于是P1进程就可以开始使用打印机,然后一直到P1再把打印机释放,然后又把这个打印机资源,交给别的进程使用,那这就是整形信号量做的一个事情,其实通过对比大家会发现,整形信号量,在wait这个原语当中的这两个操作,其实逻辑上来看和,啊双标志先检查法当中,先检查后上锁,其实是以做的其实做的是一样的事情,大家可以对比着来,串联一下这两个知识点,那么因为它是用一个原语来实现的,检查和上锁,所以它这两个操作一气合成,就避免了啊双标志先检查法那种,就是两个进程同时进入临界区的问题,第二点,在整形信号量机制当中,我们可以看到这个地方有一个y循环,如果说此时一个进程,暂时进不了临界区,暂时使用不了这个资源,那么他会一直卡在这个循环,这因此会发生进程一直占用处理机,盲等的这种情况,并不满足让权等待的原则,那这个地方可能会有一个疑问,如果一个进程暂时进不了临界区,也就意味着他,他被卡在wait这个原语的,啊这个y循环里,那么既然wait原语它是不可被中断的,那么也就意味着,当前正在执行y循环的这个进程,是不是一直不会被切换呢,这个地方确实是一个让人感觉不太,严谨的地方啊,那有兴趣的同学可以自己下去,研究一下,但是我们在很多经典的教材当中,其实他们都是这么写的,所以这个地方我们姑且认为他,没有问题,不会导致,一个进程一直占用处理机的情况吧,那在整形信号量当中,其实比较容易考察的是他存在的问题,这经常会把这个,整形信号量和记录型的信号量做对比,那么他俩的区别就在于,整形信号量不满足安全等待,会发生盲等,所以这个地方是大家需要重点关注的,而刚才提出的问题,有兴趣的同学可以自己下去研究一下,那今,
三、记录型信号量
接下来我们介绍第二种信号量,叫做记录型信号量,刚才的整形信号量有一个很大的缺陷,就是如果一个进程暂时进不了临界区,那个资源数呃系统的那种资源暂,时不够的话他会一直占用处理机,一直循环检查,从而导致盲等的情况,不满足让权等待的原则,所以后来人们又提出了记录行信耗量,就是为了解决刚才所说的那个问题,这种信号量,是用一个记录型的数据结构来表示,其中value表示的是当前,这种系统资源的剩余数量,比如说刚才咱们提到的打印机,第二个比较重要的是,在这种信号量当中,他还会呃保持一个指向,等待这种系统资源的等待对列,指向等待他的那些进程,那具体的,咱们之后用呃一个具体的例子来,再展开细料,对于记录型信号量的wait操作,是这样的,首先执行了wait操作,就意味着某一个进程,它需要使用,啊与这个信号量对应的那种系统资源,所以我们会把这个资源的数量,也就是它的value的值,做一个简易的操作,当它简易之后,又会对这个value的值进行一个检查,如果说我们在执行了简易操作之后,导致这个value的值已经小于0了,那么就说明,他在简易之前,其实已经没有这种系统资源了,因此在这个时候,是没有系统资源可以分配给当前申请,啊这种资源的那个进程的,因此,在这个地方需要执行一个block原语,这个原语的作用就是,把当前的这个进程,阻塞起来,主动的阻塞放弃处理机,并且把它挂到,呃这个信号量对应的这个对列,等待对列当中,这是wait操作,而sign的操作是当一个进程,它在使用完这种系统资源的时候,会执行的一个原语操作,首先是会把这种资源的数量,进行一个加一的操作,如果value的值在加一之后,仍然小于等于0,那么就说明,在这个进程释放啊这个资源之前,依然还有,依然还有一些进程是处于等待队列的,所以就需要再调用一个wake up原语从,s呃,也就是从这个信号量对应的等待队列,当中唤醒其中的某一个进程,然后让他从阻塞态回到就绪态,并且把这他所申请的,他所等待的这个资源分配给他,所以这就是wait原语和signal,原语要做的两件事情,他们做的一个共同点是,不管怎么说,肯定是对value value的值,先减减或者先加加,然后之后,还会再对value的值做做一个检查呃,来再来判断是否需要对进程进行阻塞,或者是否需要唤醒某一个进程,那具体我们来看一个呃例子,
三、例子
如果说一个计算机当中,计算机系统当中有两台打印机,那么我们可以把初始的,呃这个信号量的初始值把它设置为2,就是这个value值设置为2,而刚开始,等待这个,呃打印机资源的等待对列肯定是空的,各个进程在使用这个打印机资源之前,需要先用wait原语来申请打印机资源,在使用完之后,又需要执行signal原语来,释放一个打印机资源,所以所有各个进程的,代码大概是这个样子,这些省略号就是其余的,部分那,假如说刚开始CPU是为,P0这个进程服务的,那么当他执行到wet原语的时候,首先会执行的事情是value减减对吧,所以这个s点value这个值它会由2减为1,之后啊,系统判断此时是有打印机资源的,所以会把其中的一个打印机分配给,P0进程,然后P0可以往下开始使用打印机,之后切换到了呃p e进程,CPU为p e进程服务,那么p e进程,同样的它执行wait原语的时候,其实也是在申请一个打印机资源,那首先做的事情是会会让,s点value的值,做一个减一的操作,所以这个值从一变为了0,之后系统会把打印机分配给PE进程,然后PE进程可以开始使用打印机,那么我们可以看到,当这个value的值变为了0的时候,此时系统当中的所有的打印机,刚好就已经全部分配给了,呃某一些进程,说明资源恰好分配完毕,那么接下来如果CPU在为P2进程执行,而P2进程同样需要使用打印机资源,他在执行wait操作的时候,同样首先肯定是让,这个,信号量的value值做一个减一的操作,所以它会由0这个value会由0变为-1,当value的值在减一之后小于0,那么就说明呃,此时系统当中已经没有多余的资源,可以分配给这个进程了,因此,这个进程会主动的在wait原语当中,主动的执行一个block原语,也就是把自己阻塞的原语,所以P2进程它会被挂到呃,打印机这种资源的等待对列里,那在这个地方我们会发现,当这个呃value的值等于-1的时候,是有一个进程在等待打印机资源,刚好就是这个负数的绝对值,接下来CPU在转向为P3进程服务,那么同样的,他在执行wait的原语的时候,首先是对呃value值减一,同样的当它减一之后小于0,所以也会知道,此时这种资源,打印机资源已经分配完毕,所以P3进程也会主动的执行block原语,因此P3也会被插入到等,相应的等待队列的对位,就是这个样子,那么由于P2和P3都不能执行,因此接下来CPU只能为P0或者P1服务,那假设接下来CPU是为P0进程服务的,P0在使用完打印机之后,执行了一个signal原语,还记得sickle原语做了什么事情吗,首先它会让,这个value的值做一个加一的操作,所以value会从-2变成-1,而如果value的值在加一之后,它依然是小于等于0的,那么就说明此时在等待队列当中,依然还有一些进程正在等待,所以P0进程在signal原语当中,它会主动的啊执行一个wake up原语,用来唤醒信号量,对应的等待对列当中的某,呃对头的进程,也就是P2,所以P2进程会从阻塞对列,的放回就绪对列,并且会把,P0刚,刚才释放的这个打印机资源分配给P2,就是这个样子,那么接下,来,P0在执行了其他语句之后就执行完毕,那之后,如果说CPU再接着为P2这个进程服务,P2就可以得以开始使用打印机资源,然后在使用完了之后,会对打印机资源进行释放,执行一个signal原语,那么同样的,它首先是会对,呃value的值进行加一的操作,那由于他加一之后,他的值依然是小于等于0的,所以说明此时在等待队列当中,还是有进程正在等待这种资源,所以他也会执行一个wake up原语,来唤醒,此时处于等待队列对头的这个进程,也就是P3进程,因此在执行了这个wake up原语之后,P3进程会从阻塞态变回呃就绪态,并且这个,P2刚才释放的这个打印机资源,会被分配给P3,然后,P3的信息会从这个等待对列当中消失,这样的话,这个等待队列就变为了空的状态,那接下来PR在执行完剩余的代码,然后就呃结束了,在之后如果说CPU又回到了为P1服务,那么P1当它使用完打印机资源之后,又会对打印机进行释放,那此时它会对,首先,是会对value的值进行一个加一的操作,于是从value的值从0变为1,而由于啊加一之后他已经大于0了,所以说明此时在等待队列当中,已经没有进程,在等待了,所以P1进程他在执行signal操作的时候,并不需要执行wake up原语,那接下来,系统会回收,这个分配给P1的打印机资源,让P1得以继续往下执,行,那最后还有P3这个进程没有结束,所以呃CPU会为P3进程服务,那么P3在使用完打印机之后也是会,呃对打印机资源进行释放,同样的这个value的值会加一变为2,然后之后,系统回收打印机资源,然后P3得以顺利的执行最后结束,那么这就是一个,记录型信号量的一个具体的例子,相信根据通过刚才的动画,大家应该能够比较形象的理解,好的那么我们在,
结合刚才的wait和signal的这个代码,再来呃进行一个总结,wait和signal这两个原语,分别可以用于对,系统资源的申请和释放的时候,那么,这个value的初值,其实是表示,系统当中某一种资源的数目,如果对一个信号两s进行一次p操作,也就是申请这个,信号两s对应的那种系统资源的话,那么就意味着,这个p操作会导致,系统的这种剩余资源数会减一,所以我们需要首先进行一个,value减减这样一个操作,那如果说在他简易之后,发现他的值已经小于0了,那么就意味着这种资源,其实之前就已经分配完毕了,所以他需要主动的调用block,原语来进行自我阻塞,这会导致,这个进程的状态从运行态变为阻塞态,并且这个进程相关的信息会被挂到,呃s这个信号量对应的等待对列当中,用于之后的唤醒工作,那么可以看到,这种机制其实遵,循了所谓的让权等待原则,因为当一个进程他暂时申请呃,得不到他所,申请的这种系统资源的时候,他会主动的进行自我阻塞,主动的放弃CPU,所以就不会出现,之前的那些解决方案当中,盲等的这种现象,另外呢,如果对一个信号量s进行v操作,那么就意味着,呃需要释放一个,与s这个信号量相对应的那种资源,所以我们此时需要,对这个value的值进行加一的操作,那如果说加一之后,它的仍然是小于等于0的,就意味着此时,在呃s对应的等待队列当中,依然还有进程在等待这种资源的分配,所以就需要调用wake up原语,从这个队列的对头当中,把对头的那个进程给唤醒,也就是让他从阻塞态重新回到就绪态,并且把那个资源分配给这个进程,所以这就是记录型信号量,在p操作和v操作当中做的事情,那么我们再来啊简单的回顾,
四、总结
录下这个小节讲了,整形信号量和记录型信号量,其中整,形信号量比较容易考察的,是它存在的问题,也就是不满足让群等待的原则,有可能会出现盲等的现象,而记录行信耗量,可以说是操作系统这门课当中,最重要的知识点啊,在在大题和小题当中,都会有很高的概率,会考察记录行信耗量,那么我们需要注意的是,那么大家需要自己在,草稿纸上动手写一下,记录型信号量的PV,操作还有,在什么条件下需要执行block和wake up,这两个原语,一定不能用死记硬背的方式,需要把这个地方彻底的理解,那从刚才的讲解我们也知道,这种记录型的信号量其实很方,可以很方便的用于,实现系统资源的申请和释放,这样的一个呃操作,那除此之外,记录型信号量还可以实现进程互斥,进程同步这两个咱们之前提过的问题,但这两个问题,咱们会放到下个小节的视频当中,再进行讲解,另外我们需要强调的一点是,如果说在题目当中出现了呃,对某一个信号量s的p操作和v操作,那如果说题目中没有特别说明的话,这个s啊,这个信号量指的都是记录型的信号量,也就是说,如果说这个批操作啊,暂时得不到呃他所申请的资源的话,那么这个进程不会忙的而是会被,而是会进入到阻塞的状态,好的,那么以上就是这个小节的全部内容,
No.19 用信号量机制实现进程互斥、同步、前驱关系
各位同学大家好,在这个小节中,我们要学习,怎么用信号量机制来实现进程的同步,互斥的关系,那么我们之前学习了啊,互斥的几种软件实现方式,和硬件实现方式,但是这些实现方式都有一个共同的,缺点,就是没有办法实现让权等待这个原则,而信号量机制当中,设置了进程的阻塞和唤醒,就刚好可以解决啊让权等待这个问题,所以信号,量机制是一种更先进的解决方式,那我们上个小节介绍了信号量是什么,信号量机制的PV操作,分别做了一些什么事情,给跨考同学的建议是,不要先一头钻进大码里,而是要注意理解信号量背后的含义,其实一个信号量,它无非就是对应了某一种资源,而信号量的值,表示的是这种资源的剩余数量,如果它的值小于0的话,那么就说明,此时至少有一个进程正在等待,这种资源,而如果一个进程,执行了对某一个信号量的p操作,那么他想表达的其实是啊,申请一个这种资源,如果资源不够的话,这个进程就会执行vlog,源于主动的阻塞,而如果一个进程,他对某一个信号量执行微操作的话,那么就说明,这个进程想要释放一个这种资源,想要产生一个这个资源,如果此时有进程正在等待这个资源,那么他就会用wake up原语,唤醒一个正在等待的阻塞进程,那我们来看一下,如何利用这种机制实现进程的互斥
一、信号量机制实现进程互斥
那经过之前的学习我们知道,系统当中的某一些资源,是必须互斥访问的,而访问这种系统资源的那段代码叫做,临界区,所以既然这个资源需要互斥访问,那么就说明同一时刻只能有一个进程,进入临界区代码,所以要解决进程互斥的问题,我们首先要做的是呃要划定临界区,也就是说,哪一段代码是用于访问临界资源的,另外为了实现对临界区的互斥访问,我们需要设置一个互斥信号量叫Mutex,Mutex就是英文的互斥的意思,把这个信号量的初始值设为1,就像这个样子,然后当一个进程要进入临界区之前,需要对Mutex执行p操作,在出了临界区之后,需要对Mutex执行v操作,所有的进程都是这样,这样就可以实现各个进程对临界区,这段代码的,互斥访问了,那我们在开篇提到过信号量,它其实就是用于表示某一种资源,那我们可以这么理解,这个互斥信号量Mutex,我们可以认为它所表示的资源是,进入临界区的名额,它的初始值为一,那么就说明,刚开始可以进入这个临界,区的名额只有一个,那当某一个进程,对Mutex执行p操作的时候,其实在背后的逻辑就是说,我想申请一个进入临界区的名额,那如果名额这种资源,此时还有剩余的话,那么这个进程就可以顺利进入临界区,而如果此时另一个进程也尝试,执行p操作,也就说他也尝试申请一个名额,那么我们知道这个名额总共只有一个,所以此时这个进程,对于名额,这种资源的申请就得不到满足,他必须阻塞在这个地方等待,而直到这个进程他使用完临界区之后,他又对Mutex执行微操作,也就说他会归还这个名额,那在这个时候就可以把P2进程给唤醒,让他进入临界区,所以可以看到,我们用这样的方式就实现了各个进程,对临界区的互斥访问,那在上个小节中,我们对信号量是这么定义的,simple这个其实就是信号量的英文单词,那这个地方需要提醒大家的是,如果题目没有特别说明的话,我们对一个信号量的定义,只需要像这个题目这样,啊3FORMUTEX等于多少,用用这种方式来定义就可以了,我们并不需要啊,写出这个信号量的数据结构,当然大家也要能够自己写出啊,信号量定义的这个数据结构,那这个地方,我们虽然,使用了这样一个简单的方式来定义,但是只要我们用Sam for,这个关键字来开头的话,那么就意味着这个信号量,它并不是整形信号量,它是一个记录型的信号量,也就说这个信号量是带有排队阻,塞的这个功能的,并不会盲等,那这是大家在做题的时候需要注意的,第一个点,第二个点,对于不同的临界资源,我们需要设置不同的护持信号量,比如说,我们的系统中有P1和P2这两个进程,他们需要啊访问打印机这种临界资源,而P3和P4,他们需要访问摄像头啊这个临界资源,那在这种情况下,我们要给访问打印机的临界区,设置一个信号量Mutex 1,要给访问摄像头的这个临界区,设置另一个信号量Mutex 2,另外必须注意的是,PV操作必须成对的出现,如果缺少这个p操作,那么就呃,没办法保证,各个进程互斥的访问临界资源的,这个事情,如果缺少v操作的话,那么就有可能会导致某一些进程阻,塞了之后永远得不到唤醒,那这是大家在做题的时候,需要注意的3个点,接下来我们再来看第二个问题,
二、信号量机制实现进程同步
耗量机制来实现进程的同步,那进程的同步这个概念我们已经,有一段时间没提了,这再来简单的复习一下,所谓的同步,就是说要让各个并发执行的进程,按照啊要求的顺序,有序的推进,比如说有P1 P2这两个进程,当他们在系统当中并发的执行的时候,啊由于系统的环境很复杂,所以操作系统在调度的时候,有可能是P1线上处理及运行,有可能是P2线上处理及运行,比如说,P2先上处理机运行了代码4和代码5,而此时他时间片用完了那又切换回P1,然后P1运行了代码一代码2,接下来又切换回P2运行了代码6,等等等等,总之由于这两个进程在系统中是并,发的运行的,因此,他们之间的这些代码执行先后顺序,是我们所不可预知的,而有的时候,我们又必须让这些代码的执行顺序,按照我们想要的那种方式进行,比如说当这两个进程并发执行的时候,P2的代码4必须基于P1的代码一,代码2的运行结果才可以,执行那么在这种情况下,我们就必须保证代码4,是在代码一和代码2之后,才执行的,所以这就是所谓进程同步的问题,我们要解决他们之间,并发运行存在的异步性,让他们按照我们想要的顺序啊,相互配合,着有序的推进,那我们怎么用信号量,机制来实现进程同步呢,
首先我们要做的就是要分析,在什么地方需要实现所谓的同步关系,也就是说,要在什么地方,需要保证所谓一前一后的两个操作,某一个操作一定要在前,而另一个操作一定要在后,这是所谓一前一后的意思,第二步我们要设置一个同步信号量s,它的初始值为0,那以刚才P1 P2为例,如果,代码4必须在代码2之后才能执行的话,那么P2在代码4之前,需要对s这个信号量执行一个p操作,而P1在代码2之后,需要对s这个信号量执行一个v操作,我们来分析一下会发生什么情况,假如刚开始是P1被调度,他线上处理及运行,那么P1运行了代码一代码2之后,执行了呃v操作,这个v操作会导致s的值加1,这个值本来是0,那在加一之后s的值变为了一,那接下来如果P2上处理机运行的话,当他对,s这个信号量执行p操作的时候,嗯就会发现s的值是一表示啊,有可用的资源,所以P2这个进程并不会被阻塞,它就可以往下执行代码4,那刚才我们所说的这种情况是,先执行了代码一代码2,然后再执行了代码4,那再来看第二种情况,假设刚开始是P2先上处理机运行,那么P2首先会对s这个信号量执行,一个p操作,那由于s的值刚开始是0,所以P2会在这个地方被阻塞,它暂时没办法运行代码4,而直到P1上处理机运行,运行了代码一代码2之后啊,他对s这个信号量执行的一个v操作,那根据上个小节我们讲的逻辑,这个v操作会唤醒此时正在等待,s这个信号量的进程,也就是会唤醒P2这个进程,也就是说只有P1执行了v操作之后,P2才有可能被唤醒上处理机运行,那这就保证了,代码2一定是在代码4之前执行的,我们依然是用信号量代表某种资源,这样的思路来分析这个问题,这个信号量s它表示某种资源,那具体是什么资源没必要关心,刚开始s的值是0,就意味着刚开始这种资源是没有的,而P2要执行代码4之前,他一定需要获得这个资源,所以他在执行代码4之前,需要执行一个p操作,但是呢这种资源只有P1能够释放,所以,当P2申请这种资源得不到满足的时候,它就会被阻塞,而由于这种资源只有P1能够产生,所以只有P1能够在某一个特定的位置,唤醒P2这个进程,所以这就实现了进程之间的同步关系,那么我自己对这种同步关系,进行了一个小小的总结,如果要实现进程之间的同步,那么我们需要在前操作之后,执行v操作,在后操作之前执行p操作,什么意思呢,代码2是必须在,前执行的代码4是必须在后执行的,所以代码2是所谓的,必须在前的这个操作,代码4是必须在后执行的操作,因此我们需要在前操作,也就是代码2之后,执行一个v操作,在后操作也就是代码4之前,执行一个p操作,那我们再把这个表数精简一点,就是前v后p,我们设置一个信号量初始值为0,表示刚开始这种资源是没有的,而只有执行前面那个操作的进程,可以释放这种资源,所以这是前v,而执行后面那个操作的进程,在他的操作之前,需要申请一个这个资源,所以这是后p,当他申请的这个资源,得不到满足的时候,这个进程就会阻塞,只能由前面那个进程把他唤醒,那这个技巧,是解决进程同步问题的一个关键,我们来看一下,怎么利用这个技巧,来解决更复杂的进程同步问题
三、信号量机制实现前驱关系
我们的课本上,专门讲了一个叫做进程的前驱关系,那这个图很好理解,其实他想表达的就是,只有S1这个事件发生了之后,或者说只有S1这个代码执行了之后,才能执行S2和S3,而只有执行了S2之后,才可以执行S4和S5,另外只有执行了S3 S4 S5之后才能执行S6,那我们假设这几句代码分别是P1 P2 P3,P4一直到P6这几个进程需要执行的,那我们来看一下,怎么用信号量机制解决这么复杂的,进程之间的同步,问题首先我们需要分析的是,在这个前驱图当中,其实它包含了很多对的啊,进程同步关系,每一条线,其实就是代表一个一前一后的,同步问题,所以我们需要给每一对啊,这种一前一后的这种同步关系,都设置一个同步信号量,那我们这就分别用a b c d e f g,来分别表示啊,每一个这种同步关系,并且同步信号量的初值都是0,也就是说这种资源刚开始是没有的,这种资源,只能由前面这个操作相关的进程,来产生那由于S1必须在前,S2必须在后,所以当S1这个事件发生了之后,也就这个前操作之后,我们需要对相应的信号量,执行一个v操作,这是前v,而当后面这个操作发生之前,我们需要对相应的这个同步信号量,执行一个,p操作这是后p,前面这个操作完成了就执行v,后面这个操作开始前就执行p,前v后p,那其他的所有的这些同步关系也都是,呃一葫芦画瓢,所有都是前v后p,那这样的话我们就可以很轻松的啊,用PV操作,实现这么复杂的进程之间的同步关系,简单的对照图看一下,S1这个操作完成了之后,他需要执行两个v操作,一个是v a一个是v b,那对应的就是代码的这个部分,S1之后执行v a和v b,而S2这个代码执行之前,他需要执行一个PA操作所,以S2之前有一个PA,而S2执行之后,它又需要对c和d分别执行v操作,所以它后面又有VC和VD,那再来看S6,S6这个代码执行之前,它需要对,这几个同步信号量都执行p操作,所以S6之前,需要进行PE PF和PG,总之虽然这个同步关系有很多层,但是我们只要呃知道前v后p这个,技巧,我们就可以把这个同步关系很清晰的,很轻松的表达出来了,那这个地方,希望大家呃暂停来分析一下这个代码,如果各个进程,以不同的顺序上处理机运行的话,到底能不能,实现我们这表达的这么多的呃,同步关系呢,比如说,刚开始是P5这个进程上处理机运行,那么他所要做的第一件事是对d,这个信号量执行一个p操作,而由于d刚开始的值为0,所以他会被阻塞在这个地方,因此接下来就会发生进程调度,切换为另一个进程,那假设接下来上处理机运行的是哎,P2这个进程,那他对a这个信号量执行p操作,同样的他也会被阻塞在这个地方,那除非P1进程上处理机运行了,当他执行了S1这个代码之后,他会执行VA和VB操作,那VA这个操作会把P2这个进程给唤醒,所以接下来P2这个进程才会执行,S2这个代码,也就是说S2肯定是在S1之后执行的,这和这个前驱图所反应的啊,关系是一样,的那当P2执行了S2这个代码之后,他又会对d,这个信号量执行一个v操作,这个v操作又会唤醒,刚才被阻塞的P5这个进程,那之后P5才可以执行S5这句代码,所以对于d这个信号量的PV操作,也保证了,S5这句代码,一定是在S2这句代码之后才能执行的,这也和我们这,所反映的这对同步关系是一样的,那剩下的就不再展开,大家可以暂停自己给自己出一些题,来分析一下,这些同步关系到底是如何被满足的,好的那么这个小节中
四、总结
我们介绍了很重要的知识点,怎么用信号量机制实现进程的互斥,同步,几乎每一年都至少有个大提示要,考察这个信号量机制实现,互斥和同步的,所以,对于这个小节的掌握是十分重要的,那么如果要用信号量机制,实现进程互斥的话,我们可以设置一个初始值唯一的,互斥信号量,并且在临界区之前执行p操作,临界区之后执行v操作,而如果要实现进程的同步的话,那么我们需要设置一个呃,初始值为0的同步信号量,另外需要在前操作之后执行v操作,需要在后操作之前执行p操作,也就是前v后p,用这样的方式,就可以保证啊,进程之间一前一后的这种同步关系了,那这是互制和同步问题的一个基本,套路,那最后我们所介绍的进程的前驱关系,它本质上也是一个进程同步的问题,只不过它是多级的同步,但只要我们能掌握,前威后辟的这种技巧的话,嗯前驱问题其实也很好解决,那对于信号量机制的考察,除了实现互持和同步之外,有的时候有可能会考察呃,用信号量机制来实现资源分配的问题,比如说系统中有3个打印机,那么这种情况下我们就需要把,打印机对应的那个信号量初始值,设置为3,然后,当一个进程需要申请使用这个资源,的时候,就需要对这个资源所对应的信号量,执行p操作,然后使用完了之后就需要执行v操作,那这一点其实只要掌握了信号量,他在背后所表示的逻辑也并不难理解,那这个小节中,我们只是简单的介绍了PV操作的一个,简单的使用技巧,并没有涉及实际的题目,那从下个小节开始,我们会介绍几个,很经典的进程同步问题,用来帮助大家,更好的学习如何用信号量机制,解决复杂的进程同步,进程互斥的问题,好的,那么以上就是这个小节的全部内容
No.20 生产者消费者问题
一、问题描述
各位同学大家好,在这个小节中,我们会学习一个经典的进程同步互,斥的问题,并且尝试用上个小节学习的PV操作,也就是信号量机制,来解决这个生产者消费者问题,问题的描述是,这样的在一个系统当中,有一组生产者进程和一组消费者进程,生产者进程每次生产一个产品,并且放入缓冲区,那这儿,缓冲区其实就是用来存放数据的,呃一片区域,我们可以把它理解为一个一个小格子,每一个缓冲区,也就是每一个小格子里边,可以放一个产品,一个也就是一坨数据,然后消费者呢,又会从这些缓冲区当中,每次取出一个产品,也就是每次取出一坨数据,那生产者和消费者这两组进程,他们会共享的使用啊,一个初始为空大小为n的缓冲区,也就是有n个小格子可以放n坨产品,那需要注意的是,这个缓冲区是有容量限制的,他只能放5个产品,那只有缓冲区没有满的时候,生产者才可以往这个,缓冲区里放入产品,比如说像这个样子,因为缓冲区大小为5,所以他最多只能放入5个产品,也就5坨数据,那如果缓冲区此时已经满了,但是生产者进程,还想尝试往里边写数据,那生产者进程必须阻塞等待,等这个缓冲区被取空的时候,他才可以往里边写数据,那有没有发现,这个条件,就是我们上小节分析的同步问题,也就是说缓冲区没满的时候,生产者才能生产,这是一对一前一后的同步关系,另外呢消费者进程,他会从这个缓冲区里取走这些产品,就是取走这些数据,就像这个样子,那当缓冲区没满的时候,也就是有空闲的缓冲区的时候,生产者就可以继续生产,也就是说,当一个消费者从缓冲区取走数据之后,如果此时有生产者是处于阻塞,状态的,那么消费者进程应该把生产者进程给,唤醒让他重新回到旧绪态,不过呢生产者进程只是回到了就绪台,这并不意味着,生产者进程会立即往里边写数据,所以接下来有可能是消费者进程继续,往把这些缓冲局里的数据依次取走,那需要注意的是,只有缓冲区不空的时候,也就是说只有缓冲区里有数据,有产品的时候,消费者进城才可以从,缓冲区里取走数据,不然的话消费者进城就必须阻塞等待,所以其实这个条件,就是第二对的同步关系,也就是只有缓冲区没空的时候,或者说缓冲区里有产品的时候,消费者才可以取走数据才可以消费,那最后一个条件是,这个缓冲区它是一种临界资源,各个进程必须互斥的访问缓冲区,那这我们来解释一下,为什么必须互斥的访问缓冲区,那假设这个系统当中,此时有两个,呃生产者进程正在并发的运行,那么第一个生产者进程在,检查缓冲区的时候,发现哎,这个缓冲区此时都是空的呃,所以我可以挑选其中的某一片,往里边写入数据,与此同时,第二个生产者进程他也并发的运行,他也发现此时这个缓冲区都是空的,因此他也会挑选其中的一块啊,写入数据,那假设下面的这个生产者进程,往这写了一篇数据,而上面这个生产者进程,他自己挑选的也是这一片区域,那接下来这个生产者进程,也往这片区域冲入自己的数据,那这样的话,他就会把前面那个,进程的数据给覆盖掉,所以可以看到,如果各个进程同时访问缓冲区的话,那么有可能会出现一系列的问题,比如说刚才我们提到的数据覆盖,所以缓冲区这种资源,它是一种临界资源,各个进程必须互斥的访问,那么既然需要互斥的访问,这其实就是我们之前学过的,互斥的问题,刚才我们做的这一系列分析,找出了这个问题里边,所隐藏的一系列的同步和互斥的关系,那第二步我们需要做的是啊,要根据各个进程操作的流程,来确定PV操作的大致顺序,那其实很简单,就是我们上个小节中提到过的,前v后p,第一对关系是,只有当缓冲区里有产品的时候,消费者进城他才可以消费,另外呢只有缓冲区没满的时候,生产者进城才可以生产,所以这样的两对一前一后的同步关系,我们就需要给他们分别,设置一个同步信号量,并且在前面这个动作完成之后,需要对这个,同步信号量执行一个v操作,在后面这个动作开始之前,需要对这个同步信号量执行一个,p操作那我们一会来解释一下,这两个同步信号量,它在背后的含义,那这是这两对同步关系里边,PV操作的一个大致顺序,那缓冲区里有产品,这个事件是生产者进程出发的,也就是说,当一个生产者进程往缓冲区里,放入一个产品,放入一个数据之后,他就需要对,这个信号量执行一个微操作,而当消费者进城,从缓冲区里取走数据之前,他需要对这个型号量执行一个p操作,那下面的这个独木关系也一样,那除了这两对同步关系之外,我们还需要实现对啊,缓冲区这种临界资源的互斥访问,那互斥的实现其实是很简单的,我们只需要设置一个互斥信号量Mutex,并且让它的初值为一,然后在临界区的前面和后面,分别对互斥信号量执行PV操作,就可以了,那接下来我们应该做的,就是要确定这些,啊信号量的初始值,对于呼斥信号量来说,一般初始值就是一,就像我们刚才所说的那样,而对于同步信号量来说,他的初始值,我们要看他所对应的那种资源,初始值是多少,那我们来看一下这两对同步关系啊,所对应的这种资源到底是什么呢,那先来看上面这对同步关系,消费者进城,他在消费之前需要消耗什么资源呢,需要消耗的是产品对吧,所以它的这个p操作,其实是在申请一个产品,申请一个数据,因此for这个呃同步信号量,它所对应的资源应该是产品的数量,也就是非空缓冲区的数量,而从题目当中给的条件我们知道,缓冲区刚开始都是空的,也就是说刚开始产品的数量,产品这种资源的数量应该是0,因此负这个呃同步信号量的初始值,我们就把它设置为0,表示刚开始没有产品这种资源,而这种资源只能由生产者进程,生产了之后,呃来释放,那再来看第二个同步信号量MT,那我们知道生产者进程,每生产一个产品,就需要消耗一个空闲的缓冲区,因此empty这个同步信号量,它所对应的资源就应该是空闲缓冲区,这种资源,它的数量就是空闲缓冲区的数量,当一个消费者进城取走了产品,也就是呃做了消费的动作之后,他就可以释放一个empty,也就是释放一个空弦的缓冲区,那从题目给的条件我们知道,刚开始缓冲区都是空的,有n个缓冲区,因此空弦缓冲区它的初始值应该是n,所以这样我们就确定了这两个,同步信号量的一个初始值,那接下来就是代码的实现
二、问题分析
生产者进程他要做的事情,就是不断的生产一个产品,并且把产品放到缓冲区,而消费者进程他要做的事情,就是不断的从缓冲区里取走一个产品,并且使用这个产品消费这个产品,那通过刚才的分析我们知道,生产者进程在把产品放入缓冲区之前,需要申请一个空闲的缓冲区,因此当他放入产品之前,需要对MT这个,同步信号量执行一个p操作,而当他把产品放入缓冲区之后,其实就相当于,产品这一种呃资源的数量加一了,或者说非空缓冲区的数量加一了,因此当它放入产品之后,需要对这个型号量执行一个微操作,表示增加一个这种资源,而消费者进程的分析也类似,当他从缓冲区取走一个产品之前,需要先对这个for变量进行一个p,操作,也就是要申请消耗一个产品这种资源,而当他取走了一个产品之后,空闲缓冲区的数量就会加一,因此在这个操作之后,需要对MT进行一个微操作,表示要增加一个空闲缓冲区,那这样我们就实现了两对同步关系,另外,题目中要求缓冲区必须互斥的访问,它是临界资源,所以在访问缓冲区呃前后,分别要对Mutex这个互斥信号量执行,p和v操作,用于实现对缓冲区的互斥访问,那从这个例子当中我们可以看到,实现互制的PV操作,它是在同一个进程内进行的,这个进程对临界区上了锁,而当他访问完临界区之后,又需要的对这个临界区进行解锁,所以这个PV操作,是在同一个进程的呃代码当中实现的,而同步关系就不一样了,我们会发现,这个进程生产者进程里边是呃,对负变量执行了v操作,而消费者进程里边,是对负变量执行了p操作,也就是对同一个变量的PV,这两个操作,是分别需要在不同的两个进程之间,执行的,执行v操作的这个进程会唤醒啊,相对应的执行p操作的这个进程,那如果结合刚才我们给的这个,同步关系的呃前驱图,就会发现,这个代码其实完全可以根据这个,呃前驱图的关系来得出,那这大家可以自己暂停来体会一下,前v后p,那接下来我们要思考的一个问题是,能否改变,
相邻的PV操作的顺序呢,也就是说我们刚才是,先对同步信号量执行了p,再对护斥信号量执行了p,而我们现在尝试把它颠倒一下,先对这个护斥信号量执行p操作,再对同步信号量执行p操作,会发生什么情况呢,我们来看一下,假设,此时这个缓冲区里边已经泛满了产品,也就是MT等于0,for等于n,那此时如果生产者进程执行了一,使mutext的值变为了0,接着他在执行2这一句,而此时由于empty的值已经是0了,所以生产者进程会被阻塞在2这一句,他需要等待empty这种资源,那这个时候啊发生了调度,消费者进程上处立即运行,那消费者进程在执行3这一句的时候,他会发现此时Mutex的值已经是0了,所以消费者进程,他会被阻塞在3这一句,他需要等待生产者进程释放Mutex,所以这就导致消费者进城也被阻塞了,那我们再捋一捋这种情况下,生产者进城他在等待消费者进城,对MT执行微操作,而消费者进程,他又在等待生产者进程,对Mutex执行微操作,也就是说,他们在相互等待对方呃唤醒自己,而两个人其实都被阻塞都在睡眠,而那这种情况,就是我们之后会学习到的死索,进程和进程之间发生了循环,等待被对方唤醒,这样的一个呃神奇的情况,那发生了死索之后,这两个进程就都不能往下执行,都不能推进下去了,那同样的大家可以分析一下,当负等于0当MT等于n的时候,如果按341这样的顺序执行,是不是也会发生死索,所以在这个地方啊我们要强调的是,实现互斥的这个p操作,一定要在实现同步的p操作之后,他们的顺序是不能颠倒的,如果颠倒的话,那么就有可能出现我们这,所提到的死锁的问题,所以这是大家需要啊,特别特别注意的地方,那接下来我们再思考一下,如果微操作的这个顺序颠倒的话,会不会呃出现问题呢,其实是不会的,因为微,操作并不会导致任何一个进程阻塞,所以他们俩的顺序颠倒,并不会发生这种循环等待的问题,所以两个微操作的顺序是可以呃,交换的,那最后我们再来考虑一个问题,生产者生产一个产品,和消费者使用一个产品,是否可以放到PV操作之间呢,其实逻辑上看,是可以放到PV操作这里边的,但是如果我们把这两部分的处理,都放到PV操作里边的话,那么就会导致临界区代码变得更长,也就是说,一个进程对临界区上锁的时间会增长,那这样的话肯定不利于各个进程啊,交替的来使用这个临界区资源,所以,我们要让临界区的代码尽可能的短,因此呃逻辑上来看,把这两个部分放进去是没有问题的,但是实际上会对系统的效能产生影响,因此并不建议啊,把这两个部分的操作放到PV操作里面
三、总结
当中我们介绍了一个很经典的,进程的同步互斥问题,就是生产者消费者模型,那我们根据这个问题,给出了做PV操作相关题目的一个呃,解题思路,这个解题思路,我们会在之后的呃那些问题讲解当中,再继续应用,那生产者消费者问题,它是一个互斥和同步的综合问题,既包含了互斥又包含了同步,但是对于我们初学者来说,比较困难的是要,发现,这个题目当中隐含的两对同步关系,有时候是,生产者进程在等待消费者进程,有的时候又是消费者进程,需要等待生产者进程,所以这是这个题目当中的难点,他是两对啊,不同的一前一后的这种关系,所以我们相应的也需要,给他们设置两对不同的信号量,但是对这些信号量的操作,肯定都是,遵循一个前威后劈的这个原则,那根据上个小节的讲解,相信这个地方已经不难理解了,另外还需要再强调一遍的是,实线互斥和实线同步,是两个不同的p操作,而实线互斥的那个p操作,一定要在实线同步的p操作之后,否则会导致死锁的问题,这个大家再暂停回忆一下,好的,那么以上就是这个小节的全部内容,那么在接下来的讲解当中,我们会适当的加快一点点速度,我们不会再像之前一样,呃每一个,同步信号量都来分析它背后的含义,这个需要大家自己去练习和体会,好的那么以上就是这个小节的,全部内容
No.21 多生产者-多消费者
一、问题描述
各位同学大家好,在这个小节中,我们会学习一个多生产者,多消费者的这样一个问题模型,先来看一下问题的描述,假设桌子上面有一个盘子,每次只能向这个盘子里放一个水果,有4个人父亲母亲女儿和儿子,父亲每一次会向盘子里放一个苹果,而女儿专门等着吃盘子里边的苹果,所以如果盘子里有苹果的话,女儿会把,这个苹果给取出并且把它吃掉,另外母亲会专门往盘子里边放橘子,儿子又专门等着,母亲把橘子放到盘子里,之后,他会把盘子里的橘子给取出并且吃掉,如果说呃由于这个盘子的容量是呃,只能装一个水果,所以,如果父亲把苹果装到了盘子里的话,那么,如果这个苹果还暂时没有被女儿取出,这个时候母亲又包了一个橘子,并且他尝试把橘子放到这个盘子里,但由于这个盘子里只能装一个水果,所以这种这个时候母亲把,橘子放入盘子这个行为应该被阻止,另外呢由于儿子只吃橘子,所以此时,如果盘子里装的是一个苹果的话,那么如果这个儿子正在尝试从,呃盘子里取出水果,那这个水果由于不是他想吃的,所以他的这种行为也应该被阻止,或者说阻塞,那这个问题其实我们可以把它抽象为,啊咱们上一个小节学过的,生产者消费者模型,我们可以把嗯这个盘子看作是一个大,小为一初始为空的缓冲区,然后,把父亲和母亲看作是两个生产者进程,把女儿和儿子,分别看作是两个消费者进程,当然呃,与上一节所讲的生产者,消费者模型不同的是,这个这个小节中的这个例子,这些生产者和这些消费者,他们所生产的东西,和他们所消费的东西类型是不一样的,而上个小节我们介绍的例子当中,所有的生产者生产的都是一种东西,而所有的消费者也都是消费,同一种东西,所以,这就是为什么把这个小节的这个问题,称作多生产者多消费者的,原因所谓的多不是指多个,应该把它理解为是多类,不同类别的生产者,和不同类别的消费者,他们所需要生产的和所需要消费的啊,这些产品是不一样的,那么,这个问题我们怎么用PV操作来解决呢,我们用上个小节学习过的方法,来一步一步依次分析
二、问题分析
首先我e们来看一下,这个题目当中所描述的场景,总共有几类进程,很显然父亲母亲女儿儿子,他们各属于一类进程,另外呢,他们之间是否存在同步和互斥的关系,之前我们说过,这个题目当中,这个盘子我们可以把它看作是一个,缓冲区而上节我们聊过,对于缓冲区的访问,一般来说都需要互斥的进行,所以我们需要实现,对盘子这种缓冲区的互斥访问,另外是否存在啊,这种一前一后的同步关系呢,首先父亲要将苹果放入盘子之后,女儿才能取到苹果,所以父亲进城和女儿进城,他们之间有一对同步关系,另外母亲要把橘子放入盘子之后,儿子才可以取到橘子,所以他们俩之间也存在一对同步关系,第三只有盘子为空的时候,父亲或母亲才可以从呃,才可以把水果放到盘子当中,而这个地方的盘子为空这个事件,其实既可以由儿子出发,也可以由女儿出发,假如说盘子里放的是橘子,那么盘子为空这个事件就应该由,儿子来触发,呃由儿子取走橘子,而如果说盘子里放的是苹果的话,那就应该由女儿取走苹果,然后由女儿来触发盘子为空这个事件,而只有盘子为空这个事件发生之后,才能允许父亲进城或母亲进城,往盘子里放入水果,所以女儿和儿子,他们可能会触发一个事件来引发啊,父亲或者母亲往盘子里啊放入进,放入水果,那这就是这个题目当中所包含的,互斥关系和同步关系,第二步,我们来看一下各个进程之间的,PV操作流程大概是什么样的,我们知道实现互斥其实很简单,无非就是在访问这个互临界资源的,之前和访问临界资源之后,分别对互斥变量,实行一个p操作和一个v操作,而实现同步关系,其实我们之前也说过,呃我们只需要遵循一个原则,就是所谓的前v后p,也就是前面的这个事件发生了之后,我们需要执行一个v操作,而后面的这个事件发生之前,我们需要执行一个p操,作那就是这个样子,那么对于实现互斥关系来说,我们当然是需要设置一个呃,初值为一的这种互斥信号量,而对于这些同步关系来说,我们需要根据具体的情况,来判断每一个同步变量应该设为多少,父亲进城需要把苹果放入盘子,女儿才能取到苹果,所以我们需要设置一个同步信号量,叫做apple,用来实现这个同步关系,而由于刚开始盘子里面是没有苹果的,所以我们把这个值设为,初始值设为0,只有父亲对,这个同步信号量执行v操作之后,女儿对这个,信同步信号量执行的p操作,才不会被阻塞,那同样的,对于母亲进城来说,我们也设置一个叫做orange,也就是橘子的一个同步信号量,它的初始值也设为一,因为刚开始盘子里边是没有橘子的,另外呢只有盘子为空的时候,父亲和母亲才可以放入水果,而刚开始盘子本来就是空的,所以父亲和母亲在刚开始,其实就可以向盘子里放入一个,呃水果,所以这个地方像这一对同步关系,我们就需要,为他设置一个初值为一的同步信号量,用来表示此时盘子是否为空,那么来看一下具体的代码实现
我们总共声明了四个信号量一个,其中一个是互持信号量,另外三个是同步信号量,父亲进城和母亲进城,做的事情,就是不断的准备一个自己的水果,然后再把自己的水果放到盘子里,而女儿进城,和儿子进城做的事情就是不断的从,盘子中取出自己喜欢的水果,并且把这个水果给吃掉,父亲进城在放入水果之前,需要先检查这个盘子是否为空,所以在苹果放入盘子之前,父亲进城需要对,这个信号量进行一个p操作,来检查,此时盘子中到底还可以放多少个水果,那如果这个苹果已经放入盘子之后,父亲进城又需要对呃apple,这个同步信号量执行一个v操作,用来呃让apple的值加一,来告诉女儿进城,呃此时,苹果盘子中的苹果数量已经加一了,而母亲进城也一样,他在把橘子放入盘子之前,需要同样,需要对盘子中还可以放多少个水果,进行检查,如果说此时这个盘子中,已经有别的水果,那么母亲进城会被阻塞,在这个地方会被阻塞,而当母亲进城把橘子放入盘子之后,他同样也需要对,orange这个同步信号量执行一个v操作,来通知儿子进城,此时盘子当中的橘子数已经加一了,那么女儿进城和儿子进城在,取出自己喜欢的水果之前,分别需要检查此时这个盘子当中,是否已经有自己喜欢的,水果所以,女儿进城是对apple,这个信号量执行p操作,而儿子进城是对apple这个,orange这个信号量执行p操作,而女儿进城在从盘子中取出苹果之前,需要先检查此时苹果的,是盘子中苹果的数量是否足够,如果没有苹果的话它将被阻塞,而当他把苹果取出之后,又需要对plate这个信号量执行微操作,用来告诉父亲进城和母亲进城,此时盘子已经变空了,那么儿子进城也和女儿进城也类似,只不过是他在检查的时候,是检查盘子当中是否有橘子,那这样的话,我们就实现了啊,咱们在这个图中表示的这些同步关系,另外,我们还需要实现各个进程对盘子这种,缓冲区的护持访问,所以我们在这些进程访问盘子之前,对这个护持信号量执行一个p操作,访问之后又执行一个微操作,分别是对这个临界区进行加锁和解锁,其他的这些进程也一样,那接下来我们再来考虑一个问题,可不可以不要这个护持信号量呢,就是这个样子我们把护持信号量去掉,并且把,对护持信号量的PV操作也都去掉,那我们来分析一下啊如果是这样的话,这些进程会怎么并发执行,刚开始由于呃,apple和orange这两个信号量的数量都为0,所以女儿进城和儿子进城,无论谁上处理机运行,肯定在执行到这个p操作的时候,都会被阻塞,那么我们假设,刚开始是由父亲进城上处理机运行,那么他首先会对盘子这个同步信号量,执行一个p操作,由于刚开始这个信号量的值位一,也就是说盘子这种资源还足够,所以父亲进城,他可以顺利的跳过这个p操作,然后开始啊把苹果放入盘子当中,而这个时候如果说切换回母亲进城,那么当母亲进城对盘子的信号量执,行p操作的时候,由于这个值已经变为了0,盘子这种资源已经不够了,所以母亲进城在这个时候会被阻塞,等待盘子,而当父亲进城把苹果放入盘子之后,他又会对apple,这个同步信号量执行一个v操作,这个时候女儿进城他又会被唤醒,然后从盘子当中取出苹果,之后女儿进城,又会对plate,这个同步信号量执行一个v操作,由于之前,母亲进城,是因为这个信号量而被阻塞的,所以当他执行v操作之后,母亲进城就会被唤醒,之后母亲进城就可以开始,顺利的访问这个临界区资源,而当母亲进城,在访问盘子这种临界资源的时候,由于plate的值为0,然后orange和apple的值也都为0,所以除了母亲进城以外,别的这些进城即使上处理机运行,也肯定会被卡在这个p操作这儿,也就会被阻塞,所以通过刚才的分析我们会发现啊,在这个题目当中,我们即使不设置专门的互斥信号量,Mutex我们依然可以实现这些进程对,盘子这种临界区的互斥访问,为什么呢,其实原因在于这个题目当中
的这个缓冲区的大小只为一,大家可以自己再尝,试分析一下更多的情况,apple orange和plate这三个同步信号量,同一时刻最多只有一个会是一,而这几个进程,刚开始,都需要对其中的某一个信号量执行,p操作由于这三个同步信号,量当中,同一时刻最多只有一个的值会是一,所以这些进程执行各自的p,操作的时候,最多只有一个进程不会被阻塞,可以顺利的,啊进入这个临界区进行访问,那假如我们把盘子的容量设为2,也就是这个缓冲区的容量,把它设为2的话,会发生什么情况呢,假设刚开始盘子就可以放两个水果,那么刚开始父亲进城执行p操作,发现盘子资源足够,所以他可以进入临界区开始访问盘子,而母亲进城在执行批操作之后,也发现盘子这种资源依然是足够的,所以他同样也会进入这个,临界区对盘子这种临界资源进行访问,所以这就发生了父亲进城和母亲进城,两个进城同时访问,盘子这种临界资源的情况,那通过上个小节的讲解我们知道,如果两个生产者进城,他们同时对一个缓冲区,啊进行访问的话,那么有可能会导致数据覆盖的问题,这个地方也一样,因此如果我们,在生产者消费者问题当中,遇到了缓冲区大于一的情况,那么我们就必须设置一个呼斥信号量,啊Mutex,来保证,各个进程是可以呼斥的访问缓冲区的,而如果缓冲区大小等于一的话,那么我们即使不设置这个护持信号量,呃,有可能也可以实现护斥访问临界区,这个事情,当然这不是绝对的,只是有可能不需要设置护持信号量啊,要具体问题具体分析,hello please stop,hello please stop hello please stop,如果大家在考试的时候遇,到缓冲区大小为一的情况的时候,那么呃可以自己分析一下,如果能确定,呃不需要使用护赤笔信号量的话,那么呃不设置也可以,但是如果来不及仔细分析的话,呃大家最好是是加上这个护赤信号量,因为加上了肯定也没错,我们需要注意的是,上个小节强调过的那个问题,实现互斥的,对于Mutex那个信号量的p操作,一定要在实现同步的p操作之后,否则是有可能会引起思索的,这个点大家还能不能回忆起来呢,那么和之前的小节一样,大家也需要体会,PV操作这种相关的题目的解题思路,和上个小节介绍的,经典的生产者消费者问题,不太一样的是这个
四、总结
小节啊这个模型是多生产者多消费者,他的同步关系,要比,之前那个小节所介绍的同步关系,要复杂的多,大家需要注意一个事情,在分析这种同步问题的时候,我们不能从,单个进程的行为的角度来出发,而需要把这种一前一后的事情,把它看作是两个事件的前后关系,这句话我们不太容易理解,我们直接来看例子,如果说在这个题目当中,我们是从单个进程,行为的角度来考虑的话,那么我们会有这样的结论,首先从题目当中我们可以知道,如果盘子里边装的是苹果,那么一定要女儿取走苹果之后,母父亲和母亲才可以放入水果,所以当女儿进城取走苹果之后,可能会导致父亲进城,啊可以放入水果,同样,也可能导致母亲进城可以放入水果,另外如果盘子里装的是橘子的话,那么儿子取走橘子之后,可能会导致父亲进城可以放入水果,也可能会导致母亲进城可以放入水果,那么如果我们从这个,呃这种单个进城的行为,来分析的话,那仅仅这两句话,我们就可以分析出,这样的四个同步关系,那么啊有四个同步关系,是不是就意味着,我们要设置四个同步信号量,来分别实现,这四个一前一后的这种关系了呢,当然不是,其实正确的分析方法,我们应该从事件的角度来考虑,我们应该把啊,刚才所描述的这种,进程行为的前后关系,也就是女儿娶走,取走水果这个行为,导致父亲可以放入水果,同样也导致母亲可以放入水果,从这种行呃进程行为的前后关系,我们可以把它抽象为,一对事件的前后关系,我们应该把进程同步问题,把它理解为,某一个事件,一定要求发生在另一个事件之前,而不是某一个进程的行为,要发生在另一个进程的行为之前,那么,我们如果从事件的角度来考虑的话,刚才所描述的这两句话,其实我们可以抽象为两个事件,盘子变空的事件,可一定要发生在,放入水果这个事件之前,而盘子变空这个事件,既可以由儿子进城来引发,也可以由女儿进城来引发,放水果的这个事件,既可以是父亲进城来执行,也可以是母亲进城来执行,所以刚才我们看起来有四对同步,关系其实我们如果把,从事件的角度来考虑的话,我们可以把它抽象为一对,事件的前后关系,而这些事件可以由哪个进程来引发,那么这个进程就需要对,这个事件对应的,啊同步信号量执行一个微操作,同样的父亲进城和母亲进城,都有可能会执行放入水果这个事件,所以在放他们,执行各自的放入水果事件之前,我们需要对,这个事件对应的同步信号量执行,分别执行呃一个p操作,那这个地方,大家还需要自己认真的体会啊,这对于初学者来说,确实比较容易犯这样的问题,那如果能把这个部分的内容理解了,以后,再做同步相关的这些大题的时候,应该问题就不大了,好的那么这就是这个小节的全部内容
No.22 吸烟者问题
No.23 读者写者问题
No.24 哲学家进餐问题
No.25 管程
各位同学大家好,在这个小节中,我们会为大家介绍管程相关的内容,首先会从历史发展的角度,来让大家了解,为什么要引出引入管程这种机制,另外管程的定义和基本特征是什么呢,这一点是,考试当中比较容易作为选择题,考察的一个知识点,之后为了让大家,能够更形象的理解管城到底是什么,我们会有两个拓展,第一个拓展是用管城,来解决生产者消费者问题,第二个拓展是,会介绍一种在Java当中,类似于管城的机制,那么首先来看
一、为什么要引入管程
为什么要引入管程,在管程引入之前,其实人们用来实现进程同步和互斥,主要是使用信号量机制,就是咱们之前学过的那种PV操作,但是信号量机,制存在的问题就是编写程序困难,易出错这点大家在做题的时候,应该也有体会过,比如说咱们在,生产者消费者问题当中提到过,如果说实现互试的批操作,在实现同步的批操作之前,那么就有可能会引起思索的状态,比如说如果我们按这样的方式来写,生产者和消费者的话,那么按照123这样的顺序来执行,那这两个进程都会被,都就会进入一种思索的状态,所以我们在使用信号量机制的时候,就不得不关心这些PV操作的顺序,这就造成了我们在编写程序的时候,很困难并且极易出错的这种问题,所以人们就想到能不能设计一种机制,能够让程序员写程序的时候,不需要再关注这么复杂的PV操作啊,可以让写代码更加轻松呢,在1973年 有一个叫做什么branchansen的人,在Pasca语言里首次引入了管城的成分,管城其实它是一种高级的同步机制,它本质上也是用于实现,进程的护齿同步的,只不过它比之前的这种啊信号栏机制,要更方便易用一些,是一种更高级的同步机制,那么什么是,
二、基本特征
管程管程又有什么基本特征呢,在聊这个问题之前咱们必须再次强调,管程其实和之前学过的PV操作一样,它也是用来实现进程的互持和同步的,而进程之间要实现互持和同步,是因为,进程之间可能会共享某些数据资源,比如说像生产者消费者问题当,中,生产者和消费者都需要共享的访问,缓冲区这一种资源,所以,为了实现各个晋城对一些共享资源的,啊互斥或者同步的访问的话,那么广城就要有这样一些部分组成,第一,局部与管城的共享数据结构说明,比如说咱们刚才提到的生产者,消费者问题当中,生产者和消费者,都需要共享访问的那个缓冲区,其实我们可以用一种,数据结构来表示这个缓冲区,对缓冲区进行管理,所以在管城当中需要定义一种对,和这种共享资源相对应的,这种共享数据结构,第二对,上面所提到的这种,数据结构进行操作的一组过程,挂考的同学可能不太容易理解,什么叫过程,其实过程,可以直接理解为他就是所谓的函数,所以第二句话也可以这么说,管程当中还需要定义,对之前所提到的这种共享数据结构,进行操作的一组,函数第三,还需要有对,局部与管程的共享数据,设置初始值的语句,特别绕反正就是对这个呃,数据结构要进行初始化的一些语句,也需要在管程当中说明,第四管程还需要有一个名字,如果学过面相对象设计的同学,可能会发现,管程的定义,其实就有点类似于我们的类,对吧,在类当中我们可以定义一些数据,并且还可以定义对这些,数据进行操作的一组函数,一组过程,另外,我们还可以在这个类当中定义一些,对这些数据进行初始化的语句,当然,如果没有学过面相对象语言的同学啊,理解不了,也没有关系,咱们之后还会用别的例子,来让大家理解,为了用管程,实现进程之间的互持和同步,那管程有这样一些特征,局部与管程的数据,只能被局部与管程的过程所访问,第二,一个进程只有通过调用管程内的过程,才能进入管程,访问共享数据,这个有点像英语的常难句是不是,其实他们说的事情也很简单,就是说,管程当中定义的这些共享的数据结构,只能被管程当中,定义的这一些函数所修改,所以如果我们想要修改,管程当中的这一些共享数据结构的话,我们只能通过调用,管程提供的这些函数,来间接的修改这些数据结构,其实这就是第一句和第二句的意思,第三每次仅允许一个进程,在管程内执行某个内部过程,就是说管程当中虽然定义了很多函数,但是同一时刻肯定只有一个进程,在使用管程当中的某一个函数,别的进程如果,也想使用呃,这个管程当中的某些函数的话,那只要之前的这个进程还没有用完,别的进程就暂时不能啊,开始执行管程的这些函数,所以这是第三句的意思,每次仅允许一个进程在管程内,执行某个内部过程,那为什么要这么设计呢,我们可以想一下,比如说我们把,生产者消费者问题当中的缓冲区,定义为了,管程当中的某一种共享数据结构,那按照之前咱们学习的内容我们知道,各个进程对缓冲区的访问,必须是互斥的,也就是有一个进程在访问缓冲区的,时候别的进程肯定不能同时访问,必须先等待,所以如果我们能够保证,每一次仅有一个进程,能在管程当中的某一个,内部过程当中执行的话,那么这就意味着,每一次对这个共享数据结构的访问,肯定只有一个进程正在进行,而不可能有多个进程,正在同时的访问这个共享数据结构,所以这就是管程的精髓所在,那接下来我们用一个具体的例子,看一下管程
三、例子解决生产者消费者问题
是怎么解决生产者消费者问题的,需要注意的是,在这个地方并没有按照,某一种严格的语法规则来进行表述,这个地方只是为了让大家容易,能够理解,所以用了,类似于语言的北代码来表示,过程当中的这一些逻辑,我们可以用程,序设计语言当中,提供的某一种特殊的语法,比如说monitor and monitor,用这样一对关键字来定义啊,一个管程,就是指中间的这个部分,就是管程的内容,那管程的名字叫producer consumer,另外我们可以定义一些条件变量,用来实现同步,还可以定义一些普通的变量,用来表示我们想要记录的信息,比如说缓冲区当中的产品个数,那其实除此之外,我们还需要定义对,缓冲区进行描述的一些数据结构,如果为了方便我们这就省去了,那生产者进程,想要往缓冲,区里放入一个自己新生产的产品,可以直接调用管程当中,定义的这个insert函数就可以实现,像之前咱们呃用PV操作,的时候,生产日进程也需要有一堆PV操作,但如果采用了管程,那这个代码就变得特别简洁,首先就是生产一个产品,之后就是定义这个管程,当中的insert函数,然后把自己生产的这个产品,作为这个函数的参数,传进去接下来的问题,消生产者进程就不用管了,接下来就由管程来负责解决剩下的,什么护持啊,同步啊一系列很复杂的问题,同样的消费者进程也可以,很简单的调用,管程当中定义的某一个函数,就可以实现,从,缓冲区当中取出一个产品这样的事情,所以消费者进程的代码,也变得非常简洁,而,取从缓冲区当中取出一个产品的时候,缓冲区空了怎么办,然后对缓冲区的护斥怎么办,这些消费者进程都不用关心,剩下的都是管程会负责解决的问题,我们定义了管程之后,在编译的时候,其实会由编译器负责实现,各个进程互斥的进入管程当中的过程,这样一件事情,举个例子,比如说有两个生长者进程并发的执行,并且先后都调用了,管程的insert这个过程或者说这个函数,那么由于,刚开始没有任何一个进程,正在访问这个管程当中的某一个函数,所以第一个生长者进程,在调用insert函数的时候,是可以顺利的执行下去的,他会执行完一系列代码,包括判断缓冲区是否满了,或者此时是否有,一些是消费者进程需要唤醒,这系列的事情都是在管程,的insert函数里边,进行完成的,而如果,在第一个进程没有执行完这个函数,相应的这一系列逻辑的时候,第二个进程就尝试着,也想调用insert函数,那么由于变异器实现的这些功能,它会暂时阻止第二个进程,进入insert函数,所以就会把,第二个进程阻塞在insert函数后面,就类似于一个排队器让它先等待,等第一个进程访问完了insert函数之后,才会让第二个进程,开始进入insert函数,然后执行相应的这一些逻辑,所以其实互斥的使用某一些共享数据,这是由编译器负责为我们实现的,程序员在写程序的时候,不需要再关心如何实现互斥,只需要直接调用,管程提供的这一系列的方法,其实他本身就已经能够保证,这是互斥的进行的,那除了互斥之外,管程还可以实现进程的同步,我们可以在,管程当中设置一些条件变量,比如说在这个地方,我们设置了负和MT这两个条件变量,还有与他们对应的呃等待和唤醒操作,用来实现进程的同步问题,比如说如果有两个消费者进程先执行,生产者进程后执行,那么第一个消费者进程在执行的时候,首先是调用了这个管程的remove,这个过程,或者说这个函数,那首先需要判断,此时缓冲区里是否有可用的产品,那由于刚开始count的值本来就是0,所以第一个消费者进程需要执行wait,也就是等待操作,于是第一个消费者进程会等待在,empty这个条件变量相关的这个对列当,中同样的,第二个消费者进程,开始执行remove函数的时候,也会发现此时count的值是0,所以他也需要执行这个等待操作,同样的也会插入到,empty这个条件变量对应的对位,就像这个样子,那之后,如果有一个生产者进程开始执行,那他会执行这个管程的insert函数,或者说这个过程,那么呃他会把,他自己生产的产品放入到缓冲区当中,并且会检查,自己放入的这个产品,是不是这个缓冲区当中的第一个产品,那如果说是第一个产品的话,就意味着,此时有可能有别的,消费者进程正在等待,我的这个产品,所以接下来,这个生产者进程,在执行insert函数的时候,也会在这在其中执行一个唤醒操作,signal操作,用于唤醒,等待在empty这个条件变量,对应的等待队列当中的某一个进程,那一般来说,都是唤醒排在对头的这个进程,也就是第一个消费者进程,那由于第一个消费者进程被唤醒了,之后他就可以开始往下执行,首先是执行cont减,减就是让cont的值由一又变回了0,然后再检查在自己取走这个产品之前,啊缓冲区是不是已经满了,如果说缓冲区啊,之前是已经已经是满的,那么就意味着有可能会有伸长者进城,需要被唤醒,于是这个消费者进城,又会调用一个,呃对for这个条件变量的signal,也就是唤醒操作,那原理呢,和刚才咱们介绍,MT这个的原理其实是一样的,最后REMO,函数会返回一个,消费者进程想要的产品,对应的一个呃可以理解为是指针,所以第一个消费者进程,就可以通过这样一个步骤,就可以取出他想要的产品,而在取产品的过程当中,如何实现对缓冲区的互斥访问,或者呃,当缓冲区当中没有产品的时候,自己的这个消费者进程应该怎么处理,这一切都不需要消费者进程再来关心,这一切都是由广程负责解决的,所以从这个例子当中可以看到,在采用了管程这种机制之后,实现进程的护持和同步,这些事情就变得简单多了,我们只需要调用管程当中的某一些,过程,来完成我们想要完成的事情就可以了,那么我们再根据刚,
才的例子,用自己的话,对管程的特点进行一个描述,一我们需要在管程当中定义一些,共享数据,比如说,像生产者消费者问题当中的缓冲区,对应的数据结构,我们就可以在管程当中定义,第二我们可以在管程当中定义一些,用于访问这些共享数据的入口,这个入口其实就是所谓的函数,或者之前那种说法当中所谓的过程,比如说在生产者消费者问题当中,我们定义了一个insert函数和一个,呃remove函数,通过这两个入口,我们可以对缓冲区进行操作,第三,其实我们在生产者消费者进程当中,是不可以直接访问这个共享缓冲区的,我们只能通过管程当中,定义的这些特定的入口,也就是他提供的这些函数,才能访问这个共享的缓冲区,第四管程当中有很多很很多入口,比如说有insert入口有remove这样的入口,也就是有很多个函数,但是每一次管程当中,只能开放其中的一个入口,并且只能让一个进程或线程进入,所以这种特性就可以保证,在一个时间段呢,最多只会有一个进程,在访问我们的共享数据,比如说刚才提到的这种缓冲区,但需要注意的是,这种互斥访问管程当中各个,入口的这种特性,这种互斥特性,它是由编译器负责实现的,程序员其实并不需要关心,第五我们可以在管程当中,设置一些条件变量,和对应的等待唤醒操作,来解决同步问题,可以让一个进程或线程,在条件变量上等待,比如说咱们刚才举到的,在MT这个条件变量上等待的例子,那,如果一个进程在条件变量上等待的话,那这个进程应该先释放管程的使用权,也就是要让出这个出入口,另外我们还可以通过唤醒操作,把等待在条件变量上的进程或者线程,唤醒那么通过刚才的例子我们知道,程序员,其实可以通过某一种特殊的语法,来定义一个管程,比如说,monitor和and monitor这样一对关键字,之后其他的程序员就可以通过,这个管程当中定义的这些特殊的入口,或者说这些特定的函数,就可以很方便的实现进程的,同步和互斥了,有没有发现,其实在管程当中,实现了我们之前PV操作当中,需要实现的什么啊排队啊阻塞啊,呼斥啊这一系列的问题,我们只需要简单的调用一个,特殊的入口,一个一个一个函数,就可以很方便的使用了,其实这就是程序设计当中,所谓封装的思想,把一些复杂的细节隐藏了,对外只需要提供一个简单易用的接口,那么其实Java
四、Java中的管程机制
这个呃synchronized,这样一个关键字来定义一个,来描述一个函数的话,那这个函数就可以保证,同一时间段内只能被一个线程所调用,比如说我们定义一个叫做,呃monitor的一个类,然后这个类当中的某一个函数,我们用呃,synchronized这样一个关键字进行描述,这就意味着insert这个函数,同一时间段内只能被一个线程所调用,或者说即便多个线程,都几乎同时调用了insert函数,但后面调用的那些线程,是需要排队等待的,只有当前使用insert函数的那个线程,执行完了相应的代码,最后退出了这个函数之后,下一个县城才可以开始进入这个函数,然后执行其中的代码,那这个地方就不再展开细聊了,对于不熟悉Java的同学,看不懂也没有关系,这个地方只不过是为了让大家知道,管城这个概念其实离我们并不遥远,我们自己也会用到和,管城类似的一种机制,只有熟悉了一个概念之后,再遇到与这个概念相,相关的一些知识点的时候,大家才不会有那种恐惧的感觉,另外呢如果熟悉加尔的同学,在时间允许的情况下,其实也可以自己动手,用呃这个关键字来试,尝试实现一下,生产者消费者问题当中的管陈,到底应该怎么定义,那这个小节我
五、总结
我们介绍了管程相关的内容,管程的引入,其实就是为了解决信号量机制,编程麻烦,容易出错的问题,管程其实无非也就是为了实,现进程的同步和互斥,只不过是实现起来会更方便一些,另外,我们介绍了管程的组成和基本特征,啊在考试当中,最容易考察的其实是管程的这,两个基本特征,首先外部的进程或者线程,只能通过管程提供的特定入口,这个入口是什么意思,现现在应该已经知道了,其实也就是管程当中定义的某一些,函数或者说过程,才可以访问管程当中定义的那些,共享数据,另外呢每一次仅允许一个进程,在管程内执行某个内部过程,那这两点,是最容易在选择题当中进行考察的,另外我们补充了两个知识点阿,进程呼斥访问,管程的特性,其实是由编译器负责实现的,程序员并不需要关心,另外呃,可以在管程当中设置条件变量,还有等待唤醒操作,就可以解决管程的同步问题,那管程其实就是应用了封装的思想,进程同步互斥这些复杂的细节,隐藏在了管程定义的那些啊函数之内,而对外只提供一个简单易用的啊,函数调用的接口,所以管程是应用了封装的思想,那以上就是这个小节的,全部内容
No.26 死锁
各位同学大家好,在这个小节中,我们会介绍死索的相关基本概念,我们会用两个例子,让大家比较直观的体会到,死锁现象,另外我们会解释进程死锁,进程饥饿,还有死循环,这几个比较容易让人混淆的概念,他们之间有什么联系和区别,之后我们会介绍,死锁产生的四个必要条件,什么时候会发生死锁,发生死锁是之后应该做什么处理,或者说我们应该怎么避免死锁的发生,这就是,这就是这个小节最后会提到的内容,那么我们我们会按照从上至下的顺序,依次讲解,其实死锁这个概念
一、什么是死锁
之前,哲学家进仓问题当中就已经提到过,如果五个哲学家进程都并发的执行,那么他们有可能依次拿起自己左手,边的筷子,但是之后,当他们都啊,尝试拿起右手边的筷子的时候,发现右边那只筷子已经被,另一个哲学家,就是自己右手边的哲学家所占领了,所以这种情况下就会发生这种,循环等待的现象,每个哲学家手里都持有一只,他的左边那个哲学家想要的筷子,但是他同时又在等待他,右边的那个哲学家,手里的那只筷子,所以在这种情况下,所有的哲学家都是阻塞状态,没有一个哲学家进程可以,顺利的往下推进,因此这种情况下,就称为发生了所谓的死锁现象,再来看另外一个例子
啊我记得之前在什么时候听过一首歌,歌词是这样什么我爱你你爱他他爱他,他爱我这个世界每个人都爱别人,大概是这个样子,不一定准确啊,大家可以去搜一下应该可以搜到,那么我们可以从资源占有的角度,来分析一下,他这个歌词,描述的这段关系,为什么看起来那么纠结,总共有四个人我你他他,我爱你说明你拥有我的心,你爱他说明他拥有你的心,他爱他说明他拥有他的心,他又爱我所以说明我有他的心,因此这四个人或者说这四个进程,他们每个进程都持有一种,啊独特的临界资源,但是由于我爱你,所以其实我想要拥有的是你的心,而他爱他所以他想拥有的是他的心,也就是这个,所以我这个进程他是占有了他的心,同时在等待你的心,而他这个进程是占有了你的心,但是又在等待他的心,也就是我手里的资源是他想要的,而他手里的资源又是我想要的,这就发生了刚才咱们像哲学家,问题一样的那种循环等待的现象,而你和他其实也一样的,你占有我的心但是在等待他的心,他占有呃他的心但是又在等待我的心,所以这两个进程,他们之间也存在这样的呃循环等待的,关系因此,这两组进程,就都发生了所谓的死锁现象,所以即使所谓的死锁并不难理解,就是指在并发环境下,各个进程,因为竞争资源而造成的一种互相等,待对方手里资源的这种情况,导致了各个进程都阻塞,都无法向前推进,因为都在等待着对方,先放下自己手里的资源,但是每个进程都会持有,当前的这些资源不放,因此这就是所谓的死锁现象,如果发生了这种死锁现象的话,那么如果没有外力干涉,或者或者说没有操作系统干涉的话,那么这些进程就都没办法,顺利的向前推进了,接下来咱们再来看,3个比较容易让人混淆的概念
二、三个的区别
锁饥饿和死循环,所谓的死索刚才已经解释过,是指各个进程,互相等待对方手里的资源,而导致所有的这些进程都阻塞,而无法向前推进的现象,而饥饿这个概念,咱们在之前处理机调度的时候提到过,是由于进程长期得不到想要的资源,而无法向前推进的现象,比如说咱们之前介绍短进程优先,呃算法的时候,如果说用短进程优先算法,那么系统中有源源不断的短,进程到来的话,长,进程就会一直得不到处理机这种资源,所以就导致了长进程饥饿的现象,而死循环是指某种进程,在执行的过程中啊,因为遇到一些问题,所以一直跳不出某一个循环,比如说y循环或者for循环这样的现象,相信如果大家有编程经验的话,呃每个人应该都写过死循环,当然,有时候这个死循环是因为我们自己,程序逻辑的bug而导致的,有的时候又是程序员故意设计的,比如说之前,咱们啊写过的那些PV操作的代码,其实就是故意设计成死循环的,那么呃这所指的死循环,其实特指的是这种呃,程序逻辑bug导致的死循环,导致了一个进程,没有办法按照我们期待的那样,顺利的往下推进,那这三个概念的共同点在于,他们都是呃,进程发生了某种异常的状况,而无法继续往下推进的这种现象,所以这三个概念,其实比较容易在选择题当中放在一起,啊对大家进行考察,那么除了,进程都无法向前推进这个共同点之外,他们又有一些很明显的区别,死锁现象咱们刚才解释过,是各个进程循环,等待对方手里拥有的那种资源,而导致的,所以发生死锁的话,肯定,至少是有两个或者两个以上的进程,同时发生死锁,因为如果只有一个进程的话,那么怎么循环等待对方手里的资源呢,对吧所以,由于死锁的进程是在等待,某一种资源的分配,因此,发生死锁的进程肯定是处于阻塞态的,而饥饿和思索不同,在刚才的短进程优先算法,这个分析过程中,我们也会发现,即使系统当中只有一个长进程,然后有别的源源不断的短进程到达,那这个的长进程,有可能是他自己处于饥饿的状态,因此如果说发生饥饿的话,可能是只有一个进程发生饥饿,而死锁的话,肯定是两个,或者两个以上的进程同时死锁,发生饥饿的进程有可能是,长期得不到自己想要的某种资源,比如说IO设备,当然有可,能是像上面这所所提到的这样,是长期得不到处理机的服务,所以发生饥饿啊这种现象的进程,他有可能是处于阻塞态的,有可能是处于就绪态的,另外处于死循环这种状态的进程,有可能只有一个,并且死循环状态的进程,它是可以上处理机运行的,这个如果写过程序的话,大家应该也有这种体会,比如说自己写一个死循环,不断的输出hello word,那么你会发现那个呃,屏幕上不断的会有hello word被打印出来,所以死循环的进程,其实可以上处理机运行的,因此处于这种状态的进程,他有可能是处于运行态的,而死索和饥饿的进程,他们肯定不会是运行态,思索和饥饿的问题一般来说,是由于操作系统,分配资源的这种策略不合理而导致的,比如说,像刚才咱们提到的饥饿啊这种现象,就是由于,操作系统分配处理机这种资源的,策略不合理,所以导致了肠颈臣饥饿,而对于死循环这种现象来说,它是由写程序的那个人导致的,是由代码逻辑的错误导致的,所以,死索和饥饿应该是操作系统需要关心,需要尝试解决的问题,而死循环,应该是写程序的那个应用程序员,来关心的问题,所以在操作系统这门课中,大家会发现呃,有的地方会说怎么解决死锁,有的地方说怎么解决饥饿,但是他从来不说怎么解决死循环,因为解决死循环,根本就不是操作系统应该,来负责处理的问题,所以这就是死索饥饿死,循环这三个概念的啊联系和区别
三、死锁产生的必要条件
其实,死索的发生是需要遵循一定的条件的,总共有4个条件,这些条件当中只要有任何一条不成立,那死索就不会发生,第一个条件是互斥条件,是指,这些进程并发执行的进程只有对,必须互斥使用的资源进行争抢的时候,才会导致死索,比如说咱们之前提到的哲学家问题,筷子这种资源,它就是一种只能互斥使用的资源,只要其中一个哲学家把,呃那个筷子占有了之后,别的哲学家就,暂时不可以使用这只筷子,所以这是互制条件,但是像内存扬声器这样,同时可以让多个进程使用的资源,是不会导致思索的,因为,各个进程在申请使用这些资源的时候,并不会呃,并不用阻塞等待这种资源,所以,当然就不会发生咱们之前提到的那种,互相等待的现象,因此也必然不会发生思索,所以一定要满足这个互斥条件,才有可能发生死索,第二个条件是不可剥夺条件,是指各个进程在获得资源,这个资源没有使用完之前,是不能由其他进程强行抢夺的,只能由这个进程自己主动释放,比如说咱们之前提到的哲学家问题,每个哲学家,自己手里拿了一只筷子之后,那其他的哲学家是不能从自己,手里抢走这只,筷子的那如果说,其他哲学家可以抢走,啊自己想要的那只筷子的话,那么抢得筷子的,抢得两只筷子的哲学家,肯定就可以顺利,的吃饭,就不会出现所有的哲学家都被阻塞,发生死索的那种问题,因此也一定要满足不可剥夺条件,才有可能发生死索,第三个条件是请求和保持条件,还是以哲学家问题作为例子,每一个哲学家,在保持了一个资源不放的同时,他还在请求另一个新的资源,而那个新的资源,又恰好是被别的哲学家进程所持有的,所以他们满足了请求和保持的条件,因此发生了死锁,而如果这个条件不满足的话,这个死锁现象也是不会发生的,第四个条件是循环等待条件啊,是指存在一种进程资源的循环等待链,这个咱们之前已经聊过,就是每一个,哲学家,都在等待自己右手边的那个哲学家,放下放下资源,所以这就形成了一种,资源的循环等待链,那么如果说没有这种循环等待的话,那死锁也是不会发生的,但是这个地方需要注意的一个问题是,在发生死锁的时候,一定是会有这种啊,循环等待资源的现象的,但是发生了循环等待资源现象的时候,未必就一定发生了死锁,比如说此时有一个神秘的第六人,第六个哲学家,他拥有一个资呃一支筷子资源,这个筷子,是可以让3号哲学家的右手,来拿起使用的,所以3号哲学家右手没有筷子的时候,他其实,记载等待4号哲学家放下这只筷子,同时他其实也可以等待这个哲学家,放下这只筷子,因为,这只筷子也可以让3号的右手来使用,所以在这种情况下,虽然我们看起来他是,有一个循环等待的这种呃,这种关系的,但是只要这个哲学家,他放下了这支筷子资源,那么这个筷子资源,就可以被分配给3号哲学家,那这个时候,3号哲学家就可以顺利的开始吃饭,于是他就不需要,再等待别的哲学家手里的资源了,因此可以看到,即使发生了循环等待资源的现象,但是如果还有别的进程持有一个,同类型的可以替代的资源,那么死锁是不一定发生的,就像刚才这个例子一样,但是如果说一个系统当中,每一类资源都只有一个,那么,循环等待就是思索的充分必要条件了,就像刚才没有这个哲学家,那种时候一样,那 3号哲学家右手可以用的筷子,其实只有一个,这类的资源只有一个,在发生了循环等待现象,同时又满足,系统中每类资源,只有一个这个条件的时候,那循环等待就必然是会导致思索的,那么
四、什么时候会发生死锁
什么时候会发生死索呢,咱们书上给出了这样的三种情况,就是对呃一种不可剥夺的系统资,源的竞争的时候,有可能会引起死索,第二,进程推进顺序非法也会导致死锁问题,比如说两个并发的进程P1和P2,他们分别申请,并占有了R1和R2这两种资源,但是之后呃P1又紧接着申请R2资源,而P2又紧接着申请R1这种资源,那这种情况下就发生了呃死锁现象,但如果说我们改变一种,啊进程的推进顺序,比如说先由P1上处理,机运行那么P1一气呵成,先申请R1资源再申请R2资源,那么这两个资源都同时归P1所占有,那么P1就可以顺利的往下执行下去,所以如果用我们刚才所说的这种方法,来推进这些进程的话,那么P1进程是不会被阻塞的,只有P2会被阻塞,所以这种情况下就不会再发生思索,那么第三种情况,当信号量使用不当的时候,也会发生死锁现象,比如说咱们之前在讲,生产者消费者问题的时候,就提到过,如果说,实现互斥的对Mutex那个变量的p操作,在实现同步的p操作之前,那么就有可能会导致死锁,这个地方大家也趁着呃咱们提到,再来回忆一下,就是这个知识点,其实,我们可以把护持信号量和同步信号,量也看作是一种抽象的系统资源,所以其实上面这么多东西,我们可以总结为一句话,就是说当对不可剥夺的资源的,分配不合理的时候,就有可能会导致死锁,那么当死锁发生的时候,我们应该怎么做,或者说我们应该
五、死锁的处理策略
该怎么避免死锁的发生呢,一般来说,对于死锁问题的处理策略有这样三种,第一种叫预防死锁,就是会想办法破坏,刚才咱们聊到的4个呃,死锁的必要条件当中的一个或者几个,这是预防死锁的策略,第二种叫做避免思索,用某一种算法来检查,防止系统啊进入不安全的状态,这个咱们之后还会展开细聊,我们会介绍,银行家算法就是避免思索的这种方案,第三种思索的检测和解除,前面这两种解决方案,是不会引起死锁现象的,而第三种解决方案是允许死锁的发生,但是呃操作系统会,要负责检查到底有没有死锁发生,如果此时发生了,那么就需要通过某种策略来解除死锁,那这3个方法,也是咱们之后的小节,会依次展开细聊的3种啊,思索处理的策略,这地方先让大家形成一个框架,暂时不展开细聊,那么这个小节我
六、总结
们介绍了死锁相关的一些基本知识,包括什么是死锁,另外死锁和饥饿,死循环这几个比较容易混淆的概念,它们有什么区别,这个大家一定要着重体会啊,很容易作为选择题来作为考察,之后我们要介绍了死锁,产生的4个必要条件,其中循环等待条件的这个知识点,是比较容易,作为选择题的陷阱来考察的,循环等待未必导致死锁,而死锁一定是会有循环等待现象的,那当然其他的这些条件也需要体会,并且通过课后的习题,来进行进一步的巩固,最后我们简单的提了一下,死锁处理的3种策略,那这3种策略,会在之后的小节依次展开细聊,那么预防死锁,是要破坏死锁产生的这4个必要条件,所以在之后学习预防死锁的过程当中,也会对这几个必要条件,再重新的进行复习和巩固,好的,那么以上就是这个小节的全部内容
No.27 死锁的处理策略—预防死锁
各位同学大家好,在上一小节的末尾,我们简单提到了死锁处理的几种策略,预防死锁,避免死锁和死锁的检测和解除,那这个小节,我们会重点介绍预防死锁这种,策略那这种策略的主要思想是,要想办法破坏,死锁产生的4个必要条件,当中的某一个,那经过上一个小节的学习我们知道,只要这4个必要条件,当中的某一个或者几个不成立,那死锁就肯定不会发生,那首先,我们来看一下能不能破坏互斥条件
一、破坏互斥条件
所谓的互斥条件是指,呃对于必须互斥使用的资源的增强,才会导致进程之间的思索,那如果说我们能把一种互斥,必须互斥使用的资源,改造成一种可以同时使用的资源的话,那么这种资源,是不是就不会再导致思索了呢,其实这件事情是可以实现的,比如说咱们在之后的章节,会学到一种叫做spooning技术的东西,操作系统采用spooning技术之后,就可以把独占必须独占使用的设备,在逻辑上改造成,啊可以共享使用的设备,那我们以用spooning技术,改造打印机为例,在采用spooning技术之前,如果说,两个进程都想使用打印机这种资源,那么首先进程一他在使用打印机,并且他还没有使用完打印机,那这个时候,如果进城2也想使用打印机的话,他的这个行为应该被阻止,进城2会进入到,阻色态来等待进城一释放打印机资源,因为打印机这种资源,他是必须互斥使用的资源,同一时刻只能供一个进城使用,而如果采用了spooning技术之后,各个进程对打印机发出的请求,会首先被输出进程给接收,同样的进程2如果也想输出的话,也会直接被输出进程给接收,那么当他们的这些,请求被接收并且被响应了之后,这些进程,就可以开始顺利的,往下执行别的事情了,那之后,输出进程会根据各个进程的请求啊,把它依次放到打印机上打印输出,就像这样子,所以其实采用了spooning技术之后,虽然打印机它是一种,必须护翅使用的独占设备,但是进程一和进程2他们俩啊,即便是同时想要使用打印机的话,那么他们的请求也可以被及时的接收,也就是被输出进程先暂时为他们保管,然后之后再慢慢的输出,所以从逻辑上看对于这些进程来说,在他们看来,他们所使用的就并不是一个独占设备,而是一个共享设备,在他们看来是这样的,但其实是操作系统采用了spooling技术,把沪赤啊使用的资源,改造成了一个一个,可以共享使用的资源,因此其实沪赤条件是可以破坏的,但是在很多时候其实啊,并不是所有的设备,都可以被改造成共享的设备,并且甚至为了系统安全,有很多地方还必须保护,保护保证这种啊护斥性,所以啊破坏护斥条件这个思路,其实适用的范围并不广,这是它的缺点,那么我们能不能换一个思路,
二、破坏不剥夺条件
是否能破坏不剥夺条件,所谓的不剥夺条件,是指进程所获得的资源,在没有完,没有使用完之前,不能由其他进程强行夺走,只能主动释放,那么我们可以用这样的方案,来破坏这个条件,比如说,当一个进程他在请求一种新的资源,但是这个新的资源暂时不能分配给他,暂时得不到满足的时候,那这个进程就必须立即释放,他手里保持的所有资源,等之后如果还想使用这些资源的话,那他就需要重新申请,所以如果采用了这种方案,那么一个进程他手里的资源,哪怕暂时还没有用完,那也有可能会需要主动释放,这就破坏了不可剥夺的条件,我们还可以用另外一种方式来,破坏这个条件,比如说,当一个进程需要使用某一种资源,但是这个资源,正在被别的进程所占用的时候,那么可以由操作系统协助,把他想要的那种资源强行的剥夺,但是这种方式一般来说,需要考虑到各个进程的优先级,优先级高的进程可以把别的,进程的手里的资源给剥夺,并且归自己使用,比如说咱们之前讲处理机调度的时候,讲过剥夺调度方式,就是如果说,一个系统当中,有优先级更高的进程到达的话,那么处理机这种资源会被强行的剥夺,分给优先级更高的那个进程使用,那这种方案也会有一些缺点,比如说实现起来会比较复杂,第二如果说一个进程,他所获得的资源需要强行释放或者,强行被剥夺的话,那么有可能造成这个进程,之前那个阶段的工作失效,所以这种方法一般来说只适用于啊,一些比较容易,保存和恢复状态的资源,比如说像CPU,那么在CPU资源被剥夺的时候,之前在CPU上运行的那个进程,它的CPU寄存器之类的一些,中间数据就需要被保存,所以其实从这个角度来看也可以发现,要这这种方式实现起来,其实还是比较复杂的,那么第3点,如果我们反复的申请和释放呃,资源的话,那么显然会增加系统开销,从而降低系统的吞吐量,这个比较容易理解,第4点如果我们采用第一种方法,那么就意味着,只要暂时得不到某种资源,那么,这个进程手里的资源就需要全部放弃,那以后如果还需要这些资源的话,又需要重新再从头申请,因此如果一直发生这种呃,全部放弃的这种事情的话,那么这个进程就有可能会一直保,持饥饿的状态,他没办法往下推进,那这是破坏不可剥夺条件,这种策略的一些缺点,接下来我们再来看一下
三、破坏请求和保持这个必要条件
能不能破坏请求和保持这个必要条件,所谓请求和保持条件,是指进程已经保持了至少一个资源,但是又提出了新的资源请求,但是这个资新的资源,又被其他的进程所占有,那此时这个进程呃会被阻塞,但是同时又对自己,手里的资源保持不放,所以这是,请求和保持条件,我们可以采用静态分配方法来破坏,这个条件,就是在一个进程运行之前,一次性申请完他所需要的全部资源,那如果说这些资源暂时还,不能全部分配给他,那这个进程就暂时不能投入运行,而一旦这个进程投入运行之后,他所需要的这些资源,就会一直归他所有,所以如果采用这样的方式的话,当然就不会存在,一边保持着某一种资源不放,一边又请求一个新的资源这样的事情,这就破坏了请求和保持条件,因为如果采用这种方法的话,那这个进程在投入运行之后,肯定就已经,拥有了自己所需要的全部资源,已经不会再发出新的请求了,那这种方法呢,它实现起来比较简单,但是也有明显的缺点,有的资源,它可能只需要使用很短的时间,但是由于所有的资源,在刚开始就需要被分配给进程,并且在它整个运行期间啊,这些资源都会被它被这个进程所占有,所以啊,对于那些使用的频率不高的资源,其实是会有很很多时间是闲置的,这就造成了系统资源的严重浪费,系统资源的利用率会降低,另外呢这种策略也有可能会导致,进程的饥饿现象,比如说一个系统当中,有资源一和资源2这两种资源,然后有a类b类c类这三种进程,a类进程只需要使用资源一,就可以开始投入运行,而b类进程,只需要使用资源2就可以开始运行,而c类资源,它需要同时,拥有资源一和资源2才可以投入运行,那如果说系统当中有源源不断的a类,a类和b类进程到达的话,那么资源一一旦被释放,那它就会被,它又会被立即分配给下一个a类进程,资源2一旦被释放,他又被,他也会被立即分配给下一个b类进程,而除非资源一和资源2都没有进程,使用的时候,都空闲的时候,他们才有可能同时被分配给一个,c类进程,这样的话,c类进程才可以开始投入运行,所以很显然,这种方式是有可能会导致,c类进程饥饿的,那么接下来我们再来看是否能破坏,呃循环等待条件
四、破坏循环等待条件
所谓循环等待条件就是指一种,存在一种进程资源的循环等待链,链中的每一个进程已获得的资源,同时再被下一个进程所请求,那么我们可以采用顺序,资源分配法来破坏这个条件,就是,可以首先给系统当中的各种资源,进行一个编号那,并且规定各个进程,必须按照编号递增的顺序来,依次请求这些资源,而对于编号相同的那些同类资源,必须一次申请完,我们可以这么来考虑按照这种规则,一个进程只有占有了,小编号的资源的时候,他才有资格去申请更大编号的资源,并且已经持有了大编号资源的进程,是不可能,逆向的回来申请小编号资源的,所以如果发生进程的相互等待的话,那么只有可能是小编,拥有小编号资源的进程,在等待拥有大编号资源的进程,而不可能是拥有大编号资源的进程,反向回来,呃等待拥有小编号资源的进程,因此这就不可能发生循环等待链,所以这就是这种方案,能可以破坏循环等待条件的原因,那么,我们还可以从另外一个角度来分析,假设一个系统当中有10个资源,编号分别为11-10号,那么P1进程占用了1号和3号,P2占用了2号和4号P3占用了5号和7号,所以不管在什么时刻,系统当中肯定会有一个进程,他所拥有的资源编号是最大的,比如说像这个地方的P3进程,那么也就意味着大于7号的那些资源,八九十号资源此时肯定是空闲的,没有被任何进程所占用,所以如果说P3进城继续往下执行的话,那么他所申请的资源,肯定只可能是大于7号的那些资源,也就是8号九号十号,而这些资源,肯定可以畅通无阻的全部分配给,P3进城因此,至少P3进城,是可以,顺利的获得所有他所需要的资源,并且顺利的执行结束的,所以从这个角度来分析,也不可能出现所有的进程都阻塞,的这种,现象,不过和之前的那几种方案一样,破坏循环等待条件,其实也存在一些缺点,因为系统当中的各种资源,都需要给他一个编号,那如果说系统当中想要新增一种设备,那么呃,就有可能会,需要,重新对所有的这些设备都进行编号,那显然这是很不方便的,第二,如果说啊一个进程他实际使用资源,的顺序,和这些编号递增的顺序不一致的话,就有可能会导致资源的浪费,比如说P3进程需要使用5号资源打印机,也需要使用7号资源扫描仪,但是实际的使用过程中,P3进程它是需要先使用扫描仪,再使用打印机的,但是由于这个编号递增的这种要求,P3进程又必须先,申请占有他暂时用不到的那个资源,也就是打印机,之后打印机会空闲很长一段时间,一直到扫描仪被使用完了,才会回头再使用打印机这种资源,所以,这就造成了打印机资源的长时间空闲,因此就导致了系统资源的浪费,第三因为必须按照这些呃,编号递增的顺序来申请资源,所以用户编程是很麻烦的,还是用P3进程为例,比如说在一个系统当中,打印机的编号是5号,扫描仪的编号是7号,那一个用户程序,如果既需要使用打印机,又需要使用打扫描仪的话,那用户编程的时候就需要先啊先编写,申请使用打印机的那个代码,因为那个因为5号,因为打印机的编号是更小的,之后再写呃,申请使用扫描仪的那个代码,而如果换一个系统,另一个系统对扫描仪和打印机的编号,刚好是相仿的,扫描仪的编号更小打印机的编号更大,那么用户的程序就需要呃,为此发生改变,他需要把申请扫描仪资源的代码,把它放到申请打印机,呃那个代码之前,所以很显然,这种方式会造成用户编程的极大不便,可以看到这个小节
五、总结
绍的预防死所的这些策略,或多或少都存在一些缺陷,那啊,这个小节的内容,比较容易,结合死所产生的4个必要条件啊,在选择题当中进行考察,同学们主要是以理解为主,不需要死记硬背,当然也需要啊稍微的记忆一下,大概可以用什么样的方法来破坏这些,必要条件,另外呢,之前咱们在讲哲学家进餐问题的时候,讲到了3种啊处理思索的方法,那学习完这个小节的内容之后,大家可以再回头去分析一下,那三种方法分别是破坏了哪一个条件,那复习考研的过程中,其实这样的事情是很关键的,需要在学习后面的内容之后,前面的内容进行联系,把所有的这些知识都织成一个网状,而不是一个独立的点状的知识,好的,那么以上就是这个小节的全部内容,
No.28 死锁的处理策略—避免死锁
各位同学大家好,在这个小节中,我们会介绍死锁处理的第二种策略,避免死锁,我们会用一个例子来说明,什么是安全序列,什么是系统的不安全状态,安全序列不安全状态,他们之和死锁之间有什么联系呢,那最后,我们会给出银行家算法的具体步骤,银行家算法,是用于避免思索的最著名的一个算法,那这个算法很容易在,小题和大题当中进行考察,所以这个小节的内容是十分重要的,那么我们首先来看一下,什么是安全序列
一、安全序列
假设你是一个成功的银行家,手里掌握着100个亿的资金,那有三个企业想要找你贷款,分别是企业b企业a和企业t,简称BAT是吧,那各个企业在找你贷款之前,都和你约定好了,他们最多会跟你借多少钱,但是江湖中有一个不成文的规矩,如果你给这些企业借的钱,借的总数,达不到,你最开始给的这个承诺的数字的话,那么不管之前你给他们借多少钱,那这些钱都拿不回来了,所以由于这个规矩,我们在给这些企业借钱放贷款的时候,需要额格外的小心,那假如说刚开始,BAT这三个企业分别从你这借了20,10和30亿那我们可以把这些已知数据,把整理成一个表,这三个企业对,钱的需求分别是7040和50亿,他们已经从你的手里借走了2010和30亿,那如果说用呃他们的最大需求,减掉此时已经借走了多少钱的话,我们就可以计算出,每一个企业在之后最多还有可能,向你借多少钱,那此时BAT总共从你手上借走了60亿,所以你手上还会有40亿的资金,那接下来,如果说企业b还想跟你借30亿,你敢借吗,假如说我们答应了他的这个请求的话,情况会变成这个样子,企业b从你手上借借走了以前的20,再加新借的30,总共借走了30亿,之后他最多还会借呃50,减掉刚借走的30就是最多还会再借20亿,但是这个时候由于你手里只有了10,亿的资金,所以如果说之后,BAT这三个企业同时向你提出,再借20亿的请求,那么你手里的这个资金,是不是就,没办法满足这些企业的请求了,所以经过慎重的考虑,给b借30亿这个请求是不能答应的,会不安全,那如果说啊此时是a想再跟你借20亿,敢借吗还是用之前的思路来分析,如果给a再借20亿的话,那么a的数据会有一定的变化,就变成这个样子,那你的手里就只剩20亿,接下来其实可以这么弄,手里的20亿可以先全部借给t,由于t最多还只会借20亿,所以如果给他20亿的话,那么肯定已经可以满足,t的最大需求了,那之后等t把钱用完了之后再还回来,于是你的手里的钱就变成了20亿,再加上t手里的这30亿,总共会有50亿的资金,之后这50亿就可以再借给b企业,于是,b企业的最大需求也可以得到满足,等b企业用完钱再还回来之后,那你手里的钱就变成了70亿,之后再再把这个,呃a想需要的这10亿再借给他,于是所有的钱就都可以收回来了,所以和之前所说的给b借,30亿那种情况不同,如果给a借了20亿,那这个局并不是一个死局,我们只需要按TBA这样的顺序,依次给各个企业借钱,那么呃,这些企业总是可以依次的得到满足的,那其实我们还可以换一种顺序,比如说这20亿先给a再借10亿,那么a用完钱之后,他会把手里的30亿也全部还回来,于是你手里的钱就变成了20,加上a手里的30亿也就是50亿,之后再把其中的20亿给t,于是t用完了之后再把钱还回来,你就有80亿,最后你再把b需要的50亿全部给b,于是b的需求也可以得到满足,最后b也会把钱全部还回来,所以如果我们按ATB这样的顺序,依次满足各个,企业的需求的话,那么其实这些钱也并不是一个死局,因此经过考虑,a想借20亿的这个请求,我们是可以答应的,是安全的,所以通过刚才的分析我们发现,有的资源请求我们是不能答应的,而有的资源请求我们是可以答应的,比如说给AG20亿之后,我们依然可以找到像TBA,这样的所谓的安全序列,那安全序列就是指如果说系统按照,啊安全序列的这个顺序,依次给,各个进程或者说各个企业分配资源,那么,这些企业进程就都可以顺利的完成,最后都把自己手里,的资源呃依次归还系统,所以,如果我们能够找到一个安全序列的话,那么我们就称,此时这个系统就是处于安全状态的,当然通过之前的分析我们也知道,安全序列是不有时候并不唯一,那如果分配了资源之后,再也找不到任何一个所谓的安全序列,那么系统就进入了不安全状态,这就意味着之后,有可能,所有的进程就都无法顺利的执行下去,比如说假如之,后BAT都提出再借20亿的请求的话,那么这些请求都暂时得不到满足,所以所有的企业都会被阻塞,于是系统就进入了死锁的状态,当然如果说有一些进程,提前归还了一些资源的话,那么啊系统有可能重新回到安全状态,比如说a企业先归还了10亿,于是你的手里就会有20亿,之后你就可以找到一个安全序列TBA,按照这样的顺序,给各个企业依次借钱的话,那么呃整个系统就不会死,那如果,系统中能找到一个所谓的安全序列,说明系统是处于安全状态的,在这种情况下一定不会发生死锁,而如果系统进入了不安全状态,也就是找不到任何一个安全序列,那么就有可能会发生死锁,注意只是有可能并不一定发生死锁,比如说在这个系统中,如果BAT没有提出再借20亿的请求的话,那么这个系统虽然是处于不安全状态,但是呃暂时还没有发生死索,一直到BAT提出这个请求之后,所有的,企业进程都没办法顺利的执行下去啊,都都会被阻塞,那这种情况下,系统才是真正的发生了死锁,因此进入不安全状态未必发生死锁,但是如果发生了死锁,一定是处于不安全状态,这个地方很容易作为选择题考察,大家一定要理清楚啊,这几个状态的关系,那根据之前的这些分析,我们得到了一个新的处理死锁的思路,我们可以在分配资源之前,预先判断一下,这次分配是不是会导致系统进入不,安全的状态,如果系统会进入不安全状态,那么就意味着,这次分配之后有可能会发生死锁,那我们就不应该允许这次分配,如果系统不会进入不安全状态,那么就意味着这次分配是安全的,即使进行了分配,那系统也暂时不可能发生思索,所以,这也是银行家算法的一个核心思想,银行家
二、银行家算法
家算法刚开始是,荷兰学者迪金斯特拉提出来的,又是这个人,那刚开始设计这个算法的时候,是为了确保,银行在发放现金贷款的时候,不会发生,不能满足所有客户需求的这种情况,就像刚才咱们分析的那样,但之后,这个算法又被用在了操作系统当中,用于避免思索,那我们来看一下,怎么把这种思想,应用于操作系统的避免思索,首先来思考一下,之前咱们提到BAT的这个例子当中,只有一种类型的资源,就是钱,但是在计算机当中可能会有打印机,有扫描仪,有摄像头,各种各样的资源,那我们怎么把刚才那种算法思想,拓展到有多种资源的情况呢,其实很简单,我们只需要把单位的数字,拓展为多维的项量,就可以很方便的计算了,比如说一个系统当中有五个进程,P0到P4 然后有三种资源,20到二二零二一二二二,那这三种资源刚开始的数量分别是157,比如说毗邻,对这三种资源的最大需求,我们可以用一个呃三维的限量来表示,就是753 现在系统给P0分,配了这三种资源分别是零一0这么多,那如果我们把啊,系统已经给各个进程分配的这些,资源依次加起来,我们就可以得到,此时,已经总共分配出去了725这么多的资源,剩余的资源数,我们用它初始数量减掉已分配的数量,就可以得到了,也就是332,那么和之前的这种思想一样,我们可以用,啊各个进程对资源的最大需求,减掉此时已经给进程分配的资源数,就可以得到,此时啊,各个进程最多还需要多少资源,于是就得到了这样的结果,那么我们根据刚才分析的情况,来看一下,此时这个系统,到底是不是处于安全状态,我们可以尝试找到一个安全区
那由于此时系统当中还剩余的资源数,分别是332,我们可以用,呃这个数字,和此时各个进程,最多还需要多少资源,这个数字进行一个对比,首先和P0进程对比发现,剩下的这些资源,满足不了P0的最大需求,接下来和P1对比,经过对比发现,此时剩余的这些资源数量,是可以满足P1的最高需求的,所以就意味着,如果我们把这些资源全部分配给,P1的话,那P1肯定是可以顺利的执行结束,等P1结束了,他又会把自己手里的这些资源,给归还给系统,所以啊等P1结束了,那系统的资源数就可以增加到,啊这么多,其实就是把P0,P1的这些已占有资源数,和此时的剩余资源数已相加就可以,得到P1归还之后的资源数也就是532,所以我们可以把P1先加入安全序列,然后把剩余可用资源值改成532这么多,接下来再进行第二轮的对比,首先是P0进程,此时的剩余资源数,暂时满足不了P0的最大需求,接下来是P2进程,发现第一种资源的数量也满足不了,P2的最大需求,之后是P3进程,发现此时的剩余资源数,已经可以满足P3的最大需求了,所以接下来,如果把这些资源全部分配给P3的话,那么P3肯定可以顺利的执行结束,并且会把他,手里的这些资源归还给系统,于是系统当中的资源数量就可以变为,743这么多,那么我们再把剩余资源数,呃进行一个更新,再进行第三轮的对比,那接下来的对比其实和之前是一样的,这就不再追追溯了,经过五轮循环之后,呃可以把P0 P2,P4这些进程,也依次加入到这个安全序列当中,最终我们就得到了一个呃,包含所有进程的安全序列,那刚才所说的这个算法,就是所谓的安全性算法,我们可以很方便的用代码来实现,上面的这些流程,无非就是,一些数组再加上一些循环的逻辑啊,我们可以每一轮都从,编号比较小的这些进程开始检查,但是实际上如果我们在考试当中,一般来说是用笔算的,可以用一种更快的方法,来找到安全序列,比如说刚开始
系统当中剩余的剩余的,资源是332这么多,那么经过快速的对比我们会发现,332这么多的资源,可以满足P1和P3这两个,竞争的需求,于是我们可以把P1和P3,直接就都加入安全序列当中,等P1和P3运行结束,并且归还了自己手里的资源之后,那么系统当中的资源数就可以变成啊,这么多加这么多再加这么多,也就是743这么多,于是第二轮对比发现,743这么多的资源已经可以满足剩下的,P0 P2 P4所有的这些呃进程的需求了,因此我们就可以把P0 P2,P4这些进程全部都加入安全序列当中,所以这种方法,就可以更快的找到一个安全序列,而既然此时是有安全序列的,说明此时系统是处于安全状态的,暂时不可能发生思索,那接下来,我们再来看一个找不到安全序,列的例子,我们把各个进程的最大需求,啊稍微这个数据稍微改一下,相应的最多还需要多少,这个数据也会有一定的更改,那么刚开始剩余332这么多资源,经过对比发现,P1和P3这两个进程是可以得到满足的,所以P1和P3可以先加入安全序列,但是等P1和P2 P3归还了系统资源之后,系统的资源数量,总共还会有7次3这么多,经过下一轮的对比发现P0,P2 P4这几个进程的需求都得不到满足,对于P0来说,第一个资源的数量是不够的,对于P2来说,第二个资源的数量是不够的,对于P4来说第三,个资源的数量是不够的,所以到这一步分析我们就发现嗯,在这种情况下,我们并找不到一个完整的安全序列,所以,如果系统的资源分配情况是这样的,就说明此时系统处于不安全状态,是有可能发生思索的,那么经过刚才的分析,相信大家应该已经能够呃理解,怎么用安全性算法来判断,系统到底是不是处于安全状态了,那么接下来我们来看一下,
用代码应该怎么实现,银行加算法,假设一个系统中有n个进程,m种资源,比如说之前咱们举的这个例子,就是有5个进程,然后3种资源,那我们可以让每个进程在运行之前,先声明一下自己对各种资源的最大,最大需求数,于是可以用一个n行m列的矩阵,叫做Max的矩阵,来表示各个进程对各种资源的需求,最大需求数量,那这种n行m列的矩阵,其实可以用二维数组,就可以很方便的实现了,同样的,我们可以用一个n行m列的矩阵,叫做allocation的矩阵,来表示此时,系统已经给各个进程分配了多少资源,那如果把这两个矩阵一相减,我们就可以得到一个逆的矩阵,就是用来表示,各个各个进程,最多还需要多少各种资源,另外呢我们还可以用一个长度为,m的一维数组available,来表示,此时系统当中到底还剩余多少资源,像刚才的例子就是还剩余332,当然的,某一个进程向系统申请的资源,我们也可以用一个,长度为m的一位数组,叫做request的数组来进行表示,比如说此时进程批0向系统提出申请,要第一种资源两个,第二种资源一个第三种资源一个,那么根据银行家算法的思想,我们可以预判一下,如果我们答应了这次申请资源的请求,那么是否会导致系统进入不安全状态,那第一步我们需要先检查,这个进程提出的资源申请,是否超过了他之前承诺的,那个最大的需求量,我们可以用request数组,和逆的矩阵对应的那一行进行,呃这些元素依次进行对比,那如果说进程提出的这个请求,并没有超过,他自己之前所承诺的,所需要的最大值的话,那么我们就认为这次申请是合理的,于是我们就可以进入第二步,否则我们就认为出错,第二步我们需要判断,此时系统当中剩余的资源数量,是不是能够满足他的这次申请的量,如果能够满足,那么我们就可以跳到下一步,如果不能够满足,我们就就意味着此时系统当中呃,尚且没有足够的资源,这样的话这个进程就应该阻塞等待,第三步,我们需要试探着把这个资源分配给,进程,也就是按照他的请求给他进行分配,并且修改一些相应的数据,但是要注意的是,这个地方并不是真的分配,只是用来做预判使用的,那如果我们答应了他的请求的话,系统当中可用的资源数就需要减少,也就是用这个值减掉,这个值得到一个新的值也就变成了121,另外,已分配给这个进程的这些呃资源数量,也需要做相应的改变,就是0+21+10+1,于是这个数值变为了 221这么多,最后我们还需要更新一下逆的矩阵,对应的值,就是用前面的这个减掉这个,于是得到了一个新的值532,所以,我们试探着把资源分配给这个进,程的时候,其实改变了这样三组数据之后,我们就可以根据呃这组数据,来用刚才的安全性算法来判断一下,此时系统,是不是处于安全状态,也就是尝试着找到一个安全序列,如果说安全的话,我们才可以真正的把这些资源分配给,进程,如果说此时系统进入了不安全状态,那么我们就,需要把这些数据回退到之前的那一步,然后不答应这个进程的请求,让这个进程暂时阻塞等待,所以这就是银行家算法的一个流程,所以银行
三、总结
行家算法其实也就做了这样几件事,首先是检查本次申请的资源数量,是否超过了他之前申明的最大需求数,第二,要检查此时系统剩余的可用资源数,是否还能满足这次的请求,那如果这两个判断都能通过的话,接下来就可以开始试探着分配,更改各个数据结构,第四步,就是结合我们新得到的这一系列数据,和安全性算法,来检查这次分配,是否会导致系统进入不安全的状态,那么安全性算法其实要做的事,情也很简单,就是要检查当前剩余的可用资源数,是否能够满足某个进程的最大需求,如果可以的话,那么这个进程就可以被加入完全序列,并且之后是肯定可以把这个进程的,资源全部回收的,于是我们更新了,系统当中的剩余可用资源数,之后再进行下一轮的检查,不断重复这样的过程,我们就可以知道,最终是否可以让所有的进程,都加入安全序列,如果能找到安全序列,就证明这次分配是安全的,于是我们就可以真正的把这些资源,都分配给,进程而如果找不到安全序列,就说明这次分配是不安全的,所以我们就需要让这个进程暂时阻塞,等待,在考察银行家算法的时候,一般来说,会告诉我们,此时系统当中还有多少可用资源,并且会告诉我们,Max矩阵和allocation矩阵,我们可以根据这两个矩阵,自己来算出逆的矩阵到底是多少,之后我们就可以用这一系列的流程,来判断此时系统的安全性如何,那么大家在学习这个算法的时候,一定要先理解他这个算法的逻辑,然后再尝试着用,自己用这个代码去实现这个逻辑,而不是反过来先钻到代码里,再来想代码表示的逻辑是什么样的,这样的话很容易蒙圈,那么除了这个算法的具体步骤之外,之前提到的不安全状态和思索,还有安全状态,他们之间的关系,也经常容易在选择题当中进行考察,那这个小节内容十分重要,大家还需要再回归课本并且,结合课后习题进一步的巩固,好的那么这就是这个小节的全部内容
No.29 死锁的处理策略—死锁的检测与解除
各位同学大家好,在这个小节中,我们会学习思索处理的最后一种策略,思索的检测和解除,那么在之前的小节当中我们学习了,预防思索和避免思索这两种策略,这两种方式都不会允许思索的发生,那如果我们不采取这两种策略的话,当中就很有可能发生死锁,所以啊我们就需要设计一种算法,用来检测出,此时系统当中是否发生死锁,并且还要设计一种算法啊,当我们发生,发现已经有死锁发生的时候,需要把这个死锁想办法解除掉,所以我们先来看一下第一个啊,死锁的检测要怎
一、死锁的检测
怎么实现,我们可以设置一个数据结构,用来保存系统资源的请求和分配信息,之后还,再根据这个数据结构记录的信息,再设计一种算法,用来检测这个系统当中,是否已经进入了思索,状态,我们可以用一种叫做资源分配图的,数据结构,来保存系统当中的呃各种资源的情况,那在这种图当中会有两种节点,第一种叫做呃竞争节点,每一个节点对应一个进程,第二种叫做资源节点,每一每一个节点对应一类资源,注意是一类资源,那么一般来说,会用一个矩形来表示一类资源那,矩形当中的这一些小圆,就是表示这一类资源有几个,比如说在这个图当中,R R二这种资源就有两个,R1这种资源有3个,另外资源分配图当中有两种边,一种是进程节点指向资源节点的边,这种边又称作为请求边,就是用来表示啊,一个进程对某一种资源的请求,那每一条边对应的就是一个资源,所以在这个情况来看啊,P1进程此时正在请求啊,被分配一个R2资源,P2进程,此时正在被正在请求分配一个R1资源,另外呢,还有资源节点指向进程节点的边,这种边又称作分配边,就是表示这种资源已经给,各个进程分配了几个,那同样的每一条边也对应一个资源,比如说现在这个系统当中,已经给P1进程分配了两个R1资源,给P2进程分配了一个R1资源,而R2资源只给PR分配了一个,那学习过数据结构的同学,可以动手试一下,要怎么定义出这样一个图的数据结构,那么接下来
既然有了数据结构,我们再来考虑一下,怎么给予这种数据结构,来分析系统此时是否处于思索状态,我们可以这么考虑,如果说系统当中剩余的可用资源数,足够满足进程的需求,进程的请求的话,那么理论上这个进程就不应该被阻塞,他可以顺利的执行下去,比如说像这个图中,P1进程他请求一个单位的R2资源,而R2资源现在只被分配出,去了一个它的总数是两个,所以R2资源现在剩余空闲的还有一个,所以,P1进程的这种请求是可以被满足的,所以啊P1进程应该不会被阻塞,它可以顺利的执行下去,但是,P2进程此时请求一个单位的R1资源,而由于R1资源此时已经分配出去了12,33个所以R1资源已经没有空闲的,因此P2进程的这些请求不能被满足,而,P1进程既然可以顺利的执行下去的话,那么等P1进程顺利的执行完了,他就可以把自己现在手里的资源,全部归还给系统,并且等P1结束之后,他应该也不会再请求,使用任何一种资源,所以假设现在P1顺利的执行结束了,那么我们就可以把,和P1相连的所有的这些边全部干掉,就像这样子,那P1把R1资源归还之后啊,现在R1资源空闲的还有两个,而P2只需要一个,所以P2进程的这个请求,接下来也可以被满足,所以P2本来是呃阻塞的,那现在P2就可以被唤醒,然后正常的执行下去,等P2执行完了之后,他也会归还所有的这些资源,并且会呃,并且不会再对任何一种资源提出请求,所以我们接下来也可以把,P2呃相连的这些边全部干掉,那么当P2归还了这些系统资源之后,也有可能会有别的进程被唤醒,只不过在这个图当中只有两个进程,所以到这步为止我们就可以认为,这些进程实际上是可以,顺利的依次执行结束的,因此如果,我们按照刚才所说的这一系列方法,能够消除所有的这些相连的边的话,那么我们就称这个资源分配图,是可完全简化的,那么既然可完全简化,就意味着此时并没有死索发生,我们还可以从另外一个角度来考虑,其实刚才我们分析的这个过程,相当于找到了一个安全序列,优先的把,给P1那么P1可以执行结束,等P1执行结束之后P2也可以执行结束,所以如果按P1 P2 这样的,序列来依次执行的话,那么所有的进程是都可以,顺利的执行结束的,这就和上一小节介绍的安全序列,是一种原理,那么既然我们能够找到一个安全序列,就说明此时系统是处于安全状态了,既然处于安全状态,就意味着此时系统肯定没有发生死锁,相反的,如果我们不能把所有的边都消除的话,那么就说明此时系统已经发生了死锁,那我们再来看一个,不能消除所有边的情况,把刚才的这个资源分配图稍微的,改一下让p申请两个R2资源,然后再增加一个持有R2资源的P3进程,还是用刚才的方法来分析,PE进程此时请求两个RR资源,而RR资源已经全部分配出去了,没有空闲了,所以PE进程应该被阻塞,而PR进程此时请求R1资源,REG资源也全部分配出去了,也没有空闲了,所以PR进程也需要被阻塞,此时可以顺利执行下去的只有P3进程,那么当P3执行结束之后,会归还他所拥有的全部资源,接下来R2资源已经有一个空闲的,但是由于P1进程需要的是两个RR资源,所以此时,RR这种资源的数量,依然不够满足P1的需求,所以P1依然会被阻塞,那P2进程也一样,此时没有空闲的R1资源,所以P2进程也会继续阻塞,所以我们就不能像刚才那样把,p p 2相连的这些边给干掉,那么到这一步为止,我们就不能继续啊化解下去了,所以,这种情况就是不能消除所有边的情况,那这种情况下系统就发生了死锁,大家可以结合这个图来分析一下,此时是否满足,死锁发生的4个必要条件,4个必要条件分别又是什么呢,那如果我们不能消除所有的边的话,最终还连着一些边的那些进程,就是处于死锁状态的进程,像P3进像刚才的P3进程,可以把与他相连的所有边都干掉,所以P3进程并不是死锁状态,只有P1和P2是死锁状态的进程,这就是死锁检测的一个思想,那么我们用刚才的这种方式,理解了它的原理之后
那这一段描述就应该更容易理解了,我们需要在资源分配图当中,找到一个既不阻塞,又不是孤典的进程,PI,不阻塞的意思是说,这个进程,申请的这些资源的数量,足够满足他的需求,比如说像P1进程就是不阻塞的进程,而P2进程申请的R1资源,已经没有足够的,剩余资源可以分配给他了,所以P2进程是一个阻塞的进程,另外不是估点这个条件指的是,与这个进程至少有一个边相连,那么P1和P2显然都不是估点,所以在这个状态下,满足既不阻塞,又不是估点的进程就只有P1,这个进程,接下来我们可以,消去它所有的请求边和分配边啊,也就是把和P1相连的所有的边都干掉,使之成为孤立的节点,那由于此时已经没有边和他相连了,所以此时PE,就变成了之前所提到的所谓的孤点,那么当P1释放了之前持有的资源之后,P2这个进程就可以被唤醒,于是P2也变成了既不阻塞,又不是孤典的进程,所以接下来,我们就需要把,P2相连的所有的这些边给干掉,那么由于我们可以用这种方式,干掉所有的边,所以这个图是可完全简化的,因此此时系统并没有发生思索现象,那如果这个图不可以完全简化的话,那此时系统就发生了死锁,这就是著名的死锁定义啊,感兴趣的同学可以上网查一下文献,它是怎么证明的,那现在我们已经迈出了第一步,我们已经可以有办法检测出,此时系统是否发生了死锁,接下来,我们就要想办法怎么解除这个死锁,一旦检测出死锁的发生,就应该立即解除死锁,那这儿需要补充的一点是,并不是系统中,所有的进程都处于死锁状态,用死锁检测算法化简资源分配图之后,还连,
二、死锁的解除
沿着边的那些进程就是思索进程,比如说刚才提到这个例子,P3就是没有思索的,而P1和P2是处于思索状态的进程,解除思索有这样的几种方法,第一种叫做资源剥夺法,就是会暂时挂起某一些死锁进程,既然挂起的是死锁进程,那么就意味着我们此时不能把P3挂起,因为它不处于死锁状态,我们可以选择,P1和P2之间的某一个进程,把它暂时挂起,也就是暂时放到外存上,然后并且抢占他现在持有的这些资源,把这些资源分配给呃所需要的进程,但是同时,我们也应该防,止被挂起的进程长时间得不到资源,而导致饥饿的现象,第二种方法叫做撤销进程法,或者叫终止进程法,就是可以强制的撤销部分,甚至是全部的思索进程,并且剥夺这些进程的资源,但这种方式简单粗暴,但是需要付出的代价可能会很大,因为有的进程,他有可能已经运行了很长时间了,已经接近结束了,但如果这个时候把他呃强行撤销的话,就意味着之前的那些工作全部白做,功亏一篑之后还可还必须从头再来,所以撤销进程法的代价其实是很大的,那第三种方法叫做进程回退法,可以选择一个或者多个死锁进程,让他们回退到足以避免死锁的地步,比如说我们可以让,P1进程一直回退到,他只持有一个R1资源的呃那个时候,那这样的话就可以空出一个R1资源,先分配给P2进程,至少先保证P2,进程是可以顺利的执行下去的,但是要实现这种所谓的进程回退,操作系统就需要,记录这些进程的执行历史信息,设置还原点,所以进程回退法其实也不太容易实现,那接下来我们再来考虑一下,我们可以用什么样的方式来决定,到底要让哪一个进程做出牺牲,比如剥夺他的资源,或者直接把他干掉或者让他回退,可以从这样一些角度来考虑,比如说进程优先级,那当然,进程优先级低的我们可以对他下手,或者我们可以看,这些进程到底执行了多长时间,那执行时间越长,说明啊让他回退或者是把他撤销的话,那我们付出的代价就会更大,毕竟之后还需要从头再来吗,所以,我们可以选择执行时间更少的进程,呃让他做出牺牲,第三我们还可以考虑,各个进程到底还有多久可以完成,那显然我们可以优先让,呃马上就可以执行结束的那些进程,优先的获得资源,然后牺牲别的那些进程,第四我们还可以考虑,进程已经使用了多少种资源,那如果一个进程持有很多很多个资,源的话那把这个进程撤销,或者把这个进程的资源给剥夺的话,那就意味着,这个死锁的局面就可以被尽快的解除,所以我们可以优先把,拥有更多资源的进程,先把它让,让它做出牺牲,第五,我们还可以考虑这些进程是交互式的,还是批处理式的,交互式的,就意味着这些进程是在和用户交互的,如果把交互式的进程干掉的话,那么对用户肯定是极其不爽的,而对于批处理式的这些进程来说,他无非就是在做一些计算,用户对于,啊这这种类型的进程的及时反馈,其实并不是那么在意,所以,我们可以优先牺牲批处理师的进程,那么这就是思索解除的一些策略,那这个小节
三、总结
我们介绍了死锁的检测和解除,考试当中,比较容易常考的是死锁,检测相关的部分,需要理解资源分配图的这两种,节点和两种边,分别是什么意义,另外,更需要着重理解并记住思索检测算法,其实我们可以用一种更经典的语言,把思索检测算法把它概括出来,其实无非就是依次消除,与不阻塞近程相连的边,直到无边可消,而所谓不阻塞进程,指的其实就是申请的资源数,还足够的那些进程,所以我们其实可以用这样一句话,就把思索检测算法整个给概括出来,那思索检测算法相关的题,一般来说是会,直接给出直接画出资源分配图的,当然大家也要小心,和数据结构结合考察,所以如果到后期时间充裕的话,也可以自己尝试动手实现一下,这个思索检测算法,其实也并不复杂,那对于思索的解除,一般来说只会在选择题里进行考察,稍微有个印象就可以了,好的,那么以上就是这个小题的全部内容,,