管道-pipe

本文详细介绍了Unix/Linux环境下进程间通信机制之一的管道(pipe)。主要内容包括管道的基本使用方法、如何利用管道实现父子进程间的通信,以及如何将管道与标准输入输出相结合。此外,还探讨了管道在关闭后的读操作行为及一些高级应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

#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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值