admin 管理员组

文章数量: 887017

【操作系统】

笔记说明:

  • 本文是为了找工作,所以考研中考的计算等细节有的并未记录
  • 有的地方(比如生产者消费者问题)在java中见太多了,就记录的少
  • 本文适合找工作及基于本文笔记再扩容笔记

中断

人们发明了操作系统(作为计算机的管理者),引入中断机制,实现了多道程序并发执行。

中断本质:发生中断就意味着需要操作系统介入,开展管理工作。

CPU收到计时部件发出的中断信号,切换为核心态对中断进行处理。

  • 中断发生时,cpu立即进入核心态。
  • 当中断发生后,当前运行的进程暂停运行,并由操作系统内核对中断进行处理
  • 对于不同的中断信号,会进行不同的处理

发生中断就意味着需要操作系统介入,开展管理工作。

由于操作系统的管理工作(比如进程切换、分配I/O设备等)需要使用特权指令,因此CPU要从用户态转为核心态。中断可以使CPU从用户态切换为核心态,使操作系统获得计算机的控制权。有了中断,才能实现多道程序并发执行。

用户态–>核心态 是通过中断实现的,并且中断是唯一途径

中断类型:

  • 内中断(系统调用、异常、例如、陷入):与当前执行的指令有关,中断信号来源于CPU内部。
    • 自愿中断:指令中断(系统调用时访问的指令,陷阱、陷入trap)(有意为之的异常)
    • 强迫中断:
      • 硬件故障fault:缺页。(由错误条件引擎id,可能被故障处理程序修复)
      • 软件中断:整数除0。(不可恢复的致命错误造成的结果,终止处理程序不再将控制返回给引发终止的应用程序)
  • 外中断:与当前执行的指令无关,中断信号来源于CPU外部
    • 外设请求:IO操作完成发出的中断信号(IO请求中断)
    • 人工干预:用户强制终止一个进程
内中断:

外中断:
  • 执行完每个程序之后,CPU都要检查当前是否有外部中断信号(如用户通过键盘输入了一个字符)
  • 如果检测到外部中断信号,则需要保护被中断进程的CPU环境(如程序状态字SPW,程序计算器PC,各种通用寄存器)
  • 根据中断信号类型转入相应的中断处理程序(核心态)
  • 恢复原进程的CPU环境并退出中断,返回原进程继续往下进行

系统调用

问题:操作系统为什么要提供“系统调用”功能?
生活场景.你去学校打印店打印论文,当你按下“打印”之后,打印机开始工作。你的论文打印到一半时,另一位同学按下了“打印”按钮开始打印他自己的论文。
最终,你的论文和该同学的论文页面并没有混杂在一起,都是按顺序依次打印的

思考:如果各个进程可以随意地使用打印机,会发生什么情况?
你的论文打印到一半时,另一位同学按下了“打印”按钮开始打印他自己的论文。
结果,你的后半部分论文与该同学的页面混杂在一起了。

解决方法.操作系统提供“系统调用”功能,用户进程想要使用打印机这种共享资源,只能通过系统调用向操作系统发出请求的操作系统会对各个请求进行协调管理。

应用程序通过系统调用请求操作系统的服务。系统中的各种共享资源都由操作系统统一掌管,因此在用户程序中,凡是与资源有关的操作(如存储分配、《/0操作、文件管理等),都必须通过系统调用的方式向操作系统提出服务请求,由操作系统代为完成。这样可以保证系统的稳定性和安全性,防止用户进行非法操作。

普通应用程序可直接进行系统调用,也可使用库函数。有的库函数涉及系统调用,有的不涉及
编程语向上提供库函数。有时会将系统调用封装成库函数,以隐藏系统调用的一些细节,使系统调用更加方便。
操作系统向上提供系统调用
裸机
  • 不涉及系统调用的库函数:如的“取绝对值”的函数
  • 涉及系统调用的库函数:如“创建一个新文件”的函数

进程

在多道程序环境下,程序的执行属于并发执行,此时它们将失去其封闭性,并具有间断性,以及其运行结果不可再现性的特征。由此,决定了通常的程序是不能参与并发执行的,否则,程序的运行也就失去了意义。为了能使程序发执行并且可以对并发执行的程序加以描述和控制,人们引入了进程的概念。

为了使参与并发执行的每个程序(含数据)都能独立地运行,在操作系统中必须为之配置一个专门的数据结构,称为进程控制块(Process Control Block,PCB)。系统利用PCB来描述进程的基本情况和活动过程,进而控制和管理进程。这样由程序段、相关的数据段和PCB三部分便构成了进程实体(又称进程映像,简称镜像)。一般情况下,我们把进程实体就简称为进程,例如,所谓创建进程,实质上是创建进程实体中的PCB;而撤消进程,实质上是撤消进程的PCB

进程的定义:

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

进程与程序的联系

  • 程序是产生进程的基础

  • 程序的每次运行构成不同的进程

  • 进程是程序功能的体现

  • 通过多次执行,一个程序可对应多个进程;通过调用关系,一个进程可包括多个程序。

  • 进程是动态的,程序是静态的:程序是有序代码的集合;进程是程序的执行,进程有核心态/用户态

  • 进程是暂时的,程序是永久的:进程是一个状态变化的过程,程序可长久保存

  • 进程与程序的组成不同:进程的组成包括程序,数据和进程控制块(进程的状态信息)

