管道专题

linux管道基本概念


管道基本概念及操作

管道是Unix中最古老的进程间通信的形式。

我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”,下面是一个简单的例子.
ls | wc -l ps –u wbm01|grep “aa”

** 管道的本质:**

固定大小的内核缓冲区

管道限制:

管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道。

管道只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。

匿名管道pipe

功能:创建一无名管道

原型:

#include <unistd.h>
int pipe(int fd[2]);

参数:

fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端

返回值:成功返回0,失败返回错误代码

管道创建后示意图

管道基本编程示例

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>

#define ERR_EXIT(m) \
    do \
    { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

//这个例子中,父进程如果读不到数据会阻塞到read函数那里
int main01(void)
{
    int pipefd[2];
    pid_t pid;

    //创建管道文件,并打开
    if (pipe(pipefd) == -1)
    {
        printf("pipe() err..\n");
        return -1;
    }
    pid = fork();
    if (pid == -1)
    {
        printf("fork err..\n");
        return -1;
    }

    if (pid == 0)
    {
        close(pipefd[0]); //子进程关闭读管道
        write(pipefd[1], "hello hello....", 6);//向管道中写数据
        close(pipefd[1]);
        printf("child .....quit\n");

    }
    else if (pid > 0)
    {
        int len = 0;
        char buf[100] = { 0 };
        close(pipefd[1]); //父进程关闭写管道
        len = read(pipefd[0], buf, 100); //从管道中读数据
        printf("len = %d, buf:%s \n", len, buf);

        close(pipefd[0]);
    }

    wait(NULL);
    printf("parent ..quit\n");
    return 0;
}

//阻塞非阻塞场景
int main02(void)
{
    int ret = 0;
    int pipefd[2];

    ret = pipe(pipefd);
    if (ret < 0)
    {
        perror("ddd");
        exit(0);
    }
    int pid;
    pid = fork();
    if (pid == -1)
    {
        perror("dd");
        exit(0);
    }

    if (pid == 0) //子进程
    {
        close(pipefd[0]);
        sleep(5);
        write(pipefd[1], "childaaaaaaaaa", 6);
        printf("chile quit\n");
        close(pipefd[1]);
        exit(0);
    }

    close(pipefd[1]);
    char readbuf[1000] = { 0 };
    int n = 0;

    int flags = fcntl(pipefd[0], F_GETFL); 
    flags = flags | O_NONBLOCK;
    ret = fcntl(pipefd[0], F_SETFL, flags); //用fcntl函数可以设置文件描述符阻塞或者非阻塞
    if (ret == -1)
    {
        printf("fcntl err.\n");
        exit(0);
    }

    n = read(pipefd[0], readbuf, sizeof(readbuf));
    if (n < 0)
    {
        printf("read非阻塞返回。。。。。立马返回");
        //errno
    }
    printf("%s\n", readbuf);
    close(pipefd[0]);

    wait(NULL);

    printf("parent quit\n");
}

//当所有的写端被关闭,此时读的话会返回0
int main(void)
{
    int ret = 0;
    int pipefd[2];

    ret = pipe(pipefd);
    if (ret < 0)
    {
        perror("ddd");
        exit(0);
    }
    int pid;
    pid = fork();
    if (pid == -1)
    {
        perror("dd");
        exit(0);
    }

    if (pid == 0) //子进程
    {
        close(pipefd[0]);
        close(pipefd[1]);//写端被关闭
        exit(0);
    }

    close(pipefd[1]); //写端被关闭
    char readbuf[1000] = { 0 };
    int n = 0;
    sleep(3);

    n = read(pipefd[0], readbuf, sizeof(readbuf));
    printf("所有的写端都关闭,此时 n = %d \n", n);
    //当所有的写端被关闭,此时读的话会返回0
    printf("%s\n", readbuf);
    close(pipefd[0]);

    wait(NULL);

    printf("parent quit\n");
}

管道和文件描述符在一起

管道命令的编程实现:

ls | wc –w

从下面的这个例子里,我们可以更加深刻地领会|符号的实现原理。

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>

#define ERR_EXIT(m) \
    do \
    { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

int main21(void)
{
    int pipefd[2];
    pid_t pid;
    if (pipe(pipefd) == -1)
    {
        printf("pipe() err..\n");
        return -1;
    }
    pid = fork();
    if (pid == -1)
    {
        printf("fork err..\n");
        return -1;
    }
    if (pid == 0)//子进程
    {
        close(pipefd[0]);
        //复制文件描述符pipefd[1],给标准输出,言外之意:execlp的ls命令输出到管道中
        dup2(pipefd[1], STDOUT_FILENO);
        //这里需要注意一点,那就是复制后,pipefd[1]的应用计数会增加,下面关闭pipefd[1]不会真正关闭该文件
        close(pipefd[1]);//关闭写端

        execlp("ls", "ls", NULL); 
        //如果替换新的进程印象失败,则会执行下面一句话    
        sprintf(stderr, "execute the cmd ls err..\n");
        exit(0);

    }
    else if (pid > 0) //父进程
    {
        int len = 0;
        char buf[100] = { 0 };
        close(pipefd[1]);
        //复制文件描述符pipefd[0],给标准输入,言外之意:execlp的wc命令从管道中读
        dup2(pipefd[0], STDIN_FILENO);
        close(pipefd[0]);
        //len = read(pipefd[0], buf, 100);
        execlp("wc", "wc", "-w", NULL);//执行wc命令,相当于从
        printf("len:%d, buf:%s \n", len, buf);

        //close(pipefd[0]);
    }

    wait(NULL);
    printf("parent ..quit\n");
    return 0;
}


int main(int argc, char *argv[])
{
    close(0); //关闭标准输入
    open("makefile", O_RDONLY); //makefile文件变成标准输入
    close(1);//关闭标准输出
    open("makefile2", O_WRONLY | O_CREAT | O_TRUNC, 0644); //maifle2变成标准输出

    execlp("cat", "cat", NULL); //替换进程印象后,执行cat命令

    //cat命令 从标准输入中按行读,紧接着写到标准输出
    return 0;

}

dup2函数:

作用:

dup2函数使得newfd成为oldfd,即将oldfd的值赋给newfd

函数原型:

int dup2(int oldfd, int newfd);

参数:两个都为文件描述符

返回值:如果成功,返回新的描述符,否则返回-1,设置errno

管道的本质是内核的一块缓冲区。

管道的读写规则

当没有数据可读时

O_NONBLOCK disableread调用阻塞,即进程暂停执行,一直等到有数据来到为止。

O_NONBLOCK enableread调用返回-1errno值为EAGAIN
当管道满的时候

O_NONBLOCK disablewrite调用阻塞,直到有进程读走数据

O_NONBLOCK enable:调用返回-1errno值为EAGAIN

当管道不停的被写,写满的时候

O_NONBLOCK disablewrite调用阻塞

O_NONBLOCK enable:调用返回-1errno值为EAGAIN

如果所有管道写端对应的文件描述符被关闭,则read返回0

如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE。也就是所谓的管道破裂,这在socket编程中很常见。

当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

下面是一个例子:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>


#define ERR_EXIT(m) \
        do \
        { \
                perror(m); \
                exit(EXIT_FAILURE); \
        } while(0)

#define TEST_SIZE 68*1024   //68K


int main(void)
{
    char a[TEST_SIZE];
    char b[TEST_SIZE];

    memset(a, 'A', sizeof(a));
    memset(b, 'B', sizeof(b));

    int pipefd[2];

    int ret = pipe(pipefd);
    if (ret == -1)
        ERR_EXIT("pipe error");

    pid_t pid;
    pid = fork();
    if (pid == 0) //A子进程写68K数据A
    {
        close(pipefd[0]);
        ret = write(pipefd[1], a, sizeof(a));
        printf("apid=%d write %d bytes to pipe\n", getpid(), ret);
        exit(0);
    }

    pid = fork();


    if (pid == 0) //B子进程写68K数据B
    {
        close(pipefd[0]);
        ret = write(pipefd[1], b, sizeof(b));
        printf("bpid=%d write %d bytes to pipe\n", getpid(), ret);
        exit(0);
    }


    close(pipefd[1]);

    sleep(1);
    int fd = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    char buf[1024 * 4] = { 0 };
    int n = 1;
    while (1)
    {
        //父进程4k 4k的读数据,发现AB进程是交叉的写数据到管道。
        //多个进程往管道里面,写数据。
        ret = read(pipefd[0], buf, sizeof(buf));
        if (ret == 0)
            break;
        printf("n=%02d pid=%d read %d bytes from pipe buf[4095]=%c\n", n++, getpid(), ret, buf[4095]);
        write(fd, buf, ret);

    }
    return 0;
}

上面其实是一个很恐怖的东西,我用vim处理了一下test.txt我们来看一下这个文件中的数据。我做了如下处理:

70A为一行,后面加了换行符。每70B为一行,后面加了换行符。

AB之间加入换行符。

A一直持续到了937行!

我们计算一下:936 * 70 + 16 = 65536,一个字符一个字节,恰好64K

B写入了(995 – 936 + 1) * 70 + 36 = 4236,后面的写入就有一些乱了。

后面全部是B

从上面的结果可以看到,如果写入的数据量很大的话,其实挺乱的,这个东西。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,928评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,192评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,468评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,186评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,295评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,374评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,403评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,186评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,610评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,906评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,075评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,755评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,393评论 3 320
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,079评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,313评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,934评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,963评论 2 351

推荐阅读更多精彩内容

  • 前言 管道是UNIX环境中历史最悠久的进程间通信方式,也是最简单的进程间通信方式,一般用来作为IPC的入门,最合适...
    GeekerLou阅读 1,146评论 0 6
  • 本文摘抄自linux基础编程 管道指的是从一个进程连接数据流到另一个进程。它具有以下特点: 管道是半双工的,数据只...
    lintong阅读 3,520评论 0 8
  • 一.管道机制(pipe) 1.Linux的fork操作 在计算机领域中,尤其是Unix及类Unix系统操作系统中,...
    Geeks_Liu阅读 3,686评论 1 9
  • 我进入大学也过去一学期了,新学期过去一个月,总结了上个学期,开始了新的生活,从对这个学校的拒绝,到现在的接受...
    多遥远多纠结多想念阅读 313评论 0 0
  • 在SDKVisersion 23 中可以正常使用getDrawable(int ID),升级为25后报错: 解决办...
    fireflyyx阅读 609评论 0 0