操作系统大题复习
一、经典进程同步问题
1.1生产者和消费者
由三个进程get,copy和put以及两个缓冲区buffer1和buffer2完成一项输入/输出操作。进程get的功能是把一张卡片上的信息从读卡机上读进buffer1;进程copy的功能是把buffer1中的信息复制到buffer2;进程put的功能是取出buffer2中的信息并从打印机上打印输出。试用P、V操作完成这三个进程间的尽可能并发正确执行的关系(用程序或框图表示),并指明信号量的作用和初值。
分析:可设置6个信号量mutex1,mutex2,empty1,empty2,full1,full2。
mutex1和mutex2是互斥信号量,初值为1,分别用于对buffer1和buffer2的互斥访问;
empty1和empty2为同步信号量,初值为1,分别表示buffer1和buffer2是否空闲,1表示空闲,0表示不空闲;
full1和full2为同步信号量,初值为0,分别表示buffer1和buffer2中是否有可取用的信息,1表示有可取用的信息,0表示无可取用的信息。
semaphore mutex1, mutex2, empty1, empty2, full1, full2 ;
mutex1=mutex2=1; //互斥信号量
empty1=empty2=1; //生产者进程的同步信号量
full1=full2=0; //消费者进程的同步信号量
process get( ) //读进程(生产者进程)
{
while (1) {
从读卡机读入一张卡片的信息;
P(empty1); //看看buffer1是否空闲
P(mutex1); //互斥访问buffer1
将信息放入buffer1;
V(mutex1);
V(full1); //通知进程copy,buffer1中已有信息可取(若copy正在等待,则唤醒之)
}
}
process copy( ) //复制进程(既是消费者又是生产者进程)
{
while (1) {
P(full1) //看看buffer1是否有信息可取
P(mutex1); //互斥访问buffer1
从buffer1中复制出信息;
V(mutex1);
V(emtpy1); //通知get,buffer1中的信息已取走(可能唤醒get)
P(empty2); //看看buffer2是否空闲
P(mutex2); //互斥访问buffer2
将复制的信息放入buffer2;
V(mutex2);
V(full2); //通知put,buffer2中已有信息
}
}
process put( ){ //输出进程(消费者进程)
while (1) {
P(full2); //测试buffer2中是否有信息
P(mutex2); //互斥访问buffer2
从buffer2中取出信息;
V(mutex2);
V(empty2); //通知copy,buffer2中的信息已取走
}
}
1.2哲学家进餐
一张圆桌边上坐着五名哲学家,没两名哲学家之间的桌上摆着一根筷子,两根筷子之间时一碗米饭,哲学家不是吃饭就是思考。只有哲学家饥饿时,才试图拿起左右两根筷子(一根一根的拿起)。若筷子已在他人手上,则需要等待。饥饿的哲学家只有同时拿起两根筷子才能进餐,进餐完毕后,放下筷子继续思考。
初始方案
定义信号量数组chopstick[5] = {1,1,1,1,1} ,用于对 5 根筷子进行互斥访问。哲学家按顺序编号为0~4,哲学家 i 左边筷子编号为 i ,右边筷子编号为 (i+1)%5 。
semaphore chopstick[5] = {1,1,1,1,1};
philosopher(){
while(1){
p(chopstick[i]);
p(chopstick[(i+1)%5]);
吃饭;
v(chopstick[i]);
v(chopstick[(i+1)%5]);
思考;
}
}
方法一:至多允许 4 名哲学家同时进餐
至多只允许四位哲学家同时去拿左筷子,最终能保证至少有一位哲学家能进餐,并在用完后释放两只筷子供他人使用。设置一个初值为 4 的信号量 num,只允许 4 个哲学家同时去拿左筷子,这样就能保证至少有一个哲学家可以就餐,不会出现饿死和死锁的现象
semaphore chopstick[5] = {1,1,1,1,1};
semaphore num = 4;
philosopher(int i){
while(1){
p(num); //请求进餐
p(chopstick[i]); //拿左筷子
p(chopstick[(i+1)%5]); //拿右筷子
吃饭;
v(chopstick[i]);
v(chopstick[(i+1)%5]);
v(num);
思考;
}
}
方法二:仅当一名哲学家左右两边的筷子都可用时,才允许他抓起筷子
将拿左右筷子的操作看成一个连贯的操作,在其周围加上pv操作,从而保证拿起筷子的过程不被打断,保证同时持有左右两根筷子
semaphore chopstick[5] = {1,1,1,1,1};
semaphore mutex = 1;
philosopher(){
while(1){
p(mutex);
p(chopstick[i]);
p(chopstick[(i+1)%5]);
v(mutex);
吃饭;
v(chopstick[i]);
v(chopstick[(i+1)%5]);
思考;
}
}
方法三:对哲学家顺序编号,要求齐数号哲学家先拿起左边的筷子,然后再拿右边的筷子,而偶数号哲学家刚好相反
semaphore chopstick[5] = {1,1,1,1,1};
philosopher(int i){
while(1){
if(i % 2 != 0) { //若为奇数号哲学家
p(chopstick[i]); //先拿左筷子
p(chopstick[(i+1)%5]); //后拿右筷子
吃饭;
v(chopstick[i]);
v(chopstick[(i+1)%5]);
思考;
} else { //若为偶数号哲学家
p(chopstick[(i+1)%5]); //先拿右筷子
p(chopstick[i]); //后拿左筷子
吃饭;
v(chopstick[(i+1)%5]);
v(chopstick[i]);
思考;
}
}
}
1.3读者-写者
读者优先:为实现对共享文件的互斥访问,设置互斥信号量rw,初值为1;再设置一个整型变量count表示正在读的进程数目。
但这时有一个问题:第一个读进程进来时,需要对共享文件进行加锁,即if(count == 0) P(rw);,然后count++,这时第二个读进程进来,会触发P(rw),那么读进程便无法执行下去。
为此需要设置一个互斥信号量mutex来保证各个读进程对count的互斥访问
semaphore rw = 1, mutex = 1;
int count = 0;
void reader(){
while(TRUE){
wait(mutex);//保护count共享变量,在第二个读进程进来时,不会触发if判断
if(count == 0) wait(rw);//第一个读进程到达时,写进程不能进行
count++;
signal(mutex);
...
读文件
...
wait(mutex);
count--;
if(count == 0) signal(rw);//保证最后一个读进程读完时,释放rw
signal(mutex);
}
}
void writer(){
while(TRUE){
wait(rw);
写文件
signal(rw);
}
}
写者优先:一旦有写者到达,后续的读者必须等待。增加一个信号量,在写进程到达时封锁后续读进程
semaphore rw = 1, mutex = 1, w = 1;
int count = 0;
void reader(){
while(TRUE){
wait(w);
wait(mutex);//保护count共享变量,在第二个读进程进来时,不会触发if判断
if(count == 0) wait(rw);//第一个读进程到达时,写进程不能进行
count++;
signal(mutex);
signal(w);
...
读文件
...
wait(mutex);
count--;
if(count == 0) signal(rw);//保证最后一个读进程读完时,释放rw
signal(mutex);
}
}
void writer(){
while(TRUE){
wait(w);
wait(rw);
写文件
signal(rw);
signal(w);
}
}
二、进程调度算法
实例:从P1到P4有4个进程,每个进程的到达时间和运行时间如下表所示:
带权周转时间=周转时间/执行时间
周转时间=等待时间+执行时间
先来先服务(FCFS)调度算法
短作业(进程)优先调度算法SJ§F
非抢占:
抢占:
高响应比优先调度算法(HRRN)
按优先权=(等待时间+执行时间)/执行时间
优先权大的先执行。
时间片轮转法(RR)
用于分时系统中的进程调度。每次调度时,总是选择就绪队列的队首进程,让其在CPU上运行一个系统预先设置好的时间片。一个时间片内没有完成运行的进程,返回到绪队列末尾重新排队,等待下一次调度。
实例:
进程A、B、C、D需要运行的时间分别为20ms、10 ms、15 ms、5 ms,均在0时刻到达。到达的先后次序为A、B、C、D。如果时间片分别为1 ms和5ms,计算各个进程的带权周转时间和平均带权周转时间。
带权周转时间W,即:W = T/R。其中T为周转时间,R为实际运行时间。
平均带权周转时间为:
三、银行家算法(死锁问题)
假定系统中有五个进程{P0、P1、P2、P3、P4}和三类资源{A、B、C},各种资源分别是10、5、7,在T0时刻的资源分配情况如下图所示:回答以下两个问题。
MAX:进程所需资源。
Allocation:系统已经分配给进程的资源数。
Need:进程还需要的资源数; Need = MAX - Allocation。
Available:系统剩余的资源数; Available = Total - Allocation。
(1)判断T0时刻的安全性,即是否存在安全序列?
判断安全性时需要画出以下表格:
Work:当前所剩资源。
work + allocation:计算机处理完当前进程后所剩资源(小于系统所提供资源)。
如果work >=need 即可执行当前进程(Finish=true)。
如果每个进程Finish=true,即系统是安全的。
存在安全序列{P1, P3 , P4 ,P2 , P0}。
(2)若P1发出请求Request(1,0,2)后,系统能否将资源分配给它?
判断步骤:
①先判断请求是否≤所需: Request (1,0,2)≤Need (1,2,2)。
②判断请求的是否≤系统还剩的: Request (1,0,2)≤Available (3,3,2)。
③根据请求资源量进行分配(更改表)。
④列表计算是否存在安全序列。
存在安全序列{P1, P3 , P4 ,P2 , P0}。
建议做书上P128第30题。(西安电子科技大学汤小丹的计算机操作系统第四版)
四、存储器管理:基于顺序搜素的动态分区分配算法
首次(最先)适应算法(FF):
要求空闲分区按首址递增的次序组织空闲分区表(队列)。 每次分配和回收后空闲分区表或空闲 分区队列都要按首址递增的次序排序。
循环首次适应算法(next fit,nf):
按分区的先后次序,从上次分配的分区起查找(到最后分区时再回到开头),找到符合要求的第一个分区。
最佳适应算法(best fit,bf):
要求按空闲区大小递增的次序组成空闲分区表(队列)。分配和回收后要对空闲区表(队列)重新排序。
最坏适应算法(worst fit,wf):
要求空闲区按大小递减的顺序组织空闲区表(或队列)。
分别是首次,循环首次,最佳,最坏。
五、存储器管理-分页存储方式:计算物理地址和有效访问时间(EAT)
5.1计算物理地址:
在采用页式储存管理的系统中,某进程的逻辑地址空间为4页(每页2048字节)。已知该进程的页面映像表(页表)如下,计算有效逻辑地址4865所对应的物理地址。
求页号:d=4865%2048=2…769 (2为页号,769为块内地址)
对页表:即块号为6
计算地址: Physical address=6*2048+769=13057
5.2计算有效访问时间(EAT)
p是页面错误率,命中率=1-p。
六、存储器管理和进程调度,作业调度相结合
七、页面置换算法(虚拟存储器)
实例:
假定系统为某进程分配了三块物理页,并有以下页面:
7,0,1,2,0,3,0,4,2,3,0,3,2,1,2,0,1,7,0,1
Y表示缺页,N表示不缺页
OPT最佳置换算法:每次选择未来长时间不被访问的或者以后永不使用的页面进行淘汰。
页面走向 | 7 | 0 | 1 | 2 | 0 | 3 | 0 | 4 | 2 | 3 | 0 | 3 | 2 | 1 | 2 | 0 | 1 | 7 | 0 | 1 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
物理块 0 | 7 | 7 | 7 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 7 | 7 | 7 |
物理块 1 | 0 | 0 | 0 | 0 | 0 | 0 | 4 | 4 | 4 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
物理块 2 | 1 | 1 | 1 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | ||
缺页 | Y | Y | Y | Y | N | Y | N | Y | N | N | Y | N | N | Y | N | N | N | Y | N | N |
缺页次数 | 9 | |||||||||||||||||||
页面置换次数 | 6 | |||||||||||||||||||
缺页率 | 9/20 |
FIFO先进先出页面置换算法:把内存中最先进入的页面予以淘汰。
页面走向 | 7 | 0 | 1 | 2 | 0 | 3 | 0 | 4 | 2 | 3 | 0 | 3 | 2 | 1 | 2 | 0 | 1 | 7 | 0 | 1 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
物理块 0 | 7 | 7 | 7 | 2 | 2 | 2 | 2 | 4 | 4 | 4 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 7 | 7 | 7 |
物理块 1 | 0 | 0 | 0 | 0 | 3 | 3 | 3 | 2 | 2 | 2 | 2 | 2 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | |
物理块 2 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 3 | 3 | 3 | 3 | 3 | 2 | 2 | 2 | 2 | 2 | 1 | ||
缺页 | Y | Y | Y | Y | N | Y | Y | Y | Y | Y | Y | N | N | Y | Y | N | N | Y | Y | Y |
缺页次数 | 15 | |||||||||||||||||||
页面置换次数 | 12 | |||||||||||||||||||
缺页率 | 15/20 |
LRU最久未使用页面置换算法:选择最近且最久未被使用的页面进行淘汰。
页面走向 | 7 | 0 | 1 | 2 | 0 | 3 | 0 | 4 | 2 | 3 | 0 | 3 | 2 | 1 | 2 | 0 | 1 | 7 | 0 | 1 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
物理块 0 | 7 | 7 | 7 | 2 | 2 | 2 | 2 | 4 | 4 | 4 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
物理块 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 3 | 3 | 3 | 3 | 3 | 3 | 0 | 0 | 0 | 0 | 0 | |
物理块 2 | 1 | 1 | 1 | 3 | 3 | 3 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 7 | 7 | 7 | ||
缺页 | Y | Y | Y | Y | N | Y | N | Y | Y | Y | Y | N | N | Y | N | Y | N | Y | N | N |
缺页次数 | 12 | |||||||||||||||||||
页面置换次数 | 9 | |||||||||||||||||||
缺页率 | 12/20 |
八、磁盘调度算法
实例:
假定某磁盘共有200个柱面,编号为0-199,如果在为访问100号柱面的请求者服务后,同时有若干请求者在等待服务,它们每次要访问的柱面号为 55,58,39,18,90,160,150,38,184。求总寻道长度和平均寻道长度。总长=每次寻道距离之和,平均寻道长度=总长/移动次数。
先来先服务算法(FCFS):根据进程请求访问磁盘的先后次序进行调度。
最短寻道时间优先算法(SSTF):其要求访问的磁道与当前磁头所在的磁道距离最近,以使每次的寻道时间最短。
扫描算法(SCAN):扫描算法不仅考虑到欲访问的磁道与当前磁道的距离,更优先考虑的是磁头的当前移动方向。例如,当磁头正在自里向外移动时,扫描算法所选择的下一个访问对象应是其欲访问的磁道既在当前磁道之外,又是距离最近的。这样自里向外地访问,直到再无更外的磁道需要访问才将磁臂换向,自外向里移动。
循环扫描算法(CSCAN):循环扫描算法规定磁头单向移动。例如,只自里向外移动,当磁头移到最外的被访问磁道时,磁头立即返回到最里的欲访磁道,即将最小磁道号紧接着最大磁道号构成循环,进行扫描。