管道pipe是Linux系统的一种IPC(进程间通信)形式,历史上,pipe是半双工的,数据只能在一个方向上流动,某些系统则提供了全双工机制,而且pipe局限于只能在具有公共祖先的进程间使用。在shell命令行中我们经常用到管道,管道符号为一个竖线“|”,管道符号左、右的命令分别在独立的父、子进程执行,左边命令的标准输出作为右边命令的标准输入,这种类型的管道因为没有名字而限于有血缘关系的进程间,因此也叫无名管道或匿名管道。
创建pipe使用如下函数:
#include <unistd.h>
int pipe(int pipefd[2]);
pipe函数执行成功时返回0,失败时返回-1并设置相应的errno。参数pipefd是个包含两个文件描述符的数组,其中pipefd[0]为读而打开,pipefd[1]为写而打开,pipefd[1]的输出是pipefd[0]的输入。
单个进程中的管道几乎没有任何用处,通常的用法是调用pipe的进程接着调用fork,父、子进程分别关闭一个文件描述符。当读一个写端已被关闭的管道时,在所有数据都被读取后,read返回0,以指示遇到了文件尾。如果写一个读端已被关闭的管道,则产生SIGPIPE信号。在写管道时,常量PIPE_BUF规定了内核中管道缓冲区的大小,这个需要我们注意。
下面是一个使用了pipe的例子,父进程把字符串“pipe from parent\n”传送到子进程,子进程把这个字符串打印出来。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
int n;
int fd[2];
pid_t pid;
char line[4096] = { 0 };
if (pipe(fd) < 0) {
printf("pipe error\n");
return -1;
}
if ((pid = fork()) < 0) {
printf("fork error");
return -1;
}
else if (pid > 0) {
close(fd[0]);
write(fd[1], "pipe from parent\n", 17);
}
else {
close(fd[1]);
n = read(fd[0], line, 4096);
write(STDOUT_FILENO, line, n);
}
exit(0);
}
对于管道,常见的操作是创建一个管道并连接到另一个进程,然后读其输出或向其输入端发送数据,为此标准IO库提供了两个函数popen和pclose。这两个函数实现的操作是:创建一个管道,调用fork产生一个子进程,关闭不使用的管道描述符,通过exec函数执行一个shell以运行命令,然后等待命令终止。函数原型如下:
#include <stdio.h>
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
popen成功时返回一个文件指针,type为“r”时文件指针连接到命令command的标准输出,type为“w”时文件指针连接到命令command的标准输入。pclose成功时返回命令command的终止状态。需要注意的是,popen决不应由设置用户ID或设置组ID程序调用,当它执行命令时,它在从调用者继承的环境中执行shell,并由shell解释执行command,一个心怀不轨的用户可以操纵这种环境,使得shell能以设置ID模式所授予的提升了的权限以非预期的方式执行命令。
下面是一个使用了popen的例子,单独执行help时,其作用是把标准输入的小写字母转换为大写字母然后打印到标准输出,help被popen以读模式打开时,popen程序首先打印提示符“prompt> ”,然后help从标准输入读取并对其进行转化后输出到popen打开的文件指针fpin,popen程序从文件指针fpin读取数据再进行一定的处理后打印到标准输出。
// help.c
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
int main()
{
int c;
while ((c = getchar()) != EOF) {
if (isupper(c)) {
c = tolower(c);
}
if (putchar(c) == EOF) {
printf("error: putchar\n");
}
if (c == '\n') {
fflush(stdout);
}
}
exit(0);
}
// popenex.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define MAXLINE (8092)
int main()
{
char line[MAXLINE] = { 0 };
FILE *fpin;
char *result = "result:";
strncpy(line, result, strlen(result));
if ((fpin = popen("help", "r")) == NULL) {
printf("error: popen\n");
}
while (1) {
fputs("prompt> ", stdout);
fflush(stdout);
if (fgets(line + strlen(result), MAXLINE, fpin) == NULL) {
break;
}
if (fputs(line, stdout) == EOF) {
printf("error: fputs\n");
}
}
if (pclose(fpin) == -1) {
printf("error: pclose\n");
}
putchar('\n');
exit(0);
}
上面的例子中,popen提供了一个连接到help标准输出的单向管道,另一种做法是在主程序中调用两次popen函数,分别连接到help的标准输入和标准输出,这样,help的输入从主程序获取,help的输出又传送给主程序,对于主程序来说,它提高了双向管道,help就是一个协同进程。
除了匿名管道pipe,还有一种有名管道FIFO,最大的不同在于FIFO可以在任意进程建通信,使用的便是这个FIFO的文件名。创建FIFO类似于创建文件:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *path, mode_t mode);
一旦创建了一个FIFO文件,就可用open打开它进行操作,其它的文件IO函数close、read、write、unlink都可用于FIFO。下面是一个使用了FIFO的例子,writefifo写数据到FIFO文件,readfifo从FIFO文件读数据。
// readfifo.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define MAXLINE (8092)
#define FIFONAME "myfifo.f"
#define FIFOMODE (0644)
int main()
{
char line[MAXLINE];
int fifo;
ssize_t nread;
if (access(FIFONAME, F_OK) == -1) {
perror("[read]");
if (mkfifo(FIFONAME, FIFOMODE) == -1) {
perror("[read]");
return -1;
}
}
if ((fifo = open(FIFONAME, O_RDONLY)) == -1) {
perror("[read]");
return -1;
}
while (1) {
memset(line, 0, MAXLINE);
nread = read(fifo, line, MAXLINE - 1);
if (nread == -1) {
perror("[read]");
break;
}
else if (nread > 0) {
printf("[read]:%s\n", line);
}
}
close(fifo);
unlink(FIFONAME);
exit(0);
}
// writefifo.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define MAXLINE (8092)
#define FIFONAME "myfifo.f"
#define FIFOMODE (0644)
int main()
{
char line[MAXLINE];
int fifo;
if (access(FIFONAME, F_OK) == -1) {
perror("[write]");
if (mkfifo(FIFONAME, FIFOMODE) == -1) {
perror("[write]");
return -1;
}
}
if ((fifo = open(FIFONAME, O_WRONLY)) == -1) {
perror("[write]");
return -1;
}
while (1) {
memset(line, 0, MAXLINE);
fgets(line, MAXLINE, stdin);
line[strlen(line) - 1] = 0;
if (write(fifo, line, MAXLINE) == -1) {
perror("[write]");
break;
}
if (line[0] == 'q') {
break;
}
}
close(fifo);
unlink(FIFONAME);
exit(0);
}