美文网首页
UNIX 下 C 实现 Pipe Descriptors 映射

UNIX 下 C 实现 Pipe Descriptors 映射

作者: Umiade | 来源:发表于2017-06-14 22:56 被阅读134次

    翻译整理自 http://unixwiz.net/techtips/remap-pipe-fds.html

    在UNIX中,通过pipe()可以创建一对单向的pipe descriptors;通过fork()可以创建一个子进程(即创建一个当前进程的“镜像”),两者初始时状态相同,执行互不干扰。利用这两点,我们可以实现进程间的数据交互,进而可以将一个进程的几个standard POSIX file通过dup2()函数被自定义的pipe所映射。这里主要介绍该实现原理和几个注意点以避免descriptor之间产生冲突引起的I/O errors。

    UNIX Pipes

    pipe(int fd[2])函数可得到两个file descriptors形成一个单向的pipe,fd[0]固定为读端,fd[1]固定为写端。

    单向pipe

    通常pipe作为进程间的读写通道,并不会用在单独的一个进程中。

    The "usual" way

    UNIX pipe由一对file descriptors组成,即一端写,一端读。对于单进程来说并没有意义,但可以作为多个进程之间的数据通道,由于UNIX pipe是单向的,所以在数据交换时常会用到多个pipe。例如两个进程之间需要完成数据的交互,那么就需要两个pipe,即四个file descriptors。
    使用举例如下:

    ...
    
    int writepipe[2] = {-1,-1}, /* parent -> child */
        readpipe [2] = {-1,-1}; /* child -> parent */
    pid_t   childpid;
    
    /*------------------------------------------------------------------------
     * CREATE THE PAIR OF PIPES
     *
     * Pipes have two ends but just one direction: to get a two-way
     * conversation you need two pipes. It's an error if we cannot make
     * them both, and we define these macros for easy reference.
     */
    writepipe[0] = -1;
    
    if ( pipe(readpipe) < 0  ||  pipe(writepipe) < 0 )
    {
        /* FATAL: cannot create pipe */
        /* close readpipe[0] & [1] if necessary */
    }
    
    #define PARENT_READ readpipe[0]
    #define CHILD_WRITE readpipe[1]
    #define CHILD_READ  writepipe[0]
    #define PARENT_WRITE    writepipe[1]
    
    if ( (childpid = fork()) < 0)
    {
        /* FATAL: cannot fork child */
    }
    else if ( childpid == 0 )   /* in the child */
    {
        close(PARENT_WRITE);
        close(PARENT_READ);
    
        dup2(CHILD_READ,  0);  close(CHILD_READ);
        dup2(CHILD_WRITE, 1);  close(CHILD_WRITE);
    
        /* do child stuff */
    }
    else                /* in the parent */
    {
        close(CHILD_READ);
        close(CHILD_WRITE);
    
        /* do parent stuff */
    }
    

    descriptors崩溃

    在pipe(fd)执行时,通常会为fd分配两个当前最小且可用的值作为文件描述符放入fd[2]数组;正常情况下不会造成问题,但如果恰好此时FD#0和#1(stdin, stdout)均可用且在该状态下是最小的,那么这两个描述符会被分配到fd中。此时,若进行上述的dup2操作,会产生矛盾,先看下面一段代码:

    #define PARENT_READ  pipe1[0]   /* fd#0 */
    #define CHILD_WRITE  pipe1[1]   /* fd#1 */
    #define CHILD_READ   pipe2[0]   /* fd#2 */
    #define PARENT_WRITE pipe2[1]   /* fd#3 */
    
    dup2(CHILD_READ=2,  0);  close(CHILD_READ=2);
    dup2(CHILD_WRITE=1, 1);  close(CHILD_WRITE=1);
    

    假如为pipe1分配了 FD#0 #1,为pipe2分配了FD#2 #3,而第一个dup2函数又将pipe2[0](CHILD_READ)指向FD#0,即指向了pipe[0](PARENT_READ)。后台守护进程检测到PARENT_READ不再工作,便会关闭PARENT_READ。接下来CHILD_READ也被关闭,那么此时PARENT进程的read和write操作将会不再有意义。

    Getting it right

    如果要进行映射的file descriptor的值和目标均不同,(即所有dup2(souce, target)调用中souces和targets的值均不同),那么可以按照上述代码片段1进行file descriptor映射,否则就需要重新设计调用逻辑。
    这里我们以上述的#0#1(stdin, stdout)为例,需要考虑souce这一file descriptor的三种情况:

    • 0 - 可能造成冲突
    • 1 - 也可能造成冲突
    • >1 - 安全

    考虑到一个pipe的两端(同一个pipe的read和write文件描述符)值是不会相等的,可以通过一个表格来审计所有需要考虑的操作。

    #define DUP2CLOSE(oldfd, newfd) ( dup2(oldfd, newfd), close(oldfd) )
    

    每个特定情况下所需的操作如下表:

    | read FD (should be 0) | write FD (should be 1) | Action |
    | :-----------: | :-------------: | : ----- |
    | 0 | 1 | nothing - it's already done |
    | >=1 | >1 | DUP2CLOSE(rfd, 0); DUP2CLOSE(wfd, 1); |
    | 0 | >1 | DUP2CLOSE(wfd, 1); |
    | >1 | 1 | DUP2CLOSE(rfd, 0); |
    | >1 | 0 | DUP2CLOSE(wfd, 1); DUP2CLOSE(rfd, 0); |
    | 1 | 0 | tmp = dup(wfd); close(wfd); DUP2CLOSE(rfd, 0); DUP2CLOSE(tmp, 1); |

    在类似的映射操作中,通过上述遍历情况矩阵的形式审计映射操作会大大减少错误的发生。

    相关文章

      网友评论

          本文标题:UNIX 下 C 实现 Pipe Descriptors 映射

          本文链接:https://www.haomeiwen.com/subject/eineqxtx.html