linux进程通信———无名管道

linux进程通信———无名管道

引言:打算认认真真实践一下进程间通信的几种方式,本篇笔记介绍了管道局限性、创建方式、代码实例、内核实现。建议感兴趣的同学,以书籍为主,建立系统知识,书本远远比博客深邃。


一、无名管道简介

1.1、局限性:

  管道是最初的unix IPC(interprocess communication)形式,其本身存在两个局限性:首先管道没有名字,他们只能在具有共同祖先的进程间使用,一般用于父子进程;其次历史上管道是半双工的,即数据只能在一个方向流动。

1.2、创建:

  所有式样的Unix都提供管道,它由pipe()函数创建,提供一个单向的数据流。pipe2()与pipe()区别在于增加了flags标志位,可以通过标志位O_NONBLOCK、O_CLOEXEC设置所创建管道的状态。

这里写图片描述
图1、pipe()函数man手册

  该函数返回两个文件描述符:fd[0]和fd[1],前者打开来读,后者打开来写。图2展示了单个进程中管道的样子:

这里写图片描述
图2、单个进程中的管道

  尽管管道是单个进程创建的,但是其很少在单个进程内使用,管道的典型用途是为父子进程提供进程间通信的手段。首先,其由一个进程(它将成为父进程)创建一个管道后调用fork派生一个自身的副本,如图3所示:

这里写图片描述
图3、进程创建管道后执行fork

  接着父进程关闭这个管道的读出端,子进程关闭同一管道的写入端,这就在父子进程间提供了一个单向的数据流,如图4所示:

这里写图片描述
图4、单个进程间的管道

  到目前为止所示的所有管道都是半双工即单向的,只提供一个方向的数据流,可以创建两个管道,父子进程各关闭相应的读写,其实际步骤如下:

1)、创建管道1(fd1[0]和fd1[1])和管道2(fd2[0]和fd2[1]);
2)、fork创建子进程
3)、父进程关闭管道1的读出端(fd1[0]),父进程关闭管道2的写入端(fd2[1]);
4)、子进程关闭管道1的写入端(fd1[1]),子进程关闭管道2的读出端(fd2[0]);

这里写图片描述
图5、提供一个双向数据流的两个管道


二、代码实例

2.1、父子进程单向传送数据:

/*******************************************************
*内容:IPC管道代码测试
*时间:2018年3月30日
*逻辑:父进程读取argv接收的字符串,将其通过管道传递给子进程,打印
*问题:在计算输入字符串长度时,开始用了sizeof(argv[1]),字符串传递不完整
       想起sizeof()运算符仅返回类型长度,argv[1]是指针,改用strlen   
*********************************************************/
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#define MAXLINE 512

int main(int argc,char *argv[])
{
    pid_t pid;
    int length;
    int flag = -1;
    int pipfd[2];
    char readbuf[MAXLINE];


    //第一步、用户输入合法性检测
    if (argc != 2) 
    {
        fprintf(stderr, "usage : %s\n", argv[0]);
        exit(1);
    }

    //第二步、创建管道
    flag = pipe(pipfd);
    if(flag < 0)
    {
        perror("pipe error");
    }

    //第三步、fork创建子进程,并构造单向流通管道
    pid = fork();
    if(pid < 0)
    {
        perror("fork error");
    }
    else if(0 == pid)
    {
        //执行子进程的动作,关闭写入端、读取数据、写至标准输出
        close(pipfd[1]); 
        memset(readbuf,0,sizeof(readbuf));
        length = read(pipfd[0], readbuf, MAXLINE);
        //printf("length = %d.\n", length);
        //write(STDOUT_FILENO, readbuf, length);
        printf("%s\n", readbuf);
    }
    else
    {
        //执行父进程的动作,关闭读出端、读argv字符串、写数据
        close(pipfd[0]);
        write(pipfd[1], argv[1], strlen(argv[1]));  
    }

    return 0;
}

执行结果:

这里写图片描述
图6、父子进程单向传递数据

2.2、父子进程双向传送数据:

/*******************************************************
*内容:利用管道,父子进程间双向传递数据
*时间:2018330*逻辑:创建两个管道,分别关闭对应读写端口
*      父进程与标准输入输出绑定,子进程仅仅回射
*问题:read()为阻塞函数,添加sleep()便于查看 
*********************************************************/


int main(int argc,char *argv[])
{
    pid_t pid;
    int length;
    int pip_in[2], pip_out[2];
    char child_recvbuf[MAXLINE];
    char father_recvbuf[MAXLINE];


    //第一步、用户输入合法性检测
    if (argc != 2) 
    {
        fprintf(stderr, "usage : %s\n", argv[0]);
        exit(1);
    }

    //第二步、创建管道
    if(pipe(pip_in)  < 0)
    {
        perror("pipe_in error");
    }

    if(pipe(pip_out) < 0)
    {
        perror("pipe_out error");
    }


    //第三步、fork创建子进程,并构造双向流通管道
    pid = fork();
    if(pid < 0)
    {
        perror("fork error");
    }
    else if(0 == pid)
    {
        //sleep(2);
        //执行子进程的动作,关闭流入管道写入端pip_in[1]、由pip_in[0]读数据 
        close(pip_in[1]); 
        memset(child_recvbuf,0,sizeof(child_recvbuf));
        length = read(pip_in[0], child_recvbuf, MAXLINE);
        printf("child receive message: %s\n", child_recvbuf);


        //关闭流出管道读入端pip_out[0],回射数据
        close(pip_out[0]); 
        write(pip_out[1], child_recvbuf, strlen(child_recvbuf));
        printf("child send message: %s\n", child_recvbuf);


    }
    else
    {
        //执行父进程的动作,操作读写端口
        close(pip_in[0]);
        write(pip_in[1], argv[1], strlen(argv[1])); 
        printf("father send message: %s\n", argv[1]);

        //sleep(3);
        //关闭流出管道写入端pip_out[1]
        close(pip_out[1]);
        memset(father_recvbuf,0,sizeof(father_recvbuf));
        length = read(pip_out[0], father_recvbuf, MAXLINE);
        printf("father receive message: %s\n", father_recvbuf);
    }

    return 0;
}

执行结果:

这里写图片描述
图7、简单 的双向通信


三、pipe管道的内核实现

  linux下的进程的用户态地址空间都是相互独立的,因此两个进程在用户态是没法直接通信的,因为找不到彼此的存在;而内核是进程间共享的,因此进程间想通信只能通过内核作为中间人,来传达信息。

这里写图片描述
图8、pipe管道的内核实现

  在一个进程中,向管道中写入数据时,其实就是写入这个缓存中;然后在另一个进程读取管道时,其实就是从这个缓存读取,实现进程的通信.关于pipe管道的实现,可以参考这篇文章,写的很详细:从内核代码聊聊pipe的实现


参考资料:
1、unix 网络编程卷II
2、Linux进程间通信(一)(经典IPC:管道、FIFO)
3、从内核代码聊聊pipe的实现

纠错与建议
邮箱:[email protected]


猜你喜欢

转载自blog.csdn.net/xd_hebuters/article/details/79774248