linux系统编程之管道(一):匿名管道和pipe函数

一、进程间通信

每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Communication)。如下图所示。

二、管道是一种最基本的IPC机制,由pipe函数创建: #include <unistd.h>

int pipe(int filedes[2]);

调用pipe函数时在内核中开辟一块缓冲区(称为管道)用于通信,它有一个读端一个写端,然后通过filedes参数传出给用户程序两个文件描述符,filedes[0]指向管道的读端,filedes[1]指向管道的写端(很好记,就像0是标准输入1是标准输出一样)。所以管道在用户程序看起来就像一个打开的文件,通过read(filedes[0]);或者write(filedes[1]);向这个文件读写数据其实是在读写内核缓冲区。pipe函数调用成功返回0,调用失败返回-1。

开辟了管道之后如何实现两个进程间的通信呢?比如可以按下面的步骤通信。

示例程序如下:

/*************************************************************************
    > File Name: process_.c
    > Author: Simba
    > Mail: dameng34@163.com
    > Created Time: Sat 23 Feb 2013 02:34:02 PM CST
 ************************************************************************/
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

int main(int argc, char *argv[])
{
    int pipefd[2];
    if (pipe(pipefd) == -1)
        ERR_EXIT("pipe error");

    pid_t pid;
    pid = fork();
    if (pid == -1)
        ERR_EXIT("fork error");

    if (pid == 0)
    {
        close(pipefd[0]);
        write(pipefd[1], "hello", 5);
        close(pipefd[1]);
        exit(EXIT_SUCCESS);
    }

    close(pipefd[1]);
    char buf[10] = {0};
    read(pipefd[0], buf, 10);
    printf("buf=%s\n", buf);

    return 0;
}

1. 父进程调用pipe开辟管道,得到两个文件描述符指向管道的两端。

2. 父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。

3. 父进程关闭管道写端,子进程关闭管道读端。子进程可以往管道里写,父进程可以从管道里读,管道是用环形队列实现的,数据从写端流入从读端流出,这样就实现了进程间通信。

三、利用pipe和dup2函数模拟命令行 ls  | wc -w 功能

/*************************************************************************
    > File Name: process_.c
    > Author: Simba
    > Mail: dameng34@163.com
    > Created Time: Sat 23 Feb 2013 02:34:02 PM CST
 ************************************************************************/
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

int main(int argc, char *argv[])
{
    int pipefd[2];
    if (pipe(pipefd) == -1)
        ERR_EXIT("pipe error");

    pid_t pid;
    pid = fork();
    if (pid == -1)
        ERR_EXIT("fork error");

    if (pid == 0)
    {
        dup2(pipefd[1], STDOUT_FILENO); //输出重定向
        close(pipefd[1]);
        close(pipefd[0]);
        execlp("ls", "ls", NULL);
        fprintf(stderr, "error execute ls\n");
        exit(EXIT_FAILURE);
    }

    dup2(pipefd[0], STDIN_FILENO);
    close(pipefd[0]);
    close(pipefd[1]);
    execlp("wc", "wc", "-w", NULL);
    fprintf(stderr, "error execute wc\n");
    exit(EXIT_FAILURE);

    return 0;
}

我们知道命令行 ls | wc -w 中 ls 会输出到管道,而wc 从管道里读取,现在使用dup2复制文件描述符,使ls 的标准输出为管道,wc 的标准输入也为管道,即使父进程先被调度,因为默认是阻塞I/O操作,故wc 会read 阻塞直到管道被子进程写入了数据。

使用管道有一些限制:

两个进程通过一个管道只能实现单向通信,比如最上面的例子,父进程读子进程写,如果有时候也需要子进程读父进程写,就必须另开一个管道。

管道的读写端通过打开的文件描述符来传递,因此要通信的两个进程必须从它们的公共祖先那里继承管道文件描述符。上面的例子是父进程把文件描述符传给子进程之后父子进程之间通信,也可以父进程fork两次,把文件描述符传给两个子进程,然后两个子进程之间通信,总之需要通过fork传递文件描述符使两个进程都能访问同一管道,它们才能通信。

参考:《APUE》、《linux c 编程一站式学习》

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏前端黑板报

HTTP2基础教程-读书笔记(四)

? 记录一下HTTP/2的底层原理,帮助理解协议实现细节。 连接 每个端点都需要发送一个连接作为最终确认使用的协议,并建立http/2连接的初始设置。客户端和...

3316
来自专栏祝威廉

ElasticSearch Recovery 分析

org.elasticsearch.indices.cluster.IndicesClusterStateService.clusterChanged 被触发后...

1013
来自专栏性能与架构

SQL与MongoDB的对应关系图表

关系数据库有一套标准的SQL语句来操作数据库,MongoDB是文档型数据库,概念与操作方式与SQL都不相同,下面的几张表全面的表达了SQL与MongoDB的对应...

3526
来自专栏同步博客

Redis应用----消息传递

  消息传递这一应用广泛存在于各个网站中,这个功能也是一个网站必不可少的。常见的消息传递应用有,新浪微博中的@我呀、给你评论然后的提示呀、赞赞赞提示、私信呀、甚...

1102
来自专栏杨建荣的学习笔记

物化视图prebuilt和在线重定义 (r10笔记第25天)

数据迁移中有一种解决方案很有亮点,如果表的数据量大,迁移涉及的表不多,同时对于维护时间有要求的情况下,物化视图的prebuilt方式就是一种很不错的选择。 大体...

3344
来自专栏魏琼东

一步一步教你使用AgileEAS.NET基础类库进行应用开发-基础篇-UDA中处理事务

前文回顾         在之前的文章一步一步教你使用AgileEAS.NET基础类库进行应用开发-基础篇-使用UDA操纵SQL语句和一步一步教你使用Agile...

21310
来自专栏JMCui

记录一次批量插入的优化历程

1122
来自专栏Java后端生活

MySQL(一)数据库的简单介绍

1648
来自专栏乐沙弥的世界

Oracle 12c CDB数据库中数据字典架构

数据字典就是元数据的集合,比如创建的表,列,约束,触发器等等这些都是元数据,需要保存到数据库中。除此之外,Oracle自身的一些数据库对象,如目录,PL/SQL...

451
来自专栏技术换美食换不换

grpc-learn

RPC框架服装屏蔽底层传输方式(TCP/UDP),序列化方式(XML/Json/二进制)和通信细节,调用者可以像调用本地接口一样调用远程服务。核心是要解决在分布...

772

扫码关注云+社区