1、概述

  无名管道用于有亲缘关系的进程间的通信,管道字如其名,它就像在两个进程之间铺设了一条管道,进程通过管道进行数据交互。无名管道是没有名字的,它由pipe或者pipe2函数创建,与之对应的是有名管道,有名管道在下一节介绍。

  以pipe函数为例,其函数原型为int pipe(int pipefd[2]); pipefd[2]是输出变量,需要传入具有两个成员的整形数组,函数调用成功后会由系统填入值,其中pipefd[0]用作读,pipefd[1]用作写,读写函数用read和write。

  pipe函数介绍如下:

函数原型 int pipe(int pipefd[2]);
头文件 unistd.h
功能 创建无名管道
参数 [out] pipefd[2]:含有两个成员的整形数组,pipe调用成功后pipefd[0]用于读数据,pipefd[1]用于写数据
返回 成功返回0,失败返回-1

 

  pipe2函数介绍如下:

函数原型 int pipe2(int pipefd[2], int flags);
头文件 unistd.h
功能 创建无名管道,并可以设置属性
参数

[out] pipefd[2]:同pipe

[in] flags:可以传入两个标志

  #O_NONBLOCK:非阻塞属性,在读数据时即使管道内没有数据,read函数也会立即返回,返回值为-1。

  #O_CLOEXEC:关闭子进程无用描述符,可参见:https://blog.csdn.net/chrisniu1984/article/details/7050663

  如果传入0,那么效果与pipe一样。

返回 成功返回0,失败返回-1

2、测试程序

  先写一个正常使用的程序:

 1 /**
 2  * filename: pipe.c
 3  * author: Suzkfly
 4  * date: 2021-02-09
 5  * platform: Ubuntu
 6  * 执行程序,父进程在终端中输入字符,按回车键将数据放入管道,子进程能将管道数据
 7  * 读出。MODE为1时读阻塞,MODE为2及MODE为3是通过两种方法实现读不阻塞。
 8  */
 9 #include <unistd.h>
10 #include <stdio.h>
11 #include <string.h>
12 #include <fcntl.h>
13 
14 #define MODE 1
15 
16 int main(int argc, const char *argv[])
17 {
18     int pipefd[2];
19     int ret = 0;
20     int pid = 0;
21     char buf[128] = { 0 };
22     int attr = 0;
23 
24 #if (MODE == 1)
25     ret = pipe(pipefd);                 /* 创建管道(默认阻塞) */
26 #elif (MODE == 2)
27     ret = pipe2(pipefd, O_NONBLOCK);    /* 创建非阻塞管道 */
28 #elif (MODE == 3)
29     ret = pipe(pipefd);                 /* 创建管道,并通过fcntl设置为非阻塞 */
30     attr = fcntl(pipefd[0], F_GETFL);
31     attr |= O_NONBLOCK;
32     fcntl(pipefd[0], F_SETFL, attr);
33 #endif
34     if (ret == -1) {
35         printf("pipe failed\n");
36         return 0;
37     }
38     
39     printf("MODE = %d\n", MODE);
40     
41     pid = fork();
42     if (pid > 0) {              /* 父进程 */
43         while (1) {
44             memset(buf, 0, sizeof(buf));
45             scanf("%s", buf);
46             ret = write(pipefd[1], buf, strlen(buf));
47             printf("parent write ret = %d\n", ret);
48         }
49     } else if (pid == 0) {      /* 子进程 */
50         while (1) {
51             memset(buf, 0, sizeof(buf));
52             ret = read(pipefd[0], buf, sizeof(buf));
53             printf("child read ret = %d\n", ret);
54             printf("child receive:%s\n", buf);
55             
56             if ((MODE == 2) || (MODE == 3)) {
57                 sleep(1);
58             }
59         }
60     }
61     
62     return 0;
63 }

测试结果:

 

 3、问题解答

  1. 用pipe创建出来的管道是单向的,是不是指的是只能从一个进程发送数据给另一个进程?

  答:不是,单向指的是文件描述符是单向的,pipefd[1]只能写数据,pipefd[0]只能读数据,在调用fork之后实际上有两套文件描述符(但是只有1个管道),任何一个进程向pipefd[1]写数据之后,任何一个进程都能从pipefd[0]中读数据出来(可以自己写自己读)。

 

  2. 如何实现进程之间数据单向性?

  答:在要写数据的进程中关闭pipefd[0],在要读数据的进程中关闭pipefd[1]。

 

  3. 如果所有的读文件描述符都被关闭了,此时仍然向管道内写数据会发生什么?

  答:写数据的进程会收到SIGPIPE(管道破裂)信号,默认情况下写数据的进程会退出,也可以通过程序捕捉该信号,自行处理该信号。

 

  4. 如果所有的写文件描述符都被关闭了,此时仍然向管道读数据会发生什么?

  答:如果管道内有数据,则可以将数据读出来,如果管道内没数据,那么read也会立即返回,返回值为0。

 

  5. 如果一直向管道写数据,但不读,会发生什么?

  经过测试,发现当下一次写操作将要超过65536字节时,写会阻塞。简单理解就是如果管道内有65536字节时,写入任意数量的字节都将阻塞,如果管道内有63K字节,那么一次性写入1024个字节不会阻塞,但如果一次性写入1025个字节则会阻塞。这65536字节就是管道容量。