进程通信之管道(pipe,fifo)
进程通信(IPC Interprocess Communication)的一般目的: 数据传输,通知事件,资源共享,和进程控制。
背景
但是对于每个进程而言其所看到的内存资源,就是他所独自占有的,所以进程间通信会比较麻烦。
原理
让不同的进程能够看到一份公共的资源。因此资源必须是存放在内核上的,在内核开辟一块缓冲区,进程1把数据从用户空间拷贝到内核缓冲区,进程2再从内核缓冲区中将数据读走,内核提供的这种机制称之为:进程间通信。
一般我们采用进程间通信的方式有:
1.管道(pipe)和有名管道(FIFO) |
2.信号(signal) |
3.消息队列 |
4.共享内存 |
5.信号量 |
6.套接字(socket) |
我们先从最简单的通信方式来说起:
匿名管道:pipe
一:管道的介绍
管道是一种最基本的进程间通信机制。管道由pipe函数来创建:
#include<unistd.h>
int pipe(int pipefd[2]);
函数介绍 | 调用pipe函数,会在内核中开辟出一块缓冲区来进行进程间通信,这块缓冲区称为管道,他有一个读端和一个写端。 |
参数介绍 | pipefd[2]参数,是包含两个整数的数组. 如果调用成功,会通过pipefd[2]传出给用户程序两个文件描述符,分别为pipefd[0]指向管道的读端,pipefd[1]指向管道的写端,那么此时这个管道对于用户程序而言就是一个文件,可以通过read(pipefd[0]);或者write(pipefd[1]);进行操作。 |
函数返回值 | pipe函数调用成功返回0,否则返回 -1。 |
二:管道创建
》父进程创建管道,得到两个文件描述符指向管道的两端
》利用fork函数创建出子进程,则子进程也得到两个文件描述符指向同一管道
》父进程关闭读端(pipefd[0]),子进程关闭写端pipefd[1],此时父进程可以往管道中进行写操作,子进程可以从管道中读取,从而实现了管道的进程间通信。
代码示例:
#include<stdio.h>
#include<unistd.h>
#include<string.h>
int main()
{
int fd[2];
if( pipe(fd) < 0)
{
printf("pipe is error\n");
exit(0);
}
pid_t pid = fork();
if(pid < 0){
printf("pid is error\n");
exit(0);
}
if(pid == 0)//child
{
close(fd[0]);//关闭读端
int i = 0;
char* mesg = NULL;
while(i<10)
{
mesg = "my name is kebi";
write(_pipe[1],mesg,strlen(mesg)+1);
sleep(1);
++i;
}
}
else//parent
{
close(fd[1]);
int j = 0;
char _mesg[12];
while(j<10)
{
memset(_mesg,'\0',sizeof(_mesg));
read(fd[0],_mesg,sizeof(_mesg));
printf("%s\n",_mesg);
j++;
}
}
return 0;
}
pipe的特点:
1.只能单向通信(半双工) |
2.只能有血缘关系的进程之间通信(父子进程) |
3.依赖于文件系统 |
4.生命周期随进程 |
5.面向字节流服务 |
6.管道内部提供了同步机制 |
说明:
管道的读写端通过打开文件描述符来传递,因此要通信的两个进程必须从他们的公共祖先那里继承管道的文件描述符。上面的例子是父进程把文件描述符传给子进程之后父子进程之间通信。也可以父进程fork()两次,把文件描述符传给两个子进程,然后两个子进程之间通信。
总之需要通过fork()传递文件描述符使两个进程都能访问同一个管道,他们才能通信。
四种特殊情况:
情况1: | 如果所有指向管道写端的文件描述符都关闭了,而仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像读到文件末尾一样。 |
情况2: | 如果指向管道写端的文件描述符没关闭,而持有管道写端的进程没有向管道中写数据,这时如果有进程从管道读端读取数据,那么管道中剩余的数据被读取后,再次read会阻塞,直至管道中有数据可以读取了才读取数据并返回。 |
情况3: | 如果所有指向管道读端的文件描述符都关闭了,这时有进程指向管道的写端write,那么该进程会收到SIGPIPE信号,通常会导致进程异常终止。 |
情况4: | 如果有指向管道读端的文件描述符没关闭,而持有管道读端的进程也没有从管道读端读取数据,这时有进程向管道写端写数据,那么管道再被写满再write会阻塞,知道管道中有空的位置了才写入数据并返回。 |
有名管道FIFO
有名管道介绍:
在管道中,只有具有血缘关系的进程才能进行通信,对于后来的命名管道,就解决了这个问题,FIFO不同于匿名管道之处在于它提供了一个路径名与之关联,以FIFO的文件形式存储在文件系统中。有名管道是一个设备文件,因此即使进程与创建FIFO的进程不存在亲缘关系,只要可以访问该路径,就能通过FIFO相互通信。值得注意的是,FIFO(first input first output)总是按照先进先出的原则工作,第一个被写入的数据首先从管道中读取。
有名管道的创建:
命令创建: | mkfifo + filename | ||||||||
函数创建: |
|
//mknod函数创建
umask(0);
if(mknod("/tmp/fifo",S_IFIFO | 0666) == -1)
{
printf("mknod is error\n");
exit(0);
}
//mkfif0函数创建
umask(0);
if(mkfifo("/tmp/fifo",S_IFIFO | 0666) == -1)
{
printf("mkfifo is error:\n");
exit(0);
}
"S_IFIFO | 0666" | 指明创建一个命名管道且存取权限为0666,即创建者,与创建者同组的用户,其他用户对该命名管道的访问权限都是可读可写的。(注意umask对生成的管道文件的影响) |
需要注意:
注意1: | 有名管道创建后就可以使用了,只是使用命名管道时,必须先调用open()将其打开。 |
注意2: | 调用open()打开有名管道的进程可能会阻塞。但如果同时用读写方式(O_RDWR)打开,则一定不会导致阻塞 |
注意3: | 如果以只读方式( O_RDONLY)打开,则调用open()函数的进程将会被阻塞直到有写方打开管道;同样以写方式( O_WRONLY)打开也会阻塞直到有读方式打开管道。 |
代码示例:
//fifo1.c
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<fcntl.h>
#include<sys/types.h>
#include<stdlib.h>
#define STRLEN 128
int main()
{
int fd = open("./FIFO",O_WRONLY);
assert(fd != NULL);
printf("FIFO open is success:\n");
while(1)
{
char p[STRLEN] = {0};
printf("please input the data:\n");
scanf("%s",p);
write(fd,p,strlen(p));
if(strncmp(p,"end",3) == 0)
break;
}
close(fd);
exit(0);
}
//fifo2.c
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<fcntl.h>
#include<sys/types.h>
#include<stdlib.h>
#define STRLEN 128
int main()
{
in fd = open("FIFO",O_RDONLY);
assert(fd != NULL);
printf("open is sucess:\n");
while(1)
{
char buff[STRLEN] = {0};
int n = read(fd,buff,127);
if(n == 0||(n == 3&&strncmp(buff,"end",3)==0))
{
printf("n is error:\n");
break;
}
int i = 0;
while(i<strlen(buff))
{
printf("%d ",buff[i++]);
}
printf("\n");
}
close(fd);
exit(0);
}
如何测试获取匿名管道的内核缓冲区的大小?
思路:我们通过现象四来实现这个检测操作。当我们不断的向管道总写入数据时,当管道满了之后会产生write阻塞。
#include"apue.h"
int main()
{
int fd[2];
if (pipe(fd) == -1)
{
printf("pipe is error:\n");
return -1;
}
pid_t pid = fork();
if (pid == 0)//child
{
int i = 0;
close(fd[0]);//关闭读端
char* child = "i am child!";
while (++i)
{
write(fd[1], child, strlen(child) + 1);
printf("pipe capacity: %d\n", i*(strlen(child) + 1));
}
close(fd[1]);
}
else if (pid > 0)//parent
{
close(fd[1]);
waitpid(id, NULL, 0);
}
else
{
printf("fork() is error:\n");
return -1;
}
return 0;
}
大小为65536,即为64k大小。