操作系统——进程调度算法与死锁

进程调度算法与死锁

  • 进程调度算法
    • 先来先服务算法
    • 短作业(进程)优先调度算法
    • 高优先权调度算法
    • 高响应比优先调度算法
    • 时间片轮转算法
    • 多级队列调度算法
  • 死锁
    • 定义
    • 死锁产生的原因
    • 死锁产生的必要条件
    • 处理死锁的方法
    • 银行家算法
  • Linux中4种锁机制
    • 互斥锁
    • 读写锁
    • 自旋锁
    • RCU

进程调度算法

先来先服务算法

每次调度都是从就绪队列中选择一个最先进入该队列的进程,为之分配处理机。

短作业(进程)优先调度算法

以作业或进程执行时间的长短来计算优先级,所需时间越少,优先级越高。

缺点:
a. 必须预知作业的运行时间;
b. 对长作业不利;
c. 人机无法交互;
d. 未考虑作业的紧迫程度。

高优先权调度算法

基于作业的紧迫程度,由外部赋予进程相应的优先级。可以保证紧迫的作业优先运行。

非抢占式调度算法
抢占式调度算法

优先级的类型

静态优先级
动态优先级

高响应比优先调度算法

高响应比优先调度算法,同时考虑了进程的等待时间和进程的执行时间。

优先权 = (等待时间 + 要求服务时间) / 要求服务时间

由于进程的等待时间和服务时间之和就是响应时间,故优先级又相当于响应比:

Rp = (等待时间 + 要求服务时间) / 要求服务时间 = 相应时间 / 要求服务时间

时间片轮转算法

就绪队列中的每次进程每次仅运行一个时间片。
当执行的时间片用完时,由一个计时器发出时钟中断请求,调度程序便据此信号来停止该进程的执行,并将它送往就绪队列的末尾;然后,再把处理机分配给就绪队列中新的队首进程,同时也让它执行一个时间片。这样就可以保证就绪队列中的所有进程在一给定的时间内均能获得一时间片的处理机执行时间。换言之,系统能在给定的时间内响应所有用户的请求。

多级队列调度算法

前面介绍的各种用作进程调度的算法都有一定的局限性。如短进程优先的调度算法,仅照顾了短进程而忽略了长进程,而且如果并未指明进程的长度,则短进程优先和基于进程长度的抢占式调度算法都将无法使用。

而多级队列调度算法将系统中的就绪队列从一个拆分为若干个,将不同类型或性质的进程放在不同的就绪队列。不同的就绪队列就可以使用不同的调度算法。目前公认的较好的调度算法。

算法实施过程
(1)应设置多个就绪队列,并为各个队列赋予不同的优先级。第一个队列的优先级最高,第二个队列次之,其余各队列的优先权逐个降低。该算法赋予各个队列中进程执行时间片的大小也各不相同,在优先权愈高的队列中,为每个进程所规定的执行时间片就愈小。例如,第二个队列的时间片要比第一个队列的时间片长一倍,……,第i+1个队列的时间片要比第i个队列的时间片长一倍。
(2)当一个新进程进入内存后,首先将它放入第一队列的末尾,按FCFS原则排队等待调度。当轮到该进程执行时,如它能在该时间片内完成,便可准备撤离系统;如果它在一个时间片结束时尚未完成,调度程序便将该进程转入第二队列的末尾,再同样地按FCFS原则等待调度执行;如果它在第二队列中运行一个时间片后仍未完成,再依次将它放入第三队列,……,如此下去,当一个长作业(进程)从第一队列依次降到第n队列后,在第n 队列便采取按时间片轮转的方式运行。
(3)仅当第一队列空闲时,调度程序才调度第二队列中的进程运行;仅当第1~(i-1)队列均空时,才会调度第i队列中的进程运行。如果处理机正在第i队列中为某进程服务时,又有新进程进入优先权较高的队列(第1~(i-1)中的任何一个队列),则此时新进程将抢占正在运行进程的处理机,即由调度程序把正在运行的进程放回到第i队列的末尾,把处理机分配给新到的高优先权进程。

死锁

定义

如果一组进程中的每一个进程都在等待仅由该组进程中其他进程才能引发的事件,那么该组进程是死锁的。

死锁产生的原因

通过源于多个进程对资源的争夺。对不可抢占资源和可消耗资源的竞争都可能产生死锁。

死锁产生的必要条件

(1)互斥条件。
进程对它所获得的资源进行排他性使用。即在一段时间内,该资源只能被一个进程占用,如果此时其他进程请求该资源,该进程只能等待,直到占有资源的进程释放资源。
(2)请求与保持条件。
进程已经占有一个资源,但又提出了新的资源请求,而该资源已经被其他进程占用,此时请求进程被阻塞,但对自己占有的资源保持不释放。
(3)不可抢占条件。
进程已获得的资源在使用完之前不可被抢占。只能使用完由自己释放。
(4)循环等待条件。
产生死锁时,必定存在一个进程——资源的循环链。即进程集合{P0, P1, …, Pn},P0在等待P1占用的资源,… Pn在等待P0占用的资源。

处理死锁的方法