进程包括:

  • PCB:操作系统通过PCB来管理进程,因此PCB应该包含操作系统对其进行管理所需的各种信息
    • 进程描述信息
      • 进程标识符PID:本进程的标识、父进程标识
      • 用户标识符UID
    • 进程控制和管理信息
      • CPU、磁盘、网络流量使用情况统计
      • 进程当前状态:就绪态/阻塞态/运行态
      • 进程优先级
    • 资源分配清单:程序段指针、数据段指针、键盘、鼠标
      • 正在使用那些文件
      • 正在使用哪些内存区域
      • 正在使用哪些IO设备
    • 处理机相关信息
      • 如PSW、PC等各种寄存器的值(用于实现进程切换)
      • ->用户可见寄存器,用户程序可以使用的数据,地址等寄存器
        ->控制和状态寄存器,如程序寄存器(PC),程序状态字(PSW)
        ->栈指针,过程调用/系统调用/中断处理和返回时需要用到它。
  • 程序段:存放程序代码
  • 数据段:程序运行时使用、产生的运算数据。如全局变量、局部变量、宏定义的常量 就存放在数据段内

PCB是给操作系统用的;程序段、数据段是给进程自己用的

程序是如何运行的:C语言代码编译翻译后得到二进制的机器指令、程序运行的过程其实就是CPU指向一条一条的机器指令的过程

运行3个QQ后,对应3个QQ进程,他们的PCB、数据段各不相同,但是程序段的内容是相同的

进程的组织方式

在一个系统中,通常有数十、数百乃至数千个PCB。为了能对他们加以有效的管理,应该用适当的方式把这些PCB组织起来。

注:进程的组成讨论的是一个进程内部由哪些部分构成的问题,而进程的组织讨论的是多个进程之间的组织方式问题

  • 链接(链表)方式
    • 按照进程状态将PCB分为多个队列
    • 操作系统持有指向各个队列的指针
    • 通常会把优先级高的进程放在队头
  • 索引方式:
    • 根据进程状态的不同,建立几章索引表
    • 操作系统持有指向各个索引表的指针

进程的状态

进程是程序的一次执行。在这个执行过程中,有时进程正在被CPU处理,有时又需要等待cpu服务,可见,进程的状态是会有各种变化。为了方便对各个进程的管理,操作系统需要将进程合理地划分为几种状态。

  • 运行:占有CPU处理机,并在CPU上运行
  • 就绪:已经具备运行条件,但由于没有CPU,而暂时不能运行
  • 阻塞:等待某一事件而暂时不能运行。等待操作系统分配打印机、等待读磁盘操作的结果
  • 创建:新建PCB、分配程序段、数据段
  • 终止

  • 运行态到阻塞态是进程自身做出的主动行为
  • 阻塞态到就绪态是被动行为

进程控制

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

如何实现进程控制:原语

进程控制相关的原语:

  • 进程的创建
  • 进程的终止
  • 进程的阻塞
  • 进程的唤醒
  • 进程的切换

原语

如何实现进程控制:用原语实现进程控制。原语的执行具有原子性, 即执行过程只能一气呵成, 期间不允许被中断。

可以用 “关中断指令” 和“开中断指令” 这两个特权指令实现原子性

正常情况: CPU每执行完一条指令都会例行检查是否有中断信号需要处理, 如果有,则暂停运行当前这段程序, 转而执行相应的中断处理程序

CPU执行了关中断指令之后, 就不再例行检查中断信号, 直到执行开中断指令之后才会恢复检查。

这样,关中断、开中断之间的这些指令序列就是不可被中断的, 这就实现了“原子性

显然,关/开中断指令的权限非常大,必然是只允许在核心态下执行的特权指令。

学习技巧:进程控制会导致进程状态的转换。无论哪个原语,要做的无非三类事情:

  • 更新PCB中的信息(如修改进程状态标志、将运行环境保存到PCB、从PCB恢复运行环境)
    • 更新所有的进程控制原语一定都会修改进程状态标志
    • 剥夺当前运行进程的CPU使用权必然需要保存其运行环境
    • 某进程开始运行前必然要恢复期运行环境
  • 将PCB插入合适的队列
  • 分配/回收资源

进程通信

进程通信:进程通信就是指进程之间的信息交换。

进程是分配系统资源的单位(包括内存地址空间),因此各进程拥有的内存地址空间相互独立。

进程1可以访问进程1的地址空间,进程2可以直接访问进程2的地址空间

为了保证安全, 一个进程不能直接访问另一个进程的地址空间。

但是进程之间的信息交换又是必须实现的。

为了保证进程间的安全通信, 操作系统提供了一些方法。

  • 共享存储
    • 基于数据结构的共享
      基于存储区的共享
  • 消息传递
    • 直接通信方式
      间接通信方式
  • 管道通信

进程通信的方式大概分为六种。

  • 1)管道:包括无名管道(pipe)及命名管道(named pipe),无名管道可用于具有父进程和子进程之间的通信。命名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。
    • 进程1写满后进程2才能读,进程2读到空后进程1才能写数据
  • 2)消息队列(message):进程可以向队列中添加消息,其它的进程则可以读取队列中的消息。
  • 3)信号(signal):信号用于通知其它进程有某种事件发生。
  • 4)共享内存(shared memory):多个进程可以访问同一块内存空间。但是是互斥访问的
  • 5)信号量(semaphore):也叫信号灯,用于进程之间对共享资源进行加锁。
  • 6)套接字(socket):可用于不同计算机之间的进程间通信。包含IP、port、传输层协议、网络地址、API等

