管道之 协同进程

协同进程:
参考: http://blog.csdn.net/astrotycoon/article/details/45874843
在shell 管道中,当一个程序产生一个过滤器的输入,又读取这个过滤器的输出,则此种过滤程序叫做协同进程.
协同进程通常在shell的后台运行,其标准输入和标准输出通过管道连接到另一个程序。
popen 与协同进程的区别:
popen只提供连接到另一个进程的标准输入或标准输出的一个单向管道;
协同进程,连接到另一个进程的两个单向管道:一个接到其标准输入,另一个则来自其标准输出;

协同进程演示: 创建两个管道, 一个是协同进程的标准输入,另一个是协同进程的标准输出,将数据写到其标准输入,经其处理后,再从其标准输出读取数据。

协同进程演示: 创建两个管道, 一个是协同进程的标准输入,另一个是协同进程的标准输出,将数据写到其标准输入,经其处理后,再从其标准输出读取数据。

这里写图片描述

第一步, 创建两个管道,fd1[2]和fd2[2](父进程在fd1[1]写入数据,再从fd2[0]读出数据)。
第二步, 调用fork()创建子进程。
第三步, 在子进程中,关闭fd1[1], fd2[0],并调用dup2使协同进程的标准输入连接到fd1[0], 标准输出连接到fd2[1],这样就将两个管道连接起来了。
再在子进程中调用execl调用编写的协同处理程序(这里的协同程序做为一般的程序编写即可,从标准输入读入数据,处理后输出到标准输出)。
第四步, 在父进程中,关闭fd1[0], fd2[1],将协同进程需要处理的数据写入fd1[1], 再从fd2[0]读出协同进程的输出即可。
程序清单15-8 对两个数求和的简单过滤程序:一个简单的协同进程,它从其标准输入读两个数,计算它们的和,然后将结果写至标准输出。

#include "apue.h"
int main(void)
{
    int     n, int1, int2;
    char    line[MAXLINE];

    while((n = read(STDIN_FILENO, line, MAXLINE)) > 0)
    {
        line[n] = 0;    /* null terminate */
        if(sscanf(line, "%d%d", &int1, &int2) == 2)
        {
            sprintf(line, "%d\n", int1 + int2);
            n = strlen(line);
            if(write(STDOUT_FILENO, line, n) != n)  err_sys("write error");
        }
        else{
            if(write(STDOUT_FILENO, "invalid args\n", 13) != 13)  err_sys("write error");
        }
    }
    exit(0);
}

对此程序进行编译,将其可执行目标代码存入名为add2的文件。
程序清单15-9从其标准输入读入两个数之后调用add2协同进程,并将协同进程送来的值写到其标准输出。
程序清单15-9 驱动add2过滤程序的程序

#include "apue.h"
static void sig_pipe(int);    /* our signal handler */
int main(void)
{
    int      n, fd1[2], fd2[2];
    pid_t    pid;
    char     line[MAXLINE];

    if(signal(SIGPIPE, sig_pipe) == SIG_ERR)    err_sys("signal error");
    if(pipe(fd1) < 0  || pipe(fd2) < 0)  err_sys("pipe error");
    if((pid = fork()) < 0)  {   err_sys("fork error"); }
    else if(pid > 0)    /* parent */
    {
        close(fd1[0]);
        close(fd2[1]);
        while(fgets(line, MAXLINE, stdin) != NULL)
        {
            n = strlen(line);
            if((m = write(fd1[1], line, n)) != n);   /* 无论这里的if条件是真是假都执行err_sys(“write error to pipe”);不知为何 */
            {
                err_sys("write error to pipe");
            }
            if((n = read(fd2[0], line, MAXLINE)) < 0)   err_sys("read error from pipe");
            if(n == 0)
            {
                err_msg("child closed pipe");     break;
            }
            line[n] = 0;    /* null terminate */
            if(fputs(line, stdout) == EOF)    err_sys("fputs error");
        }

        if(ferror(stdin))   err_sys("fgets error on stdin");
        exit(0);
    }
    else    /* child */
    {
        close(fd1[1]);
        close(fd2[0]);
        if(fd1[0] != STDIN_FILENO)
        {
            if(dup2(fd1[0], STDIN_FILENO) != STDIN_FILENO)  err_sys("dup2 error to stdin");
            close(fd1[0]);
        }

        if(fd2[1] != STDOUT_FILENO)
        {
            if(dup2(fd2[1], STDOUT_FILENO) != STDOUT_FILENO) err_sys("dup2 error to stdout");
            close(fd2[1]);
        }
        if(execl("./add2", "add2", (char *)0) < 0)    err_sys("execl error");
    }
    exit(0);
}
static void  sig_pipe(int signo)
{
    printf("SIGPIPE caught\n");
    exit(1);
}

在程序中创建了两个管道,父、子进程各自关闭它们不需要使用的端口。两个管道一个用作协同进程的标准输入,另一个则用作它的标准输出。子进程调用dup2使管道描述符移至其标准输入和标准输出,然后调用execl.
父进程write 数据并最终到大 add的输入端: 父写数据到管道1的fd1[1] 端, 子进程通过dup2将stdin指向fd1[0],从而读取父进程写入的数据并进行处理。
父进程读出数据: 父进制从管道2的fd2[0]中读取数据,子进程通过dup2将stout指向fd2[1]中,所有add处理后的数据在管道2中,父进程可以读到。

APUE2上阐述的产生死锁的条件:
但是书上描述的产生死锁的条件其实是错误的,为什么这么说呢?
fgets从标准输入读取并不会发生阻塞,因为父进程确确实实将数据写入管道了。add2计算两数之和后调用printf写入结果,问题就出在这了 – 因为操作管道的标准I/O使用的是全缓冲,所以printf之后stdio缓冲区并没有满,数据依然停留在stdio缓冲区,并未写入管道,这就导致父进程的read会一直阻塞,而子进程接下来的fgets也会一直阻塞,这才是产生死锁的真正条件!(管道+标准i/o—->全缓冲 ????????)
那么什么时候会出现书上描述的死锁情况呢 – 很简单,只有在父进程也是使用的标准I/O的情况下才会出现,这样子进程fgets读取不到数据而阻塞。
因此,除了书上提到的使用setvbuf改变缓冲类型为行缓冲的方法外,还可以在子进程的两个printf之后显示地调用fflush(stdout),这样也是可以解决问题的。
下面是有修改add2.c程序执行后:

#include "apue.h"

int main(void)
{
    int     n, int1, int2;
    char    line[30];

    /*setvbuf(stdin,NULL,_IOLBF,0);
    setvbuf(stdout,NULL,_IOLBF,0);*/

    while ( fgets(line, 30, stdin) != NULL)     
    {   
        sscanf(line, "%d%d", &int1, &int2);
        printf("a: %d, b:%d   he: %d\n", int1, int2, int1 + int2);
         write(STDOUT_FILENO, line, n);
    }
    exit(0);
}

执行: 执行pip4出现死锁, 因为fgets 是管道,是全缓冲,行缓存,
这里写图片描述

#include "apue.h"

int main(void)
{
    int     n, int1, int2;
    char    line[30];

    setvbuf(stdin,NULL,_IOLBF,0);
    setvbuf(stdout,NULL,_IOLBF,0);

    while ( fgets(line, 30, stdin) != NULL)     
    {   
        sscanf(line, "%d%d", &int1, &int2);
        printf("a: %d, b:%d   he: %d\n", int1, int2, int1 + int2);
         write(STDOUT_FILENO, line, n);
    }
    exit(0);
}

执行:
这里写图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值