您的位置:首页 > 其它

进程间的通信--------操作系统

2017-05-08 18:38 218 查看
一、要解决的问题:

如何把信息由一个进程传递给另外一个进程

如何处理两个或多个进程在临界区的问题

两个进程或多个进程的顺序问题(如何实现同步)

二、基本概念:

2.1竞争条件: 两个或多个进程读写某些共享数据时,最后的结果取决于进程运程运行的精确时序。(说到底,是访问临界资源时,a进程先访问一半时,a进程被阻塞,b进程去运行,也到了临界资源,把a的之前已经运行了的半个临界资源数据覆盖了,则下一次a在运行时,根据先前的临界区的值,就有了错误的执行结果)

2.2临界区: 为了避免竞争条件,也就是互斥访问共享资源(一个进程在使用另一个进程不得使用)。而临界区是程序片段,什么样的程序片呢?访问共享内存的程序片段。互斥即:是两个进程不同时在临区

避免竞争条件的四个条件:

任何两个进程不的同时处于临界区(竞争条件)

不应对cpu的速度和数量做任何假设(这个我自己理解为通过程序代码和运算速度,人可以猜测进入临界区的时间,让另外一个进程等待完这个时间后手动进入临界区,但这样是不可以的,必须把进入临界区的时间交给操作系统管理)

临界区外的进程不的阻塞其他进程(没有共享资源的约束,进程间不得以任何方式阻塞对方)

不的使进程无限期等待进入临界区(进程不能无限被阻塞)

三、忙等待互斥(几种互斥):

3.1屏蔽中断:单个处理器,每个进程刚进入临界区就把中断屏蔽了,在离开时打开。由于时钟中断屏蔽了,cpu不会发生进程间的切换,那么这个进程可以一直使用cpu,临界区。

问题:把中断控制权交给用户不安全。另一点是多处理器,一个cpu屏蔽中断,另外一个cpu是不会被屏蔽的。

3.2锁变量:设置一个共享锁变量,开始为零,进入为改变1,出来改变为0。这个一听就有问题,用一个共享变量去保护另一个共享资源,那么这个共享变量怎么保护?所以这个互斥是不行的。

3.3严格轮换法:这个也有一个变量turn(初始0),用于记录轮到哪一个进入临界区了。一个进程是当是0的时候进入,另一个是1的时候进入,当一个进程进入,另外一个进程不停的循环检查turn的值,知道turn改变才进入,这个循环检查就是忙等待,极为浪费cpu时间,只有在等待时间很短的时候,才用忙等待,当一个进程频繁进入临界区,另一个进程不频繁时,用严格轮换法,两个进程你一次我一次的进入,这样进入临界区频繁的进程就会被其他围在临界区的进程阻塞,违反了之前的条件。

3.4Peterson解法:

#define N 2//两个进程
int turn;
int interested
;

void enter_region(int process)
{
int other;
other=1-process;
interseted[process]=TRUE;
turn=process;
while(turn==process && interseted[other] == TRUE);
}

void leave_region(int process)
{        interested[process]=FALSE;
}


turn表示轮到谁进入临界区了,数组代表了那一个进程想进入临界区。只有另外一个进程不想进临界区,我就能进入(interseted[other]==FALSE)或者轮到我的时候另外一个进程也想进入临界区时,我先进入而另外一个在等待我出来(turn==1-process)(解决了两个几乎同时进入的情况)。

3.5TSL指令: 硬件实现。TSL RX,LOCK语句,测试并加锁,将一个内存字lock读入寄存器RX,猴后再lock上存一个非零值,读写不可分割:该指令结束前其他处理器不可以访问内存字,采用锁住总线的方式(与屏蔽中断不同)。前面有点啰嗦,简单点说:同锁变量类似,一个共享变量lock存入register中,register的值为零则可进入,为1不可进入,忙等待检测是否为0,另一个进入操作结束时,变为0;

enter_region:
TSL REGISTER,LOCK
CMP REGISTER,#0                                  //锁是0吗
JNE enter_region                                   //不是,则有进程进入临界区,忙等待
RET
leave_region:
MOVE LOCK,#0
RET


四、非忙等待的进程间的互斥:

忙等待的缺点:不允许进入临界区的进程原地等待,首先肯定会浪费cpu时间,其次,会有优优先级反转问题**:两个进程H(高优先级),L(低优先级),L在临界区,H处于就绪态运行,处于忙等待,H处于运行态,L不会被调度,无法离开临界区,H永远忙等待。

现在详细说一下优先级反转情况:①高优先级处于忙等待,低优先级无法执行,离开临界区。(就是上面说的) ②另一种是,高优先级阻塞,要等低优先级去触发实践唤醒,但低的运行时间少,搞得要等低的释放资源,若又处于中间优先级的(无共享资源)获得cpu时间反而多,高的就无法正常按规则执行了。

