#include <unistd.h>
int pipe(int file_descriptor[2]);
参数是整形数组指针,表示 2 个文件描述符。写到 file_descriptor[1] 的所有数据都可以从 file_descriptor[0] 读回来。(记忆:数据流动的方向是 1--> 0)
此外,是文件描述符(句柄)而非文件流(指针),所以必须用底层的 read 和 write 进行读写。
/* 父进程写,子进程读 */
int main(int argc, char *argv[])
{
......
if (0 == pipe(file_pipes))
{
fork_result = fork();
if (fork_result < 0) {
return -1;
}
/* 记忆:子进程从 0 开始 */
if ((pid_t)0 == fork_result) {
data_processed = read(file_pipes[0], buffer, BUFSIZ);
printf("......", ......);
return 0;
} else {
data_processed = write(file_pipes[1], some_data, strlen(some_data));
printf("......", ......);
return 0;
}
}
return -1;
}
深入:
一. 管道关闭后的读操作
如果管道为空,那么 read 操作将阻塞直到数据到达。而如果管道另一端已经关闭,那么 read 将返回 0,从而可以得知管道已经关闭。关闭不代表出错,出错返回 -1。
关于如何关闭管道:如果跨越 fork 使用管道,就会有两个不同的文件描述符可以用于写入管道,一个位于父进程,一个位于子进程。只有把父子进程中针对管道的写文件描述符都关闭,才真正认为管道已被关闭。
二. 把管道用于标准输入和标准输出
如果某个程序依赖于管道,将会使移植变得困难。但如果只依赖于标准输入和标准输出,那么情况将变得简单。于是产生了需求:将管道与输入输出相关联。
int dup(int file_descriptor);
int dup2(int file_descriptor_one, int file_descriptor_two);
dup() 的作用是复制参数中的文件描述符,让新旧两个文件描述符指向同一个文件或者管道。而且,dup() 返回的新文件描述符,总是使用最小的可用数字。而标准输入的文件描述符是 0,所以,如果我们在调用 dup() 之前,close() 掉标准输入、使得 0 这个文件描述符可用,那么,dup() 调用之后,新的文件描述符就会是 0。此时,再向标准输入执行写操作,其实就是往新的文件描述符执行写操作。
......
if ((pid_t)0 == fork_result) {
close(0);
dup(file_pipes[0]);
close(file_pipes[0]);
close(file_pipes[1]);
/* od 是从标准输入读取数据的进程,执行了前几步后,文件描述符 0 已经变成了 file_pipes[0],所以,再从标准输入读取数据,就是从 file_pipes[0] 读取数据 */
execlp("od", "od", "-c", (char *)0);
return 1;
} else {
close(file_pipes[0]);
(void)write(file_pipes[1], some_data, strlen(some_data));
return 1;
}
......
三. 一点深入思考
1. stdin、stdout、stderr 和 0、1、2 的对应关系是固定不变的。即,无论何时,访问 stdin,都是操作 0 这个文件描述符。但是,文件描述符的内容却是可变的。文件描述符是一张表,0、1、2 只是索引,而 stdin、stdout 和 stderr 只是索引的别名。
2. “如果跨越 fork 使用管道,就会有两个不同的文件描述符可以用于写入管道,一个位于父进程,一个位于子进程”,原因是,通过 fork() 创建子进程时,子进程继承父进程的环境和上下文,也包括文件描述符表。即,父子进程中存在相同的文件描述符表,各个索引指向相同的文件描述符,文件描述符的引用计数也相应增加。所以,“只有把父子进程中针对管道的写文件描述符都关闭,才真正认为管道已被关闭”
3. 关于 stdout 和 stderr,都是在屏幕输出错误信息。但前者是行缓冲,遇到新行才输出,后者无缓冲,立即输出。
4. 如果有多个进程同时往管道中写,情况会如何?数据会交错吗?是否同 FIFO 的处理机制一样?
从目前的情况看,如果写入的数据如果控制在 1500 字节以下,未发生明显的数据交错。
但是,既然 pipe 用于父子进程的通信,那么就不会存在多个进程同时写 pipe 的情况。
5. 写入的数据长度有限制吗?如果一次性写入超大的数据,会出现什么情况?
6. Apache 的 CustomLog 支持管道日志,多个子进程的日志都是通过这个管道写日志的,但是管道日志与子进程是平级的。其机制应该是,子进程将数据交给父进程,然后由父进程统一写管道日志。这样也就不存在多个进程同时写日志的情况。
7. 对比 socketpair