线程

没引入进程之前,不能同时聊QQ+听音乐

引入进程之后,能同时聊QQ+听音乐。一个进程对应一份代码,从上到下执行。

但是QQ可以视频、文字聊天、传送文件,进程是程序的一次执行。但这些功能显然不可能是由一个程序顺序处理就能实现的。

为什么引入线程:有的进程可能需要“同时”做很多事,而传统的进程只能串行地执行一系列程序。为此,引入了“线程”,来增加并发度。

每个线程可以有各自不同的代码

可以把线程理解为“轻量级进程”

线程是一个基本的cpu执行单元,也是程序执行流的最小单位。

引入线程之后,不仅是进程之间可以并发,进程内的各线程之间
也可以并发,从而进一步提升了系统的并发度,使得一个进程内
也可以并发处理各种任务(如QQ视频、文字聊天、传文件)

引入线程后,进程只作为除cpu之外的系统资源的分配单元(如打印机、内存地址空间等都是分配给进程的)。

线程则作为处理机的分配单元。

线程的属性:

  • 线程是处理机调度的单位
  • 多CPU计算机中,各个线程可占用不同的CPU
  • 每个线程都有一个线程ID、线程控制块(TCB)
  • 线程也有就绪、阻塞、运行三种基本状态
  • 线程几乎不拥有系统资源
  • 同一进程的不同线程间共享进程的资源
  • 由于共享内存地址空间,同一进程中的线程间通信甚至无需系统干预
  • 同一进程中的线程切换,不会引起进程切换
  • 不同进程中的线程切挨,会引起进程切换
  • 切挨同进程内的线程,系统开销很小
  • 切换进程,系统开销较大

线程的实现方式

用户级线程

用户级线程由应用程序通过线程库实现。
所有的线程管理工作都由应用程序负责(包括线程切换)
用户级线程中, 线程切换可以在用户态下即可完成, 无需操作系统干预。

在用户看来, 是有多个线程。 但是在操作系统内核看来, 并意识不到线程的存在。 (用户级线程对用户不透明, 对操作系统透明)

可以这样理解, “用户级线程” 就是“从用户视角看能看到的线程”

多线程模型

内核级线程才是处理机分配的单位

如果某个内核级线程处理的是某个用户及线程,如果用户级线程被阻塞的话,该内核级线程也会被阻塞

处理机调度

三个层次:

  • 高级调度(作业调度)
  • 中级调度(内存调度)
  • 低级调度(进程调度)

当有一堆任务要处理,但由于资源有限,这些事情没法同时处理。这就需要确定某种规则来决定处理这些任务的顺序,这就是“调度”研究的问题。

在多道程序系统中,进程的数量往往是多于处理机的个数的,这样不可能同时并行地处理各个进程。处理机调度,就是从就绪队列中按照一定的算法选择一个进程并将处理机分配给它运行,以实现进程的并发执行。

高级调度

由于内存空间有限,有时无法将用户提交的作业全部放入内存,因此就需要确定某种规则来决定将作业调入内存的顺序。

高级调度(作业调度)。按一定的原则从外存上处于后备队列的作业中挑选一个(或多个)作业,给他们分配内存等必要资源,并建立相应的进程(建立PCB),以使它(们)获得竞争处理机的权利。

高级调度是辅存(外存)与内存之间的调度。每个作业只调入一次,调出一次。作业调入时会建立相应的PCB,作业调出时才撤销PCB。高级调度主要是指调入的问题,因为只有调入的时机需要操作系统来确定,但调出的时机必然是作业运行结束才调出。

中级调度

引入了虚拟存储技术之后,可将暂时不能运行的进程调至外存等待。等它重新具备了运行条件且内存又稍有空闲时,再重新调入内存。

这么做的目的是为了提高内存利用率和系统吞吐量。

暂时调到外存等待的进程状态为挂起状态。值得注意的是,PCB并不会一起调到外存,而是会常驻内存。PCB中会记录进程数据在外存中的存放位置,进程状态等信息,操作系统通过内存中的PCB来保持对各个进程的监控、管理。被挂起的进程PCB会被放到的挂起队列中。

中级调度(内存调度),就是要决定将哪个处于挂起状态的进程重新调入内存。

一个进程可能会被多次调出、调入内存,因此中级调度发生的频率要比高级调度更高

低级调度

低级调度(进程调度),其主要任务是按照某种方法和策略从就绪队列中选取一个进程,将处理机分配给它。

进程调度是操作系统中最基本的一种调度,在一般的操作系统中都必须配置进程调度。

进程调度的频率很高一般几十亳秒一次。

要做什么调度发生在..发生频对进程状态的影响
高级调度 (作业调度)按照某种规则,从后各队列
中选择合适的作业将其调入
内存,并为其创建进程
外存–>内存
(面向作业)
最低无创建态–>就绪态
中级调度
(内存调度)
按照某种规则,从挂起队列
中选择合适的进程将其数据
调回内存
外存–>内存
(面向进程)
中等挂起态–>就绪态
(阻塞挂起兮阻塞态)
低级调度
(进程调度)
按照某种规则,从就绪队列
中选择一个进程为其分配处
理机
内存–>cpu最高就绪态–>运行态

