FCFS、SJF、HRRN调度算法
先来先服务算法(FCFS)
- 算法思想:公平
- 算法规则:先来先服务
- 用于作业和进程调度:用于作业考虑谁先到达后备队列,如果是进程调度的时候考虑那个进程先到就绪队列
- 是否抢占:非抢占
- 优缺点:优点就是公平,算法简单。缺点后面的短作业等待时间很长,带权周转时间很长。对后面的体验很差。对长作业有好处,但是短作业就不友好
- 是否导饥饿:不会
对于下面例题的关键
- 计算周转时间,完成时间(也就是程序执行结束的时间)-到达时间
- 带权周转时间:周转时间/运行时间,计算进程时间比起运行时间大多少倍。如果大很多倍说明进程等待的时间很长。
- 等待时间:周转时间-运行时间
短作业优先算法(SJF)
(spf)shortest process first
(sjf) shortest job first
- 算法思想:追求最少平均等待时间,最少平均周转时间,最少平均带权周转时间
- 算法规则:最短作业或者进程优先得到服务
- 用于作业或者进程:两个都可以使用
- 是否可以抢占:非抢占,但是也有抢占版本最短剩余时间优先算法(SRTN,shortest remaining time next)
- 优缺点:优点平均等待时间和平均周转时间最少 ,缺点不公平,对短作业有好处,但是长作业没有好处。可能会有饥饿
- 是否导致饥饿:会
对于下面的例题
- 由于短作业优先的规则是到达的进程或者作业中选择执行时间最短的那个先执行。那么相当于就是减少了周转时间,因为如果长作业优先,那么短作业的等待时间其实就变长了,需要等待前面的长作业。但是对于长作业放到后面的话,短作业对他的周转时间影响不是很大(除非短作业非常多)。
- 所以无论是等待时间,还是平均带权周转时间都会减少,因为周转时间减少了。运行时间并没有减少。
对于下面的例子来说
- 每次到达新的进程的时候都可以计算一下当前的进程的剩余时间,和现在所有进程的剩余时间作对比,然后选出那个最少剩余时间的进程继续运行。
- 每次抢占都是发生在新的进程到达的时候
- 抢占式能够得到更小的周转时间
- 当所有进程可以运行的时候sjf才是平均等待时间最少的算法。
细节总结
- 短作业/进程优先算法默认是非抢占式的
- sjf调度算法平均等待时间和平均周转时间最少,这是不严谨的,最短剩余时间优先算法得到的平均时间和平均周转时间更小
总结两种算法
- FCFS选择了等待时间最长的那个,没有考虑运行时间
- SJF选择执行时间最短的那个 ,没有考虑等待时间。
- 如何兼顾?
高响应比优先(HRRN,highest response ratio next)
- 算法思想:考虑等待时间和要求服务的时间
- 算法规则:每次都要计算各个作业的响应比,选择响应比最大的那个。
- 用于作业也可以用于进程
- 是否抢占:非抢占
- 优缺点:优点总和考虑了等待和执行时间,对于长作业来说等待时间长,响应比越长那么就不会造成饥饿问题
- 是否饥饿:不会
下面这个例题的关键
- 计算响应比,(等待时间+执行时间)/执行时间选择最大的那个。
- 而且每次都需要第一个程序主动放弃cpu的时候才会去计算调度。
- 相当考虑了等待时间和执行时间的综合
总结
调度算法时间片轮转、优先级、多级反馈队列
时间片轮转
- 算法思想:公平,轮转为每个进程服务,每进程在每个时间都能得到响应
- 算法规则:每个进程有一个执行时间片(100ms),执行完就要切换
- 通常只用于进程调度,这里的时间片是处理机的
- 是否抢占:抢占,通过时钟中断来阻断进程运行,切换下一个
- 优缺点
- 优点:公平,响应快,适合分时操作系统。
- 缺点:高频率进程切换,有一定的开销。不区分任务紧急程度
- 不会饥饿
下面的例题
- 实际上就是2时间作为一个时间片,而且按照队列进入的顺序来进行的时间片轮转。轮流执行,每个进程执行2个时间。
- 如果时间片是5,那么时间片就太大了,导致时间轮转就退化成为先来先到队列算法。
- 但是切换不能太频繁,因为进程切换是有相应的成本的。可能导致切换占用时间大,但是进程运行的时间减少。目标是让切换时间片的大小不超过百分之1。
优先级调度算法
- 算法思想:实时操作系统,更多场景需要根据任务的紧急程度决定处理任务的顺序。
- 算法规则:调度选择优先级更高的任务
- 可以用于作业和进程
- 是否抢占:两种都具备。如果是非抢占,那么只需要等待进程放弃处理机,如果是抢占,那么就绪队列发生变化的时候需要检查是否发生抢占
- 优缺点
- 优点:区分紧急度,重要程度,适合实时操作系统
- 缺点:如果经常有高优先级的进程到来可能导致饥饿
- 会导致饥饿
下面的例题
- 每次就绪队列更新的时候都需要查看优先级,如果运行进程的优先级比当前队列某进程的优先级更低,那么优先级更高的先去执行。
- 就绪队列未必只有一个,可以把优先级最高的放到队头。
- 静态优先级和动态,其实就是进程的优先级是否会发生改变。
如何合理分配优先级?
- 系统进程优先级高于用户进程
- 前台进程优先级高于后台进程。
- 操作系统更偏好IO型进程。能够让IO尽早进入工作。系统吞吐量会上升。
动态优先级什么时候调整?
- 追求公平和提高资源利用率。
- 进程等待时间很长可以提高优先级
- 进程占用处理机很长时间可以减少优先级。
- 进程频繁IO可以提高优先级。
前四种算法的优点
- FCFS公平
- SJF算法平均等待和周转时间比较低
- 时间片轮转可以加快响应时间
- 优先级调度灵活调整每个进程被服务的机会。
多级反馈队列调度算法
- 算法思想:对前四种算法优点折中
- 算法规则
- 设计多级就绪队列,优先级从高到低,时间片从小到大
- 新进程进入到第一级队列,并且按照FCFS等待分配,如果用完时间片进程进入下一级的优先级队列
- 只有k级队列为空,才会为k+1队头分配时间片。
- 只是用于进程调度
- 是否抢占:是抢占式的算法。
- 优缺点
- 优点:相对公平(FCFS优点),新到达的进程可以快速响应(RR时间片调度优点),短进程可以快速完成,不需要估计运行时间,可以灵活调整偏好程度。
- 会导致饥饿
总结
进程同步、进程互斥
什么是进程同步?
比如下面的例子
- 女一号需要成为老渣的初恋
- 女二号想要一个有恋爱经验的人
- 一号的指令2一定要在二号的指令1之前保证执行的顺序。
- 异步性会导致两个进程的不一致执行顺序,所以需要同步来规定执行的顺序。
- 完成进程同步其实就是让指令按照某种顺序执行。比如下面的写进程和读进程,一定是先写后读,为了保证这个那么就需要同步性。
- 同步也是直接制约关系,它指的是按成某个任务的一个或者多个进程,这些进程需要在某些位置协调他们的工作次序产生的制约关系。
什么是进程互斥?
- 一个时间段只允许一个进程使用的共享资源就是临界资源。
- 对于临界资源的访问是互斥的。互斥就是每次只能有一个进程访问,其它进程不得访问。
临界区代码
临界区访问规则
- 空闲让进,临界区空闲允许一个请求访问
- 忙则等待,如果有进程已经进入临界区,那么另一个进程就需要等待。
- 有限等待。保证进程有限的时间内进入临界区。
- 让权等待,当进程无法进入临界区,立刻让出处理机。
总结
- 介绍了同步的概念就是要让进程按照某种顺序执行
- 进程互斥的四个部分
- 进程互斥的遵循规则
进程互斥的软件实现方法
单标志法
- 算法思想:两个进程访问临界区之后会把使用临界区的权限转交给另一个进程,也就是说每个进程进入临界区的权限只能被另一个进程赋予
可以看看下面的代码
- p0必须要先访问了临界区,这个时候p1才能够访问临界区,他们的执行顺序是p0->p1->p0->p1…
- 问题就是如果p0不访问临界区,导致p1无法访问临界区,问题是现在的临界区是空闲,但是由于p0不访问导致p1无法访问,那么就会违反规则空闲让进的问题。
双标志先检查法
算法思想:设置一个boolean数组flag,数组各个元素标志各个进程想要进入临界区的意愿。
- 这种算法其实就是设置一个flag数组,flag[0]表示的是0号进程是否要进入临界区,flag[1]就是1号进程是否要进入到临界区。
- 但是由于检查和上锁并不是一次执行完的while(flag[1])检查1号进程是否访问临界资源和flag[0]=true相当于就是给临界资源上锁。但是并不是原子操作,所以可能造成flag[0]=true还没操作完就切换到另一个进程执行while(flag[0])所以判断失败也是同时进入临界区
- 违反了忙则等待的原则。
双标志后检查法
- 这种就是上面那种的调换过来的,先上锁再检查
- 这种就会导致大家都无法进入临界区。
- 解决了忙则等待,但是违反空闲让进,有限等待。还会产生饥饿问题。
Peterson算法
- 这种多了一个让操作,turn=0表示让p0先执行,turn=1表示的是让p1执行。并且还有一个flag数组表示是否正在使用临界资源。
- 这种的好处就是就算执行指令打乱,但是最后一定有一个程序是让出了执行权,所以一定能让一个进程进入到临界区并且阻塞另一个。
- 遵循了前三个规则。
总结
- 单标志算法,问题是只是检查但是不上锁,所以可能导致p0如果不访问这个临界区就会违反空闲让进的规则。
- 双标志前,检查后上锁,可能会违反忙则等待
- 双标志后,上锁后检查,违反空闲让进和有限等待的问题。
- Peterson算法:检查+让位,能够符合前三个规则,但是不能符合让权等待的原则。
进程实现互斥的硬件方法
中断屏蔽方法
- 这种就是依靠关中断来阻止处理机进行进程切换。
- 问题就是不能够在多处理机上执行。
TestAndSet指令
- 也可以被成为TestAndSetLock,TSL指令
- 这个指令不允许被中断,检查和上锁同时处理,并且返回上锁的情况。解决了双检查的问题。
- 但是仍然无法解决让权等待的原则问题。
Swap指令
- 这个指令相当于就是把lock和old交换。如果发现没人给临界区上锁,那么自己就可以用old和lock交换值完成上锁,而且这个过程是无法中断的。所以和TSL指令的逻辑基本上是一致的。
总结
- 硬件的指令通过了特殊的硬件处理完成所以是无法中断的。和软件的不同。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aJysXreW-1637672945464)(C:/Users/11914/AppData/Roaming/Typora/typora-user-images/image-20211123145659816.png)]
信号量机制
- 对于每种方案其实都是无法实现让权等待
- 而且双标志的检查法由于检查和上锁不是原子操作导致的并发问题。可能是无法进入临界区,或者是大家都无法进入临界区。
信号量机制
- 用户可以通过操作系统提供的原语对信号量进行操作。
- 信号量表示的是某种资源的数量。
- 原语是一种特殊的程序段,通过关中断和开中断实现,解决原子操作问题。
- 分为wait(S)和signal(S)也可以被称为PV操作。
整型信号量
记录型信号量
- 这种信号量除了一个value还记录一个阻塞队列
- wait如果发现资源不够也就是value<0那么就会把当前进程阻塞到等待队列
- signal释放资源,value<=0说明现在有进程在阻塞,那么就可以在进程使用完资源之后通过wakeup来唤醒等待队列的进程。
- 他可以完成让权等待
- 每次wait的时候会判断是否有足够的支援,如果没有那么就阻塞
- signal释放资源,如果有进程阻塞的时候就去唤醒等待队列。
下面的例子
- 如果value<=0的时候说明还有进程在等待资源。比如现在如果是-2说明有两个进程在等待资源,因为它的判断是先+1后判断所以是<=0作为判断。
- 如果value>0说明现在没有进程在等待,而且资源还剩一个。
总结
用信号量实现进程互斥、同步、前驱关系
信号量实现进程互斥
- 分析临界区
- 互斥信号量mutex,初值是1。
- 使用之前需要P操作,也就是wait占用资源
- 使用时候需要V操作,也就是signal释放资源。
- 对于PV操作必须成对出现
- 而且不同资源使用不同的信号量。
信号量实现进程同步
- 分析哪里需要同步
- 同步信号量S初始为0
- 前操作V
- 后操作P
下面的案例
- 实际上就是P(S)用来阻塞P2线程,因为P1没有执行V(S)所以现在S还是0,那么P(S)就会被阻塞。直到P1执行完代码1和代码2的时候才能够执行P2的代码4。。
信号量实现前驱关系
- 前驱关系实际上就是给每个前驱关系设定一个信号量,并且类似于同步关系这样的一个操作。
- 一个进程可能会有多个同步关系。
总结
- 信号量实现进程互斥方法
- 信号量实现进程同步,实际上就是依靠信号量,本质是通过PV框住优先执行的代码,不能执行的代码前加上P,需要优先执行的代码后面加上V。
- 信号量实现前驱关系依靠的就是同步关系的多个不同信号量。
生产者消费者问题
-
对于生产者来说缓冲区没有满可以继续生产,如果满就需要阻塞(同步关系) -
对于消费者来说缓冲区没有空那么就可以取出消费,如果空了就需要被阻塞。(同步关系) -
这里的缓冲区其实就是临界资源,各个进程需要互斥访问,防止生产覆盖等并发问题。(互斥)
如何实现使用信号量实现生产者和消费者的功能?
- 关系分析,分析各个进程的同步和互斥的关系
- 整理思路,确定P和V的流程
- 消费者和缓冲区的消费关系就是空的时候无法消费,这是一个同步关系
- 生产者和缓冲区的关系是缓冲区满的时候无法继续生产,这是同步关系
- 各个进程访问缓冲区之间互斥是互斥关系。
- 生产者P消耗的是缓冲区的空闲空间,V生产的是产品,消费者P是消费产品,V是释放出缓冲区的空间。
- 一共有3个信号量。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pR4HY3WJ-1637672945476)(https://gitee.com/yueyingstar/image/raw/master/image/image-20211123155404554.png)]
如何实现
- 对于生产者可以看到empty就是空闲缓冲区的数量,每次要生产就需要占用一个空闲缓冲区
- 并且在生产之前需要使用互斥量,因为操作缓冲区需要各个进程互斥
- 最后就是V生产出一个产品
- 对于消费者来说就是full就是消费一个产品,然后mutex锁住缓冲区,消费。并且V释放一个空间。
- 对于full来说也就是产品消耗量和empty空闲缓冲区数量,消费者和生产者是同步关系。但是如果要操作缓冲区的时候那么他们就是互斥的关系。
两个信号量的PV操作是否可以交换顺序?
- 对于P来说不可以,因为P(mutex)和P(empty)交换,假设现在的empty已经是0了,说明下一次执行P(empty)的话那么就会阻塞。如果阻塞切换到消费者进程发现mutex已经被生产者调用,那么就需要等待生产者处理完缓冲区的操作,最后造成了死锁的问题。
- 但是V可以,因为V谁先释放都没有任何问题。
能否把产品放到PV之间?
- 可以但是会导致代码量大,而且锁住的时间很长。导致降低并发度。
总结
- 对于消费者没有产品的时候,那么很明显就是需要等待生产者生产,这里的生产者生产就像是同步关系的代码1和代码2之后加上一个V,对于消费者来说就是代码3和代码4之前加上一个P。产生一个同步的关系。也就是在资源不够的时候才会出现需要同步的关系。如果消费者没有产品消费,那么就需要先去等待生产者生产产品出来才能使用
- 对于生产者的同步关系也是这样
- 对于操作缓存区来说那么就只能是互斥,不能够同时访问造成覆盖问题。
多生产者多消费者
对于下面这个多生产者和多消费者问题分析
- 分析出关系
- 父亲生产橘子,女儿才能拿到橘子,同步关系
- 母亲生产苹果,儿子才能拿到苹果,同步关系
- 父亲和母亲需要生产东西,就需要盘子是空的,所以需要儿子和女儿先取走这些水果。
- 整理思路
- 设置信号量
- 所以需要4个信号量。
- mutex解决盘子的互斥访问操作
- apple解决儿子等待父亲的苹果的同步关系
- plate解决还能放入多少个水果
- orange可以解决女儿等待母亲的橘子同步关系。
那么可以不使用mutex吗?
- 可以因为只能放入一个水果,所以不会发生两个进程同时访问盘子的问题
- 但是如果plate不是1而是2那么很明显父亲和母亲是可以同时访问盘子的。所以必须要有mutex
总结
如何理清同步关系?
- 下面的关系可以抽象为事件,盘子判空的事件必须要在放入水果之前。所以只需要使用一个plate变量。
吸烟者问题
问题描述其实就是三个抽烟者都需要提供者来提供自己所需要的材料,执行抽烟,并且提供者继续往下重复提供给不同抽烟者材料,然后执行抽烟这样的循环。
问题的分析
- 现在有三种组合
- 实际上对于缓冲区就只能放一个组合
- 而且组合必须对应三个抽烟者其中一个的组合,那么其中一个抽烟者才能够抽烟。这里也是有三个这样的同步关系,抽烟者必须等待组合产生,提供者必须等待抽烟者完成抽烟的信号才能放下一个组合到缓冲区。
- 然后就是提供者进行一个循环,通过i%3决定下一个到底放什么组合,然后每次循环结束都需要P(finish)等待抽烟者的信号才能继续放入组合
- 对于每个抽烟者P(offer)等待自己的组合,抽烟之后(也就是消耗缓冲区的组合),V(finish)通知提供者现在又可以提供材料了。
- 对于这里缓冲区并不需要提供互斥量。
总结
- 解决了多消费者和生产者的问题。用i实现了轮流操作。
读者写者问题
问题的描述
- 读进程不会消费文件,所以允许多个读进程同时读一个文件
- 但是写进程的写一个文件同时,读进程读这个文件,可能会导致读到脏数据
- 两个写进程同时写一个文件可能会导致,文件覆盖的问题。
分析关系
- 互斥关系:写-读和写-写,所以需要设置一个互斥量rw,对于写进程和读进程都需要访问前对rw进行PV操作。
但是怎么解决读-读共享问题?
- 其实就是通过count记录读进程的数量,如果发现count>0说明已经有读进程占有锁,所以可以直接跳过P操作。
- 并且需不需要解锁关键看自己是不是最后一个读进程。如果是count是0说明需要解锁。
如何实现?
- 其实就是写进程,写之前加锁,写之后解锁
- 对于读进程还要加上count的计算。但是这里会产生一个并发问题
这个并发问题就是如果两个读进程同时完成判断,导致第二个读进程被阻塞如何处理?
- 因为判断和count++并不是同时进行所以会产生这样的问题,那么解决办法是可以通过外面加上一个互斥量保证这是一个原子操作。
潜在问题,如果有很多读进程进来导致写进程一直被阻塞如何处理?
- 为什么会造成这个问题,原因就是读进程不需要加锁,只需要判断和加上数量,所以可以导致count累积导致很久都不会释放锁。
- 所以解决的办法就是在读写进程的上面再加上一个w写进程优先信号量。也就是如果有进程进来,那么就一定要先去获取w。
- 读者-读者:这个原因就是P(w)和V(w)只是在上锁的时候使用,所以并不会影响后面的读者继续进来。
- 写者-读者:那么写进程占有w之后就会阻塞后面的读进程,两个写进程的逻辑也是同样。
总结
- count解决了共享问题
- mutex解决了count带来的判断和count++不能一起执行的并发问题
- w解决了写进程会饥饿的问题。
哲学家进餐问题
问题描述
实际上就是哲学家的筷子互斥问题,如果出现一种情况,由于切换哲学家(进程)导致哲学家都只拿到一只筷子(临界资源),但是吃饭需要左右两双筷子,那么就会导致死锁问题。怎么解决?
解决方案
- 只允许每次4个哲学家吃饭。
- 奇数哲学家只能拿到左边的筷子,偶数只能拿到右边的筷子,而且拿到筷子才能申请另外一边,防止上面方案的拿了一只就一直在等待。
- 当左右筷子都可以使用的时候才能获取筷子。
相当于就是给拿左右筷子的代码加上互斥量,让这两个动作一气呵成。实际上更准确的描述就是每次只允许一个哲学家拿筷子,然后释放互斥量让别的哲学家拿,所以会导致有的哲学家只拿到其中一边的筷子等待。但是由于互斥量已经可以保证第一个哲学家拿到两双筷子。而且每次拿筷子的时候都是互斥的。
管程
为什么引入管程?
- 信号量机制编写代码困难,容易出错。
- 能不能让程序员不专注PV操作。
- 管程更高级的同步机制。
管程的定义和基本特征
管程是一个软件模块,它的组成部分
-
局部于管程的共享数据结构说明 -
对该数据结构进行操作的一组过程 -
对局部于管程的共享数据设置初始值的语句 -
管程有一个名字。
管程的基本特征
- 局部于管程的数据只能被局部于管程的过程访问
- 一个进程只有通过调用管程内的过程才能进入管程访问共享数据
- 每次仅仅允许一个进程在管程内执行某个内部过程
用管程解决生产者和消费者问题
两个生产者进入
- 对于生产产品,需要传入变量item。而且管程的insert操作自带互斥的作用,如果多个进程进入,那么就会阻塞一个。由编译器负责处理
两个消费者先执行,再一个生产者执行。
- 还能完成同步问题。
- 两个消费者进入由于count还是0说明现在还没有产品。所以被阻塞
- 生产者执行把产品放入缓冲区,并且检查是不是第一个产品,如果是那么可能会有消费者在等待,那么就唤醒消费者。
- 消费者取出产品,并且检查之前的缓冲区是不是满的,如果是那么就唤醒生产者。
怎么引入管程
- 在管程定义好共享数据
- 定义访问共享数据的函数入口
- 只能通过函数访问共享数据
- 每次只开放一个入口,只能让一个进程或者线程进入。
- 设置条件变量和等待唤醒操作解决同步问题。
死锁的概念
什么是死锁?
饥饿
- 进程长期得不到资源,导致的等待无法推进,只有一个进程饥饿可能是阻塞或者是就绪
死循环
死锁产生的必要条件
必须都满足
- 互斥条件:对必须互斥使用资源争抢才会导致死锁
- 不剥夺条件:进程未使用完资源之前,不能被其它进程抢走
- 请求和保持条件:进程保持至少一个资源,但是提出新的资源请求,这个资源又被其它进程占用。请求的资源的进程被阻塞,但是对自己的资源又不释放。
- 循环等待链:链中的每一个进程获取资源,但是同时被下一个的进程请求。(但是发生循环等待不一定会产生死锁)
什么时候会发生死锁?
- 对系统资源争夺,对不可剥夺资源竞争可能造成死锁。
- 进程推进顺序非法。请求和释放资源的顺序不当。比如哲学家争抢筷子的问题。
- 信号量使用不当。消费者和生产者问题 ,P(mutex)和P(empty)调换位置
死锁的处理策略
- 预防死锁。破坏死锁产生条件
- 避免死锁,防止让系统进入不安全状态,避免死锁
- 死锁的检测和解除。
死锁处理策略-预防死锁
破坏互斥条件
使用SPOOLing技术,相当于就是发送请求之后
破坏不剥夺条件
缺点
- 实现复杂
- 可能导致前一阶段的工作丢失。
- 反复申请和释放增加系统的负担。
破坏请求和保持条件
- 请求和保持条件:进程获取资源,并且申请新资源,这个资源被其它进程持有,但是该进程不释放资源并且阻
解决方案
- 静态分配,一下子分配完所有资源才能执行。 就不会保持资源的同时请求新的资源问题
缺点
- 有些资源可能只需要用一小会,但是却要保存一大段时间在进程。导致资源浪费问题。
- 可能会造成饥饿问题,比如进程C要两个资源,A和B只需要1个。如果他们所需的资源都是相同,A进程不断进来就会导致C进程永远无法执行。
破坏循环等待条件
解决方案
- 顺序资源分配法,意思就是获取资源必须是按照某种顺序的。
缺点
- 因为顺序资源分配需要分配编号,所以添加起来很麻烦。需要重新分配编号。
- 进程使用资源顺序可能和编号不同。可能导致资源空闲
- 必须按照次序申请,编程很麻烦。
总结
|