pipe的使用
管道(pipe)是POSIX标准中一种可用于进程间通信的单向数据通道。一个管道有一个写端和一个读端,数据从写端写入,从读端读取,因此读端和写端分别有一个文件描述符。
管道的读写模式可以是阻塞模式,也可以是非阻塞模式(O_NONBLOCK)。对于阻塞模式,如果管道中没有数据,那么读操作将阻塞,直到写入足够数据;如果管道已满,那么写操作将阻塞,直到读出数据留出足够空间。
管道有一个最大容量,可用pathconf(_PC_PIPE_BUF)获取。当写入的数据小于最大容量时,写入操作是原子的,内核会把数据全部写入管道;而当写入的数据大于最大容量时,则写操作不再是原子的,多个进程写的数据可能交织在一起。
用于创建管道的函数如下。
#include <unistd.h>
int pipe(int pipefd[2]);
该函数所带的参数实际上是输出参数。如果该函数成功,则返回两个文件描述符,pipefd[0]代表管道读端的文件描述符,pipefd[1]则代表管道写端的文件描述符。如果要修改管道的阻塞模式,可以用fcntl函数来实现。Linux还提供一个特有的函数pipe2,允许在创建管道时直接指定阻塞模式。该函数如下:
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <fcntl.h> /* Obtain O_* constant definitions */
#include <unistd.h>
int pipe2(int pipefd[2], int flags);
pipe函数创建的管道是匿名管道,因此只能在共享文件描述符的父子进程之间使用。Linux的man手册给出了一个例子:
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int
main(int argc, char *argv[])
{
int pipefd[2];
pid_t cpid;
char buf;
if (argc != 2) {
fprintf(stderr, "Usage: %s <string>\n", argv[0]);
exit(EXIT_FAILURE);
}
if (pipe(pipefd) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
cpid = fork();
if (cpid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (cpid == 0) { /* Child reads from pipe */
close(pipefd[1]); /* Close unused write end */
while (read(pipefd[0], &buf, 1) > 0)
write(STDOUT_FILENO, &buf, 1);
write(STDOUT_FILENO, "\n", 1);
close(pipefd[0]);
_exit(EXIT_SUCCESS);
} else { /* Parent writes argv[1] to pipe */
close(pipefd[0]); /* Close unused read end */
write(pipefd[1], argv[1], strlen(argv[1]));
close(pipefd[1]); /* Reader will see EOF */
wait(NULL); /* Wait for child */
exit(EXIT_SUCCESS);
}
}
编译并运行该程序可得到如期结果。
gcc -o test_pipe test_pipe.c
./test_pipe "Test pipe message"
Test pipe message
fifo的使用
FIFO则是一种命名的管道,但其编程使用方式与管道有所不同。首先应调用mkfifo函数来创建一个FIFO,该函数会在文件系统中创建一个FIFO特殊文件。然后进程可以分别调用open来打开这个FIFO特殊文件进行读和写。mkfifo函数原型如下。
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
该函数有两个参数,第一个参数是一个文件路径,代表要创建FIFO特殊文件;第二个参数是访问模式。如果创建成功,返回值为0。
创建好FIFO特殊文件后,任何进程都可以打开FIFO进行读写操作。但是跟普通文件不同的是,阻塞模式下open操作成对进行才能继续进行读写操作。比如一个进程如果要打开FIFO进行读,那么这个open操作会阻塞,直到有某个进程打开它进行写操作;反之亦然。如果要使用非阻塞模式,那么在open是应在flags参数中指定O_NONBLOCK。
把前面pipe的例子稍微改一下,就可以得到fifo的示例。
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
int
main(int argc, char *argv[])
{
int fd;
pid_t cpid;
char buf;
if (argc != 2) {
fprintf(stderr, "Usage: %s <string>\n", argv[0]);
exit(EXIT_FAILURE);
}
if (mkfifo("/tmp/test_fifo", 0666) == -1) {
perror("mkfifo");
exit(EXIT_FAILURE);
}
cpid = fork();
if (cpid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (cpid == 0) { /* Child reads from fifo */
fd = open("/tmp/test_fifo", O_RDONLY);
while (read(fd, &buf, 1) > 0)
write(STDOUT_FILENO, &buf, 1);
write(STDOUT_FILENO, "\n", 1);
close(fd);
_exit(EXIT_SUCCESS);
} else { /* Parent writes argv[1] to fifo */
fd = open("/tmp/test_fifo", O_WRONLY);
write(fd, argv[1], strlen(argv[1]));
close(fd); /* Reader will see EOF */
wait(NULL); /* Wait for child */
unlink("/tmp/test_fifo");
exit(EXIT_SUCCESS);
}
}
编译并运行,可得到如期结果。
gcc -o test_fifo test_fifo.c
./test_fifo "Test fifo message"
Test fifo message
popen的使用
POSIX还提供了另外一个API以便更方便地使用管道,即popen函数。该函数原型如下。
#include <stdio.h>
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
这个函数的作用是在调用popen的进程和执行command的进程之间建立一个管道。本质上,它组合pipe函数和fork函数的功能。第一个参数command指定要执行的命令,第二个参数则指定管道的类型(方向),可以为可读"r"或者可写"w",但不能为同时读写,因为管道是单向的。
对于可读"r"管道,管道的读端在调用进程,而管道的写端则在command进程的标准输出。而command进程的标准输入则继承自父进程也就是调用进程的标准输入,除非command进程自己做了重定向。默认情况下,管道是阻塞模式。也就是说command进程端写满时会等待调用进程读走才能继续;而调用进程读空时也会等待command进程写入才能继续。因此,这种管道的作用就是把command进程的标准输出重定向了。
对于可写"w"管道,管道的写端在调用进程,而管道的读端则在command进程的标准输入。而command进程的标准输出则继承自父进程也就是调用进程的标准输出,除非command进程自己做了重定向。同样,此管道默认也是阻塞模式,类似与"r"管道。因此,这种管道的作用就是把command进程的标准输入重定向了。
pclose函数用于关闭管道,它会等待关联的子进程退出后才返回。
下面这个例子演示了可读"r"管道的用法。从编译运行结果可以看到,子进程的printf输出都到了管道里,而父进程可从管道读取。
/*
* test_popen.c: demostrates popen
*/
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
static void child_proc(void)
{
printf("Hello from child\n");
exit(EXIT_SUCCESS);
}
int main(int argc, char *argv[])
{
FILE *pipefp = NULL;
char *self_exe;
char child_cmd[1024];
char buf;
if (argc == 1) {
self_exe = realpath(argv[0], NULL);
snprintf(child_cmd, sizeof(child_cmd), "%s child", self_exe);
free(self_exe);
pipefp = popen(child_cmd, "r");
if (!pipefp) {
perror("popen");
return -1;
}
while (fread(&buf, 1, 1, pipefp)) {
printf("From pipe: %c\n", buf);
}
pclose(pipefp);
} else {
child_proc();
}
return 0;
}
gcc -o test_popen test_popen.c
./test_popen
From pipe: H
From pipe: e
From pipe: l
From pipe: l
From pipe: o
From pipe:
From pipe: f
From pipe: r
From pipe: o
From pipe: m
From pipe:
From pipe: c
From pipe: h
From pipe: i
From pipe: l
From pipe: d
From pipe: