操作系统笔记——概论、进程、线程(王道408)

news2024/9/25 0:49:05

文章目录

  • 前言
  • 计算机系统概述
    • OS的基本概念
    • OS的发展历程
    • OS的运行机制
    • OS体系结构
    • OS引导
    • 虚拟机
  • 进程和线程
    • 进程和线程基础
      • 进程
      • 进程状态
      • 进程控制
      • 进程通信
      • 线程
      • 线程实现
    • CPU调度
      • 调度的层次
      • 进程调度细节
      • 调度算法
        • 评价指标
        • 批处理调度算法
        • 交互式调度方法
    • 同步与互斥
      • 基本概念
      • 互斥
        • 互斥软件实现
        • 互斥硬件实现
        • 互斥锁(自旋锁)
      • 信号量
        • 信号量机制
        • 信号量实现互斥同步
      • 经典信号量问题
        • 生产者消费者——基本的分析思路
        • 多生产者多消费者——多种生产者
        • 吸烟者问题——多功能生产者
        • 哲学家进餐问题——连续申请多个资源
        • 读者写者问题——优先级互斥问题
      • 管程
    • 死锁
      • 死锁概述
      • 预防死锁——破坏死锁条件
      • 避免死锁——单步动态预测
      • 检测和解除死锁

前言

学校OS课程的知识和408有一定的重叠,但是还不太够,因此我又一次打开了王道的OS课程。

这个笔记同理,只记最关键的内容和思考,直接针对408,基础性的概念性的知识以视频为主。

计算机系统概述

OS的基本概念

在这里插入图片描述
OS提供的服务:

  1. 用户级别:
    • GUI,就是windows
    • 命令接口
      • 联机:有交互性,即cmd命令行
      • 脱机:批处理,即.bat文件
  2. 程序员级别:
    • 系统调用:OS的api
    • 系统调用可以通过c语言的操作系统库函数调用,但是c语言本质上比系统调用还高一级

在这里插入图片描述
异步的前提是并发,程序之间交叉前进,即走走停停,无法预知。

OS的发展历程

在这里插入图片描述

  1. 手工阶段,缺点:
    • 独占
    • 人太慢,机器速度逐渐加快,人拖累机器
  2. 单通道批处理
    • 优:提升预处理速度,不拖累机器
    • 缺:独占
  3. 多通道批处理
    • 优:解决独占,实现并发,提升效率
    • 缺:无交互能力
  4. 分时系统
    • 优:解决交互能力,用户“看起来”独占
    • 缺:没有优先级
  5. 实时系统
    • 优:解决了优先级响应问题,及时可靠

OS的运行机制

在这里插入图片描述
OS内核相当于OS的管理员,因此特权指令只能是内核执行。

平时用户的特权调用,操作都是向管理员申请,而不是亲力亲为。

两个状态的切换:

  1. 升级:特权指令触发中断(硬件),OS响应中断的时候进入核心态
  2. 降级:OS主动修改PSW让出控制权(软件)
    • 修改PSW的指令,本身就是特权指令

在这里插入图片描述

中断和计组第5章衔接

涉及到进程之间的协调,就一定要OS接入,进而需要系统调用。

在这里插入图片描述

需要注意,陷入指令是用户态指令(请求),接下来才会因为内中断进入核心态(执行)

在这里插入图片描述

OS体系结构

在这里插入图片描述

我们OS学的功能,可以放在内核,也可以放在用户,这就形成了大内核和微内核的区别。

微内核暴露的接口多,易于维护和扩展,但是沟通成本大,要反复调用。

在这里插入图片描述

