计算机操作系统面试题总结(3)
消费者-生产者problem
- 只要缓冲区没满时,生产者才能把产品放入缓冲区,否则必须等待;
- 只有缓冲区不空时,消费者才能从中取出产品,否则必须等待;
- 信号量机制可以实现互斥、同步、对一类系统资源的申请和释放;
- 缓存区时临界资源,必须互斥的访问;
- 实现互斥的p操作一定要放在实现同步的p操作之后;
- v操作可以交换,不会导致死锁;
- 生产和消费problem是互斥和同步的一个综合问题
抽烟者-吸烟者problem
解决”可以生产多个产品的单生产者“问题
读者-写者problem
两个或以上的进程同时访问共享数据时不会产生副作用
但是,若此时有写进程进入,则可能发现读写不一致问题
当然,若两个写进程同时进入,则也可能发生不一致,所以写进程不能和任何进程同时进程
任何写进程进行时,都不能允许其他读者写者执行
写进程和写/读进程均要互斥;读进程与读进程不互斥,可以实现两个读进程进行。
解决办法:设置一个count整型变量,来记录当时有几个进程想读访问共享文件。
rw=1 //实现队文件的互斥访问,表示当前是否有进程在访问共享文件
count=0 //记录有几个读进程在访问文件
writer()
{
while(1)
{
p(rw); //枷锁
写文件
v(rw): //解锁
}
}
reader()
{
writer(1)
{
if(count==0)
p(rw); //第一个文件进行枷锁
count++; //访问文件的读进程+1
读文件
count--; //访问文件的读进程--
if(count==0)
v(rw); //最后一个进程进行 解锁 操作
}
}
优化后
rw=1 //实现队文件的互斥访问,表示当前是否有进程在访问共享文件
count=0 //记录有几个读进程在访问文件
mutex=1 //用于保证对count变量的互斥访问
writer()
{
while(1)
{
p(rw); //枷锁
写文件
v(rw): //解锁
}
}
reader()
{
writer(1)
{
p(mutex); //各读进程互斥访问count
if(count==0)
p(rw); //第一个文件进行枷锁
count++; //访问文件的读进程+1
v(mutex);
读文件
count--; //访问文件的读进程--
p(mutex);
if(count==0)
v(rw); //最后一个进程进行 解锁 操作
v(mutex);
}
}
思考:读者1->写者1->读者2的过程;下面👇过程可以有效防止写进程饥饿的情况发生
rw=1 //实现队文件的互斥访问,表示当前是否有进程在访问共享文件
count=0 //记录有几个读进程在访问文件
mutex=1 //用于保证对count变量的互斥访问
w=1 ///用于实现”写优先“
writer()
{
while(1)
{
p(w);
p(rw); //枷锁
写文件
v(rw): //解锁
v(w);
}
}
reader()
{
writer(1)
{
p(w); //写进程 检查
p(mutex); //各读进程互斥访问count
if(count==0)
p(rw); //第一个文件进行枷锁
count++; //访问文件的读进程+1
v(mutex);
v(w);
读文件
count--; //访问文件的读进程--
p(mutex);
if(count==0)
v(rw); //最后一个进程进行 解锁 操作
v(mutex);
}
}
读者写者问problem 核心思想在于设置了一个计数器count ;如果要实现一气呵成的完成,可以设置互斥信号量;
哲学家进餐problem
在于解决死锁问题,是一个关键性问题;
五个人 五根筷子 都分为左右两边 如何每个哲学家吃饭呢? 都别吃了吧哈哈哈哈
管程 —— 一种高级的同步机制
实现进程之间的同步和互斥,之所以要解决 是因为一些进程之间需要共享资源和分配
为了实现各个进程之间的访问共享资源时,管程是一种特殊的软件模块
管程的组成
- 局部于管程的共享数据结构说明:
- 对该数据结构进行操作的一组过程(函数)
- 对局部于管程的共享数据设置初始值的语句
- 管程有一个名字
管程的基本特征 :
- 局部与管程的数据只能被局部于管程的过程所访问
- 一个进程只有通过调用管程内的过程,才能进入管程访问共享数据
- 每次只允许一个进程在管程内执行某个内部过程
死锁
进程无法顺利向前推进的现象
死锁:循环等待对方手里的资源,导致阻塞,无法推进;(至少两个或者两个以上进程,且一定处于阻塞状态)
饥饿:可能只有一个进程发生饥饿 (阻塞态或者就绪态)
死循环:进程是可以上处理机运行,只不过无法像期待的那样顺利推进。
死锁和饥饿都是由于操作系统分配资源策略不合理引起的,管理者的问题
死循环是由代码逻辑错误而导致的,被管理者问题
产生死锁,满足以下四个条件:
- 互斥条件:互斥使用的资源之间的争抢才会导致死锁
- 不剥夺条件:进程所获得的资源在未使用完前,不能由其他进程强行夺走,只能主动释放
- 请求和保持条件:进程已经保持了至少一个资源,但又提出了新的请求,自己手上的资源又不能释放
- 循环等待条件:存在一种进程资源的循环等待连,联众的每一个进程已获得的资源同时被下一个进程所请求
PS:发生死锁时,一定有循环等待,但循环等待时未必死锁 (必要不充分)
如果同类资源数大于1,即使有循环等待也不一定发生死锁;
发生死锁的几个情况:
- 对系统资源的竞争,各个进程对不可剥夺资源的竞争
- 进程推进顺序非法,请求和释放资源的顺序不当
- 信号量的使用不当 ,如生产者-消费者的模型
避免死锁的策略:
- 预防死锁,破坏死锁产生的条件(四个必要条件)
- 避免死锁,用某种方法防止系统进入不安全状态,避免死锁——银行家算法
- 死锁的检测和解除,允许死锁发生,不过操作系统会负责检测出死锁的发生,然后采取某种措施破除死锁
1.破坏互斥条件,SPOOLing技术,把只能互斥使用的资源改造为允许共享使用,则系统上不会进入死锁状态,放入序列,逐个运行; 但使用范围不广
2.破坏不剥夺条件:变剥夺,把资源抢过来用,分配给优先级用等等。但强行释放资源,但可能导致丢失状态,所以需要保存状态,只适用易于保存和恢复状态的资源,比如cpu,并且这样缺点明显,增加了系统的吞吐量
3.破坏请求和保持条件:采用静态分配法,即在进程运行前一次性申请完它所需要的全部资源,在他资源未满情况下不允许它投入运行,这就避免了资源一直归他所有,该进程就不会在请求别的任何资源了; 缺点:资源利用率低,可能导致饥饿。
4.破坏循环等待条件:采用顺序资源分配法,给资源编号,每个进程必须按编号递增的顺序请求资源,同类资源一次性申请完。
必须先占有小编号资源,才可以申请大编号资源,按此规则,已持有大资源的进程不可以逆向返回来申请小编号资源,就不会产生循环等待情况了。
缺点:不方便增加新设备,因为要重新编号;进程实际使用资源顺序可能和编号顺序不一致,导致资源浪费;必须按照递增序列申请资源,用户编程麻烦。
银行家算法
安全序列:如果按照这样的序列分配资源,则每个进程都能顺利完成,只要能找出一个安全序列,则系统就是安全状态,当然,安全序列可能有多个;
银行家算法核心思想:在资源分配之前先预判这次分配是否会导致系统进入不安全的状态,以此决定是否答应资源分配请求。 (迪杰斯特拉发明的哦)
可以把单维的数字拓展为多维的向量。
安全性算法步骤:检查当前剩余可用资源是否 能满足某个进程 的最大需求,如果可以,就把该进程加入安全序列,并把该进程持有的资源全部回收。
补充一个小小的绕口令:系统处于不安全状态未必死锁,但死锁状态一定是处于不安全状态,系统处于安全状态一定不会死锁。 (重理解,勤动手,做个题,就明白)
死锁的检测和解除
- 用某种数据结构来保存资源的请求和分配信息 (比如:图)
- 提供一种算法,利用上述的信息来检测是否已进入死锁状态
按上述的分析,最终可以消除所有的图的边,就称这个图是可完全简化的,此时一定没有发生死锁
检测死锁的算法:
- 在资源分配图中,找不到阻塞又不是孤点的进程,消去它所有的请求边和分配边,使之成为孤立的结点;开始唤醒另一个点(即进程)
- 死锁定理:如果某时刻系统的资源分配图是不可以完全简化的,那么此时的系统就是死锁的。
★简单来说就是——依次消除与不阻塞进程相连的边,直至没有相连的边 ★
解除死锁
- 资源剥夺法:暂时挂起(放到外存上)某些死锁进程,并抢占它的资源,将这些资源分配给其他死锁的进程,但是应防止被挂起的进程长时间得不到资源而饥饿。
- 撤销进程法/终止进程法:强制撤销,但可能会付出巨大代价
- 进程回退法:让一个或多个死锁进程回退到足以避免死锁的地步,需要记录进程的历史信息,设置还原点
选择更改哪个进程:进程的优先级;已执行多长时间;还有多久能完成;进程已经使用了多少资源;进程是交互的还是批处理式,优先牺牲批处理的;