进程间的通信--------操作系统
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解法:
turn表示轮到谁进入临界区了,数组代表了那一个进程想进入临界区。只有另外一个进程不想进临界区,我就能进入(interseted[other]==FALSE)或者轮到我的时候另外一个进程也想进入临界区时,我先进入而另外一个在等待我出来(turn==1-process)(解决了两个几乎同时进入的情况)。
3.5TSL指令: 硬件实现。TSL RX,LOCK语句,测试并加锁,将一个内存字lock读入寄存器RX,猴后再lock上存一个非零值,读写不可分割:该指令结束前其他处理器不可以访问内存字,采用锁住总线的方式(与屏蔽中断不同)。前面有点啰嗦,简单点说:同锁变量类似,一个共享变量lock存入register中,register的值为零则可进入,为1不可进入,忙等待检测是否为0,另一个进入操作结束时,变为0;
四、非忙等待的进程间的互斥:
忙等待的缺点:不允许进入临界区的进程原地等待,首先肯定会浪费cpu时间,其次,会有优优先级反转问题**:两个进程H(高优先级),L(低优先级),L在临界区,H处于就绪态运行,处于忙等待,H处于运行态,L不会被调度,无法离开临界区,H永远忙等待。
现在详细说一下优先级反转情况:①高优先级处于忙等待,低优先级无法执行,离开临界区。(就是上面说的) ②另一种是,高优先级阻塞,要等低优先级去触发实践唤醒,但低的运行时间少,搞得要等低的释放资源,若又处于中间优先级的(无共享资源)获得cpu时间反而多,高的就无法正常按规则执行了。
4.1 睡眠与唤醒; 把要忙等待的进程阻塞,让另外离开临界区的进程去唤醒他,这样就可以避免忙等待了。
这里引出了经典问题:生产者与消费者 ,单用睡眠与唤醒是无法解决这个问题的,可能会产生唤醒信号丢失,即使引入新变量来解决两个进程间的,还是没有从根本上解决问题。
4.2信号量: 使用一个信号量来累计唤醒次数(大于等于0)。并对信号量定义了两种操作down和up(原来他的论文中是PV操作,只是名字不一样)。
down和up都是原子操作(即一组关联操作要么都不执行,要么执行时不可被打断),还有一点很清楚:不会有那一个进程由于up操作而阻塞的
用信号量解决生产者消费者问题:
三个信号量:full(记录缓冲区槽数被填充了的数量)
empty(记录空槽数)
mutex(不会同时访问临界区)
我们用两种方式去使用这三个信号量的:mutex适用于互斥访问(二元信号量),full与empty用于实现同步:保证某种事件的顺序发生货不发生。
这里实现很清楚了。理解信号量的思想,是由原子操作这个概念,相信它会在关键地方产生奇效。
4.3 互斥
d8c6
量
信号量是有计数能力的,而互斥量是信号量的简化版本,互斥量只适用于管理共享资源或一小段代码,由于互斥量在实现上很容易,互斥量在用户空间县城包时使用。
互斥量是一个二元信号量,有两种状态:解锁和加锁。
这与之前的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 想多个线程发信号让他都唤醒
例子:生产者发现空槽没有了,阻塞起来。当发现缓冲区满时,需要一个条件变量阻塞,不然再生产一个,存哪?没得存了,只得等待消费者通过条件变量唤醒
条件变量要和互斥量相联结,以避免出现条件竞争--一个线程预备等待一个条件变量,当它在真正进入等待之前,另一个线程这里写代码片待一个条件变量,当它在真正进入等待之前,另一个线程恰好触发了该条件
上面的代码是书上的,我自己还没有编写过多线程的程序。不过看到这段代码我有一个疑问:在消费者首先互斥使用缓冲区(只有一个字大小)时,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互斥量呢?
4.4管程 (这里先略了)
4.5消息传递
如果一个分布式系统有多个cpu,并且每个cpu有自己的私有内存,之前的原语全都失效了,因为这些原语未提供机器 间的信息交互,所以引 入了消息 。
两条系统调用的原语:send和receive
send(destination,&message) //给定一个目标发送消息
receive(sourse,&message) //从消息源接信息
如何存消息:
使用信箱,消费者生产者各有一个,这里信箱的大小是缓冲区的大小,心想可以容纳哪些已经被发送未被目标进程接收的消息 ,当成一个消息缓冲区。
4.6屏障
同步机制:一个进程组必须同时可以开始下一个阶段,不然都不可以进入。可以在每一个阶段尾设置一个屏障,先到达的被拦截,全部都到达了,才进入下一阶段。
如何把信息由一个进程传递给另外一个进程
如何处理两个或多个进程在临界区的问题
两个进程或多个进程的顺序问题(如何实现同步)
二、基本概念:
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屏障
同步机制:一个进程组必须同时可以开始下一个阶段,不然都不可以进入。可以在每一个阶段尾设置一个屏障,先到达的被拦截,全部都到达了,才进入下一阶段。
相关文章推荐
- 操作系统--进程间的通信,同步和互斥等
- 学习笔记——操作系统_Linux的进程通信
- 操作系统--进程通信
- 操作系统:进程的软中断通信实验
- 东北大学操作系统实验:进程同步和通信(生产者消费者问题模拟)
- 操作系统_04_进程同步与通信
- 操作系统:进程的管道通信实验
- Linux操作系统分析(10) - 进程通信之管道与信号量
- 操作系统中的进程通信
- 操作系统:进程通信
- 操作系统知识整理4:进程通信,信号量,死锁
- Linux操作系统分析(10) - 进程通信之管道与信号量
- 操作系统的笔记--进程的通信
- 【操作系统】进程的通信方式
- 学习笔记——操作系统_Linux进程通信之消息队列
- 操作系统课程设计父子进程的简单通信以及终止进程源码解析
- [操作系统] 进程间的通信
- 在操作系统中,进程之间是如何通信的?
- 操作系统-进程通信
- 操作系统核心原理-3.进程原理(下):进程通信