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字节就是管道容量。