前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux - pipe() 管道的使用

Linux - pipe() 管道的使用

原创
作者头像
Spell Thief
修改2020-11-02 10:39:40
9.3K0
修改2020-11-02 10:39:40
举报
文章被收录于专栏:Linux系统编程

初识 Pipe

pipe , 中文翻译为管道,是 Unix/Linux 系统中一种比较常用的 IPC(Inter Process Communication) 。下面这组 shell 命令,估计大部分人都用过或者见过。

代码语言:shell
复制
ls | wc -l

ls 和 wc 分别是两个独立的进程。shell 会将 ls 的输出结果作为 wc 的输入结果,然后再由 wc 把处理结果投放到终端上。

[^1] Using a pipe to connect two processes
[^1] Using a pipe to connect two processes

上图很生动地描绘出这组命令的工作流程。pipe 就像一根圆管,ls 的输出内容流入到圆管的一端 (标准输出)。随后,内容一直流到圆管的另一端 (标准输入) 由 wc 接收。这跟圆管只接受 byte stream,也就是说消息内容是无边界的。

创建 Pipe

代码语言:c++
复制
#include <unistd.h>

int pipe(int[2] pfd);

我们需要向 pipe() 传入一个大小为 2 的数组,与此同时内核会维护一个临时的 buffer,也就是看不见摸不着的管道。随后,内核会返回管道的读端和写端的文件描述符,它们分别存放于参数数组的第 0 个元素和第 1 个元素。

Pipe的读写

之所以叫 IPC, 顾名思义,管道就是用来让两个或者多个进程之间通信,尽管多个进程共享一个管道的情况十分少见,但我们并未被禁止这样做,但我们在大多数情况都绝不应该这样做。 管道有两端,一端为写端,另一端为读端。如果一个进程试图往一个空的管道读取数据,那么该进程将会被堵塞,直至管道非空为止。同理,如果一个进程尝试往一个已满的管道塞入更多的内容,此进程一样会被堵塞,直到管道为非满状态。

[^2] Process file descriptors after creating a pipe
[^2] Process file descriptors after creating a pipe

调用 pipe() ,再调用 fork() 。尽管,管道是在父进程创建的,但是子进程以拷贝的形式继承父进程的 open file descriptors 。

代码语言:c++
复制
#include <unistd.h>
#include <iostream>


const int BUFF_SIZE = 11;

int main()
{
    int pfd[2];

    if (pipe(pfd) == -1)
        printf("failed to create a pipe");

    switch (fork())
    {
    case -1:
        printf("failed to create a process");
    

    case 0:
        if (close(pfd[1]) == -1)
            _exit(1);

        char read_buff[BUFF_SIZE];

        int readn = read(pfd[0], read_buff, BUFF_SIZE);

        printf("child process read: %s\n", read_buff);

        _exit(0);
    }

    if (close(pfd[0]) == -1)
        exit(1);

    char write_buff[] = "hello world";

    int writen = write(pfd[1], write_buff, BUFF_SIZE);

    exit(0);

}

输出结果:

代码语言:shell
复制
[me@localhost Documents]$ ./exe
child process read: hello world

管道闭环

如果子进程是负责读,而父进程负责写的话。那么子进程在读之前必须关闭管道的写端,父进程同样地必须关闭管道的读端。不然就会形成了闭环。

[^3] Setting up a pipe to transfer data from a parent to a child
[^3] Setting up a pipe to transfer data from a parent to a child

想要正确使用管道就必须避免出现 (a) 这种情况。每个文件描述都有一个引用计数,在 (a) 的情况下,尽管父进程已经向管道输入完毕并且正确关闭掉管道的读写端,然而子进程不会收到 EOF,那么子进程会永远阻塞下去。

代码语言:c++
复制
switch (fork())
{
case -1:
    printf("failed to create a process");


case 0:
    // if (close(pfd[1]) == -1)
    //     _exit(1);

    char read_buff[BUFF_SIZE];

    int readn;
    while (readn = read(pfd[0], read_buff, BUFF_SIZE) != 0)
    {
        printf("child process read: %s\n", read_buff);
    }
    
    _exit(0);
}

if (close(pfd[0]) == -1)
    exit(1);

char write_buff[] = "hello world";

int writen = write(pfd[1], write_buff, BUFF_SIZE);
close(pfd[1]);

结语

管道的原理和使用方法都特别简单,但一定要避免出现关闭。也尽量不要有一个以上的进程共享读端或者写端,不然就会出现条件竞争。

参考

[^1] 44.2 Figure 44-2, The Linux Programming Interface

[^2] 44.2 Figure 44-3, The Linux Programming Interface

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 初识 Pipe
  • 创建 Pipe
  • Pipe的读写
  • 管道闭环
  • 结语
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档