进程调度的时机

有的系统中,只允许进程主动放弃处理机

有的系统中,进程可以主动放弃处理机,当有更紧急的任务需要处理时,也会强行剥夺处理机(被动放弃)

临界资源:一个时间段内只允许一个进程使用的资源。各进程需要互斥地访问临界资源

临界区:访问临界资源的那段代码。

内核程序临界区一般是用来访问某种内核数据结构的,比如进程的就绪队列(由各就绪进程组成的PCB组成)

进程调度的方式

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

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

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

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

进程的切换与过程

“狭义的进程调度”与“进程切换”的区别:

狭义的进程调度指的是从就绪队列中选中一个要运行的进程。(这个进程可以是刚刚被暂停执行的进程,也可能是另一个进程,后一种情况就需要进程切换)

进程切换是指一个进程让出处理机,由另一个进程占用处理机的过程。
广义的进程调度包含了选择一个进程和进程切换两个步骤。

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

1.对原来运行进程各种数据的保存
2,对新的进程各种数据的恢复
(如:程序计数器、程序状态字、各种数据寄存器等处理机现场信息,这些信息一般保存在进程控制块)

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

调度算法的评价指标

  • CPU利用率:忙碌时间/总时间
  • 系统吞吐量:完成多少道作业/总共花的时间
  • 周转时间
    • 对于计算机的用户来说,他很关心自己的作业从提交到完成花了多少时间。
      周转时间,是指从作业被提交给系统开始,到作业完成为止的这段时间间隔。
      它包括四个部分:作业在外存后备队列上等待作业调度(高级调度)的时间、进程在就绪队列上等待进程调度(低级调度)的时间、进程在CPU上执行的时间、进程等待I/O操作完成的时间。后三项在一个作业的整个处理过程中,可能发生多次。
    • 周转时间、平均周转时间
    • 带权周转时间、平均带权周转时间
  • 等待时间:进程/作业处于等待处理机状态时间之和
    • 对于进程来说,等待时间就是指进程建立后等待被服务的时间之和,在等待|/0完成的期间其实进程也是在被服务的,所以不计入等待时间。
      对于作业来说,不仅要考虑建立进程后的等待时间,还要加上作业在外存后备队列中等待的时间。
      一个作业总共需要被CPU服务多久,被|/O设备服务多久一般是确定不变的,因此调度算法其实只会影响作业/进程的等待时间。当然,与前面指标类似,也有平均等待时间,来评价整体性能。
  • 响应时间
    • 对于计算机用户来说,会希望自己的提交的请求(比如通过键盘输入了一个调试命令)尽早地开始被系统服务、回应。
      响应时间,指从用户提交请求到首次产生响应所用的时间。

思考:有的作业运行时间短,有的作业运行时间长,因此在周转时间相同的情况下,运行时间不同的作业,给用户的感觉肯定是不一样的

带权周转时间=作业周转时间/作业实际运行的时间

对于周转时间相同的两个作业,实际运行时间长的作业在相同时间内被服务的时间更多,带权周转时间史小,用户满意度更高。
对于实际运行时间相同的两个作业,周转时间短的带权周转时间更小,用户满意度更高。

调度算法

  • 先来先服务:对长作业有利,对短作业不利
  • 最短作业优先:可能产生饥饿现象
  • 最高响应比优先:
    • 响应比=(等待时间+要求服务时间)/要求服务时间
  • 时间片轮转调度算法(RR):各个进程轮流执行一个时间片(处理机的),然后重新放回就绪队列
  • 优先级调度算法
  • 多级反馈队列调度算法
    • 设置多级就绪队列,各级队列优先级从高到低,时间片从小到大
    • 新进程到达时先进入第1级队列,按FCFS原则排队等待被分配时间片,若用完时间片进程还未结束,则进程进入下一级队列队尾。如果此时己经是在最下级的队列,则重新放回该队列队尾
    • 只有第k级队列为空时,才会为k+l级队头的进程分配时间片
    • 抢占式的算法。在k级队列的进程运行过程中,若更上级的队列(1、k一1级)中进入了一个新进程,则由于新进程处于优先级更高的队列中,因此新进程会抢占处理机,原来运行的进程放回k级队列队尾。

进程同步

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

我们把一个时间段内只允许一个进程使用的资源称为临界资源。许多物理设备(比如摄像头、打印机)都属于临界资源。此外还有许多变量、数据、内存缓冲区等都属于临界资源。

对临界资源的访问,必须互斥地进行。互斥,亦称间接制约关系。进程互斥指当一个进程访问某临界资源时,另一个想要访问该临界资源的进程必须等待。当前访问临界资源的进程访问结束,释放该资源之后,另一个进程才能去访问临界资源。

do{entry section;//进入区 //检查是否可以进入临界区critical section;//临界区 //反问临界资源的那段代码exit section;//退出区 //负责接触 正在访问临界资源的标志remainder section;//剩余区 //做其他处理
} while(true)

注意:
临界区是进程中访问临界资源的代码段。
进入区和退出区是负责实现互斥的代码段。
临界区也可称为“临界段”

进程互斥

