有的时候在程序的开发过程中,两个进程之间会有数据的交互。信号的机制能够实现进程通信。它通过 “中断--响应” 的异步模式来工作。但作为通信来讲,信号还是远远不够的。因为它不能够携带任何其他的信息。只利用信号机制来实现进程通信显得捉襟见肘,并且信号的优势并不此。所以必须开发新的进程间通信的方法。本文所学习的 "匿名管道" 便是其中的一种最简单的方法。
基本概念
在讲管道的基本概念之前,首先提一个问题。如果让你来设计进程通信的方式,你会怎么做?最简单的方式莫过于两个进程打开同一个文件,这样就可以通过不断的读写操作来实现信息的交互。管道的概念与原理与上面的方法类似但不完全一样。下面来看下创建管道的函数 pipe
#include <unistd.h>
int pipe(int fd[2]);
返回值:若成功,返回0,若出错,返回-1
该函数返回两个文件描述符 fd[0]
与 fd[1]
。规定:fd[0]
用于读操作,fd[1]
用于写操作。在创建管道之后,通常的做法是调用 fork
函数,那么子进程也就继承了父进程的fd[0]
与fd[1]
。所以,这样父进程与子进程就通过这两个文件描述符建立了一个通信的 "管道" 。在通信时,父进程与子进程要么一个关闭读端要么一个关闭写端来实现 半双工 方式的数据传递。即数据流只能往一个方向走而不能同时通信。下面的这个图有助于理解 管道 的这种工作方式:
之所以称之为 "匿名" 是因为这种技术只能适用在具有亲缘关系的两个进程中。不是任意的两个进程都可以通过这种方式来实现通信。
简单的例子
下面的这个例子利用管道来实现了,子进程向父进程传递一段信息,父进程接收到并打印出来的简单功能。
int main(void) { int n; int fd[2]; pid_t pid; char line[1024]; if (pipe(fd) < 0) return; if ((pid = fork()) < 0) return; else if (pid > 0) { close(fd[0]); write(fd[1], "hello world\n", 12); } else { close(fd[1]); n = read(fd[0], line, 1024); printf("the recv msg is %s\n", line); } return(0); }
注意点
这里记录一下,管道操作中对读端,写端操作的注意点。
- 写端没有关闭的情况下,执行
read
操作:- 如果管道中没有数据,
read
将会阻塞。 - 管道的缓冲区也是有大小的,这个值记录在
PIPE_BUF
中。 read函数传入的字节数、管道中实际的字节数、管道缓冲区的大小这三者之间 会存在大小顺序的关系。read的返回值也跟着三者之间的关系有关。不过只要记住,read
返回的是实际读到的字节数,并做好返回值检查就可以在编程中避免错误了。
- 如果管道中没有数据,
- 写端关闭的情形下,执行
read
操作:这个时候
read
不会出错,但是返回值为0。 - 读端未关闭的情况下,执行
write
操作:如果管道的缓冲区已经满了,则
write
操作阻塞 - 读端关闭的情况下,执行
write
操作:这种情况是危险的,
write
返回-1,error
设为EPIPE
, 并产生SIGPIPE
信号。
popen pclose函数
在进程控制里面提到过,如果想在程序里面调用 shell 命令或者脚本文件,可以通过fork
函数自己实现,或者直接调用 system
函数。现在我们又多了一种方法,这就是popen
函数,并且popen
函数通过管道机制,可以将执行shell命令的输出结果传递到父进程,或者父进程可以通过管道向shell命令传递参数。这样的实现又极大的方便了我们编程的需求。下面先写出popen
pclose
函数的声明,再举几个例子来加深一下印象。
#include <stdio.h>
FILE *popen(const char *cmdstring, const char *type);
返回值:若成功,返回文件指针;若出错,返回NULL
int pclose(FILE *fp);
返回值:若成功,返回cmdstring的终止状态;若出错,返回-1
当 popen
函数传入的 type 为 "r"
时,表示程序可以从其返回的文件指针中读取执行 cmstring
的结果,它其实将子进程的 stdout
与返回的文件指针连接在一起。如下图所示:
当 popen
函数传入的 type 为 "w"
时,表示父进程可以通过文件指针来向子进程传递参数,它的理解可以借助于下图:
两个简单的利用 popen
函数的例子:
/* test popen output */ int main() { FILE *fp; pid_t pid; char *cmd = "ls -al"; char line[1024]; if ((fp = popen(cmd, "r")) == NULL) return; // get and print the data from child process fread(line, sizeof(char), 1024, fp); printf("%s\n", line); pclose(fp); return; } /* test popen input */ int main() { FILE *fp; pid_t pid; char *cmd = "cat "; char *msg = "hello world\n"; if ((fp = popen(cmd, "w")) == NULL) return; // get the params form the parent process fwrite(msg, sizeof(char), 12, fp); pclose(fp); return; }