1. 前言
The GNU C Library Reference Manual for version 2.35
2. 管道和 FIFO
Pipes and FIFOs
管道是一种进程间通信机制;一个进程写入管道的数据可以被另一个进程读取。数据按先进先出 (FIFO) 顺序处理。管道没有名字;它是为一次使用而创建的,并且两端必须从创建管道的单个进程继承。
FIFO 特殊文件类似于管道,但它不是匿名的临时连接,而是与任何其他文件一样具有一个或多个名称。进程按名称打开 FIFO 以便通过它进行通信。
管道或 FIFO 必须同时在两端打开。如果您从没有任何进程写入的管道或 FIFO 文件中读取(可能是因为它们都已关闭文件或退出),则读取将返回文件结尾。写入没有读取过程的管道或 FIFO 被视为错误条件;它会生成一个 SIGPIPE 信号,如果信号被处理或阻塞,则会失败并返回错误代码 EPIPE。
管道和 FIFO 特殊文件都不允许文件定位。读写操作都是顺序发生的;从文件的开头读取并在末尾写入。
2.1. 创建管道
Creating a Pipe
创建管道的原语是管道函数。这将创建管道的读取和写入端。单个进程使用管道与自己对话并不是很有用。在典型使用中,进程在派生一个或多个子进程之前创建管道(请参阅创建进程)。然后管道用于父进程或子进程之间或两个兄弟进程之间的通信。
管道函数在头文件 unistd.h 中声明。
函数:int pipe (int filedes[2])
Preliminary: | MT-Safe | AS-Safe | AC-Safe fd | See POSIX Safety Concepts.
pipe函数创建一个管道,并将管道读写端的文件描述符(分别)放入filedes[0]和filedes[1]。
记住输入端在前的一个简单方法是文件描述符 0 是标准输入,文件描述符 1 是标准输出。
如果成功,管道返回值 0。失败时,返回 -1。为此函数定义了以下 errno 错误条件:
EMFILE
该进程打开的文件过多。
ENFILE
整个系统中打开的文件太多。有关 ENFILE 的更多信息,请参阅错误代码。这个错误永远不会在 GNU/Hurd 系统上发生。
下面是一个创建管道的简单程序示例。该程序使用 fork 函数(请参阅创建进程)来创建子进程。父进程将数据写入管道,由子进程读取。
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
/* Read characters from the pipe and echo them to stdout. */
void
read_from_pipe (int file)
{
FILE *stream;
int c;
stream = fdopen (file, "r");
while ((c = fgetc (stream)) != EOF)
putchar (c);
fclose (stream);
}
/* Write some random text to the pipe. */
void
write_to_pipe (int file)
{
FILE *stream;
stream = fdopen (file, "w");
fprintf (stream, "hello, world!\n");
fprintf (stream, "goodbye, world!\n");
fclose (stream);
}
int
main (void)
{
pid_t pid;
int mypipe[2];
/* Create the pipe. */
if (pipe (mypipe))
{
fprintf (stderr, "Pipe failed.\n");
return EXIT_FAILURE;
}
/* Create the child process. */
pid = fork ();
if (pid == (pid_t) 0)
{
/* This is the child process.
Close other end first. */
close (mypipe[1]);
read_from_pipe (mypipe[0]);
return EXIT_SUCCESS;
}
else if (pid < (pid_t) 0)
{
/* The fork failed. */
fprintf (stderr, "Fork failed.\n");
return EXIT_FAILURE;
}
else
{
/* This is the parent process.
Close other end first. */
close (mypipe[0]);
write_to_pipe (mypipe[1]);
return EXIT_SUCCESS;
}
}
2.2. 到子进程的管道
Pipe to a Subprocess
管道的一个常见用途是向作为子进程运行的程序发送数据或从其接收数据。一种方法是结合使用 pipe(创建管道)、fork(创建子进程)、dup2(强制子进程使用管道作为其标准输入或输出通道)和 exec(创建执行新程序)。或者,您可以使用 popen 和 pclose。
使用 popen 和 pclose 的好处是界面更简单易用。但它没有直接使用低级函数提供那么多的灵活性。
函数:FILE * popen (const char *command, const char *mode)
Preliminary: | MT-Safe | AS-Unsafe heap corrupt | AC-Unsafe corrupt lock fd mem | See POSIX Safety Concepts.
popen函数与系统函数密切相关;请参阅运行命令。它将 shell 命令命令作为子进程执行。但是,它不是等待命令完成,而是创建一个到子进程的管道并返回一个与该管道对应的流。
如果指定“r”模式参数,则可以从流中读取以从子进程的标准输出通道中检索数据。子进程从父进程继承其标准输入通道。
同样,如果指定模式参数“w”,则可以写入流以将数据发送到子进程的标准输入通道。子进程从父进程继承其标准输出通道。
在发生错误时 popen 返回一个空指针。如果无法创建管道或流、无法派生子进程或无法执行程序,则可能会发生这种情况。
函数:int pclose (FILE *stream)
Preliminary: | MT-Safe | AS-Unsafe heap plugin corrupt lock | AC-Unsafe corrupt lock fd mem | See POSIX Safety Concepts.
pclose 函数用于关闭由 popen 创建的流。与系统函数一样,它等待子进程终止并返回其状态值。
这是一个示例,展示了如何使用 popen 和 pclose 通过另一个程序过滤输出,在本例中是分页程序更多。
#include <stdio.h>
#include <stdlib.h>
void
write_data (FILE * stream)
{
int i;
for (i = 0; i < 100; i++)
fprintf (stream, "%d\n", i);
if (ferror (stream))
{
fprintf (stderr, "Output to stream failed.\n");
exit (EXIT_FAILURE);
}
}
int
main (void)
{
FILE *output;
output = popen ("more", "w");
if (!output)
{
fprintf (stderr,
"incorrect parameters or too many files.\n");
return EXIT_FAILURE;
}
write_data (output);
if (pclose (output) != 0)
{
fprintf (stderr,
"Could not run more or other error.\n");
}
return EXIT_SUCCESS;
}
2.3. FIFO 特殊文件
FIFO Special Files
FIFO 特殊文件类似于管道,只是它的创建方式不同。不是匿名通信通道,而是通过调用 mkfifo 将 FIFO 特殊文件输入文件系统。
一旦你以这种方式创建了一个 FIFO 特殊文件,任何进程都可以打开它进行读取或写入,就像普通文件一样。但是,它必须同时在两端打开,然后才能继续对其进行任何输入或输出操作。打开一个 FIFO 进行读取通常会阻塞,直到某个其他进程打开同一个 FIFO 进行写入,反之亦然。
mkfifo 函数在头文件 sys/stat.h 中声明。
函数:int mkfifo (const char *filename, mode_t mode)
Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.
mkfifo 函数创建一个名为 filename 的 FIFO 特殊文件。 mode 参数用于设置文件的权限;请参阅分配文件权限。
mkfifo 的正常成功返回值为 0。如果出现错误,则返回 -1。除了通常的文件名错误(请参阅文件名错误)之外,还为此函数定义了以下 errno 错误条件:
EEXIST
指定的文件已经存在。
ENOSPC
无法扩展目录或文件系统。
EROFS
包含该文件的目录位于只读文件系统上。
2.4. 管道 I/O 的原子性
Atomicity of Pipe I/O
如果写入的数据大小不大于 PIPE_BUF,则读取或写入管道数据是原子的。 这意味着数据传输似乎是一个瞬时单元,因为系统中没有其他任何东西可以观察到它部分完成的状态。 原子 I/O 可能不会立即开始(它可能需要等待缓冲区空间或数据),但一旦开始,它会立即结束。
读取或写入大量数据可能不是原子的; 例如,可能会散布来自共享描述符的其他进程的输出数据。 此外,一旦 PIPE_BUF 字符被写入,进一步的写入将被阻塞,直到某些字符被读取。
有关 PIPE_BUF 参数的信息,请参阅文件系统容量限制。