为了实现对临界资源的互斥访问,同时保证系统整体性能,需要遵循以下原则

  • 空闲让进。临界区空闲时,可以允许一个请求进入临界区的进程立即进入临界区
  • 艺忙则等待。当己有进程进入临界区时,其他试图进入临界区的进程必须等待
  • 有限等待。对请求访问的进程,应保证能在有限时间内进入临界区(保证不会饥饿)
  • 让权等待。当进程不能进入临界区时,应立即释放处理机,防止进程忙等待。

进程互斥的软件实现方法:

  • 单标志法:算法思想:两个进程在访问完临葬区后会把使用临界区的权限转交给另一个进程。也就是说每个进程进入临界区的权限只能被另一个进程赋予

    问题:违背“空闲让进”的原则
  • 双标志先检查:
  • 双标志后检查
  • Peterson算法
    Peterson算法用软件方法解决了进程互斥问题,遵循了空闲让进、忙则等待、有限等待三个原则,但是依然未遵循让权等待的原则。
    Peterson算法相较于之前三种软件解决方案来说,是最好的,但依然不够好。

进程互斥的硬件实现方法:

  • 中断屏蔽方法
  • TestAndSet(TS指令/TSL指令)
  • Swap指令(XCHG指令)

信号量机制

  • 整型信号量
  • 记录型信号量

用户进程可以通过使用操作系统提供的一对原语来对信号量进行操作,从而很方便的实现了进程互斥、进程同步。

信号量其实就是一个变量(可以是一个整数,也可以是更复杂的记录型变量),可以用一个信号量来表示系统中某种资源的数量,比如:系统中只有一台打印机,就可以设置一个初值为1的信号量。

原语是一种特殊的程序段,其执行只能一气呵成,不可被中断。原语是由关中断/开中断指令实现的。软件解决方案的主要问题是由“进入区的各种操作无法一气呵成”,因此如果能把进入区、退出区的操作都用“原语”实现,使这些操作能“一气呵成”就能避免问题。

一对原语:wait(S)原语和signal(S)原语,可以把原语理解为我们自己写的函数,函数名分别为wait和signal,括号里的信号量s其实就是函数调用时传入的一个参数。

wait、signal原语常简称为P、V操作(来自荷兰语proberen和verhogen)。因此,做题的时候常把wait(S)、signal(S)两个操作分别写为P(S)、V(S),即申请、释放

  • 整型信号量

  • 记录型信号量:解决了忙等的问题

用信号量机制实现:

  • 进程互斥、
  • 进程同步:各并发进程按要求有序地推进
  • 进程前驱关系

进程互斥:

进程同步:

进程前驱关系:

生产者消费者

  • 系统中有一组生产者进程和一组消费者进程,生产者进程每次生产一个产品放入缓冲区,消费者进程每次从缓冲区中取出一个产品并使用。(注:这里的“产品”理解为某种数据)
  • 生产者、消费者共享一个初始为空、大小为n的缓冲区。
  • 只有缓冲区没满时,生产者才能把产品放入缓冲区,否则必须等待。
  • 只有缓冲区不空时,消费者才能从中取出产品,否则必须等待。
  • 缓冲区是临界资源,各进程必须互斥地访问。

如何用信号量机制(P、v操作)实现生产者、消费者进程的这些功能呢?

信号量机制可实现互斥、同步、对一类系统资源的申请和释放。

  • 设置初值为1的互斥步信号量
  • 设置初值为0的同步信号量(实现一前一后)
  • 设置一个信号量,初始值即为资源的数量(本质上也属于“同步问题”,若无空闲资源,则申请资源的进程需要等待别的进程释放资源后才能继续往下执行)
