早晨,迷糊的两个室友一个拿了牙膏,一个拿了牙刷。两个人同时,想拿到对方的东西,好在一个室友发现自己拿错了,相视一笑,说句调侃的话,就完事啦。
如果在计算机中,只用一套牙膏牙刷。此时两个进程,循环等待,便构成了死锁。听老师说这是个很严肃的问题,我们要认真对待(严肃脸)。
那么死锁了,怎么办呢?老师说,书上都写着。(鸦雀无声)好嘛,还是看书吧。
策略是:防止死锁条件的发生,此处是介绍间接的死锁预防方法(直接的死锁预防方法见死锁检测)。
条件一:互斥 由于进程之间存在临界区,为了进程则正确执行,所以该条件并不能破坏条件二:占有并等待 可以采用一次性申请所需的所有资源,已达到破坏第二条件的目的。 当自认为万事亨通,伸懒腰的时候,一个习惯性的问句还是破坏了你无事一身轻的感受。这个真的完美了吗?细想之后,很明显这个并不是很好,如果也许会很差。理由如下: 一次性申请成功,的确能保证当前进程的有效执行,但是你有没有考虑过别人的感受。其他进程必然会因为当前进程的执行而被阻塞在队列中,看着你频繁使用某一资源,空闲的资源却不给它们(要我早掐死你了)。 再想想,是否真的能一次性申请所有需要的资源的呢?如果不能申请,那么是不是依旧会发生死锁,或者是退化到单进程处理。(完美的想法,被扼杀在摇篮里)条件三:不可抢占 考虑到前面两个条件破坏的不乐观,接着想想条件三是否可以被破坏?还真的好像可以哟。 第一种:如果占有资源的进程进行进一步资源申请时,发生拒绝,那么我们释放该进程的所有资源;之后,可再申请这些资源以及另外所需的资源 第二种:如果一个进程请求当前被另一进程占有的一个资源(两进程优先级不同),则操作系统抢占另一进程,要求它释放资源。(就像妈妈抢了哥哥的玩具,然后给弟弟) 想想这两种做法,是否能实现呢?这次的想法,应该是可以实现的。只要保留被释放进程对资源需求即可(也就是哥哥会一直惦记着弟弟手里的玩具),之后运行时,重新申请即可。条件四:循环等待 看来条件三,还是可以接受的(在进程损失合理的范围内)。从条件四入手,看看如何解决。 将资源按某种次序排列,并将其作为申请顺序。不能发生进程的申请序列中前一个申请的资源次序编号大于后一个申请资源次序编号的现象。 资源的顺序R = (⋯,Ri,⋯,Rj,⋯) (i<j) 进程A对资源的申请顺序R’ = (⋯,Rj,⋯,Ri,⋯) (i<j) 出现当前情况是不被允许的,因为如果有进程B提出以下申请, R” = (⋯,Ri,⋯,Rj,⋯) (i<j) A和B进程,可能会陷入死锁。 由于考虑到未来的申请状况,与条件二一样,可能与带来效率上的不称心这些解决方法,1)效率不高;2)不够灵活。有没有再好一点的方法呢?听大书指点…策略:如果一个新进程的资源需求会导致死锁,则拒绝启动这个新进程。仅当对所有j Rj≥C(n+1)j+∑ni=1Cij 时才启动一个新进程 Pn+1 也就是说,只有当前所有进程的最大请求量加上新的进程请求可以被剩余资源满足时,才能启动该进程。 这里通过使用对死锁模型的使用,试图解决死锁问题。
来一个Dijkstra(自从学了OS,才发现Dijkstra是真腻害)提出的银行家算法。 以下提及的安全状态是,至少有一个资源分配序列不会导致死锁(即所有进程都能运行结束);不安全状态是,无资源分配序列不会导致死锁,也就是一定会产生死锁。
算法思路 该策略确保系统中进程和资源总是处于安全状态。 当进程请求一组资源时,假设同意该请求,从而改变了系统的状态,然后确定其结果是否处于安全状态。如果是,同意该请求;否则阻塞当前进程直到同意该请求后仍然是安全的。算法伪码实现(引用于OSIADP)
//全局数据结构 struct state { int resource[m]; int available[m]; int claim[n][m]; int alloc[n][m]; };//分别表示资源总量、可用资源、最大申请量、以申请资源数 //资源分配算法 if(alloc[i][j] + request[j] > claim[i][j]) //判断进程i申请的资源总量是否大于最大总量,若是,则出错 //error; else if(request[j] > available[j])//判断申请j类资源是否大于可用资源总量,若是,则阻塞当前进程 //suspend process; else {//将资源分配给进程i,并减少j类可用资源 /*define new states by: alloc[i][j] += request[j]; available[j] -= request[j]; */ } if (safe(new state))//判断资源分配后是否处于安全状态,若是,则继续运行;否则释放资源并阻塞 //carry out allocation; else { //restore original state; //suspend process; //测试安全算法(银行家算法) bool safe(state s) { int currentavail[m];//当前每类可用资源的数量 process rest[n];//剩余进程 currentavail = available; //rest = {all processes}; possible = true; while(possible) { /*find a process Pk in rest such that claim[k][j] - alloc[k][j] <= current avail; */ if(found) {//假设当前处于安全状态,模拟运行各个进程 currentavail += alloc[k][j];//结束的进程将资源释放 //rest -= {Pk};//从剩余进程中移除 } else possible = false; } return (rest == NULL)//如果剩余进程队列为空,则处于安全状态;否则,不安全 }算法优劣 该算法优点是不需要死锁预防中的的抢占和回滚进程,也就是进程在发生死锁前被阻塞,从而防止了某些进程执行过程中的被终止;也不用太在意资源的占用问题,不用循环的检查资源是否占用。 尽管不用循环的检查资源是否被占用,但是每分配一次资源,就要模拟运行直到所有进程都结束,这也是一种缺陷;还有需要给出每个进程的最大资源数量,分配的资源不可变,进程之间无关(如果有关则有部分资源被多次使用,与所建模型不符,模型中的资源是相互独立的)等等。 好像这个也有很大的问题呀!!!还有别的方法么,大书?
死锁预防通过谨慎的限制资源访问和进程之间强加约束来解决的。 死锁检测,则放纵了进程的贪婪的欲望,只要能批准的资源请求一律批准,同时有周期得执行检测循环等待的算法。 大书,又给我了方法。
死锁检测算法 算法思路 查找一个进程,是的可用资源可以满足该进程的资源请求,然后假设同意这些资源,让该进程运行直到结束,在释放它的所有资源。然后算法在寻找另一个可以满足资源请求的进程。当且仅当算法的最后结果有未标记的进程时,存在死锁,每个未标记的进程都是死锁的。 该算法并不能像死锁避免那样,提前判断当前进程是否可以导致死锁,而是更像死锁预防一样发生死锁后,在做处理(具体处理在恢复中介绍)。 算法流程(引用于OSIADP) 1.标记Allocation矩阵中一行全为零的过程。 2.初始化一个临时向量W,令其等于Allocation向量。 3.查找下标i,使进程i当前为标记且 Q 的第i行小于等于W,即对于所有的1≤K≤m,Qik≤Wk。如果找不到这样的行,则停止算法。 4.如果找到这样的行,标记进程i,并把Allocation矩阵中的相应行加到W中,也就是说,对所有的 1≤k≤m ,令 Wk=Wk+Aik 。返回步骤3. 其中的 Q 是一个请求矩阵,其中的Qij表示进程i请求的类型j的资源量 这个解决了死锁避免中的弊端(比如提前预知资源请求等),但是也不可避免的会使某些进程,甚至是所有进程突然终止,对用户产生影响。死锁, 事前要避免;事后要检测, 无论哪一套,就是破条件。