在这里插入图片描述

  1. 分层结构
    • 类似于计网的层次结构,结构清晰,通病是效率偏低
  2. 模块化
    • 主模块分离,模块之间分离,平等
      • 优点:可以同时开发,且效率不错
      • 缺点:模块间的图关系很难把握
    • 动态可加载模块。
      • 可加载说白了就是插件,有没有都不影响运行,因此可以动态加载,比如驱动
  3. 宏内核和微内核
    • 微内核相当于一个服务器,中转不同模块之间的`消息传递
  4. 外核
    • 外核可以提供一些高级的资源(未经抽象的资源)分配方式
    • 内核分配的资源都是抽象的,虚拟化的,比如虚拟地址,外核可以直接分配物理地址,在一些需要频繁跳跃的场景,外核直接分配一片连续空间效果会很好。当然,外核也负责保证安全。
    • 跳过虚拟步骤,就相当于跳过了映射层,可以提高效率,缺点是复杂。

OS引导

在这里插入图片描述

简单来说,就是开机扫ROM就可以把操作系统拉起来,但是具体还是要分几步走:

  1. 扫ROM,启动BIOS,自检
  2. 读磁盘的MBR,获取分区
  3. 从活动分区(C盘)中读PBR,获取C盘根目录
  4. 通过目录找到操作系统的程序,拉到内存中,完成OS启动

这四步环环相扣,前一个获取了信息,后一步才能根据此信息行动。

而第4步用的程序,位置一般在C:/Windows/Boot/下面。

虚拟机

在这里插入图片描述

  1. 第一类VMM,相当于传统OS的加强版,直接运行在硬件上
    • 虚拟OS看起来像一个OS,也有内核,但是实际上还是用户态,因此一个特权指令实际上要经过一次虚拟的系统调用+一次真正的系统调用。
    • 迁移性差,因为直接和硬件耦合
  2. 第二类VMM,是寄居在宿主OS之上的,分为两部分
    • 用户态的VMM和宿主的应用程序是共存的
    • 核心态的VMM是以驱动的形式存在的,持续运行在内核态

在这里插入图片描述

进程和线程

进程和线程基础

进程

在这里插入图片描述

PCB,记录进程元数据:

  1. 描述信息。用于区分进程,PID,UID
  2. 进程控制和管理信息。和进程运行状态有关
  3. 资源分配清单。
    • 资源是进程外部的,而数据段是程序产生的内部数据
  4. 处理器相关信息。寄存器上下文

进程特征:

  1. 并发性和异步是一起的
  2. 结构性指的是每个进程的结构都一样,都是PCB+数据段+程序段

进程状态

在这里插入图片描述

进程控制

在这里插入图片描述

在这里插入图片描述

创建原语分为4步:

  1. PCB创建和初始化
  2. 分配资源(此时进入就绪态)
  3. 插入就绪队列

撤销是逆过程:

  1. 剥夺所有资源,清理子进程
  2. 删除PCB

在这里插入图片描述

阻塞和唤醒,是可逆的过程

  1. 修改PCB:可逆,所以现场要保护起来
  2. 切换队列:把PCB放到对应队列中

无论是阻塞还是进程切换,都要剥夺CPU,而进程有一些内容还存在寄存器中,这些寄存器上下文就是保护现场要做的工作,是中断隐指令的内容。

在这里插入图片描述

进程通信

在这里插入图片描述

  1. 共享储存
    • 把两个进程的虚拟储存,映射到同一个物理储存区域
    • 因此要互斥访问
  2. 消息传递
    • 每一个进程都有一个消息队列
    • 直接通信:A进程把消息直接挂到B进程消息队列里
    • 间接通信:以信箱为中介,B要主动去信箱取出A发来的消息
  3. 管道通信
    • 联系生产者消费者,管道其实就是一个循环队列,是个内存缓冲区
    • 管道互斥访问,因此是半双工(像水管)

线程

在这里插入图片描述

在这里插入图片描述

  1. TCB:Thread CB
  2. 一个进程内部,线程之间资源共享
    • 因此切换成本很小,其就是为了频繁切换,提高并发性而生的。
  3. 一个程序的多线程可以放到不同进程中
    • 多核CPU的超线程
    • 但是这样就无法共享资源了,各用个的

在这里插入图片描述

线程可以理解为剥离掉公用资源后,剩下的相互独立的部分

因此线程的内容比较少,切换的时候只需要保证TCB里面的一些寄存器上下文就可以,比进程少很多。

线程实现

在这里插入图片描述

在这里插入图片描述

用户级线程,适用于早期OS没有线程管理功能的时候:

  1. 本质上是用户自己用代码(线程库)管理线程的调度
    • 在OS看来,只有一个进程而已
    • 线程的调度是纯用户态行为,这种调度非常简单(线程库的逻辑)
  2. 优缺点
    • 优:不需要系统调用,高效
    • 缺:假线程,代码在一个线程卡住,实际上都卡住了

在这里插入图片描述

内核级线程,这是经典的OS负责的线程:

  1. 优点:真并发
  2. 缺点:核心压力大

因此内核级线程又分出多种模式:

  1. 一对一
    • 这其实和内核级线程一样
  2. 多对一:多个用户级线程对应一个内核线程,而CPU只能看到内核线程
    • 这个模式比较鸡肋,和用户级线程一样
  3. 多对多:多个用户级线程,映射到多个内核线程
    • 这才是真神
    • 可以把用户线程切分成3块,每一块都用多对一模型映射到一个线程,整体上三个部分有序工作,互相之间不会拖累,兼顾了效率和并发性

在这里插入图片描述

CPU调度

调度的层次

在这里插入图片描述

作业调度,作业≈一个程序,储存的位置是外存,作业调度≈程序启动
低级调度,针对进程,切换CPU

中级调度,和作业调度一样都是在内外存之间的,区别如下:

  1. 作业调度是比较彻底,就是启动和终止
  2. 中级调度类似于休眠(挂起),暂时放到外存(手机的扩展内存技术就是这样实现的)
    • 引入7状态模型,对就绪和阻塞分别增加对应的挂起状态
    • 在挂起状态(外存里),也可以实现阻塞到就绪的转变
    • 从运行态可以跳过就绪态,直接跳到挂起

在这里插入图片描述

进程调度细节

在这里插入图片描述
进程调度时机:

  1. 主动
  2. 被动。说白了就是被抢占
    • 中断处理(中断隐指令)和原子操作,都会关中断,因此不会被打断
    • OS内核处理内核临界区的时候,权限高,不可被打断

区分一下:

  1. 狭义进程调度。在CPU空闲的时候,抓一个就绪态进程激活
  2. 进程切换。剥夺一个运行的进程,换成另外一个进程
    • 两个操作都要恢复新现场
    • 相比于调度,切换额外要做的操作是保护旧现场

广义的进程调度,可能包含了进程切换这一过程

在这里插入图片描述

最后提一嘴调度程序,我们说,调度是由OS控制的,本质上就是软件,那么说白了,负责调度的管理者,还是一个程序。

这个程序什么时候会运行呢?

  1. 如果是非抢占的时候,就是异步的运行,在特殊情况才运行(创销,阻塞唤醒)
  2. 抢占式的,那么就要定期巡查,决定一个CPU是否应该抢占

调度算法

评价指标

在这里插入图片描述

  1. 周转时间=等待+处理时间
  2. 带权周转时间
    • 本质上是个比例值,可以衡量等待时间在周转时间中的占比
    • 越大,则等待越久
  3. 等待时间。
    • 执行之前的时间,执行起来后等IO的时间不算
    • 作业的等待时间是在成为进程之前的那段时间
    • 进程的等待时间就是创建态+就绪态的那段时间
  4. 响应时间
    • 和等待时间类似,但是针对的只是一种请求,比如键盘,鼠标
批处理调度算法

在这里插入图片描述

在这里插入图片描述

对于非纯计算程序,IO时间不算等待,因此还要抛去IO操作的部分。

FCFS(其实就是FIFO),之所以对短作业不利,就是因为短作业的带权周转时间会很大,这代表其体验很差。

加粗样式

短=Short,即SJF,SPF

具体计算,要分为三部分:

  1. 还没来的作业
  2. 作业池中的作业
  3. 正在运行的作业

正在运行的作业只能在作业池中挑选,而不能是还没有进入作业池的,因此第一个任务只能是P1,即使他时间很长。

SJF分两种:

  1. 非抢占式的SJF如上
    • 比较简单,顺序操作
    • 只需要在作业完成时,分析作业池即可
  2. 抢占式的SJF(SRTN
    • 如果有一个新的作业来了,那么就有可能比当前正在运行的作业的剩余时间短,此时就把作业替换回作业池中(记得标注剩余时间)
    • 具体做题的时候,比非抢占式要额外多分析新作业来的时间点

在这里插入图片描述

  1. 题目区分细节
    • 默认非抢占,但是比较模糊
    • 考虑到抢占,SRTN的平均周转时间肯定是最少的
    • 如果默认作业没有到达顺序的先后之分,那么非抢占SJF=SRTN
  2. SJF对长作业不利,无论是否抢占,都可能造成饥饿现象

在这里插入图片描述

HRRN用到响应比指标,综合了等待时间和处理时间,在保证了优先级的前提下,修复了SJF饥饿的问题。

分析思路和SJF类似,都是分成三部分,HRRN为非抢占式的,所以只在CPU空闲的时候对作业池进行分析。

注意,其等待时间是从到达开始计数的。

交互式调度方法

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

RR(时间片轮转)

  1. 轮转本身很简单
    • 建立一个就绪队列,每一个时间片出队,处理队头对应的进程
    • 时间片完了以后就插到队尾
  2. 难在新任务来的时候,会改变队列结构
    • 新任务来了,也是插在队尾
    • 如果A任务的时间片完了,此时B任务刚来,这二者的顺序以B为先,毕竟新来的要照顾一下(注意,这个照顾顺序只针对想要同时入队的两个任务,前面早就在队里的任务仍然在前面)
  3. 如果任务处理完,时间片还没用完,会直接终止当前时间片
    • 很正常很人性化的设计,干等着没意思

因此,整个分析过程需要考虑的也是处理完以及新任务刚来的两种时刻。

严格来说,RR不会剥夺正在执行的时间片,但是先来先插到队尾的这种逻辑,以及规定时间片用完就剥夺,这两个操作具有抢占性,所以我们规定RR是抢占式的(剥夺的来源是时间片本身,而不是其他进程)

时间片太大,就会退化为FCFS,太小则切换开销占时间片的比例就太大了(类似于流水线那个感觉),效率降低。

在这里插入图片描述

优先级调度算法。优先数越高,就越排在前面激活。

此方法分为抢占式和非抢占式。
分析节点参考SJF。

在这里插入图片描述

多级反馈队列调度,这个是666,真神

我们直接分析一下其性质,相当复杂,但是又相当合理:

  1. 队列内部是RR,队列之间是抢占
  2. 关于优先级
    • 新手保护:刚来的,优先级最高
    • 耗时降级:如果耗时长,每完整执行一个时间片,优先级就会降低一级,直到降无可降
    • 被剥夺不降级:被剥夺严格来说不算执行完一个时间片,因此不降级,只是按RR原则放到了队尾。不过还会有补偿,新获得的时间片又是一个完整的时间片
  3. 关于轮转
    • 优先高级:高一级的队列为空,才对下一级进行RR遍历(有抢占性,如果高一级来了新的,则立马切到高一级)
    • 保障低级:越低级,时间片越长。虽然低优先级低人一等,但是总得让人家执行完,所以低优先级的时间片反而会更多,一旦轮到了,就可以持续执行很长时间(当然,如果你不争气,执行不完那就继续降级,又或者被强占,总之还是低人一等)

优缺点分析:

  1. 公平:FCFS
  2. 快响应:RR+优先级调度
  3. 短进程优先:SPF
  4. 自动优先级
    • 如果想要保证任务的高优先级,可以尽量让其不降级,比如如果进程因为IO主动放弃时间片,此时我们不认为其执行完毕,因此不降级,这样就保证了IO的高优先级
  5. 缺点:仍然不是绝对公平,因此会造成饥饿

在具体实践中,为了防止饥饿出现,可能一个队列会分配固定的时间上限,如果超过这个界限,还是要切换队列的,不能一直卡在高优先级队列。
此外,不同队列内部的策略还可以不一样。

在这里插入图片描述

同步与互斥

基本概念

在这里插入图片描述

异步就是无序性
同步就是有序性,A一定在B前。

遵循的原则,总结起来就是,忙可以先等一下,等久了就撤(让权),但是你不能让我等太久,过一段时间我还得拿回权利并且进入临界区。

互斥

互斥软件实现

在这里插入图片描述
在这里插入图片描述

单标志法是最原始的轮询

本质上,turn代表谦让以及指定,刚开始就指定一个进程,而一个进程执行完以后又会指定另外一个进程。
但是其问题在于,临界区使用权和CPU占用权是分裂的,让给你用,并不代表你就能用,即空闲也进不去,违背“空闲让进”

比如此时标记0进程能用临界区,但是此时CPU时间片属于1,此时while循环就会持续1个时间片,一边是忙等,另一边是空闲没人用

在这里插入图片描述

双标志法,把谦让逻辑变成了,将指定变成了排他逻辑,占用逻辑。

占用和释放,只修改自己的占用标记,不去指定他人。

双标志先检查,检查和上锁不连贯,此时,按照1526顺序,A还没上锁,B就通过检查了,就会同时进入临界区,即“忙”也能进去,违反了忙则等待。

为了修正,出现了双标志后检查法,这种方法先把临界区标记了,再检查,那么就可以确保别人拿不到临界区(缺德做法),但是这很明显不靠谱,会出现都用不上的情况,按照1526顺序,AB都标记,则会排他,那么AB就都会卡在检查阶段。

此时,即使临界区空闲,AB谁也不让着谁,谁也用不上,这违背了“空闲让进”,而且一直卡死,违背了“有限等待”

在这里插入图片描述

在这里插入图片描述

peterson算法,综合了单标志和双标志后检查法

标志代表意愿,turn代表谦让,最后被谦让的那一个,就可以获得使用权。

以1627举例,2先谦让一下,但是7后面有谦让一下,于是2就勉为其难的进入了,和现实的套路一模一样。
本质上,turn同一时间有且只能有一个值,因此同一时间只能有一个进程跳出循环,且必有一个进程跳出循环。

非常牛逼的思路,但是仍然优缺点,因为进程仍然是忙等状态,即违反了“让权等待”,但是已经是成本最低的了,这个操作可以通过时间片轮转来剥夺。

互斥硬件实现

在这里插入图片描述
关中断的缺点:

  1. 多处理机,关不了其他CPU,进程可以借助其他CPU访问临界
  2. 仅内核。因为这个操作给用户太危险

在这里插入图片描述

TestSet的操作,说白了就是用old检查,并对lock进行上锁,这是一个原子过程,检查和上锁是一气呵成的。
如果lock原来就是true,那么再上也无所谓
如果lock原来是false,那么就可以同时实现上锁+退出循环,不用担心被打断

上面这个代码只是模拟,实际上是硬件TSL指令实现的,是原子的,而软件编程是无法达到这个效果的。
缺点和peterson一样,都是忙等,不满足“让权等待”原则

还有一个Swap指令(Exchange,XCHG指令),和TSL基本一样的逻辑和特性

互斥锁(自旋锁)

互斥锁是一种思想,和mutex操作很像,就是申请和释放。

但是其申请过程是忙等的,所以TSL,swap指令都是自旋锁,单标指法其实也是自旋锁,申请过程都具有原子性

当然,正如单标志法哪里说的,忙等其实不完全忙等,时间片没了就退出(单处理器没有RR,所以就是彻底忙等)。甚至说有时候反而有意外效果,等待的时候不用切换上下文,有时候成本反而低。

在这里插入图片描述

信号量

信号量机制

在这里插入图片描述

整形信号量,说白了就是双标志先检查,但是P和V是原语,可以保证不会同时进入

但是因为底层还是一个循环,仍然会忙等,不满足让权等待。

在这里插入图片描述

记录型信号量引入阻塞队列,解决了忙等现象

  1. 原来是忙等,现在发现资源不够就丢到阻塞队列里。
    • 极限情况为0,-1后为负,此时是第一个进程阻塞
  2. 如果资源够了,且阻塞队列里有进程,就唤醒
    • 极限情况为-1,+1后为0,此时把阻塞队列里最后一个进程唤醒
信号量实现互斥同步

在这里插入图片描述

semaphore mutex = 1 :代表记录型信号量,有等待队列

互斥比较简单,mutex=1,PV夹住临界区。

在这里插入图片描述

同步是前V后P,用一个信号量关联两个进程,等V操作执行完后,P才能执行下去。

给定一个拓扑图,只需要把每一个前驱后继关系都用一个信号量定义一下即可。
之后每一个节点都是一个进程,把边定义的前驱后继关系写到进程里面即可:

  1. 入边写P
  2. 出边写V

在这里插入图片描述

经典信号量问题

生产者消费者——基本的分析思路

在这里插入图片描述

需要注意一个细节就是,P操作是不可互换的,因为mutex只夹临界区,夹得多了就会出问题(死锁)
V操作可以互换,因为V操作一定不会被卡住

在这里插入图片描述

多生产者多消费者——多种生产者

在这里插入图片描述

首先是最简单的两组关系:

  1. apple和orange各自有一对同步关系
  2. plate关系:关键在于盘子,盘子是双方的一个中介,并不能单看父或者母,要把父母统一为一方,把子女统一为另一方
    在这里插入图片描述

具体实现如下,三个同步信号量,这里将plate设置为“还可以放的空间”,因此初值为1

因此,整体就实现了一个PV结构,每一个进程,都是有P有V。
mutex实际上可有可无,因为我们这里的资源上限为1,已经相当于mutex的作用了,但是如果盘子空间变成2,就得加mutex了,否则就可能发生覆盖现象。

在这里插入图片描述

如果反过来呢,plate=盘中可用水果数量,会出问题,比如dad,此时就是V(apple),V(plate),可以看到这个进程是没有P的,也就是说不会被阻塞,他可以一直释放。

所以我们这里还可以总结出一条生产者消费者问题中,隐性的要求,就是PV一定是要成环的,相互制约,一个进程只有V无P,必然是有问题的,在我们制定信号量意义的时候,也应该考虑构造一个PV环结构。

吸烟者问题——多功能生产者

在这里插入图片描述

一个多功能生产者,给多个单功能消费者提供原料,同步关系如下

在这里插入图片描述

再拓展一下思路,如果finish定义为1呢?

那么就要把生产者的P操作放在最开始,消费者是不变的,仍然可以正常运行(因为仍然是PV环,而且PV关系没有变)

分析一下是否需要mutex,因为只有一个生产者,所以缓冲区最多有1个元素,不会出问题。
但是呢,如果有n生产者,此时因为生产者是V在前的,第一次生产不受阻塞,所以就可能会让缓冲区里存在n个元素,所以这种写法其实不好,如果是按照我那个P在前的写法,即使是有多个生产者进程,只要规定finish=1,就只能有一个元素被生产

哲学家进餐问题——连续申请多个资源

这个问题本身不难,难在如何解决死锁。

哲学家进餐问题的死锁情况为,每个哲学家都只P了一半,都卡在了第二个P上。
本质上,哲学家进餐是连续申请多个资源,如果申请的途中被卡了,而且是集体卡顿,那就死锁了。

所以解决哲学家死锁,就要从这些领域入手:

  1. 最多让n-1个哲学家同时进餐,这样就一定可以保证有1个哲学家不会被卡死
    • 可以用一个值为n-1的信号量限制
  2. 限定哲学家拿资源的顺序,强制哲学家进行两两竞争
    • 比如指定奇数哲学家先左边的,偶数先拿右边的,那么这一对哲学家必然是两个必有一个阻塞,而另一个没被阻塞的哲学家,在另一边不存在竞争,一定可以吃到
  3. 保证哲学家多个P操作的原子性
    • 在一连串P外面加个mutex即可
    • 即使一个哲学家被卡,其他哲学家也不可能是P操作被卡,即其他哲学家在吃饭,这是暂时的,可以恢复的

在这里插入图片描述

读者写者问题——优先级互斥问题

在这里插入图片描述

代码分为4个版本:

  1. 单进程读/写(全部互斥)
  2. 读者优先
  3. 读写公平
  4. 写者优先

这四个版本,2实现了多读者同读,234逐步提高写者的优先级,具体的思想如下:

  1. 插队逻辑:通过if语句,可以制造插队,提高优先级
    • 副作用是多进程可以同读同写
    • 读者优先利用了这个副作用(其本意只是提高优先级,顺带实现同读)
  2. 抵消插队逻辑:在保留插队逻辑副作用(同读)的基础上,抵消插队带来的优先级效果
    • 在插队逻辑外面加一个信号量即可(设为w)
    • 注意,对w而言,高优先级的进程,w是覆盖所有代码的,低优先级的进程,w仅覆盖进入区以及插队逻辑,由此,w提高优先级和插队逻辑的优先级效果就互相抵消了
    • 这种思路必须要基于插队逻辑才行,因为w并不是针对临界区的管控,至少还得有一个信号量(rw)管控临界区
  3. 控制同时读/写:在插队逻辑的前提下,选择性的控制是否可以同读同写
    • 读者优先中,直接对临界区信号量(rw)加插队逻辑,利用了插队的副作用,无伤实现同读
    • 写者优先不可以这么做,所以需要把插队逻辑外提到非临界区信号量(w),即使可以插队,新来的也得一起卡在临界区外面

在这里插入图片描述

这是最基础的读者优先结构,这个结构务必理解透彻了。

  1. rw:直接对临界区上锁,副作用是会造成读读互斥
  2. 插队逻辑(消除读读互斥并提升r的优先级)
    • if判断:现在加了判断,使得读者里面,只有第一个和最后一个读者需要维护锁,其他情况下,只要有读者在读,新来的就可以直接读
    • mutex:令count判断部分原子化,保护count变量

在这里插入图片描述

读写公平如上,是在读者优先的前提下修改的,仍然保证了读者不互斥的特性
但是使用信号量w额外增加了读者对写者的反制能力,说白了就是用w抵消了读者的插队能力。

这个w加的位置非常巧妙:

  1. 对于写者来说,w是覆盖了临界区的,也就是说,可以造成写者互斥的效果
  2. 对于读者来说,w只是卡在了最开始的进入区,这样就不会造成读者互斥
  3. w和rw使得读者和写者可以相互钳制
    • 读者在进入区,则写者卡在w
    • 读者在临界区(获取rw,释放w),则写者可以进一步卡在rw(已经获取了w)
    • 写者在进入区(获取w,卡在rw),其余读者/写者已经进不了进入区了(卡在w)
    • 写者在临界区,同写者在进入区

如果要实现写者优先呢?
还是插队逻辑,在读写公平的前提下,给写者增加一个插队逻辑

为了防止出现同写情况,需要将插队逻辑外提到w上。

  1. 不能像读者那样插。读者是可以共同读的,写者不行,所以不能给rw加插队逻辑
  2. 考虑给w加插队逻辑,这样,写者可以源源不断的到达“进入区”
    • 实际上,这个操作就是修改了读写公平里的这句描述:其余读者/写者已经进不了进入区了(卡在w),给写者开了个后门
  3. 为什么不能像读优先那样,直接照搬写一个写优先?
    • 因为读优先是可以同读的,但是写无法同写,所以要外提插队逻辑,所以一定是不能照搬的
    • 写优先还可以爆改一下,反正都写优先了,把读的插队逻辑去掉也是可以的,当然这样就不能同读了
semaphore rw=1; //读写公用临界区信号量
int rcount=0; //读者插队逻辑
semaphore rmutex=1;

semaphore w=1; //用于提升write的优先级
int wcount=0; //写者插队逻辑
semaphore wmutex=1;

reader(){
	while(1){
	P(w);//抵消插队逻辑优先级
	
	P(rmutex);//保护rcount
	if(rcount==0)//插队逻辑
		P(rw);
	rcount++;
	V(rmutex);
	
	V(w);//注意,抵消插队逻辑优先级的时候,被抵消方的V(w)插在临界区前
	
	写文件//临界区
	
	P(rmutex);
	rcount--;
	if(rcount==0)
		V(rw);
	V(rmutex);
	}
}

writer(){
	while(1){
	
	P(wmutex);
	if(wcount==0)//w对写者加入插队逻辑
		P(w);
	wcount++;
	V(wmutex);
	
	P(rw);//公用临界区信号量
	读文件//临界区
	V(rw);
	
	P(wmutex);
	wcount--;
	if(wcount==0)
		V(w);
	V(wmutex);
	}
}

代码分为4个版本:

  1. 单进程读/写(全部互斥)
  2. 读者优先
  3. 读写公平
  4. 写者优先

这四个版本,2实现了多读者同读,234逐步提高写者的优先级,具体的思想如下:

  1. 插队逻辑:通过if语句,可以制造插队,提高优先级
    • 副作用是多进程可以同读同写
    • 读者优先利用了这个副作用(其本意只是提高优先级,顺带实现同读)
  2. 抵消插队逻辑:在保留插队逻辑副作用(同读)的基础上,抵消插队带来的优先级效果
    • 在插队逻辑外面加一个信号量即可(设为w)
    • 注意,对w而言,高优先级的进程,w是覆盖所有代码的,低优先级的进程,w仅覆盖进入区以及插队逻辑,由此,w提高优先级和插队逻辑的优先级效果就互相抵消了
    • 这种思路必须要基于插队逻辑才行,因为w并不是针对临界区的管控,至少还得有一个信号量(rw)管控临界区
  3. 控制同时读/写:在插队逻辑的前提下,通过控制插队的信号量,选择性的控制是否可以同读同写
    • 如果把插队逻辑加在临界区信号量上,就会造成同读/写,外提则不会
    • 读者优先中,直接对临界区信号量(rw)加插队逻辑,利用了插队的副作用,无伤实现同读
    • 写者优先不可以这么做,所以需要把插队逻辑外提到非临界区信号量(w),即使可以插队,新来的也得一起卡在临界区外面

管程

PV操作和生产消费过程混在一起,耦合度高,容易出错

因此直接把控制互斥同步的部分剥离出来,封装成类(管程):

  1. 在管程内部定义变量和初始化
  2. 在管程内部定义方法,实现同步机制
  3. 至于mutex互斥,管程通过方法的互斥来实现,同一时间只能有一个进程调用管程
    • 编译器会自动实现,也就是说你只需要定义同步就可以

在这里插入图片描述

死锁

死锁概述

在这里插入图片描述

死锁的条件:

  1. 前提是争抢(互斥)
  2. 其次是占着不放(不剥夺)
  3. 不仅不放,还要持续地请求别的资源(请求和保持条件)
    • 换句话说就是,在只获取了部分资源的前提下,还要获取更多资源
  4. 满足这三个大前提,一旦成环(循环等待条件),则死锁

预防死锁——破坏死锁条件

在这里插入图片描述

  1. 破坏互斥。这个思路看着就不靠谱
  2. 破坏不剥夺。进程要反复切换,开销大,而且持续剥夺会导致饥饿
    • 方案一:主动退位
    • 方案二:OS协助剥夺
  3. 破坏保持和请求。
    • 保持和请求本质上是因为只获取了一部分资源,不得不继续请求剩余资源,那么我们干脆一次性给到位再让他启动,否则就干脆不给,没有中间态。
    • 很显然利用率低,饥饿
  4. 破坏循环等待。
    • 给资源编号,规定一个进程申请资源的编号是递增的
    • 众多进程中,必然有一个进程掌握已有的最大编号的资源(比如下图的7号资源)
    • 这个进程需要的资源编号肯定比在场所有进程资源的编号都大(至少是8),也就是说这个大编号进程要的资源,只可能是空闲的,即使其他进程卡死,这个进程一定也可以执行下去
    • 很显然,不方便,浪费
      在这里插入图片描述

避免死锁——单步动态预测

看下来可以发现,死锁预防的方法缺陷都很大。

银行家算法是一个动态预测的方法,其实和前面那个破坏保持和请求条件的思路类似,保证资源够用,具体做法如下:

  1. 每次分配之前,我都要确保分配之后仍然是安全状态。
    • 所谓的安全状态,就是分配了以后,我仍然有足够的资源让一个进程彻底执行完毕
  2. 这是做最坏的打算,只要我每一次分配都是在安全状态上,那就确保不会发生死锁。这是一种非常保守的思路。

当然,有的情况是最保守的策略都无法解决的,那就死锁是必然的,要从其他地方找问题(如下图)

在这里插入图片描述

检测和解除死锁

在这里插入图片描述

上面介绍的预防和避免,都比较简单,死锁检测通过软件的思路,设计算法去对图结构进行分析,可以得到好的全局分析结果。

在这里插入图片描述

给定一个资源分配图,图中有4类元素,注意点如下:

  1. 资源里面的点数代表资源最大出度
    • 点数-资源已有出度=可用资源数量
  2. 进程的出度代表申请的资源数

具体做的时候,就是找出能够先执行完的进程,执行完将资源释放,然后滚雪球化简资源分配图,直到所有进程执行完毕。

能够先执行完的进程怎么找呢?就是去计算一下,可用资源数量是否满足进程申请的资源数
以上图举例,P1申请1个资源,R2剩2-1=1个资源,所以P1可以执行下去,之后逐步化简就好

如果化简不完,最后有剩余的一个环,那么就代表这些进程构成了死锁循环,针对性的剥夺就好了。
这就是软件全局分析的好处,精准。

解除死锁的方法辨析:

  1. 资源剥夺法。
    • 单纯剥夺资源,进程只是挂起(歇一会),这样可能导致饥饿
  2. 撤销进程法
    • 直接remake死锁进程,啥都没了,可能造成浪费(白干了)
  3. 进程回退法
    • 类似于git的回滚,回到一个可以完全消除所有边的状态
    • 这个理想很好,实际上很难实现,因为要记录回退点

至于对哪个进程动手,归根结底就是对优先级低的动手,让出机会给高优先级任务。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1310594.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023.12.6 关于 Spring Boot 事务的基本概念

目录 事务基本概念 前置准备 Spring Boot 事务使用 编程式事务 声明式事务 Transactional 注解参数说明 Transational 对异常的处理 解决方案一 解决方案二 Transactional 的工作原理 面试题 Spring Boot 事务失效的场景有那些? 事务基本概念 事务指一…

10.CSS浮动

CSS浮动 1.介绍 在最初,浮动是用来实现文字环绕图片效果的,现在浮动是主流的页面布局方式之一 2.作用 让元素脱离标准流,同一级的浮动的元素可以并排在一排显示 3.元素浮动后的特点 脱离文档流不管浮动前是什么元素,浮动后&…

【为什么POI的SXSSFWorkbook占用内存更小?】

🔓为什么POI的SXSSFWorkbook占用内存更小? 🏆POI的SXSSFWorkbook🏆POI的SXSSFWorkbook占用内存🏆扩展配置行缓存限制 🏆POI的SXSSFWorkbook SXSSFWorkbook类是Apache POI库的一部分,它是一个流…

产品入门第二讲:Axure产品元件库的使用

📚📚 🏅我是默,一个在CSDN分享笔记的博主。📚📚 ​​​​ 🌟在这里,我要推荐给大家我的专栏《Axure》。🎯🎯 🚀无论你是编程小白,还是…

python下使用Open3D

1.切记不要安装最新的python否则无法使用open3D ,官网显示只支持python3.8-3.11 这是我安装的python版本 2.由于访问github很慢,所以我手动下载ply文件 https://github.com/isl-org/open3d_downloads/releases/download/20220201-data/fragment.ply 3…

手写进度条,鼠标移入显示悬浮框

效果 <template><div class"box"><div class"mid-box"><div class"mid-contant"><!-- 提示框 --><divv-if"hover"class"tooltip":style"{top: hovertop,}"><div>{{ ho…

c语言堆排序(详解)

堆排序 堆排序是一种基于二叉堆数据结构的排序算法&#xff0c;它的基本概念包括&#xff1a; 建立堆&#xff1a;将待排序的列表构建成一个二叉堆&#xff0c;即满足堆的性质的完全二叉树&#xff0c;可以是最大堆或最小堆。最大堆要求父节点的值大于等于其子节点的值&#x…

Linux(21):软件安装 RPM,SRPM 与 YUM

软件管理员简介 以原始码的方式来安装软件&#xff0c;是利用厂商释出的Tarball来进行软件的安装。 不过&#xff0c;你每次安装软件都需要侦测操作系统与环境、设定编译参数、实际的编译、最后还要依据个人喜好的方式来安装软件到定位。这过程是真的很麻烦的。 如果厂商先在他…

FastAPI之表单数据

FastAPI 表单数据处理教程 FastAPI 是一个现代、快速&#xff08;高性能&#xff09;的 Web 框架&#xff0c;用于构建 API&#xff0c;它用 Python 3.6类型提示的特性旨在方便和快速地设计和构建 APIs&#xff0c;并且减少代码的冗余与错误。下面将介绍如何在 FastAPI 中处理…

工业级路由器在风力发电场的远程监控技术

工业级路由器在风力发电场的远程监控技术方面具有重要的应用意义。风力发电场通常由分布在广阔地区的风力发电机组组成&#xff0c;需要进行实时监测、数据采集和远程管理。工业级路由器作为网络通信设备&#xff0c;能够提供稳定可靠的网络连接和多种远程管理功能&#xff0c;…

鸿蒙OS应用开发之登录界面

在前面学习了输入文本组件和按钮组件,可以使用这两种组件来实现一些常用的功能,比如登录界面,这种界面是每个程序员经常会到遇到的,比如让用户输入用户名称和密码。 在这里我们就来实现如下面的界面: 在上面界面里,第一个文本框用来输入用户名称,第二个用来输入用户密码…

python列表的循环遍历

数据容器&#xff1a;一个可以存储多个元素的Python数据类型 有哪些数据容器&#xff1a;list&#xff08;列表&#xff09;&#xff0c;tuple&#xff08;元组&#xff09;&#xff0c;str&#xff08;字符串&#xff09;&#xff0c;set&#xff08;集合&#xff09;&#x…

【STM32】ADC模数转换器

1 ADC简介 ADC&#xff08;Analog-Digital Converter&#xff09;模拟-数字转换器 ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量&#xff0c;建立模拟电路到数字电路的桥梁 STM32是数字电路&#xff0c;只有高低电平&#xff0c;没有几V电压的概念&#xff…

21. python __init__.py 文件的行为

重复打印行为分析 说明结论主模块主模块所在位置不会被python认为是包 说明 我在调试代码的时候&#xff0c;发现上面的print打印了两次&#xff0c;如果将图片中的 from aaa.F import Cat 改成 from F import Cat 则print只会打印一次。这是为什么呢&#xff1f; 结论 from …

华为海思、燧原、海光等齐力打破封锁,谁主AI芯片江山| 百能云芯

近期&#xff0c;美国对英伟达出口进行了限制&#xff0c;导致英伟达无法向中国大陆销售AI芯片&#xff0c;这一局势催生了中国本土IC设计企业的崛起&#xff0c;包括华为旗下的海思科技、腾讯旗下的燧原科技&#xff0c;以及海光信息和新创公司天数智芯等纷纷抢占市场。 据百能…

微信公众服务号升级订阅号

服务号和订阅号有什么区别&#xff1f;服务号转为订阅号有哪些作用&#xff1f;首先我们要知道服务号和订阅号有什么区别。服务号侧重于对用户进行服务&#xff0c;每月可推送4次&#xff0c;每次最多8篇文章&#xff0c;发送的消息直接显示在好友列表中。订阅号更侧重于信息传…

深度探索Linux操作系统 —— Linux图形原理探讨

系列文章目录 深度探索Linux操作系统 —— 编译过程分析 深度探索Linux操作系统 —— 构建工具链 深度探索Linux操作系统 —— 构建内核 深度探索Linux操作系统 —— 构建initramfs 深度探索Linux操作系统 —— 从内核空间到用户空间 深度探索Linux操作系统 —— 构建根文件系统…

基于SpringBoot+Thymeleaf+Mybatis学生信息管理系统(源码+数据库)

一、项目简介 本项目是一套基于SpringBootThymeleafMybatis学生信息管理系统&#xff0c;主要针对计算机相关专业的正在做bishe的学生和需要项目实战练习的Java学习者。 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目可以直接作为bishe使用。 项目都经过严格调试…

字符雨canvas

整体思路&#xff1a; 确定好字符雨的具体字符是什么&#xff0c;需要多少行多少列这里是写死的其实也可以用循环加随机的方式生成不一样的字符雨&#xff0c;行列也可以读一下宽度然后做一下出发算一下也行首先得有一张画布搞起&#xff0c;然后循环列数去绘画字符定时器循环…

SQL语句的执行顺序怎么理解?

SQL语句的执行顺序怎么理解&#xff1f; 我们常常会被SQL其书写顺序和执行顺序之间的差异所迷惑。理解这两者的区别&#xff0c;对于编写高效、可靠的SQL代码至关重要。今天&#xff0c;让我们用一些生动的例子和场景来深入探讨SQL的执行顺序。 一、书写顺序 VS 执行顺序 SQ…