semaphore mutex = 1;//互斥信号量,实现对缓冲区的互斥访问
semaphore empty = n;//同步信号量,表示空闲缓冲区的数量
semaphore fu11 = 0;//同步信号量,表示产品的数量,也即非空缓冲区的数量
producer(){while(1){生产一个产品;P(empty);//1P(mutex);//2把产品放入缓存区;V(mutex);V(fu11);}
}
consumer(){while(1){P(fu11);//3P(mutex);//4从缓存区取出一个产品;V(mutex);V(empty);使用产品;}
}

PV顺序问题

若此时缓冲区内己经放满产品,则empty=o,full=n

则生产者进程执行1使mutex变为0,再执行1,由于己没有空闲缓冲区,因此生产者被阻塞。

由于生产者阻塞,因此切换回消费者进程。消费者进程执行3,由于mutex为0,即生产者还没释放对临界资源的“锁”,因此消费者也被阻塞。

这就造成了生产者等待消费者释放空闲缓冲区,而消费者又等待生产者释放临界区的情况,生产者和消费者循环等待被对方唤醒,出现“死锁”

同样的,若缓冲区中没有产品,即full=o,empty=no按341的顺序执行就会发生死锁。

实现互斥的P操作一定要放在实现同步的P操作之后。

V操作不会导致进程阻塞,因此两个V操作顺序可以交换。

多生产者多消费者

  • 互斥关系:(mutex=1)对缓冲区(盘子)的访问要互斥地进行
  • 同步关系(一前一后):
    • 1,父亲将苹果放入盘子后,女儿才能取苹果
      2.母亲将橘子放入盘子后,儿子才能取橘子
      3,只有盘子为空时,父亲或母亲才能放入水果

吸烟者问题

假设一个系统有三个抽烟者进程和一个供应者进程。每个抽烟者不停地卷烟并抽掉它,

但是要卷起并抽掉一支烟,抽烟者需要有三种材料:烟草、纸和胶水。

三个抽烟者中,第一个拥有烟草、第二个拥有纸、第三个拥有胶水。供应者进程无限地提供三种材料,供应者每次将两种材料放桌子上,拥有剩下那种材料的抽烟者卷一根烟并抽掉它,并给供应者进程一个信号告诉完成了,供应者就会放另外两种材料再桌上,这个过程一直重复(让三个抽烟者轮流地抽烟)

问题分析:

假设一个系统有三个抽烟者进程和一个供应者进程。每个抽烟者不停地卷烟并抽掉它,但是要卷起并抽掉一支烟,抽烟者需要有三种材料:烟草、纸和胶水。三个抽烟者中,第一个拥有烟草、
第二个拥有纸、第三个拥有胶水。供应者进程无限地提供三种材料,供应者每次将两种材料放桌子上,拥有剩下那种材料的抽烟者卷一根烟并抽掉它,并给供应者进程一个信号告诉完成了,供应者就会放另外两种材料再桌上,这个过程一直重复(让三个抽烟者轮流地抽烟)

本质上这题也属于“生产者一消费者”问题,更详细的说应该是“可生产多种产品的单生产者一多消费者”

1.关系分析。找出题目中描述的各个进程,分析它们之间的同步、互斥关系。
2.整理思路。根据各进程的操作流程确定P、v操作的大致顺序
3.设置信号量。设置需要的信号量,并根据题目条件确定信号量初值。(互斥信号量初值一般为1,同步信号量的初始值要看对应资源的初始值是多少)

读者写着问题

有读者和写者两组并发进程,共享一个文件,当两个或两个以上的读进程同时访问共享数据时不会产生副作用,但若某个写进程和其他进程(读进程或写进程)同时访问共享数据时则可能导致
数据不一致的错误。因此要求:

  • ①允许多个读者可以同时对文件执行读操作;

  • ②只允许一个写者往文件中写信息;

  • ③任一写者在完成写操作之前不允许其他读者或写者工作;

  • ④写者执行写操作前,应让己有的读者和写者全部退出。

  • 关系分析。找出题目中描述的各个进程,分析它们之间的同步、互斥关系。

  • 整理思路。根据各进程的操作流程确定P、V操作的大致顺序

  • 设置信号量。设置需要的信号量,并根据题目条件确定信号量初值。(互斥信号量初值一般为1,同步信号量的初始值要看对应资源的初始值是多少)

两类进程:写进程、读进程

互斥关系:写进程一写进程、写进程一读进程。读进程与读进程不存在互斥问题。

写者进程和任何进程都互斥,设置一个互斥信号量rw,在写者访问共享文件前后分别执行p、v操作。

读者进程和写者进程也要互斥,因此读者访问共享文件前后也要对rw执行p、v操作。

如果所有读者进程在访问共享文件之前都执行P(rw)操作,那么会导致各个读进程之间也无法同时访问文件。Key:读者写者问题的核心思想一一怎么处理该问题呢?

P(rw)和V(rw)其实就是对共享文件的“加锁”和“解锁”。既然各个读进程需要同时访问,而读进程与写进程又必须互斥访问,那么我们可以让第一个访问文件的读进程“加锁”,让最后一个访问完文件的读进程“解锁”可以设置一个整数变量count来记录当前有几个读进程在访问文件。

读者.写者问题为我们解决复杂的互斥问题提供了一个参考思路。
其核心思想在于设置了一个计数器count用来记录当前正在访问共享文件的读进程数。我们可以用count的值来判断当前进入的进程是否是第一个/最后一个读进程,从而做出不同的处理。

另外,对count变量的检查和赋值不能一气呵成导致了一些错误,如果需要实现“一气呵成”,自然应该想到用互斥信号量。

最后,还要认真体会我们是如何解决“写进程饥饿”问题的。

绝大多数的考研pv操作大题都可以用之前介绍的几种生产者一消费者问题的思想来解决,如果遇到更复杂的问题,可以想想能否用读者写者问题的这几个思想来解决。

哲学家进程问题

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

semaphore chopstick[5]={1,1,1,1,1};
semaphore mutex = 1; //互斥地取筷子
Pi (){ //i号哲学家的进程while(1){P(mutex);P(chopstick[i]); //拿左P(chopstick[(i+1)%5]); //拿右V(mutex);吃饭;V(chopstick[i]); //放左V(chopstick[(i+1)%5]); //放右思考;}
}

管程

  • 为什么要引入管程
  • 管程的定义和基本特征
  • 拓展1:用管程解决生产者消费者问题
  • 拓展2:Java中类似于管程的机制

信号量机制存在的问题:编写程序困难、易出错
能不能设计一种机制,让程序员写程序时不需要再关注复杂的pv操作,让写代码更轻松呢?
1973年,BrinchHansen首次在程序设计语言(Pascal)中引入了“管程”成分—一种高级同步机制

管程的定义和基本特征

管程是一种特殊的软件模块,有这些部分组成:

  • 1.局部于管程的共享数据结构说明;
  • 2.对该数据结构进行操作的一组过程,
  • 3,对局部于管程的共享数据设置初始值的语句;
  • 4.管程有一个名字。

跨考Tips:“过程”其实就是“函数”

管程的基本特征:

  • 1.局部于管程的数据只能被局部于管程的过程所访问;
  • 2.一个进程只有通过调用管程内的过程才能进入管程访问共享数据;
  • 3.每次仅允许一个进程在管程内执行某个内部过程。

拓展1:用管程解决生产者消费者问题

monitor ProducerConsumer{condition full,empty;//条件变塑用来实现同步(排队)int count=0;//缓冲区中的产品数void insert(ltem item){ //把产品壅te仞放缓冲区if(count==N)wait(full);count++;insert_item(item);if(count==1)signal(empty);}ltem remove(){//缓冲区中取出一个产品if(count==0)wait(empty);count--;if(count==N-1)signal(full);return remove_item();}
}
end monitor;
producer(){while(1){item=生产一个产品;ProducerConsumer.insert(item);}
}consumer(){while(1){item=ProducerConsumer.remove();消费产品item;}
}

每次仅允许一个进程在管程内执行某个内部过程。

例1:两个生产者进程并发执行,依次调用了insert过程
例2:两个消费者进程先执行,生产者进程后执行

引入管程的目的无非就是要更方便地实现进程互斥和同步。
需要在管程中定义共享数据(如生产者消费者问题的缓冲区)

需要在管程中定义用于访问这些共享数据的“入口”一一其实就是一些函数(如生产者消费者问题中,可以定义一个函数用于将产品放入缓冲区,再定义一个函数用于从缓冲区取出产品)

只有通过这些特定的“入口”才能访问共享数据

管程中有很多“入口”,但是每次只能开放其中一个“入口”,并且只能让一个进程或线程进入(如生产者消费者问题中,各进程需要互斥地访问共享缓冲区。管程的这种特性即可保证一个时间段内最多只会有一个进程在访问缓冲区。注意:这种互性由编器塗员买的,程序员不用关心)

可在管程中设置条件变量及等待/唤醒操作以解决同步问题。可以让一个进程或线程在条件变量上等待(此时,该进程应先释放管程的使用权,也就是让出“入口”);可以通过唤醒操作将等待在条件变量上的进程或线程唤醒。

程序员可以用某种特殊的语法定义一个管程(比如.monitor Producerconsumer。。。endmonitor;)
之后其他程序员就可以使用这个管程提供的特定“入口”很方便地使用实现进程同步/互斥了。

死锁

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

  • 互斥条件:只有对必须互斥使用的资源的争抢才会导致死锁(如哲学家的筷子、打印机设备)。
    像内存、扬声器这样可以同时让多个进程使用的资源是不会导致死锁的(因为进程不用阻塞等待这种资源)。
  • 不剥夺条件:进程所获得的资源在未使用完之前,不能由其他进程强行夺走,只能主动释放。
  • 请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求而该资源又被其他进程占有,此时请求进程被阻塞,但又对自己己有的资源保持不放。
  • 循环等待条件:存在一种进程资源的循环等待链,链中的每一个进程己获得的资源同时被下一个进程所请求。
    • 注意!发生死锁时一定有循环等待,但是发生循环等待时未必死锁(循环等待是死锁的必要不充分条件)
      如果同类资源数大于1,则即使有循环等待,也未发生死锁。但如果系统中每类资源都只有一个,那循环等待就是死锁的充分必要条件了。

什么时候会发生死锁

1.对系统资源的竞争。各进程对不可剥夺的资源(如打印机)的竞争可能引起死锁,对可剥夺的资源(CPIJ)的竞争是不会引起死锁的。

2,进程推进顺序非法。请求和释放资源的顺序不当,也同样会导致死锁。例如,并发执行的进程PI、P2分别申请并占有了资源R1、R2,之后进程P1又紧接着申请资源R2,而进程P2又申请资源R1,两者会因为申请的资源被对方占有而阻塞,从而发生死锁。

3.信号量的使用不当也会造成死锁。如生产者·消费者问题中,如果实现互斥的p操作在实现同步的p操作之前,就有可能导致死锁。(可以把互斥信号量、同步信号量也看做是一种抽象的系统资源)

总之,对不可剥夺资源的不合理分配,可能导致死锁。

死锁的处理策略

  • 1.预防死锁。破坏死锁产生的四个必要条件中的一个或几个。
  • 2.避免死锁。用某种方法防止系统进入不安全状态,从而避免死锁(银行家算法)
  • 3,死锁的检测和解除。允许死锁的发生,不过操作系统会负责检测出死锁的发生,然后采取某种措施解除死锁。

预防死锁

互斥条件:

只有对必须互斥使用的资源的争抢才会导致死锁。
如果把只能互斥使用的资源改造为允许共享使用,则系统不会进入死锁状态。比如:SP00Ling技术。

操作系统可以采用SPOOLing技术把独占设备在逻辑上改造成共享设备。比如,用SPOOLing技术将打印机改造为共享设备,“

破坏不剥夺条件

不剥夺条件:进程所获得的资源在未使用完之前,不能由其他进程强行夺走,只能主动释放。

方案一:当某个进程请求新的资源得不到满足时,它必须立即释放保持的所有资源,待以后需要时再重新申请。也就是说,即使某些资源尚未使用完,也需要主动释放,从而破坏了不可剥夺条件。

方案二:当某个进程需要的资源被其他进程所占有的时候,可以由操作系统协助,将想要的资源强行剥夺。这种方式一般需要考虑各进程的优先级(比如:剥夺调度方式,就是将处理机资源强行剥夺给优先级史高的进程使用)

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

可以采用静态分配方法,即进程在运行前一次申请完它所需要的全部资源,在它的资源未满足前,不让它投入运行。一旦投入运行后,这些资源就一直归它所有,该进程就不会再请求别的任何资源了。
该策略实现起来简单,但也有明显的缺点

有些资源可能只需要用很短的时间,因此如果进程的整个运行期间都一直保持着所有资源,就会造成严重的资源浪费,资源利用率极低。另外,该策略也有可能导致某些进程饥饿。

破坏循环等待条件

循环等待条件.存在一种进程资源的循环等待链,链中的每一个进程已获得的资源同时被下一个进程所请求。

可采用顺序资源分配法。首先给系统中的资源编号,规定每个进程必须按编号递增的顺序请求资源,同类资源(即编号相同的资源)一次申请完。

原理分析:一个进程只有己占有小编号的资源时,才有资格申请更大编号的资源。按此规则,己持有大编号资源的进程不可能逆向地回来申请小编号的资源,从而就不会产生循环等待的现象。

避免死锁

  • 不允许死锁发生

    • 静态策略:预防死锁
    • 动态策略:避免死锁
      • 什么是安全序列
      • 什么是系统的不安全状态,与死锁有何联系
      • 如何避免系统进入不安全状态—银行家算法
  • 允许死锁发生

    • 死锁的检测和解除

所谓安全序列,就是指如果系统按照这种序列分配资源,则每个进程都能顺利完成。只要能找出一个安全序列,系统就是安全状态。当然,安全序列可能有多个。

如果分配了资源之后,系统中找不出任何一个安全序列,系统就进入了不安全状态。这就意味着之后可能所有进程都无法顺利的执行下去。当然,如果有进程提前归还了一些资源,那系统也有可能重新回到安全状态,不过我们在分配资源之前总是要考虑到最坏的情况。

如果系统处于安全状态,就一定不会发生死锁。如果系统进入不安全状态,就可能友生死锁(处于不安全状态未必就是发生了死锁,但发生死锁时一定是在不安全状态)

因此可以**在资源分配之前预先判断这次分配是否会导致系统进入不安全状态,以此决定是否答应资源分配请求。**这也是银行家算法的核心思想。

银行家算法

银行家算法是荷兰学者Dijkstra为银行系统设计的,以确保银行在发放现金贷款时,不会发生不能满足所有客户需要的情况。后来该算法被用在操作系统中,用于避免死锁。

核心思想:在进程提出资源申请时,先预判此次分配是否会导致系统进入不安全状态。如果会进入不安全状态,就暂时不答应这次请求,让该进程先阻塞等待。

此时系统是否处于安全状态?
思路:尝试找出一个安全序列.“{PI}P3 PO,P2,P4}
依次检查剩余可用资源(3,3,2)是否能满足各进程的需求

可满足PI需求,将PI加入安全序列,并更新剩余可用资源值为(5,3,2)
依次检查剩余可用资源(5,3,2)是否能满足剩余进程(不包括己加入安全序列的进程)的需求

可满足P3需求,将P3加入安全序列,并更新剩余可用资源值为(7,4,3)

依次检查剩余可用资源(7,生3)是否能满足剩余进程(不包括己加入安全序列的进程)的需求.

以此类推,共五次循环检查即可将5个进程都加入安全序列中,最终可得一个安全序列。该算法称为安全性算法。可以很方便地用代码实现以上流程,每一轮检查都从编号较小的进程开始检查

实际做题时可以更快速的得到安全序列。

死锁的检测和解除

  • 死锁检测算法:用于检测系统状态,以确定系统中是否发生了死锁。
  • 死锁解除算法:当认定系统中己经发生了死锁,利用该算法可将系统从死锁状态中解脱出来。

资源分配图

两种结点

  • 进程结点:对应一个进程
  • 资源结点:对应一类资源,一类资源可能有个

两种边

  • 进程结点—>资源结点:表示进程想申请几个资源〔每条边代表一个)
  • 资源节点—>进程结点.表示已经为进程分配了几个资源(每条边代表一个)

检测死锁的算法:
1)在资源分配图中,找出既不阻塞又不是孤点的进程Pi(即找出一条有向边与它相连,且该有向边对应资源的申请数量小于等于系统中已有空闲资源数量。如下图中,RI没有空闲资源,R2有一个空闲资源。若所有的连接该进程的边均满足上述条件,则这个进程能继续运行直至完成,然后释放它所占有的所有资源)。消去它所有的请求边和分配变,使之称为孤立的结点。在下图中,
PI是满足这一条件的进程结点,于是将PI的所有边消去。

2)进程Pi所释放的资源,可以唤醒某些因等待这些资源而阻塞的进程,原来的阻塞进程可能变为非阻塞进程。在下图中,P2就满足这样的条件。根据1)中的方法进行一系列简化后,若能消去途中所有的边,则称该图是可完全简化的。

内存

本文标签: 操作系统