Linuxc - 未命名管道-pipe

linuxc-未命名管道-pipe

–详情见《unix环境高级编程》15.2小节

管道试UNIX系统IPC的最古老形式,也是最常用的IPC。

pipe管道的局限性:

  1. 历史上,它们是半双工的(即数据只能在一个方向上流动)。现在,某些系统提供全双工管道,但是为了最佳的可移植性,我们决定预先假定系统支持全双工管道。
  2. 管道只能在具有公共祖先的两个进程之间使用。通常,一个管道由一个进程创建,在进程调用fork之后,这个管道就能在父进程和子进程之间使用了。

相关函数

#include <unistd.h>
int pipe(int fd[2]);    //创建一个管道

#include <stdio.h>
//创建一个连接另一个进程的管道
FILE *popen(const char *cmdstring, const char *type); //s:FILE * err:NULL
int pclose(FILE *fp);                                  //s:终止状态 err:-1

popen和pclose函数的实现

#include "apue.h"
#include <errno.h>
#include <fcntl.h>
#include <sys/wait.h>

/*
 * Pointer to array allocated at run-time.
 */
static pid_t    *childpid = NULL;

/*
 * From our open_max(), {Prog openmax}.
 */
static int        maxfd;

FILE *
popen(const char *cmdstring, const char *type)
{
    int        i;
    int        pfd[2];
    pid_t    pid;
    FILE    *fp;

    /* only allow "r" or "w" */
    if ((type[0] != 'r' && type[0] != 'w') || type[1] != 0) {
        errno = EINVAL;
        return(NULL);
    }

    if (childpid == NULL) {        /* first time through */
        /* allocate zeroed out array for child pids */
        maxfd = open_max();
        if ((childpid = calloc(maxfd, sizeof(pid_t))) == NULL)
            return(NULL);
    }

    if (pipe(pfd) < 0)
        return(NULL);    /* errno set by pipe() */
    if (pfd[0] >= maxfd || pfd[1] >= maxfd) {
        close(pfd[0]);
        close(pfd[1]);
        errno = EMFILE;
        return(NULL);
    }

    if ((pid = fork()) < 0) {
        return(NULL);    /* errno set by fork() */
    } else if (pid == 0) {                            /* child */
        if (*type == 'r') {
            close(pfd[0]);
            if (pfd[1] != STDOUT_FILENO) {
                dup2(pfd[1], STDOUT_FILENO);
                close(pfd[1]);
            }
        } else {
            close(pfd[1]);
            if (pfd[0] != STDIN_FILENO) {
                dup2(pfd[0], STDIN_FILENO);
                close(pfd[0]);
            }
        }

        /* close all descriptors in childpid[] */
        for (i = 0; i < maxfd; i++)
            if (childpid[i] > 0)
                close(i);

        execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
        _exit(127);
    }

    /* parent continues... */
    if (*type == 'r') {
        close(pfd[1]);
        if ((fp = fdopen(pfd[0], type)) == NULL)
            return(NULL);
    } else {
        close(pfd[0]);
        if ((fp = fdopen(pfd[1], type)) == NULL)
            return(NULL);
    }

    childpid[fileno(fp)] = pid;    /* remember child pid for this fd */
    return(fp);
}

int
pclose(FILE *fp)
{
    int        fd, stat;
    pid_t    pid;

    if (childpid == NULL) {
        errno = EINVAL;
        return(-1);        /* popen() has never been called */
    }

    fd = fileno(fp);
    if (fd >= maxfd) {
        errno = EINVAL;
        return(-1);        /* invalid file descriptor */
    }
    if ((pid = childpid[fd]) == 0) {
        errno = EINVAL;
        return(-1);        /* fp wasn't opened by popen() */
    }

    childpid[fd] = 0;
    if (fclose(fp) == EOF)
        return(-1);

    while (waitpid(pid, &stat, 0) < 0)
        if (errno != EINTR)
            return(-1);    /* error other than EINTR from waitpid() */

    return(stat);    /* return child's termination status */
}