4.1 睡眠与唤醒; 把要忙等待的进程阻塞,让另外离开临界区的进程去唤醒他,这样就可以避免忙等待了。

这里引出了经典问题:生产者与消费者 ,单用睡眠与唤醒是无法解决这个问题的,可能会产生唤醒信号丢失,即使引入新变量来解决两个进程间的,还是没有从根本上解决问题。

4.2信号量: 使用一个信号量来累计唤醒次数(大于等于0)。并对信号量定义了两种操作down和up(原来他的论文中是PV操作,只是名字不一样)。

down:
if(semaphore>0)
semaphore--;
else
sleep(process);
up:
if(semaphore==0)
wakeup(everySleepOnThisSemaphoreProcess);
else
semaphore++;


down和up都是原子操作(即一组关联操作要么都不执行,要么执行时不可被打断),还有一点很清楚:不会有那一个进程由于up操作而阻塞的

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

三个信号量:full(记录缓冲区槽数被填充了的数量)

empty(记录空槽数)

mutex(不会同时访问临界区)

我们用两种方式去使用这三个信号量的:mutex适用于互斥访问(二元信号量),full与empty用于实现同步:保证某种事件的顺序发生货不发生。

#define N 100 //缓冲区槽数
typedef int semaphore;
semaphore mutex=1;
semaphore empty=N;
semaphore full=0;

void producer(void)
{
int item;
while(TRUE)
{
item=producer_item();

down(&empty);//这两句若颠倒,则有问题。先进入缓冲区,发现不能生产了,堵住了,出不去
down(&mutex);//生产者消费者都堵塞了,这里为什么是empty?down是减,生产有空的是要减空的
insert_item(item);
up(&mutex);//up操作不阻塞,无所谓先后
up(&full);//这里是full?生产一个加一个,可以消费了,up去唤醒消费者
}
}

void consumer(void)
{
int item;
while(TRUE)
{
down(&full);
down(&mutex);
item=remove_item();
up(&mutex);
up(&empty);
consume_item(item);
}
}


这里实现很清楚了。理解信号量的思想,是由原子操作这个概念,相信它会在关键地方产生奇效。

4.3 互斥
d8c6


信号量是有计数能力的,而互斥量是信号量的简化版本,互斥量只适用于管理共享资源或一小段代码,由于互斥量在实现上很容易,互斥量在用户空间县城包时使用。

互斥量是一个二元信号量,有两种状态:解锁和加锁。

mutex_lock:
TSL REGISTER,MUTEX             //将互斥量复制到寄存器,并且将互斥量置为1
CMP REGISTER,#0                //互斥量是0?
JZE ok                         //是 结束
CALL thread_yield              //稍后再试
JMP MUTEX_lock                 //返回调用者,进入临界区
ok:     RET

mutex_unlock:
MOVE MUTEX,#0
RET


这与之前的TSL的enter_region进入临界区失败时,处于忙等待状态。这样有时钟存在,始终会调用其他进程。而在用户空间中的线程中,没有时钟中断存在,此线程永远霸占cpu

Pthraed中的互斥:

之前说过一些Pthread线程包关于线程创建销毁的一些函数,这里说与一下关于互斥量的Pthraed用。

pthraed_mutex_init 创建一个互斥量

pthread_mutex_destroy 撤销一个已经存在的信号量

pthraed_mutex_lock 获得一个锁或阻塞

pthraed_mutex_trylock 获得一个锁或失败

pthraed_mutex_unlock 释放一个锁

Pthraed中的同步:

为达到的条件而阻塞

pthread_cond_init 创建一个条件变量

pthread_cond_init 创建一个条件变量

pthread_cond_destroy 撤销一个条件变量

pthread_cond_wait 阻塞以等待一个信号

pthraed_cond_signal 向另一个线程发信号唤醒它

pthraed_cond_broadcast 想多个线程发信号让他都唤醒

例子:生产者发现空槽没有了,阻塞起来。当发现缓冲区满时,需要一个条件变量阻塞,不然再生产一个,存哪?没得存了,只得等待消费者通过条件变量唤醒

条件变量要和互斥量相联结,以避免出现条件竞争--一个线程预备等待一个条件变量,当它在真正进入等待之前,另一个线程这里写代码片待一个条件变量,当它在真正进入等待之前,另一个线程恰好触发了该条件

#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>

#define MAX 1000

pthread_mutex_t the_mutex;//互斥量

pthread_cond_t condc,condp;//条件变量

int buffer=0;//缓冲内容

void *producer(void *ptr)
{
for(int i=1;i<=MAX;i++)
{
pthread_mutex_lock(&the_mutex);//互斥使用缓冲区
while(buffer!=0)
{
pthread_cond_wait(&condp,&the_mutex);//阻塞以等待信号
}

buffer=i;
printf("producer %d\n",i);
pthread_cond_signal(&condc);//向另一个进程发出信号唤醒
pthread_mutex_unlock(&the_mutex);//释放缓冲区
}
pthread_exit(0);
}

