在多道程序正常运行过程中,属于不同进程的页面被分散存放在内存页框中,当发生缺页异常时,如果已无空闲页框,系统要选择一个驻留页面进行淘汰。在此讨论的是所有驻留页面都可作为置换对象的情况,而不管页面所属进程的全局页面置换算法。
当要调入一页而必须淘汰旧页时,应该淘汰以后不再访问的页,或距现在最长时间后才访问的页。
然而,程序页面引用串是无法预知的,不可能对程序的运行过程做出精确断言,不过此理论算法可用做衡量各种具体算法的标准。
实现方法:
P[0]
,P[1]
,…,P[m-1]
,P[i](i=0,1,.,m-1)
存储一个装入内存中的页面的页号。k
指示当前调入新页时应淘汰页在页号表中的位置,P[k]
。P[k]=新页的页号,k=(h+1)%m
就可以了,Belady异常,分配给进程的页框数目边多,缺页次数反而也跟着增多。
为了能准确地淘汰最近最少使用的页面,必须维护一个特殊队列页面淘汰队列,此队列存放当前在内存中的所有页号,每访问一页时就调整一次,使队列尾总是指向最近访问的页,队列头就是最近最少使用的页,显然,发生缺页异常时总是淘汰队列头所指页面;而执行页面访问后,需要从队列中把此页调整到队列尾。
LRU算法的实现需要硬件支持,关键是确定页面最后访问以来所经历的时间,可采用多种模拟方法。
FIFO算法会把经常使用的页面淘汰掉,为了避免这一点,可对算法进行改造,把FIFO算法与页表中的“引用位”结合起来使用,实现思想如下:首先检查FIFO页面队列中的队首,这是最早进入内存的页面,
如果其“引用位”是0,那么,这个页面最早进入并且最长时间未被使用,选择此页面淘汰;
如果其“引用位”是1 ,说明虽然它进人内存时间较早,但最近仍在使用,于是将其“引用位”清0,并把这个页面移至队尾,把它看做个新调入的页,再给一次机会。
这一算法称为第二次机会页面替换算法( Second Chance Replacement ,SCR),其含义是最先进入内存的页面如果最近还在被使用(其“引用位”总保持为1),仍然有机会像新调入页面一样留在内存中。如果内存中的页面都被访问过,即它们的“引用位”均为1 ,那么,第一遍检查把所有页面的“引用位”清0,第
二遍又找出队首,并把此页面淘汰,此时,SCR算法便退化为FIFO算法。
如果利用标准队列机制构造FIFO队列,SCR 算法可能产生频繁的出队和人队,实现代价较高。作为SCR的一种改进,可采用循环队列机制构造页面队列,形成类似于钟表面的环形表,队列指针相当于钟表表针,指向可能要淘汰的页面,这就是时钟页面替换算法(Clock policy replacement, Clock)的得名。此算法与SCR算法在本质上没有区别,仅仅是实现方法有所改进,仍要使用页表中的“引用位”,把进程已调人内存的页面链接成循环队列,用指针指向循环队列中下一个将被替换的页面。
设系统中有3种类型的资源(A、B、C)和4个进程(P1、P2、P3、P4),A资源的总量为9,B资源的总量为3,C资源的总量为6,在T0时刻系统状态如下表所示,系统采用银行家算法实施死锁避免策略。
试问:T0时刻的各资源剩余数量为多少?T0时刻是否为安全状态?若是,请给出安全序列。
做题视频
答:
A | B | C |
---|---|---|
1 | 1 | 2 |
need | A | B | C |
---|---|---|---|
P1 | 2 | 2 | 2 |
P2 | 1 | 0 | 2 |
P3 | 1 | 0 | 3 |
P4 | 4 | 2 | 1 |
P2,P3,P1,P4
设系统中有4中类型的资源(A、B、C、D)和5个进程(P0、P1、P2、P3、P4),A资源的总量为3,B资源的总量为12,C资源的总量为14,D资源的总量为14。在T0时刻系统中个资源使用情况的状态如下表所示,系统采用银行家算法实施死锁避免策略。
试问:T0时刻的各资源剩余数量为多少?T0时刻的是否为安全状态?若是,请给出其中可能的一种安全序列,并依照该序列,写出各资源的回收步骤。(6分)
“先来先服务”算法(FCFS)
磁盘臂随机移动。
“电梯调度”算法
每次总是选择沿移动方向最近的那个柱面,若同一柱面上有多个请求,还需进行旋转优化。
“最短查找时间优先”算法
先执行查找时间最短的请求。
“扫描”算法
移动臂每次沿一个方向移动,扫过所有柱面,遇到最近的I/O请求便进行处理,直到到达最后一个柱面后,再向相反的方向移动回来。
“循环扫描”算法
上述扫描算法中,到达最后一个柱面后,不是反方向,而是同一方向从另一端开始扫描。
中断,中断就是遇到紧急事件时,系统暂时停止对当前程序的执行,转去执行紧急程序,待执行完毕后,再返回执行之前的程序或调度其他应用程序的过程。
中断如异常,时钟中断等
异常(内中断),指当前运行指令引起的中断时间,如地址异常,算术异常等
操作系统,计算机最基础的系统软件,管理软硬件资源,控制程序执行,改善人机界面,合理组织计算机流程,为用户使用计算机提供良好的运行环境,操作系统是计算机系统资源的管理者,用户与计算机之间的接口,扩充机器,操作系统的特征是并发,共享,虚拟,异步。
操作系统的基础抽象是进程,虚存,文件。其中进程是处理器的抽象,虚存是内存的抽象,文件是设备的抽象。
信号量机制能用于解决进程间的各种同步问题。
例:进程P2中的语句y要使用进程P1中语句x的运行结果
解:只有当语句x执行完成之后语句y才可以执行。
所以要设置一个信号用来两个进程传递信息(x执行完成的信息)
设S为实现进程P1、P2 同步的公共信号量,初值为0。
/**
* P1说:P2你收到我发的信号再执行,所以P2一直等P1发信号
*/
semaphore S=0;
p1() {
...
x;
V(S); //通知p2我执行完了语句x
...
}
p2() {
...
P(S);
y;
...
}
若P2先执行到P(S)时, S为0,执行P操作会把进程P2阻塞,并放入阻塞队列;
当进程P1中的x执行完后,执行V操作,把P2从阻塞队列中放回就绪队列,当P2得到处理机时,就得以继续执行。
信号量机制也能很方便地解决进程互斥问题。
例:每次只允许一个进程进入临界区
告诉别人不用(使1→0)
和等待别人不用(等0→1)
告诉别人可以用(使0→1)
semaphore S=1;
P1() {
...
P(S);
进程P1的临界区;
V(S);
...
}
P2() {
...
P(S);
进程P2的临界区;
V(S);
...
}
当没有进程在临界区时,任意一个进程要进入临界区,就要执行P操作,把S的值减为0,然后进入临界区;
当有进程存在于临界区时,S的值为0,再有进程要进入临界区,执行P操作时将会被阻塞,直至在临界区中的进程退出,这样便实现了临界区的互斥。
互斥是不同进程对同一信号量进行P、V操作实现的,一个进程成功对信号量执行了P操作后进入临界区,并在退出临界区后,由该进程本身对该信号量执行V操作,表示当前没有进程进入临界区,可以让其他进程进入。
下面简单总结一下PV操作在同步互斥中的应用:
在同步问题中,若某个行为要用到某种资源,则在这个行为前面P这种资源一下;
若某个行为会提供某种资源,则在这个行为后面V这种资源一下。
在互斥问题中,P、V操作要紧夹使用互斥资源的那个行为,中间不能有其他冗余代码。
semaphore a1=a2=b1=b2=c=d=e=0;
S1() {
...
V(a1);
V(a2);
}
S2() {
P(a1);
...
V(b1);
V(b2);
}
S3() {
P(a2);
...
V(c);
}
S4() {
P(b1);
...
V(d);
}
S5() {
P(b2);
...
V(e);
}
S6() {
P(c);
P(d);
P(e);
...
}
找出问题中的进程数,并分析它们之间的同步和互斥关系。
同步、互斥、前驱关系直接按照上面例子中的经典范式改写。
找出解决问题的关键点,并根据做过的题目找出求解的思路。
根据进程的操作流程确定P操作、V操作的大致顺序。
根据上面的两步,设置需要的信号量,确定初值,完善整理。
一组生产者进程和一组消费者进程共享一个初始为空、大小为n的缓冲区,
只有缓冲区没满时,生产者才能把消息放入缓冲区,否则必须等待;
只有缓冲区不空时,消费者才能从中取出消息,否则必须等待。
由于缓冲区是临界资源,它只允许一个生产者放入消息,或一个消费者从中取出消息。.
关系分析
生产者和消费者对缓冲区互斥访问是互斥关系,
同时生产者和消费者又是一个相互协作的关系,
只有生产者生产之后,消费者才能消费,它们也是同步关系。
整理思路
这里比较简单,只有生产者和消费者两个进程,
正好是这两个进程存在着互斥关系和同步关系。
那么需要解决的是互斥和同步PV操作的位置。
信号量设置。
信号量mutex作为互斥信号量,用于控制互斥访问缓冲池,互斥信号量初值为1;
信号量full用于记录当前缓冲池中的“满”缓冲区数,初值为0。
信号量empty用于记录当前缓冲池中的“空”缓冲区数,初值为n。
semaphore mutex=1;
semaphore empty=n;
semaphore full=0;
producer() {
while(1) {
produce an item in nextp;
P(empty);
P(mutex);
add nextp to buffer;
V(mutex);
V(full);
}
}
consumer() {
while(1) {
P(full);
P(mutex);
remove an item from buffer;
V(mutex);
V(empty);
consume the item;
}
}
该类问题要注意对缓冲区大小为n的处理,当缓冲区中有空时,便可对empty变量执行P操作,一旦取走一个产品便要执行V操作以释放空闲区。
对empty和full变量的P操作必须放在对mutex的P操作之前。
若生产者进程先执行P(mutex),然后执行P(empty),消费者执行P(mutex),然后执行P(full), 这样可不可以?
答案是否定的。设想生产者进程已将缓冲区放满,消费者进程并没有取产品,即empty=0,当下次仍然是生产者进程运行时,它先执行P(mutex)封锁信号量,再执行P(empty)时将被阻塞,希望消费者取出产品后将其唤醒。轮到消费者进程运行时,它先执行P(mutex),然而由于生产者进程已经封锁mutex信号量,消费者进程也会被阻塞,这样一来生产者、消费者进程都将阻塞,都指望对方唤醒自己,因此陷入了无休止的等待。同理,若消费者进程已将缓冲区取空,即full=0,下次若还是消费者先运行,也会出现类似的死锁。
不过生产者释放信号量时,mutex、 full 先释放哪一个无所谓,消费者先释放mutex或empty都可以。
桌子上有一个盘子,每次只能向其中放入一个水果。
爸爸专向盘子中放苹果,妈妈专向盘子中放橘子,
儿子专等吃盘子中的橘子,女儿专等吃盘子中的苹果。
只有盘子为空时,爸爸或妈妈才可向盘子中放一个水果;
仅当盘子中有自己需要的水果时,儿子或女儿可以从盘子中取出。
关系分析
这里的关系要稍复杂一些。由每次只能向其中放入一只水果可知,爸爸和妈妈是互斥关系。
爸爸和女儿、妈妈和儿子是同步关系,而且这两对进程必须连起来,
儿子和女儿之间没有互斥和同步关系,因为他们是选择条件执行,不可能并发。
整理思路
这里有4个进程,实际上可抽象为两个生产者和两个消费者被连接到大小为1的缓冲区上。
信号量设置
首先将信号量plate设置互斥信号量,表示是否允许向盘子放入水果,初值为1表示允许放入,且只允许放入一个。
信号量apple表示盘子中是否有苹果,初值为0表示盘子为空,不许取,apple=1表示可以取。
信号量orange表示盘子中是否有橘子,初值为0表示盘子为空,不许取,orange = 1表示可以取。
semaphore plate = 1, apple = 0, orange = 0;
dad() {
while(1) {
prepare an apple;
P(plate);
put the apple on the plate;
V(apple);
}
}
mom() {
while(1) {
prepare an orange;
P(plate);
put the orange on the plate;
V(orange);
}
}
son() {
while(1) {
P(orange);
take an orange from the plate;
V(plate);
eat the orange;
}
}
daughter() {
while(1) {
P(apple);
take an apple from the plate;
V(plate);
eat the apple;
}
}
有读者和写者两组并发进程,共享一个文件,当两个或以上的读进程同时访问共享数据时不会产生副作用,但若某个写进程和其他进程(读进程或写进程)同时访问共享数据时则可能导致数据不一致的错误。
因此要求:
①允许多个读者可以同时对文件执行读操作;
②只允许一个写者往文件中写信息;
③任一写者在完成写操作之前不允许其他读者或写者工作;
④写者执行写操作前,应让已有的读者和写者全部退出。
关系分析
由题目分析读者和写者是互斥的,写者和写者也是互斥的,而读者和读者不存在互斥问题。
整理思路
两个进程,即读者和写者。 写者是比较简单的,它和任何进程互斥,用互斥信号量的P操作、V操作即可解决。
读者的问题比较复杂,它必须在实现与写者互斥的同时,实现与其他读者的同步,因此简单的一对P操作、V操作是无法解决问题的。
这里用到了一个计数器,用它来判断当前是否有读者读文件。当有读者时,写者是无法写文件的,此时读者会一直占用文件,当没有读者时,写者才可以写文件。同时,这里不同读者对计数器的访问也应该是互斥的。
信号量设置。
首先设置信号量count为计数器,用于记录当前读者的数量,初值为0;
设置mutex为互斥信号量,用于保护更新count变量时的互斥;
设置互斥信号量rw,用于保证读者和写者的互斥访问。
int count = 0;//区分读-读和读-写,只有当count=0时读写才有关系
semaphore mutex = 1;//互斥访问count?
semaphore rw = 1;//实现写-读互斥、写-写互斥
writer() {
while(1) {
P(rw);
writing;
V(rw);
}
}
reader() {
while(1) {
P(mutex);
if(count == 0)
P(rw);
count++;
V(mutex);
reading;
P(mutex);
count--;
if(count == 0)
V(rw);
V(mutex);
}
}
int count = 0;
semaphore mutex = 1;
semaphore rw = 1;
semaphore w = 1;
writer() {
while(1) {
P(w);//等待没有写进程访问缓冲区
P(rw);
writing;
V(rw);
V(w);
}
}
reader() {
while(1) {
P(w);
P(mutex);
if(count == 0)
P(rw);
count++;
V(mutex);
V(w);
reading;
P(mutex);
count--;
if(count == 0)
V(rw);
V(mutex);
}
}
一张圆桌边上坐着5名哲学家,每两名哲学家之间,的桌上摆一根筷子,两根筷子中间是一碗米饭。
哲学家们倾注毕生精力用于思考和进餐,哲学家在思考时,并不影响他人。
只有当哲学家饥饿时,才试图拿起左、右两根筷子(一根一根地拿起)。
若筷子已在他人手上,则需要等待。
饥饿的哲学家只有同时拿到了两根筷子才可以开始进餐,进餐完毕后,放下筷子继续思考。
关系分析
5名哲学家与左右邻居对其中间筷子的访问是互斥关系。
整理思路
显然,这里有5个进程。 本题的关键是如何让一名哲学家拿到左右两根筷子而不造成死锁或饥饿现象。
解决方法有两个:
一是让他们同时拿两根筷子;
二是对每名哲学家的动作制定规则,避免饥饿或死锁现象的发生。
信号量设置
定义互斥信号量数组chopstick[5]={1,1,1,1,1},用于对5个筷子的互斥访问。
哲学家按顺序编号为0-4,哲学家i左边筷子的编号为i,哲学家右边筷子的编号为(i + 1)%5。
semaphore chopstick[5] = {1,1,1,1,1};
Pi() {
do {
P(chopstick[i]);
P(chopstick[(i+1)%5]);
eat;
V(chopstick[i]);
V(chopstick[(i+1)%5]);
think;
}while(1);
}
semaphore chopstick[5] = {1,1,1,1,1};
semaphore mutex = 1;//互斥访问桌子,一个人拿筷子时其他人都不能拿
Pi() {
do {
P(mutex);
P(chopstick[i]);
P(chopstick[(i+1)%5]);
V(mutex);
eat;
V(chopstick[i]);
V(chopstick[(i+1)%5]);
think;
}while(1);
}
假设一个系统有三个抽烟者进程和一个供应者进程。
每个抽烟者不停地卷烟并抽掉它,但要卷起并抽掉一支烟,抽烟者需要有三种材料:烟草、纸和胶水。
三个抽烟者中,第一个拥有烟草,第二个拥有纸,第三个拥有胶水。
供应者进程无限地提供三种材料,供应者每次将两种材料放到桌子上,拥有剩下那种材料的抽烟者卷一根烟并抽掉它,
并给供应者一个信号告诉已完成,此时供应者就会将另外两种材料放到桌上,如此重复(让三个抽烟者轮流地抽烟)。
关系分析。
供应者与三个抽烟者分别是同步关系。
由于供应者无法同时满足两个或以上的抽烟者,三个抽烟者对抽烟这个动作互斥(或由三个抽烟者轮流抽烟得知)。
整理思路。
显然这里有4个进程。供应者作为生产者向三个抽烟者提供材料。
信号量设置。
信号量offer1、offer2、offer3分别表示烟草和纸组合的资源、烟草和胶水组合的资源、纸和胶水组合的资源。
信号量finish用于互斥进行抽烟动作。
semaphore S[3] = {0,0,0};
semaphore finish = 0;
int random;
process pp() {
while(1) {
random = 任意一个整数随机数;
random =random%3;
V(S[random]);
P(finish);
}
}
process p0() {
while(1) {
P(S[0]);
consume;
V(finish);
}
}
process p1() {
while(1) {
P(S[1]);
consume;
V(finish);
}
}
process p2() {
while(1) {
P(S[2]);
consume;
V(finish);
}
}
理发店有一位理发师、一把理发椅和n把等候理发的顾客坐的椅子,如果没有顾客,理发师便在理发椅上睡觉,一个顾客到来时,它必须叫醒理发师。如果理发师正在理发时又有顾客到来,则如果有空椅子可坐,就坐下来等待,否则就离开。
/*
等待位置是竞争资源设为n+1;
理发资源设为0;
*/
semphaore w_cheats=n+1;
semaphore b_cheats=0;
process barber() {
while(true) {
V(b_cheats);
if(w_cheats==n+1)
sleep();
}
}
process customer() {
if(b_cheats==0)
leave();
else {
P(w_cheats);
P(b_cheats);
be_cut();
V(w_cheats);
}
}
/*
*/
int waiting=0;
int chairs=n;
semaphore customes,barbers,mutex;
customers=0;barber=0;mutex=1;
process barber() {
while(true) {
P(customer);
P(mutex);
waiting--;
V(barbers);
V(mutex);
cut_hair();
}
}
process customer() {
P(mutex);
if(waiting<chairs) {
waiting++;
V(customes);
V(mutex);
P(barbers);
get_haircut();
}
else
V(mutex);
}
有n个进程将字符逐个读入到一个容量为80的缓冲区中(n>1),当缓冲区满后,由输出进程Q负责一次性取走这80个字符。这种过程循环往复,请用信号量和P、V操作写出n个读入进程(P1,P2,…,Pn)和输出进程Q能正确工作的动作序列。
n个输入进程;
缓冲区为80;//等满
一个输出进程Q
semaphore empty=0,full=0,mutex=1;
int count=80;
void input(i=1,2,...,n) {
while(true) {
P(mutex);
if(count==0) {
V(full);
P(empty);
}
else
count--;
V(mutex);
输入;
}
}
void output() {
while(true) {
P(full);
读出所有数据;
count=80;
V(empty);
}
}
var mutex,empty,full:semaphore;
count,in:integer;
buffer:array[0...79]of char;
mutex:=1;empty=80;full=0;
count=0;in=0;
process Pi(i=1,...,n)
begin
L:
读入一字符到x;
P(empty);
P(mutex);
Buffer[in]=x;
in=(in+1)%80;
count++;
if(count==80) {
count=0;V(mutex);V(full);
}
else V(mutex);
goto L;
end;
process Q
begin
while(true) {
P(full);
P(mutex);
for(int j=0;j<80;j++)
read buffer[j];
in:=0;
V(mutex);
for(int j=0;j<80;j++)
V(empty);
}
goto L;
end;
有五个哲学家围坐在一圆桌旁,桌中央有一盘通心面,每人面前有一只空盘子,每两人之间放一把叉子。每个哲学家思考、饥饿、然后吃通心面。为了吃面,每个哲学家必须获得两把叉子,且没人只能直接从自己左边或右边取叉子。
semaphore fork[5];
for(int i=0;i<5;i++)
fork[i]=1;
cobegin
process philosopher_i() {
while(true) {
think();
P(fork[i]);
P(fork[(i+1)%5]);
eat();
V(fork[i]);
V(fork[(i+1)%5]);
}
}
coend
(1)至多允许四个哲学家同时取叉子
semaphore fork[5];
for(int i=0;i<5;i++)
fork[i]=1;
semaphore room=4;
cobegin
process philosopher_i() {
while(true) {
think();
P(room);
P(fork[i]);
P(fork[(i+1)%5]);
eat();
V(fork[i]);
V(fork[(i+1)%5]);
V(room);
}
}
coend
(2)奇数号先取左边的叉子,偶数号先取右手边的叉子。
void philosopher(int i)
{
if i mod 2 == 0 then
{
P(fork[i]);
P(fork[(i+1)mod 5]);
eat();
V(fork[i]);
V(fork[(i+1)mod 5]);
}
else
{
P(fork[(i+1)mod 5]);
P(fork[i]);
eat();
V(fork[(i+1)mod 5]);
V(fork[i]);
}
}
(3)一次性拿到两个叉子。
semaphore fork[5];
for(int i=0;i<5;i++)
fork[i]=1;
semaphore mutex=1;
cobegin
process philosopher_i() {
while(true) {
think();
P(mutex);
P(fork[i]);
P(fork[(i+1)%5]);
V(mutex);
eat();
V(fork[i]);
V(fork[(i+1)%5]);
}
}
coend
有一个铁笼子,每次只能放入一个动物。猎手向笼中放入老虎,农夫向笼中放入羊;动物园等待取笼中的老虎,饭店等待取笼中的羊。请用P、V操作原语写出同步执行的程序。
semaphore scage=1;
semaphore stiger=0;
semaphore ssheep=0;
void hunter() {
while(true) {
P(scage);
将虎放入笼中;
V(stiger);
}
}
void peasant() {
while(true) {
P(scage);
将羊放入笼中;
V(ssheep);
}
}
void zoo() {
while(true) {
P(stiger);
将虎从笼中取出;
V(scage);
}
}
void hotel() {
while(true) {
P(ssheep);
将羊从笼中取出;
V(scage);
}
}
void main() {
parbegin(hunter,peasant,zoo,hotel);
}
/*
铁笼子=1;
羊=0;
老虎=0;
*/
void hunter() {
while(true) {
P(longzi);
V(tiger);
}
}
void farmer() {
while(true) {
P(longzi);
V(sheep);
}
}
void zoo() {
while(true) {
P(tiger);
}
}
void cantoon() {
while(true) {
P(sheep);
}
}
某大型银行办理人民币存储业务,由n个储蓄员负责。每个顾客进入银行后先至取号机取一个号,并且在等待取找到空沙发坐下等着叫号。取号机给出的号码依次递增,并假定有足够多的空沙发容纳顾客。当一个存储员空闲下来,就叫下一个号。请用信号量和P,V操作正确编写储蓄员进程和顾客进程的程序。
n个储蓄员;
多个顾客m;
号码order=0;
process customer() {
V(顾客);
等待;m++;
P(业务员);
被处理;
order++;
}
process serverI() {
while(true) {
P(顾客);
P(next);
处理业务;
V(储蓄员);
}
}
var customer_count,server_count,mutex:semaphore;
customer_count:=0;server_count:=n;
mutex:=1;
process customeri(i=1,2,...)
begin
take a number;
P(mutex);
等待区找到空沙发坐下;
V(mutex);
V(customer_count);
P(server_count);
end;
proces serversj(j=1,2,3,...)
begin
L:
P(customer_count);
P(mutex);
被呼号顾客离开沙发走出等待区;
V(mutex);
为该号客人服务;
客人离开;
V(server_count);
go to L;
end;