进程间通信——管道

来源:互联网 发布:bose蓝牙音响 知乎 编辑:程序博客网 时间:2024/04/30 13:22

1 管道

       作为进程之间的通信手段之一,本质就是内核中的一块有限的缓冲区(64K字节,不同操作系统不一样),而且独立于文件系统,自己构成一个只存在于内存中的文件系统(可追踪内核的pipe.c存在于文件系统目录下)。其特点如下。

1)半双工通信,同一时间数据只能从一端流向另一端;

2)分为匿名管道和有名管道。而且对于匿名管道,只能用于具有共同祖先的进程之间通信,即父子进程或者父孙进程等有关系的进程之间通信,有名管道则没有此限制;

3)填充的是文件描述符数组,其中fd[0] 表示读端,fd[1]表示写端。其在创建子进程前后的关系如下图所示。


2 通用编程方式

2.1 匿名管道

       如下表所示,需要注意的就是,父子进程可以关闭管道无用的端口。

  1 

//假设用例是子进程通过管道发送给父进程

  2 

// 子进程需要关闭读端,父进程需要关闭写端

  3 

intmain(int argc,char *argv[])

  4 

{

  5 

    int pipefd[2];

  6 

    if (pipe(pipefd) == -1)

  7 

        ERR_EXIT("pipe error");

  8 

    pid_t pid;

  9 

    pid = fork();

 10 

    if (pid == -1)

 11 

        ERR_EXIT("fork error");

 12 

    if (pid ==0)

 13 

    {

 14 

        close(pipefd[0]);   

 15 

        write(pipefd[1],"hello", 5);

 16 

        close(pipefd[1]);

 17 

        exit(EXIT_SUCCESS);

 18 

    }

 19 

    close(pipefd[1]);

 20 

    char buf[10] = {0};

 21 

    read(pipefd[0], buf,10);

 22 

    printf("buf=%s\n", buf);

 23 

    return0;

 24 

}

2.2 有名管道

       简单示例,如下表所示。注意打开方式的不同,除了下示的open打开一个已经存在的管道文件外,还可以通过mkfifo函数创建,事实上它也是一个命令。

       其用法说明如下。

1)有名管道,是一种特殊的文件类型,即通过ls可以查看到其指定为“p”,除此之外还有六种类型,共计七种,d-目录文件,l-符号链接文件,s-套接字文件,b-块设备文件,c-字符设备文件,“-”表示普通文件。

2) 打开方式有所不同,有可能会阻塞。没错,打开时就阻塞。具体可参见下表,注意区分O_NONBLOCK使能后,对应的错误。

 

O_NONBLOCK disable

O_NONBLOCK enable

READ

阻塞,直至有程序以写打开该管道

返回成功

WRITE

阻塞,直至有程序以读打开该管道

返回失败,错误码为ENXIO

3)示例程序

  1 

intmain(int argc,char *argv[])

  2 

{

  3 

    int fd;

  4 

/*    fd = open("p1", O_RDONLY);*/

  5 

    fd = open("p1", O_RDONLY | O_NONBLOCK);

  6 

   

  7 

    if (fd == -1)

  8 

        ERR_EXIT("open error");

  9 

 

 10 

    printf("open succ\n");

 11 

    return0;

 12 

}

2.3 对比

       主要体现在两点,一是打开方式,匿名管道不可能会发生阻塞;二是用途,匿名管道不能用于不具备亲缘关系的进程之间通信。

 

3 读写规则

       若管道的写端不存在,继续读该管道时,认为已经读到了末尾,返回0;

       若管道的读端不存在,继续写该管道时,返回错误;

       [可见系统对写采取0容忍的态度 ]

       若管道不存在可读数据,那么在非阻塞模式下返回-1,且errno等于EAGAIN,而在阻塞模式下则会继续阻塞直至有数据;

       若管道已经满时,那么在非阻塞模式下同样返回-1,且errno等于EAGAIN,在阻塞模式下则继续阻塞,直至有空间可写;

       当写入的数据大于PIPEBUF(在文章一开始处已经介绍了管道本质就是内核的一块缓冲区大小为PIPEBUF 64K),不会保证写入的原子性。如下代码所示。

  1 

intmain(void)

  2 

{

  3 

    char a[TEST_SIZE];

  4 

    char b[TEST_SIZE];

  5 

    char c[TEST_SIZE];

  6 

 

  7 

    memset(a,'A', sizeof(a));

  8 

    memset(b,'B', sizeof(b));

  9 

    memset(c,'C', sizeof(c));

 10 

 

 11 

    int pipefd[2];

 12 

 

 13 

    int ret =pipe(pipefd);

 14 

    if (ret == -1)

 15 

        ERR_EXIT("pipe error");

 16 

 

 17 

    pid_t pid;

 18 

    pid = fork();

 19 

    if (pid ==0)//第一个子进程

 20 

    {

 21 

        close(pipefd[0]);

 22 

        ret = write(pipefd[1], a, sizeof(a));

 23 

        printf("apid=%d write%d bytes to pipe\n",getpid(), ret);

 24 

        exit(0);

 25 

    }

 26 

 

 27 

    pid = fork();

 28 

 

 29 

   

 30 

    if (pid ==0)//第二个子进程

 31 

    {

 32 

        close(pipefd[0]);

 33 

        ret = write(pipefd[1], b, sizeof(b));

 34 

        printf("bpid=%d write%d bytes to pipe\n",getpid(), ret);

 35 

        exit(0);

 36 

    }

 37 

 

 38 

    pid = fork();

 39 

 

 40 

   

 41 

    if (pid ==0)//第三个子进程

 42 

    {

 43 

        close(pipefd[0]);

 44 

        ret = write(pipefd[1], c, sizeof(c));

 45 

        printf("bpid=%d write%d bytes to pipe\n",getpid(), ret);

 46 

        exit(0);

 47 

    }

 48 

 

 49 

 

 50 

    close(pipefd[1]);

 51 

   

 52 

    sleep(1);

 53 

    int fd =open("test.txt", O_WRONLY | O_CREAT | O_TRUNC,0644);

 54 

    char buf[1024*4] = {0};

 55 

    int n =1;

 56 

    while (1)

 57 

    {

 58 

        ret = read(pipefd[0], buf, sizeof(buf));

 59 

        if (ret ==0)

 60 

            break;

 61 

        printf("n=%02d pid=%d read %d bytes from pipe buf[4095]=%c\n", n++,getpid(), ret, buf[4095]);

 62 

        write(fd, buf, ret);

 63 

 

 64 

    }

 65 

    return0;   

 66 

}

       其结果,如下图所示。“A”、“B”、“C”完全是乱序输出的,并不是按顺序,写完A再写B和C的。


0 0