进程间通信之— 管道
在Linux系统中,有时候需要多个进程之间相互协作,共同完成某项任务,进程之间或线程之间有时候需要传递信息,有 时候需要同步协调彼此工作,则就会出现进程间通信(interprocess communication 或者 IPC)
Linux下进程间通信有4种方式:管道,消息队列,信号量和共享内存。
学习进程间通信必须了解临界资源的概念
- 临界资源:把多个进程(执行流)能够看到访问的共同资源叫临界资源,管道是一种临界资源。
- 临界区:把访问临界资源的代码叫临界区。
- 进程间通讯的本质是二进程共享资源(不同进程看到公共资源),一进程以读方式一进程以写方式打开同一份文件
linux 下一切接文件,管道也是一种特殊的文件。 - 互斥:在任意一个时刻只能有一个“人”访问临界资源,采用原子性原则访问。
- 原子性操作:狭义的原子性操作指该操作的汇编代码只有一句(该操作不可分),广义的原子操作指一个操作正在执行 时决不会被切出。原子操作要么做了,要么没做,不存在正在做。
管道的五个特点
- 管道只支持文件间的单向传输
- 管道面向字节流(以字节单位方式传输)
- 依赖于文件—-管道生命周期随进程(管道也是文件–> 有文件描述符表–> PCB –> 进程)
- 同步(一进程向管道文件写端写,同时一进程在管道文件读端读,两方面同时)
- 管道分匿名管道和命名管道。
匿名管道只能在有亲缘关系的进程间通信,命名管道可以在无亲缘关系的进程间通信。
使用管道的注意
- 使用管道需要注意以下四种特殊情况(假设都是阻塞I/O操作,没有设置O_NONBLOCK标志)
- 如果所有指向管道写端的文件描述符都关闭了,(管道写端的引用计数为0),而仍然有进程从管道的读端读取数据,那么管道中剩余的数据都被读取之后,再次read将会返回0,就像读到文件结尾一样。也就是说,写端不会写,读端读完之后就会再等着写端去写,但是写端关闭了啊,不会写了,所以就出现上面说的情况。这就体现出了管道的同步机制。
- 如果有指向管道写端的文件描述符没有关闭,(管道写端的引用计数大于0)而持有管道写端的进程也没有向管道中写数据,这时有进程管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。通俗讲就是,读端读数据,一直读,但是写端不写了,而且写端并没有关闭,所以这时读端就会一直等着写端去写。这就造成了阻塞式等待。
- 如果所有指向管道读端的文件描述符都关闭了(管道读端的引用计数为0),这时有进程向管道的写端写数据,那么该进程会收到SIGPIPE,通常会导致进程异常终止。所以进程就会异常退出了。
- 如果有指向管道读端的文件描述符没关闭(管道读端的引用计数大于0)而持有管道读端的进程也没有从管道中读取数据,这时有进程向管道写端写数据,那么在管道写满时再写将会阻塞,直到管道中有了空位置才写入并返回,也就是管道的同步机制。
- 管道文件的大小是有限的写代码时一次不可一读写太多。
管道创建的图示
- A进程创建一个文件
- B进程拿到这个同样的文件
- A进程关闭读端 B进程关闭写端。(通过改变文件描述符表操作)
代码实现
//匿名管道:
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
int main()
{
int _pipe[2];//定义参数
int ret = pipe(_pipe);
if(ret==-1)//创建管道失败
{
printf("creat pipe error!errno code is :%d\n",errno);//错误码
return 1;//返回值,这样你就会知道到底是哪里出现了错误
}
pid_t id = fork();
if(id<0)//创建子进程失败
{
printf("fork error!");
return 2;
}
else if(id==0)
{
//child
close(_pipe[0]);//关闭读端
int i = 0;
char *_mesg = NULL;
while(i<100)
{
_mesg = "I am child!";
write(_pipe[1],_mesg,strlen(_mesg)+1);//xie
sleep(1);
i++;
}
}
else
{
//father
close(_pipe[1]);//关闭写端
char _mesg_c[100];
int j = 0;
while(j<100)
{
memset(_mesg_c,'\0',sizeof(_mesg_c));
read(_pipe[0],_mesg_c,sizeof(_mesg_c));
printf("%s\n",_mesg_c);
j++;
}
}
}
//命名管道:
//写端
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
int main()
{
umask(0);
if(mkfifo("./mypipe",0666 | S_IFIFO)<0)
{
perror("mkfifo error");
return 1;
}
int fd = open("./mypipe",O_RDONLY);
if(fd<0)
{
printf("open file error!\n");
return 2;
}
char buf[1024];
while(1)
{
ssize_t ret = read(fd,buf,sizeof(buf)-1);
if(ret>0)//error or end of file
{
buf[ret] = 0;
printf("client say# %s\n",buf);
}
else if(ret==0)
{
printf("client quit !server begin quit!\n");
break;
}
}
close(fd);
return 0;
}
//读端
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
int main()
{
int fd = open("./mypipe",O_WRONLY);
if(fd<0)
{
printf("open file error!\n");
return 2;
}
char buf[1024];
while(1)
{
printf("please enter # ");
fflush(stdout);
ssize_t ret = read(0,buf,sizeof(buf)-1);
if(ret>0)
{
buf[ret-1] = 0;
write(fd,buf,strlen(buf));
}
}
close(fd);
return 0;
}