Home > Article > System Tutorial > Linux Pipes and FIFO Application Note
Самое распространенное место для труб — это оболочка, например:
рррееЧтобы выполнить указанную выше команду, оболочка создает два процесса для выполнения ls
и wc
соответственно (через fork()
и exec ( )
Завершено), следующим образом:
Как видно из рисунка выше, конвейер можно рассматривать как набор водопроводных труб, который позволяет данным передаваться от одного процесса к другому, что также является источником названия конвейера.
Как видно из рисунка выше, к каналу подключены два процесса, так что процесс записи ls
подключает свой стандартный вывод (дескриптор файла равен 1) к сегменту записи входящего канала , Процесс чтения wc
подключает свой стандартный ввод (дескриптор файла 0) к концу канала чтения. Фактически эти два процесса не знают о существовании канала, они просто читают и записывают данные из стандартного файлового дескриптора. Оболочка должна выполнять всю работу.
Канал представляет собой поток байтов, то есть при использовании канала нет понятия сообщений или границ сообщений:
lseek()
нельзя использовать в канале для рандомизации Доступ к даннымЕсли вам необходимо реализовать концепцию дискретных сообщений в конвейере, то эту работу необходимо выполнить в приложении. Хотя это возможно, если вы столкнетесь с такой необходимостью, лучше использовать другие механизмы IPC, такие как очереди сообщений и сокеты датаграмм.
Попытки чтения из пустого в данный момент канала будут блокироваться до тех пор, пока в канал не будет записан хотя бы один байт.
Если конец канала для записи закрыт, процесс чтения данных из канала увидит конец файла после чтения всех оставшихся данных в канале (т. е. read()
возвращает 0 ) .
Направление передачи данных в конвейере — одностороннее. Один конец канала используется для записи, а другой — для чтения.
On some other UNIX implementations, especially those evolved from System V Release 4, the pipe is bidirectional (so-called stream pipe). Bidirectional pipes are not specified in any UNIX standard, so it is best to avoid relying on this semantics even on implementations that provide bidirectional pipes. As an alternative, a UNIX domain stream socket pair (created via the socketpair()
system call) can be used, which provides a standard two-way communication mechanism and whose semantics are equivalent to stream pipes.
If multiple processes write to the same pipe, then if the amount of data they write at one time does not exceed PIPE_BUF bytes, then it can be ensured that the written data will not be mixed with each other.
SUSv3 requires PIPE_BUF to be at least _POSIX_PIPE_BUF(512)
. An implementation SHOULD define PIPE_BUF (in <limits.h></limits.h>
) and/or allow calls to fpathconf(fd,_PC_PIPE_BUF)
to return the actual upper bound for atomic write operations. PIPE_BUF varies on different UNIX implementations. For example, on FreeBSD 6.0, its value is 512 bytes, on Tru64 5.1, its value is 4096 bytes, and on Solaris 8, its value is 5120 bytes. On Linux, the value of PIPE_BUF is 4096.
write()
The call will block until all data is written to the pipe)The PIPE_BUF limit only takes effect when data is transferred to the pipe. When the data written reaches PIPE_BUF bytes, write()
will block if necessary until there is enough free space in the pipe to complete the operation atomically. If the data written is larger than PIPE_BUF bytes, then write()
will transfer as much data as possible to fill the entire pipe, and then block until some reading process removes the data from the pipe. If such a blocking write()
is interrupted by a signal handler, then the call will be unblocked and return the number of bytes successfully transferred to the pipe, which will be less than the number of bytes requested to write number of bytes entered (so-called partial write).
A pipe is actually a buffer maintained in kernel memory. The storage capacity of this buffer is limited. Once the pipe is filled, subsequent writes to the pipe are blocked until the reader removes some data from the pipe.
SUSv3 does not specify the storage capacity of the pipeline. In Linux kernels earlier than 2.6.11, the storage capacity of the pipe is consistent with the system page size (for example, 4096 bytes on x86-32), and starting from Linux 2.6.11, the storage capacity of the pipe is 65,536 byte. The storage capabilities of pipes on other UNIX implementations may vary.
一般来讲,一个应用程序无需知道管道的实际存储能力。如果需要防止写者进程阻塞,那么从管道中读取数据的进程应该被设计成以尽可能快的速度从管道中读取数据。
#include int pipe(int fd[2]);
pipe()
创建一个新管道fd
中返回两个打开的文件描述符,一个表示管道的读取端 fd[0]
,一个表示管道的写入端 fd[1]
调用 pipe()
函数时,首先在内核中开辟一块缓冲区用于通信,它有一个读端和一个写端,然后通过 fd
参数传出给用户进程两个文件描述符,fd[0]
指向管道的读端,fd[1]
指向管道的写段。
不要用 fd[0]
写数据,也不要用 fd[1]
读数据,其行为未定义的,但在有些系统上可能会返回 -1 表示调用失败。数据只能从 fd[0]
中读取,数据也只能写入到fd[1]
,不能倒过来。
与所有文件描述符一样,可以使用 read()
和 write()
系统调用来在管道上执行 IO,一旦向管道的写入端写入数据之后立即就能从管道的读取端读取数据。管道上的 read()
调用会读取的数据量为所请求的字节数与管道中当前存在的字节数两者之间的较小值。当管道为空时,读取操作阻塞。
You can also use stdio functions (printf()
, scanf()
, etc.) on the pipe. You only need to first use fdopen()
to get a The file stream corresponding to a descriptor in filedes
is enough. But the stdio
buffering issue needs to be addressed when doing this.
Pipes can be used for internal communication within the process:
Pipes can be used for in-process communication in a kinship relationship (the child process will inherit a copy of the file descriptor in the parent process):
It is not recommended to use a single pipe as full-duplex, or to use it as half-duplex without closing the corresponding read/write end. This is likely to lead to a deadlock: if two processes try to When data is read from a pipe, it is impossible to determine which process will read successfully first, resulting in two processes competing for data. To prevent this race condition from occurring, you need to use some kind of synchronization mechanism. At this point, you need to consider the deadlock problem, because a deadlock may occur if both processes try to read data from an empty pipe or try to write data to a full pipe.
If we want a bidirectional data flow, we can create two pipes, one in each direction.
In fact, pipes can be used for communication between any two or even more related processes, as long as the pipe is created through a common ancestor process before the series of fork()
calls that create the child process.
Closing unused pipe file descriptors is not just to ensure that a process does not exhaust its file descriptor limit.
The process reading data from the pipe will close the write descriptor of the pipe it holds, so that when the other process completes the output and closes its write descriptor, the reader will be able to see the end of the file. On the other hand, if the reading process does not close the writing end of the pipe, then after other processes close the writing descriptor, the reader will not see the end of the file even if it has read all the data in the pipe. Because at this time the kernel knows that at least one pipe write descriptor is open, causing read()
to block.
当一个进程视图向一个管道中写入数据但没有任何进程拥有该管道的打开着的读取描述符时,内核会向写入进程发送一个 SIGPIPE
信号,默认情况下,这个信号将会杀死进程,但进程可以选择忽略或者设置信号处理器,这样 write()
将因为 EPIPE
错误而失败。收到 SIGPIPE
信号和得到 EPIPE
错误对于标识管道的状态是有意义的,这就是为什么需要关闭管道的未使用读取描述符的原因。如果写入进程没有关闭管道的读取端,那么即使在其他进程已经关闭了管道的读取端之后,写入进程仍然能够向管道写入数据,最后写入进程会将数据充满整个管道,后续的写入请求会将永远阻塞。
当管道被创建之后,为管道的两端分配的文件描述符是可用描述符中数值最小的两个,由于通常情况下,进程已经使用了描述符 0,1,2,因此会为管道分配一些数值更大的描述符。如果需要使用管道连接两个过滤器(即从 stdin
读取和写入到 stdout
),使得一个程序的标准输出被重定向到管道中,就需要采用复制文件描述符技术。
int pfd[2]; pipe(pfd); close(STDOUT_FILENO); dup2(pfd[1],STDOUT_FILENO);
上面这些调用的最终结果是进程的标准输出被绑定到管道的写入端,而对应的一组调用可以用来将进程的标准的输入绑定到管道的读取端上。
popen()
#include FILE *popen (const char *command, const char *mode);
pipe()
和 close()
是最底层的系统调用,它的进一步封装是 popen()
和 pclose()
popen()
函数创建了一个管道,然后创建了一个子进程来执行 shell,而 shell 又创建了一个子进程来执行command
字符串
mode
参数是一个字符串:
mode
是 r
)还是将数据写入到管道中(mode
是 w
)command
中进行双向通信mode
的取值确定了所执行的命令的标准输出是连接到管道的写入端还是将其标准输入连接到管道的读取端popen()
在成功时会返回可供 stdio
库函数使用的文件流指针。当发生错误时,popen()
会返回 NULL
并设置 errno
以标示出发生错误的原因popen()
调用之后,调用进程使用管道来读取 command
的输出或使用管道向其发送输入。与使用 pipe()
创建的管道一样,当从管道中读取数据时,调用进程在 command
关闭管道的写入端之后会看到文件结束;当向管道写入数据时,如果 command
已经关闭了管道的读取端,那么调用进程就会收到 SIGPIPE
信号并得到 EPIPE
错误#include int pclose ( FILE * stream);
pclose()
函数关闭管道并等待子进程中的 shell 终止(不应该使用 fclose()
函数,因为它不会等待子进程。)pclose()
Upon success, it will return the termination status of the shell in the child process (that is, the termination status of the last command executed by the shell, unless the shell was killed by a signal) system()
, if the shell cannot be executed, then pclose()
will return a value just like the shell in the child process by calling _exit(127)
To terminate the samepclose()
returns −1. One of the errors that may occur is that the termination status cannot be obtainedWhen performing a wait to obtain the status of a shell in a child process, SUSv3 requires pclose()
to be the same as system()
, i.e. internally waitpid ()
Automatically restart a call after it is interrupted by a signal handler.
Like system()
, popen()
should never be used in a privileged process.
popen
Advantages and disadvantages:
command
command, the program first starts the shell to analyze the command
string. You can use various shell expansions (such as wildcards), so that we can use popen()
Calling very complex shell commandspopen()
call, not only a requested program but also a shell needs to be started. That is, each popen()
will start two processes.从效率和资源的角度看,popen()
函数的调用比正常方式要慢一些pipe()` VS `popen()
pipe()
是一个底层调用,popen()
是一个高级的函数pipe()
单纯的创建管道,而 popen()
创建管道的同时 fork()
子进程popen()
在两个进程中传递数据时需要调用 shell 来解释请求命令;pipe()
在两个进程中传递数据不需要启动 shell 来解释请求命令,同时提供了对读写数据的更多控制(popen()
必须时 shell 命令,pipe()
则无硬性要求)popen()
函数是基于文件流(FILE)工作的,而 pipe()
是基于文件描述符工作的,所以在使用 pipe()
后,数据必须要用底层的read()
和 write()
调用来读取和发送由于 popen()
调用返回的文件流指针没有引用一个终端,因此 stdio 库会对这种流应用块缓冲。这意味着当 mode 的值为 w 来调用 popen()
时,默认情况下只有当 stdio 缓冲区被充满或者使用 pclose()
关闭了管道之后才会被发送到管道的另一端的子进程。在很多情况下,这种处理方式是不存在问题的。But if you need to ensure that the child process can receive data from the pipe immediately, then you need to call fflush()
periodically or use the setbuf(fp, NULL)
call to disable stdio buffering. This technique can also be used when creating a pipe using the pipe()
system call and then using fdopen()
to obtain an stdio stream corresponding to the writing end of the pipe
If the process calling popen()
is reading from the pipe (i.e. mode
is r
), then things are not that simple. In this case if the child process is using the stdio library, then - unless it explicitly calls fflush()
or setbuf()
, its output will only be filled up by the child process The stdio buffer is not available to the calling process until fclose()
is called. (The same rules apply if the process that is reading from a pipe created using pipe()
and writing to the other end is using the stdio library.) If this is a problem, Then the measures that can be taken are relatively limited, unless the source code of the program running in the child process can be modified to include calls to setbuf()
or fflush()
.
If the source code cannot be modified, you can use a pseudo terminal to replace the pipe. A pseudo-terminal is an IPC channel that appears to a process as a terminal. The result is that the stdio library outputs the data in the buffer line by line.
Although the above pipeline realizes inter-process communication, it has certain limitations:
In order to enable communication between any two processes, named pipes (named pipe or FIFO) are proposed:
read()
,write()
,close()
。与管道一样,FIFO 也有一个写入端和读取端,并且总是遵循先进先出的原则,即第一个进来的数据会第一个被读走mkfifo
命令可以在 shell 中创建一个 FIFO:mkfifo [-m mode] pathname
pathname
是创建的 FIFO 的名称,-m
选项指定权限 mode
,其工作方式与 chmod
命令一样fstat()
和 stat()
函数会在 stat
结构的 st_mode
字段返回 S_IFIFO
,使用 ls -l
列出文件时,FIFO 文件在第一列的类型为 p
,ls -F
会在 FIFO 路径名后面附加管道符 |
#include #include int mkfifo(const char *pathname,mode_t mode);
mode
参数指定了新 FIFO 的权限,这些权限会按照进程的 umask
值来取掩码open() O_RDONLY
标记)将会阻塞直到另一个进程打开 FIFO 以写入数(open() O_WRONLY
标记)为止。相应地,打开一个 FIFO 以写入数据将会阻塞直到另一个进程打开 FIFO 以读取数据为止。换句话说,打开一个 FIFO 会同步读取进程和写入进程。如果一个 FIFO 的另一端已经打开(可能是因为一对进程已经打开了 FIFO 的两端),那么open()
调用会立即成功。在大多数 Unix 实现上(包含 Linux),当打开一个 FIFO 时可以通过指定 O_RDWR
标记来绕过打开 FIFO 时的阻塞行为。这样,open()
会立即返回,但无法使用返回的文件描述符在 FIFO 上读取和写入数据。这种做法破坏了 FIFO 的 IO 模型,SUSv3 明确指出以 O_RDWR
标记打开一个 FIFO 的结果是未知的,因此出于可移植性的原因,开发人员不应该使用这项技术。对于那些需要避免在打开 FIFO 时发生阻塞的需求,open()
的 O_NONBLOCK
标记提供了一种标准化的方法来完成这个任务:
open(const char *path, O_RDONLY | O_NONBLOCK); open(const char *path, O_WRONLY | O_NONBLOCK);
在打开一个 FIFO 时避免使用 O_RDWR
标记还有另外一个原因,当采用那种方式调用 open()
之后,调用进程在从返回的文件描述符中读取数据时永远都不会看到文件结束,因为永远都至少存在一个文件描述符被打开着以等待数据被写入 FIFO,即进程从中读取数据的那个描述符。
tee
创建双重管道线shell 管道线的其中一个特征是它们是线性的,管道线中的每个进程都能读取前一个进程产生的数据并将数据发送到其后一个进程中,使用 FIFO 就能够在管道线中创建子进程,这样除了将一个进程的输出发送给管道线中的后面一个进程之外,还可以复制进程的输出并将数据发送到另一个进程中,要完成这个任务就需要使用 tee
命令,它将其从标准输入中读取到的数据复制两份并输出:一份写入标准输出,另一份写入到通过命令行参数指定的文件中。
mkfifo myfifo wc -l
当一个进程打开一个 FIFO 的一端时,如果 FIFO 的另一端还没有被打开,那么该进程会被阻塞。但有些时候阻塞并不是期望的行为,而这可以通过在调用 open()
时指定 O_NONBLOCK
标记来实现。
如果 FIFO 的另一端已经被打开,那么 O_NONBLOCK
对 open()
调用不会产生任何影响,它会像往常一样立即成功地打开 FIFO。只有当 FIFO 的另一端还没有被打开的时候 O_NONBLOCK
标记才会起作用,而具体产生的影响则依赖于打开 FIFO 是用于读取还是用于写入的:
open()
调用会立即成功(就像 FIFO 的另一端已经被打开一样)open()
调用会失败,并将 errno
设置为 ENXIO
为读取而打开 FIFO 和为写入而打开 FIFO 时 O_NONBLOCK
标记所起的作用不同是有原因的。当 FIFO 的另一个端没有写者时打开一个 FIFO 以便读取数据是没有问题的,因为任何试图从 FIFO 读取数据的操作都不会返回任何数据。但当试图向没有读者的 FIFO 中写入数据时将会导致 SIGPIPE
信号的产生以及 write()
返回 EPIPE
错误。
在 FIFO 上调用 open()
的语义总结如下:
在打开一个 FIFO 时,使用 O_NOBLOCK
标记存在两个目的:
O_NOBLOCK
标记以便读取数据,接着打开 FIFO 以便写入数据例如,下面的情况将会发生死锁:
read()
和 write()
O_NONBLOCK
标记不仅会影响 open()
的语义,而且还会影响——因为在打开的文件描述中这个标记仍然被设置着——后续的 read()
和 write()
调用的语义。
有些时候需要修改一个已经打开的 FIFO(或另一种类型的文件)的 O_NONBLOCK
标记的状态,具体存在这个需求的场景包括以下几种:
O_NONBLOCK
打开了一个 FIFO 但需要后续的 read()
和 write()
在阻塞模式下运行pipe()
返回的一个文件描述符的非阻塞模式。更一般地,可能需要更改从除 open()
调用之外的其他调用中,如每个由 shell 运行的新程序中自动被打开的三个标准描述符的其中一个或 socket()
返回的文件描述符,取得的任意文件描述符的非阻塞状态O_NONBLOCK
设置的开启和关闭状态当碰到上面的需求时可以使用 fcntl()
启用或禁用打开着的文件的 O_NONBLOCK
状态标记。通过下面的代码(忽略的错误检查)可以启用这个标记:
int flags; flags = fcntl(fd, F_GETFL); flags != O_NONBLOCK; fcntl(fd, F_SETFL, flags);
通过下面的代码可以禁用这个标记:
flags = fcntl(fd, F_GETFL); flags &= ~O_NONBLOCK; fcntl(fd, F_SETFL, flags);
read()
和 write()
的语义FIFO 上的 read()
操作:
只有当没有数据并且写入端没有被打开时阻塞和非阻塞读取之间才存在差别。在这种情况下,普通的 read()
会被阻塞,而非阻塞 read()
会失败并返回 EAGAIN
错误。
当 O_NONBLOCK
标记与 PIPE_BUF
限制共同起作用时 O_NONBLOCK
标记对象管道或 FIFO 写入数据的影响会变得复杂。
FIFO 上的 write()
操作:
O_NONBLOCK
flag causes write()
on a pipe or FIFO to fail (with error EAGAIN
) when data cannot be transferred immediately. This means that after writing PIPE_BUF
bytes, if there is not enough space in the pipe or FIFO, then write()
will fail because the kernel cannot complete the operation immediately And partial writes cannot be performed without violating the atomicity requirement for write operations of no more than PIPE_BUF
bytesPIPE_BUF
bytes, the write operation does not need to be atomic. Therefore, write()
transfers (partially writes) as many bytes as possible to fill the pipe or FIFO. In this case, the value returned from write()
is the actual number of bytes transferred, and the caller must then retry to write the remaining bytes. But if the pipe or FIFO is full, so that not even one byte can be transferred, then write()
will fail and return EAGAIN
errorThe above is the detailed content of Linux Pipes and FIFO Application Note. For more information, please follow other related articles on the PHP Chinese website!