void *consumer(void *ptr)
{
int i;
for(i=1;i<=MAX;i++)
{
pthread_mutex_lock(&the_mutex);
while(buffer==0)
pthread_cond_wait(&condc,&the_mutex);
printf("consumer %d\n",buffer);
buffer=0;
pthread_cond_signal(&condp);
pthread_mutex_unlock(&the_mutex);
}
pthread_exit(0);
}

int main()
{
pthread_t pro,con;//线程号
pthread_mutex_init(&the_mutex,0);//创建一个互斥变量
pthread_cond_init(&condc,0);//创建一个条件变量
pthread_cond_init(&condp,0);
pthread_create(&con,0,consumer,NULL);//创建线程
pthread_create(&pro,0,producer,NULL);
pthread_join(pro,0);//如果没有pthread_join;主线程会很快结束从而使整个进程结束,从而使创建的线程没有机会开始执行就结束了
pthread_join(con,0);//加入pthread_join后,主线程会一直等待直到等待的线程结束自己才结束,使创建的线程有机会执行
//pthread_join函数会让主线程阻塞,直到所有线程都已经退出

pthread_cond_destroy(&condc);//销毁条件变量
pthread_cond_destroy(&condp);
pthread_mutex_destroy(&the_mutex);//销毁互斥变量

}


上面的代码是书上的,我自己还没有编写过多线程的程序。不过看到这段代码我有一个疑问:在消费者首先互斥使用缓冲区(只有一个字大小)时,buffer是0,阻塞了,那么生产者无法进入缓冲区,然后无法唤醒消费者,这就不对了。我大约是相信书的内容不会轻易出逻辑错误,并且我对pthraed_cond_wait()函数不熟悉,我想问题出在这里,查了一下,发现如下:

pthread_cond_wait(&condc,&the_mutex);

//pthread_cond_wait会先解除之前的pthread_mutex_lock锁定的the_mutex,(这里解决了问题)

//然后阻塞在等待队列里休眠,直到再次被唤醒

//(大多数情况下是等待的条件成立而被唤醒,唤醒后,

//该进程会先锁定先pthread_mutex_lock(&the_mutex);,

//再读取资源用这个流程是比较清楚的

这里为什么调用pthraed_cond_wait()来解除the_mutex互斥量呢?

这里引用一下别人的代码看看:
[不然转载,贴一个连接](http://blog.csdn.net/bolike/article/details/9025389)
一个线程完成一项任务(涉及共享资源)
另一个线程需要共享资源达到一定条件时才能触发下一步,这样这个线程需要不断地判断到底达到条件没?浪费了cpu时忙    (上面的互斥量使用只是不让两个线程同时进入临界区,但是忙等待是没法解决的,而sleep()的话无法知道到底需要多长时间,不可行)
此时有条件变量的话会好很多,不用忙等待了。


4.4管程 (这里先略了)

4.5消息传递

如果一个分布式系统有多个cpu,并且每个cpu有自己的私有内存,之前的原语全都失效了,因为这些原语未提供机器 间的信息交互,所以引 入了消息 。

两条系统调用的原语:send和receive

send(destination,&message) //给定一个目标发送消息

receive(sourse,&message) //从消息源接信息

设计要点:
在网络上通信进程,消息可能丢失,为了反之丢失,双方达成协议,接收方在
接受到消息后,回送一个确认消息 ,如果发送方一段时间间隔未收到确认消息,则重发消息
若确认消息丢失,发送者有发送一次,,接收者需要判断是一个新消息还是重发的就消息很重要,一般是在原始消息中嵌入一个连续序号。
进程命名必须没有二义性,身份验证也需要解决

用消息传递解决生产者消费者问题:


#define N 100

void producer(void)
{
int item;
message m;

while(TRUE)
{
item=produce_item();//生产
receive(consumer,&m);//接受消费者发送的空缓冲区
build_message(&m.item);//建立新消息
send(consumer,&m);//发送
}
}

void consumer(void)
{
int item,i;
message m;

for(i=0;i<M;i++)//发送所有空消息
send(prosucer,&m);
while(TRUE)
{
receive(producer,&m);//接受一个消息
item=extract_item(&m);//处理消息,缓冲区产生一个新的空消息
send(producer,&m);//发送空消息
consume_item(item);//处理
}
}


如何存消息:

使用信箱,消费者生产者各有一个,这里信箱的大小是缓冲区的大小,心想可以容纳哪些已经被发送未被目标进程接收的消息 ,当成一个消息缓冲区。

4.6屏障

同步机制:一个进程组必须同时可以开始下一个阶段,不然都不可以进入。可以在每一个阶段尾设置一个屏障,先到达的被拦截,全部都到达了,才进入下一阶段。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  操作系统 通信