(1)预防死锁
a. 破坏互斥条件:当一个进程在请求资源时,它不能占有不可抢占资源。
b. 破坏请求与保持条件:当一个已占有某些不可抢占资源,提出新的资源请求不能得到满足时,待以后需要时再重新申请。这意味着进程占有的资源会被暂时释放,或者说呗抢占了。
c. 破坏循环等待条件:对系统内所有资源进行线性排序,为每个资源类型赋予唯一的编号。
规定每个进程必须按照资源编号递增的顺序请求资源。

(2)避免死锁
同样属于事先预防策略。在资源动态分配过程中,使用某种方法防止系统进去不安全状态,从而避免发生死锁。例如银行家算法。

(3)检测死锁
允许进程在运行过程中发生死锁,但可以通过检测机构及时地检测出死锁的发生,然后才去适当的措施,将死锁从死锁中解脱出来。

(4)处理死锁
检测出死锁的发生,然后才去适当的措施,将死锁从死锁中解脱出来。通常的方法是撤销一些进程,回收他们的资源,将它们分配给其他阻塞的进程。

银行家算法

银行家算法是一种“死锁避免算法”,它的主要思想是:在资源分配之前先进行试探,如果试探结果是安全的,就按照此次试探进行资源分配,否则进程阻塞。

银行家算法的数据结构

(1)一个Available数组。这是一个含有M个元素的数组,其中的每一个元素代表可利用的资源数目。

(2)最大需求Max矩阵。这是一个n x m 的矩阵,它定义了系统中N个进程中,每个进程对m类资源的最大需求。如果Max[i,j]=K,则表示进程i需要Rj类资源的最大数组为K。

(3)分配矩阵Allocation。这也是一个n x m 的矩阵它定义了系统中每一类资源当前已分配给每一进程的资源数。如果Allocation[i,j]=K,则表示进程 i 当前已分得Rj类资源的数目为K。

(4)需求矩阵Need。这也是一个n x m 的矩阵,用以表示每一个进程尚需的各类资源数。如果Need[i,j]=K,则表示进程i还需要Rj类资源K个方能完成任务。

银行家算法的实现
设Request i是进程Pi的请求向量,如果Rquesti[j]=K,表示进程P需要K个Rj类型的资源。当Pi发出资源请求后,系统按以下步骤进行分配。
(1)如果Request i [j] <= Need[i, j] 则转向步骤(2),否则出错,请求的资源数量大于资源的最大需求量。
(2)Request i [j] <= Available[j] 则转向步骤(3) ,否则出错,请求的资源数量大于系统剩余资源。
(3)系统试探吧资源分配给Pi,并修改以下数据结构的值:

Available[j] = Available[j] - Request i [j];
Need[i, j]    = Need[i, j] - Request i [j];
Allocation[i, j] = Allocation[i, j] + Request i [j];

(4) 系统执行安全性算法,检查此次分配是否安全,若安全则将资源分配给Pi,若不安全,本次试探作废,恢复原来的资源分配状态。让进程Pi等待。

安全性算法
安全性算法数据结构:

(1)设置一个工作向量Work 初始化为 Work = Available
(2)Finish 数组,表示系统是否有足够的资源分配给进程,使之运行完成。初始化为false,有足够资源分配给进程时再令Finish[i]=true;

安全性算法:

从进程集合中找出一个进程:满足下列条件:

1、Finish[i] = false;
2、Need[i,j] <= Work[j];

随后当进程Pi获得资源后,可顺利执行,直至完成,并释放出资源,因此需要执行:

Work[j] = Work[j]+Allocation[i,j]l;
Finish[i] = true;

然后重复以上步骤。
如果Finish数组均满足=true;则安全,否则不安全!

Linux中4种锁机制

互斥锁

mutex,⽤于保证在任何时刻,都只能有⼀个线程访问该对象。当获取锁操作失败时,线程会进⼊睡眠,等待锁释放时被唤醒。

读写锁

rwlock,分为读锁和写锁。处于读操作时,可以允许多个线程同时获得读操作。但是同⼀时刻只能有⼀个线程可以获得写锁。其它获取写锁失败的线程都会进⼊睡眠状态,直到写锁释放时被唤醒。 注意:写锁会阻塞其它读写锁。当有⼀个线程获得写锁在写时,读锁也不能被其它线程获取;写者优先于读者(⼀旦有写者,则后续读者必须等待,唤醒时优先考虑写者)。适⽤于读取数据的频率远远⼤于写数据的频率的场合。

自旋锁

spinlock,在任何时刻同样只能有⼀个线程访问对象。但是当获取锁操作失败时,不会进⼊睡眠,⽽是会在原地⾃旋,直到锁被释放。这样节省了线程从睡眠状态到被唤醒期间的消耗,在加锁时间短暂的环境下会极⼤的提⾼效率。但如果加锁时间过⻓,则会⾮常浪费CPU资源。

RCU

即read-copy-update,在修改数据时,⾸先需要读取数据,然后⽣成⼀个副本,对副本进⾏修改。修改完成后,再将⽼数据update成新的数据。使⽤RCU时,读者⼏乎不需要同步开销,既不需要获得锁,也不使⽤原⼦指令,不会导致锁竞争,因此就不⽤考虑死锁问题了。⽽对于写者的同步开销较⼤,它需要复制被修改的数据,还必须使⽤锁机制同步并⾏其它写者的修改操作。在有⼤量读操作,少量写操作的情况下效率⾮常⾼。

你可能感兴趣的:(操作系统)