示例

  1. 创建一个从父进程到子进程的管道,并且父进程经由该管道向子进程传送数据。

    //pipe
    #include "apue.h"
    
    int
    main(void)
    {
        int        n;
        int        fd[2];
        pid_t    pid;
        char    line[MAXLINE];
    
        if (pipe(fd) < 0)
            err_sys("pipe error");
        if ((pid = fork()) < 0) {
            err_sys("fork error");
        } else if (pid > 0) {        /* parent */
            close(fd[0]);
            write(fd[1], "hello world\n", 12);
        } else {                    /* child */
            close(fd[1]);
            n = read(fd[0], line, MAXLINE);
            write(STDOUT_FILENO, line, n);
        }
        exit(0);
    }
  2. 编写一个程序,功能是调用已有的分页程序每次一页地显示已产生的输出。

    //pipe
    #include "apue.h"
    #include <sys/wait.h>
    
    #define    DEF_PAGER    "/bin/more"        /* default pager program */
    
    int
    main(int argc, char *argv[])
    {
        int        n;
        int        fd[2];
        pid_t    pid;
        char    *pager, *argv0;
        char    line[MAXLINE];
        FILE    *fp;
    
        if (argc != 2)
            err_quit("usage: a.out <pathname>");
    
        if ((fp = fopen(argv[1], "r")) == NULL)
            err_sys("can't open %s", argv[1]);
        if (pipe(fd) < 0)
            err_sys("pipe error");
    
        if ((pid = fork()) < 0) {
            err_sys("fork error");
        } else if (pid > 0) {                                /* parent */
            close(fd[0]);        /* close read end */
    
            /* parent copies argv[1] to pipe */
            while (fgets(line, MAXLINE, fp) != NULL) {
                n = strlen(line);
                if (write(fd[1], line, n) != n)
                    err_sys("write error to pipe");
            }
            if (ferror(fp))
                err_sys("fgets error");
    
            close(fd[1]);    /* close write end of pipe for reader */
    
            if (waitpid(pid, NULL, 0) < 0)
                err_sys("waitpid error");
            exit(0);
        } else {                                        /* child */
            close(fd[1]);    /* close write end */
            if (fd[0] != STDIN_FILENO) {
                if (dup2(fd[0], STDIN_FILENO) != STDIN_FILENO)
                    err_sys("dup2 error to stdin");
                close(fd[0]);    /* don't need this after dup2 */
            }
    
            /* get arguments for execl() */
            if ((pager = getenv("PAGER")) == NULL)
                pager = DEF_PAGER;
            if ((argv0 = strrchr(pager, '/')) != NULL)
                argv0++;        /* step past rightmost slash */
            else
                argv0 = pager;    /* no slash in pager */
    
            if (execl(pager, argv0, (char *)0) < 0)
                err_sys("execl error for %s", pager);
        }
        exit(0);
    }
    //popen
    #include "apue.h"
    #include <sys/wait.h>
    
    #define    PAGER    "${PAGER:-more}" /* environment variable, or default */
    
    int
    main(int argc, char *argv[])
    {
        char    line[MAXLINE];
        FILE    *fpin, *fpout;
    
        if (argc != 2)
            err_quit("usage: a.out <pathname>");
        if ((fpin = fopen(argv[1], "r")) == NULL)
            err_sys("can't open %s", argv[1]);
    
        if ((fpout = popen(PAGER, "w")) == NULL)
            err_sys("popen error");
    
        /* copy argv[1] to pager */
        while (fgets(line, MAXLINE, fpin) != NULL) {
            if (fputs(line, fpout) == EOF)
                err_sys("fputs error to pipe");
        }
        if (ferror(fpin))
            err_sys("fgets error");
        if (pclose(fpout) == -1)
            err_sys("pclose error");
    
        exit(0);
    }
  3. 实现进程竞争的函数:TELL_WAIT、TELL_PARENT、TELL_CHILD、WAIT_PARENT和WAIT_CHILD。

    #include "apue.h"
    
    static int    pfd1[2], pfd2[2];
    
    void
    TELL_WAIT(void)
    {
        if (pipe(pfd1) < 0 || pipe(pfd2) < 0)
            err_sys("pipe error");
    }
    
    void
    TELL_PARENT(pid_t pid)
    {
        if (write(pfd2[1], "c", 1) != 1)
            err_sys("write error");
    }
    
    void
    WAIT_PARENT(void)
    {
        char    c;
    
        if (read(pfd1[0], &c, 1) != 1)
            err_sys("read error");
    
        if (c != 'p')
            err_quit("WAIT_PARENT: incorrect data");
    }
    
    void
    TELL_CHILD(pid_t pid)
    {
        if (write(pfd1[1], "p", 1) != 1)
            err_sys("write error");
    }
    
    void
    WAIT_CHILD(void)
    {
        char    c;
    
        if (read(pfd2[0], &c, 1) != 1)
            err_sys("read error");
    
        if (c != 'c')
            err_quit("WAIT_CHILD: incorrect data");
    }
  4. 过滤程序:它变换运行命令的输入或输出。(就是改变另一个程序的输入或输出)

    操作过滤程序。它将标准输入复制到标准输出,在复制时将大写字符变换为小写字符。

    //popen1.c
    #include "apue.h"
    #include <sys/wait.h>
    
    int
    main(void)
    {
        char    line[MAXLINE];
        FILE    *fpin;
    
        if ((fpin = popen("myuclc", "r")) == NULL)
            err_sys("popen error");
        for ( ; ; ) {
            fputs("prompt> ", stdout);
            fflush(stdout);
            if (fgets(line, MAXLINE, fpin) == NULL)    /* read from pipe */
                break;
            if (fputs(line, stdout) == EOF)
                err_sys("fputs error to pipe");
        }
        if (pclose(fpin) == -1)
            err_sys("pclose error");
        putchar('\n');
        exit(0);
    }
    //myuclc.c
    #include "apue.h"
    #include <ctype.h>
    
    int
    main(void)
    {
        int        c;
    
        while ((c = getchar()) != EOF) {
            if (isupper(c))
                c = tolower(c);
            if (putchar(c) == EOF)
                err_sys("output error");
            if (c == '\n')
                fflush(stdout);
        }
        exit(0);
    }
  5. 协同进程:UNIX系统过滤程序从标准输入读取数据,向标准输出写数据。几个过滤程序通常在shell管道中线性连接。当一个过滤程序既产生某个过滤程序的输入,又读取该过滤程序的输出时,它就变成了协同进程(coprocess)。bash、cshell、sh等所执行的命令都是协同进程。

    注意:

    • 协同进程中的io缓冲为全缓冲。当无法改变协同进程中的缓冲方式时,就要使协同进程认为它的标准输入输出都被连接到了一个终端,才能正常显示。(见19章-伪终端)
    • 协同进程中的stderr输出。有些实时刷新的程序经常会将输出放到stderr上。
    //pipe4.c
    #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 (write(fd1[1], line, n) != n)
                    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);
    }
    //add2.c
    #include "apue.h"
    
    int
    main(void)
    {
        int        int1, int2;
        char    line[MAXLINE];
        
        if (setvbuf(stdin, NULL, _IOLBF, 0) != 0)//改变in/out的缓冲方式
            err_sys("setvbuf error");
        if (setvbuf(stdout, NULL, _IOLBF, 0) != 0)
            err_sys("setvbuf error");
        
        while (fgets(line, MAXLINE, stdin) != NULL) {
            if (sscanf(line, "%d%d", &int1, &int2) == 2) {
                if (printf("%d\n", int1 + int2) == EOF)
                    err_sys("printf error");
            } else {
                if (printf("invalid args\n") == EOF)
                    err_sys("printf error");
            }
        }
        exit(0);
    }
    原文作者:Jonathan
    原文地址: https://segmentfault.com/a/1190000019795644
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