操作系统IPC(1)

来源:互联网 发布:做标书的软件 编辑:程序博客网 时间:2024/06/06 01:58

上一篇日志主要介绍了IPC的竞争条件,时隔一周多,终于抽个时间继续IPC。

 

接下来要介绍一个竞争条件的经典例子----生产者和消费者问题。

首先来介绍什么是生产者消费者。在操作系统中生产者和消费者(producer--consumer)代表了两类进程:输入和输出。顾名思义,生产者是生产商品(item),消费者是要消费商品,即拿走item。在现实生活中,工厂生产商品,我们到超市去买。注意这就多了一个名词——超市。那么我们为什么不直接去工厂买商品呢?原因很多,一个重要的原因是不方便,并且你也不知道跑老远到工厂后,工厂是否存有你要的商品。如果你去超市的话,那就很方便了,即便是超市没有,你可以告诉超市它这个商品缺货了,那么超市可以告诉工厂这个商品缺货了,此时工厂可以再生产。这个过程就暗含了一个通信。但是计算机是如何实现IPC的以及如何解决IPC所遇到的问题?

 

在计算机中,有个名词就Buffer——缓冲区。Buffer的作用和上面提到的超市的作用差不多。还有个名词叫Global variety——全局变量,每个进程都可以读写这个变量,是进程间通信的信号灯。

 

介绍一下生产者和消费者的行为。

生产者在生产商品前,要先读全局变量Count,Count是用来记录缓冲区中item的个数的,如果读出的Count=N,N是缓冲区的容量,则说明缓冲区已满,接下来生产者去睡觉;如果读出的Count=0,则说明缓冲区是空的,接下来生产者生产一件商品item、对Count进行加1操作、调一个系统调用wakeup()给消费者发送唤醒消息(生产者认为消费者处于睡觉状态);如果读出的0<Count<N,则生产一件商品放到缓冲区、对Count进行加1。消费者在消费之前,也是要先读出全局变量Cont,如果读出的Count=0,消费者则去睡觉;如果读出的Count在0<Count<N,则在缓冲区中取出一件商品、对Count进行减1操作;如果读出的Count=N,则在缓冲区中取出一件商品、对Count进行减1操作、调用一个系统调用wakeup()给生产者发送唤醒消息(消费者认为生产者处于睡眠状态)。

 

生产者和消费者问题。

会出现这种情况,当共享变量Count=0时,这时消费者来读Count,读出的是0,刚完成读这条指令,被中断了。这时生产者来了,读出的Count=0,然后生产者就生产了一件商品、对Count进行加1操作、调用wakeup()。但是此时的消费者并未处于睡眠状态,只是被中断,所以这个wakeup()信号将会丢失。不论进程调度器何时让消费者运行,消费者都将处于睡眠状态,而生产者迟早会把缓冲区填满Count=N,这样生产者和消费者都处于睡眠状态,两个进程都去睡了,并且将永远睡下去,没人干活了,这是用计算机的人不想看到的。这就是生产者和消费者问题的一个实例。

 

信号量(semaphore)。

先说一下信号量的由来。Dijkstra(荷兰人)在1965年提出了一个建议,就是用一个整型变量来记录消息的个数(如生产者和消费者中的唤醒消息),他把这种类型的变量叫作信号量。知道信号量的来源就理解了信号量的作用了。

 

原子性操作(atomic action).

所谓的原子性操作就是不可分割的操作,即操作过程中不会发生中断(中断既是进程并发的前提,也是IPC问题的根源,这应该是中断的两面吧)。

 

信号量的两种操作——down和up。

信号量有两种操作,也仅有两种。一种是down,一种是up,对应于很多中国教材中的P、V操作。这里来解释一下,其实在Dijkstra提出信号量的论文中,有的就是P、V操作,并不是down和up。因为P、V是荷兰语中的down和up,但是整个计算机就是美国的天下,美国人当然不愿意用P、V,所以在很多英文原著中都是使用down和up。这算一个小插曲吧。

我习惯于down和up,所以就用down和up来介绍这两种操作。down操作是对信号量进行减1,进程检查信号量的值,如果信号量小于等于0,调用down的进程将去睡觉;如果信号量大于0,此进程继续。up操作是对信号量进行加1,进程检查信号量的值,如果此时的信号量小于等于0,证明此时有进程处于睡眠状态,就唤醒一个睡眠的进程;如果信号量的值大于0,进程继续。

down和up操作都是原子性的。原著中用了描述down的原话是"Checking the value,changing it,and possibly going to sleep,are all done as a single,indivisible atomic action."正是原子性,才保证了信号量能够解决IPC问题。

 

用信号量来解决生产者消费者问题。

首先设三个信号量,full、empty、mutex。full用了记录缓冲区中已有的商品件数,empty用来记录缓冲区中还可以放的商品个数,mutex用来实现同一时间只有一个进程能进入缓冲区。

原著中的伪代码源程序是:

#define N 100
typedef int semaphore;
semaphore mutex=1;
semaphore full=0;
semaphore empty=N;

void producer()
{
int item;

while(true)
{
item=produce_item();
down(&empty);
down(&mutex);
insert_item(item);
up(&mutex);
up(&full);
.
.
.
}
}

void consumer()
{
int item;
while(true);
{
down(&full);
down(&mutex);
item=remove_item();
up(&mutex);
up(&empty);
.
.
.
}
}至于IPC中的哲学家就餐问题和读者写者问题,就不打算写了。个人感受是,操作系统是一门重要的课,也是一门难学的课,只有重复的看书才能真正的理解。  
原创粉丝点击