【Linux进程间通信】 - 匿名管道

原创作品,转载请标明:http://blog.csdn.net/Xiejingfa/article/details/50819660

进程间通信,英文又称作IPC(InterProcess Communication)就是在不同的进程之间交换信息。我们知道,进程的用户是相空间互独立的,不能相互访问,所以为了使得进程间可以相互通信,操作系统需要提供一种介质使通信双方都可以访问。类似这样的介质有很多,比如磁盘上的文件,我们只要协调好进程访问文件的顺序,一个进程先“写”,一个进程后“读”,这样,两者就可以相互交换信息了。又比如我们经常使用的数据库,也可以作为信息交换的媒介。但是这些通信手段效率都太低了,需要在系统级别提供高效的进程间通信手段。今天我们就来介绍一种最古老的进程间通信方式 – 管道。

1、什么是管道?

如果你熟悉Linux系统,一定不会对管道感到陌生。在Linux系统中,我们经常通过符号“|”来使用管道,用以连接两个或多个命令。那管道是什么?实际上,管道是进程与进程间的数据流通道,它使得数据可以以一种“流”的形式在进程间流动。

管道也是Unix/Linux系统中一种最常见的进程间通信方式,它在两个通信进程之间实现一个数据流的通道从而进行信息传递。

管道通信具有以下特点:

  1. 管道是没有名字的,因此有称为匿名管道。它在系统中是没有实名的,不能在文件系统中看到管道。
  2. 在大多数系统中,管道通信是半双工的,也就是说数据只能在一个方向上流动。
  3. 管道中传送的是无格式的字节流。
  4. 管道的缓冲区是有限的,它的大小在include/linux/limits.h中由PIPE_BUF定义。
  5. 管道只能用于具有亲缘关系的进程间进行通信,如父子进程或者兄弟进程。

2、管道的创建

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()函数创建了一个管道。到这里你可能会感到奇怪:不是说管道是进程间通信的方式吗?在单个进程中创建管道,然后从一段写入从另一端读出有什么意义?下面我们重点讲解一下管道如何实现进程间通信。

3、管道的读写

正如你所想的,在单个进程中的创建管道几乎没有意义。前面我们说过管道只能用于父子进程或者兄弟进程间进行通信,通常我们先创建pipe,接着调用fork函数,从而创建从父子进程或者兄弟进程间的ipc通道。

为什么这样就能创建进程通信的管道呢?其中的功臣就是fork函数。fork函数创建出来的子进程是父进程的一个拷贝,子进程从父进程中得到数据段和堆栈段的拷贝。这样,父子都拥有事先创建管道的文件描述符,都可以往管道中读写信息。一般用于文件的IOh函数如read、write等都可以用于管道的读写。

如下图所示:

【Linux进程间通信】 - 匿名管道_第1张图片

经过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函数创建两个子进程,在两个兄弟子进程中维护通信管道。

4、管道读写的规则

管道写入的规则:程序向管道写入数据时,如果缓冲区有空闲空间,写进程立即向管道写入数据。如果缓冲区已满,那么写操作就一直阻塞到读进程读走数据为止。

管道读取的规则:如果请求的字节数大于PIPE_SIZE,则返回管道中现有的字节数。如果请求的字节数不大于PIPE_SIZE,则返回请求的字节数或现有数据的字节数(管道中的数据量小于请求的数据量)。

如果读写一端已经被关闭的管道是呢?

如果读一个写端已经被关闭的管道时,当所有的数据都被读取后,read返回0,表示文件结束。

如果写一个读端已经被关闭的管道,则产生SIGPIPE信号。如果忽略该信号或从处理程序中返回后,write函数返回-1,并且errno被设置为EPIPE。

参考资料:《UNIX环境高级编程》

你可能感兴趣的:(linux,pipe,进程间通信,匿名管道)