原创作品,转载请标明:http://blog.csdn.net/Xiejingfa/article/details/50819660
进程间通信,英文又称作IPC(InterProcess Communication)就是在不同的进程之间交换信息。我们知道,进程的用户是相空间互独立的,不能相互访问,所以为了使得进程间可以相互通信,操作系统需要提供一种介质使通信双方都可以访问。类似这样的介质有很多,比如磁盘上的文件,我们只要协调好进程访问文件的顺序,一个进程先“写”,一个进程后“读”,这样,两者就可以相互交换信息了。又比如我们经常使用的数据库,也可以作为信息交换的媒介。但是这些通信手段效率都太低了,需要在系统级别提供高效的进程间通信手段。今天我们就来介绍一种最古老的进程间通信方式 – 管道。
如果你熟悉Linux系统,一定不会对管道感到陌生。在Linux系统中,我们经常通过符号“|”来使用管道,用以连接两个或多个命令。那管道是什么?实际上,管道是进程与进程间的数据流通道,它使得数据可以以一种“流”的形式在进程间流动。
管道也是Unix/Linux系统中一种最常见的进程间通信方式,它在两个通信进程之间实现一个数据流的通道从而进行信息传递。
管道通信具有以下特点:
Linux下管道是通过pipe函数创建的,函数原型如下:
#include <unistd.h>
int pipe(int pipefd[2]); // 若成功则返回0,出错则返回-1
其中参数pipefd[2]是一个长度为2的文件描述符数组,pipefd[0]是读端,pipefd[1]是写端。
管道的关闭则使用close函数。
#include <unistd.h>
int close(int fd);
管道关闭时,需要将pipefd[0]和pipefd[1]都关闭掉。
我们先用一段代码演示一下管道的创建:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main()
{
char buf[128];
int fd[2];
if (pipe(fd) < 0)
{
printf("pipe error!\n");
exit(1);
}
write(fd[1], "Hello World!", 12);
bzero(buf, sizeof(buf));
read(fd[0], buf, sizeof(buf));
printf("%s\n", buf);
close(fd[0]);
close(fd[1]);
return 0;
}
代码输出如下:
Hello World!
在上面的例子中,我们使用pipe()函数创建了一个管道。到这里你可能会感到奇怪:不是说管道是进程间通信的方式吗?在单个进程中创建管道,然后从一段写入从另一端读出有什么意义?下面我们重点讲解一下管道如何实现进程间通信。
正如你所想的,在单个进程中的创建管道几乎没有意义。前面我们说过管道只能用于父子进程或者兄弟进程间进行通信,通常我们先创建pipe,接着调用fork函数,从而创建从父子进程或者兄弟进程间的ipc通道。
为什么这样就能创建进程通信的管道呢?其中的功臣就是fork函数。fork函数创建出来的子进程是父进程的一个拷贝,子进程从父进程中得到数据段和堆栈段的拷贝。这样,父子都拥有事先创建管道的文件描述符,都可以往管道中读写信息。一般用于文件的IOh函数如read、write等都可以用于管道的读写。
如下图所示:
经过fork函数调用之后,父子进程间存在两种数据流向的管道:一个是从父进程到子进程的数据通道,另一个是从子进程到父进程的数据通道。管道只支持半双工通信,所以我们必须关闭其中一个数据通道!!
比如:如果你想建立一个父进程到子进程的数据通道,就要在父进程中关闭读端,在子进程中关闭写端。
通常,利用pipe和fork的组合,我们可以构造父子进程或者兄弟进程间任意方向的的数据通道。
示例:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#define BUF_SIZE 256
int main()
{
int fd[2];
char data[] = "Hello, I am parent!";
char buf[BUF_SIZE];
pid_t pid;
if (pipe(fd) < 0)
{
printf("pipe error!\n");
exit(1);
}
pid = fork();
if (pid < 0)
{
printf("pipe error!\n");
exit(1);
}
else if (pid == 0)
{
close(fd[1]);
int len = read(fd[0], buf, sizeof(buf));
printf("child: %s\n", buf);
}
else
{
close(fd[0]);
write(fd[1], data, strlen(data));
printf("parent: %s\n", data);
sleep(1);
}
}
输出如下:
parent: Hello, I am parent!
child: Hello, I am parent!
上面的例子演示了利用管道在父进程到子进程的通信,而管道在兄弟进程间通信则需要在父进程调用两次fork函数创建两个子进程,在两个兄弟子进程中维护通信管道。
管道写入的规则:程序向管道写入数据时,如果缓冲区有空闲空间,写进程立即向管道写入数据。如果缓冲区已满,那么写操作就一直阻塞到读进程读走数据为止。
管道读取的规则:如果请求的字节数大于PIPE_SIZE,则返回管道中现有的字节数。如果请求的字节数不大于PIPE_SIZE,则返回请求的字节数或现有数据的字节数(管道中的数据量小于请求的数据量)。
如果读写一端已经被关闭的管道是呢?
如果读一个写端已经被关闭的管道时,当所有的数据都被读取后,read返回0,表示文件结束。
如果写一个读端已经被关闭的管道,则产生SIGPIPE信号。如果忽略该信号或从处理程序中返回后,write函数返回-1,并且errno被设置为EPIPE。
参考资料:《UNIX环境高级编程》