写在前面:
- 本系列笔记主要以《计算机操作系统(汤小丹…)》为参考,大部分内容出于此书,笔者的工作主要是挑其重点展示,另外配合下方视频链接的教程展开思路,在笔记中一些比较难懂的地方加以自己的一点点理解(重点基本都会有标注,没有任何标注的难懂文字应该是笔者因为强迫症而加进来的,可选择性地忽略)。
- 视频链接:操作系统(汤小丹等第四版)_哔哩哔哩_bilibili
一、生产者-消费者问题
1、利用记录型信号量解决生产者-消费者问题
(1)假定在生产者和消费者之间的公用缓冲池中具有n个缓冲区,这时可利用互斥信号量mutex实现诸进程对缓冲池的互斥使用,利用信号量empty和full分别表示缓冲池中空缓冲区和满缓冲区的数量。又假定这些生产者和消费者相互等效,只要缓冲池未满,生产者便可将消息送入缓冲池;只要缓冲池未空,消费者便可从缓冲池中取走一个消息。
int in = 0, out = 0;
item buffer[n];
semaphore mutex = 1, empty = n, full = 0;
void producer()
{
do
{
producer an item nextp; //生产产品
…
wait(empty); //缓冲池容量-1
wait(mutex); //占用缓冲池(进入临界区)
buffer[in] = nextp; //缓冲池装货
in = (in+1)%n; //修改生产者进程指向缓冲池的指针
signal(mutex); //释放缓冲池(离开临界区)
signal(full); //缓冲池货物量+1
}while(TRUE);
}
void consumer()
{
do
{
wait(full); //缓冲池货物量-1
wait(mutex); //占用缓冲池(进入临界区)
nextc = buffer[out]; //缓冲池出货
out = (out+1)%n; //修改消费者进程指向缓冲池的指针
signal(mutex); //释放缓冲池(离开临界区)
signal(empty); //缓冲池容量+1
consumer the item in nextc; //消费产品
…
}while(TRUE);
}
(2)注意事项:
①在每个程序中用于实现互斥的wait(mutex)和signal(mutex)必须成对地出现。
②对资源信号量empty和full的wait和signal操作,同样需要成对地出现,但它们分别处于不同的程序中。例如,wait(empty)在计算(生产者)进程中,而signal(empty)则在打印(消费者)进程中,计算进程若因执行wait(empty)而阻塞,则以后将由打印进程将它唤醒。
③在每个程序中的多个wait操作顺序不能颠倒。应先执行对资源信号量的wait操作,然后再执行对互斥信号量的wait 操作,否则可能引起进程死锁。
2、利用AND信号量解决生产者-消费者问题
对于生产者-消费者问题,也可利用AND信号量来解决,即用Swait(empty, mutex)来代替wait(empty)和wait(mutex),用Ssignal(mutex, full)来代替signal(mutex)和signal(full);用Swait(full, mutex)代替wait(full)和wait(mutex),以及用Ssignal(mutex, empty)代替Signal(mutex)和Signal(empty)。
int in = 0, out = 0;
item buffer[n];
semaphore mutex = 1, empty = n, full = 0;
void producer()
{
do
{
producer an item nextp; //生产产品
…
Swait(empty, mutex); //缓冲池容量-1,占用缓冲池(进入临界区)
buffer[in] = nextp; //缓冲池装货
in = (in+1)%n; //修改生产者进程指向缓冲池的指针
Ssignal(mutex, full); //释放缓冲池(离开临界区),缓冲池货物量+1
}while(TRUE);
}
void consumer()
{
do
{
Swait(full, mutex); //缓冲池货物量-1,占用缓冲池(进入临界区)
nextc = buffer[out]; //缓冲池出货
out = (out+1)%n; //修改消费者进程指向缓冲池的指针
Ssignal(mutex, empty); //释放缓冲池(离开临界区),缓冲池容量+1
consumer the item in nextc; //消费产品
…
}while(TRUE);
}
二、哲学家进餐问题
1、问题描述
有五个哲学家共用一张圆桌,分别坐在周围的五张椅子上,在圆桌上有五个碗和五只筷子,他们的生活方式是交替地进行思考和进餐。平时,一个哲学家进行思考,饥饿时便试图取用其左右最靠近他的筷子,只有在他拿到两只筷子时才能进餐;进餐毕,放下筷子继续思考。
根据以上规则可以得出,相邻两位哲学家不能同时进餐,最多只能有两人同时进餐。
2、用记录型信号量解决哲学家进餐问题
(1)放在桌子上的筷子是临界资源,在一段时间内只允许一个哲学家使用。为实现对筷子的互斥使用,用一个信号量表示一只筷子,五个信号量构成信号量数组。
semaphore chopstick[5] = {1, 1, 1, 1, 1}; //筷子数组
void eater_i() //第i位哲学家的活动
{
do
{
wait(chopstick[i]); //拿起左边的筷子
wait(chopstick[(i+1)%5]); //拿起右边的筷子
… //只要少一根筷子都吃不上饭,要将自己阻塞起来等筷子
//吃饭
…
signal(chopstick[i]); //放下左边的筷子
signal(chopstick[(i+1)%5]); //放下右边的筷子
…
//思考
…
}while(TRUE)
}
(2)假如五位哲学家同时饥饿而各自拿起左边的筷子时,就会使五个信号量chopstick均为0,当他们再试图去拿右边的筷子时,都将因无筷子可拿而无限期地等待,从而进入死锁。对于这样的死锁问题,可采取以下几种解决方法:
①至多只允许有四位哲学家同时去拿左边的筷子,最终能保证至少有一位哲学家能够进餐,并在用毕时能释放出他用过的两只筷子,从而使更多的哲学家能够进餐。
semaphore chopstick[5] = {1, 1, 1, 1, 1}; //筷子数组
semaphore count = 4; //最多允许4位哲学家拿左边的筷子
void eater_i() //第i位哲学家的活动
{
do
{
wait(count); //“举手”示意要拿起左边的筷子
wait(chopstick[i]); //拿起左边的筷子
wait(chopstick[(i+1)%5]); //拿起右边的筷子
… //只要少一根筷子都吃不上饭,要将自己阻塞起来等筷子
//吃饭
…
signal(chopstick[i]); //放下左边的筷子
signal(chopstick[(i+1)%5]); //放下右边的筷子
…
//思考
…
}while(TRUE)
}
②仅当哲学家的左、右两只筷子均可用时,才允许他拿起筷子进餐,否则一根筷子都不能拿。
semaphore chopstick[5] = {1, 1, 1, 1, 1}; //筷子数组
void eater_i() //第i位哲学家的活动
{
do
{
Swait(chopstick[i] ,chopstick[(i+1)%5]); //同时拿起两边的筷子
… //只要少一根筷子都吃不上饭,要将自己阻塞起来等筷子
//吃饭
…
Ssignal(chopstick[i], chopstick[(i+1)%5]); //同时放下两边的筷子
…
//思考
…
}while(TRUE)
}
③规定奇数号哲学家先拿他左边的筷子,然后再去拿右边的筷子,而偶数号哲学家则相反。按此规定,将是1、2号哲学家竞争1号筷子,3、4号哲学家竞争3号筷子,即五位哲学家都先竞争奇数号筷子,获得后,再去竞争偶数号筷子,最后总会有一位哲学家能获得两只筷子而进餐。
semaphore chopstick[5] = {1, 1, 1, 1, 1}; //筷子数组
void eater_i() //第i位哲学家的活动
{
do
{
if(i % 2 == 1) //奇数号哲学家
{
wait(chopstick[i]); //拿起左边的筷子
wait(chopstick[(i+1)%5]); //拿起右边的筷子
}
else //偶数号哲学家
{
wait(chopstick[(i+1)%5]); //拿起右边的筷子
wait(chopstick[i]); //拿起左边的筷子
}
…
//吃饭
…
signal(chopstick[i]); //放下左边的筷子
signal(chopstick[(i+1)%5]); //放下右边的筷子
…
//思考
…
}while(TRUE)
}
3、利用AND信号量解决哲学家进餐问题
在哲学家进餐问题中,要求每个哲学家先获得两个临界资源(筷子)后方能进餐,这在本质上就是前面所介绍的AND同步问题(也就是上面解决死锁的第二种方法),故用AND信号量机制可获得最简洁的解法。
三、读者-写者问题
1、问题描述
一个数据文件或记录可被多个进程共享,把只要求读该文件的进程称为“Reader进程”,其它进程则称为“Writer进程”。允许多个进程同时读一个共享对象,因为读操作不会使数据文件混乱;但不允许一个Writer进程和其它Reader进程或Writer进程同时访问共享对象,因为这种访问将会引起混乱。
所谓“读者-写者问题”是指保证一个Writer 进程必须与其它进程互斥地访问共享对象的同步问题。
2、用记录型信号量解决读者-写者问题
为实现Reader与Writer进程间在读或写时的互斥,设置一个互斥信号量wmutex,另外再设置一个整型变量readcount表示正在读的进程数目。
①只要有一个Reader进程在读,便不允许Writer进程去写,因此仅当readcount=0,表示尚无Reader进程在读时,Reader进程才需要执行wait(wmutex)操作,若wait(wmutex)操作成功,Reader进程便可去读,相应地,做readcount+1操作。
②仅当Reader进程在执行了readcount减1操作后其值为0时,才须执行signal(wmutex)操作,以便让Writer进程写操作。
③因为readcount是一个可被多个Reader进程访问的临界资源,因此也应该为它设置一个互斥信号量rmutex。
semaphore rmutex = 1, wmutex = 1;
void Reader()
{
do
{
wait(rmutex);
if(readcount == 0) //如果该进程是第一个读文件的,要负责给写进程上锁
wait(wmutex); //如果文件正在被写进程访问,读进程将会自我阻塞
readcount++; //读者+1
signal(rmutex);
…
perform read operation; //读操作
…
wait(rmutex);
readcount--; //读者-1
if(readcount == 0) //如果该进程是最后一个读完文件的,要负责给写进程解锁
signal(wmutex);
signal(rmutex);
}while(TRUE)
}
void Writer()
{
do
{
wait(wmutex); //检查是否被上锁(未上锁则上锁后开始读)
perform write operation; //写操作
signal(wmutex); //解锁,其它进程可以进行读写
}while(TRUE)
}
3、利用AND信号量解决读者-写者问题
增加一个限制,最多只允许R个读者同时读,为此又引入一个信号量L,并赋予其初值为RN,通过执行wait(L,1,1)操作来控制读者的数目。
①每当有一个读者进入时,就要先执行wait(L,1,1)操作,使L的值减1。
②当有RN个读者进入读后,便减为0,第RN+1个读者要进入读时,必然会因wait(L,1,1)操作失败而阻塞。
int RN;
Semaphore L = RN, mx = 1; //mx用来标志文件是否有写进程访问
void Reader()
{
do
{
Swait(L, 1, 1); //文件被本进程读,允许读的读者-1
Swait(mx, 1, 0); //没有写进程访问文件则可以读
…
perform read operation; //读操作
…
Ssignal(L,1); //本进程读文件结束,允许读的读者+1
}while(TRUE)
}
void Writer()
{
do
{
Swait(mx, 1, 1; L, RN, 0); //没有读进程和其它写进程访问文件,方可开始写
perform write operation; //写操作
Ssignal(mx, 1); //结束写
}while(TRUE)
}