next up previous contents
下一节: 6.2.3 简易管道 上一层: 6.2 半双工 UNIX 管道 前一节: 6.2.1 基本概念

6.2.2 在 C 中创建管道

使用 C 编程语言创建“管道”可能比我们简单的 shell 示例要稍微复杂一些。 要在 C 中创建一个简单的管道,我们使用 pipe() 系统调用。 它接受一个参数,这是一个包含两个整数的数组,如果调用成功,该数组将包含两个新的文件描述符,用于管道。 创建管道后,进程通常会派生一个新进程(记住子进程会继承打开的文件描述符)。


  SYSTEM CALL: pipe();                                                          

  PROTOTYPE: int pipe( int fd[2] );                                             
    RETURNS: 0 on success                                                       
             -1 on error: errno = EMFILE (no free descriptors)                  
                                  EMFILE (system file table is full)            
                                  EFAULT (fd array is not valid)                

  NOTES: fd[0] is set up for reading, fd[1] is set up for writing

数组中的第一个整数(元素 0)被设置为可读,而第二个整数(元素 1)被设置为可写。 直观地说,fd1 的输出变成 fd0 的输入。 再次说明,所有通过管道传输的数据都通过内核。

        #include <stdio.h>
        #include <unistd.h>
        #include <sys/types.h>

        main()
        {
                int     fd[2];
                
                pipe(fd);
                .
                .
        }

记住,在 C 语言中,数组名会退化为指向其第一个成员的指针。 如上所示,fd等价于&fd[0]。 一旦我们建立了管道,我们就会 fork 出新的子进程

        #include <stdio.h>
        #include <unistd.h>
        #include <sys/types.h>

        main()
        {
                int     fd[2];
                pid_t   childpid;

                pipe(fd);
        
                if((childpid = fork()) == -1)
                {
                        perror("fork");
                        exit(1);
                }
                .
                .
        }

如果父进程想要从子进程接收数据,它应该关闭 fd1,而子进程应该关闭 fd0。 如果父进程想要向子进程发送数据,它应该关闭 fd0,而子进程应该关闭 fd1。 由于描述符在父进程和子进程之间共享,我们应该始终确保关闭我们不关心的管道末端。 从技术角度来看,如果不显式关闭管道的不必要端,则永远不会返回 EOF。

        #include <stdio.h>
        #include <unistd.h>
        #include <sys/types.h>

        main()
        {
                int     fd[2];
                pid_t   childpid;

                pipe(fd);
        
                if((childpid = fork()) == -1)
                {
                        perror("fork");
                        exit(1);
                }

                if(childpid == 0)
                {
                        /* Child process closes up input side of pipe */
                        close(fd[0]);
                }
                else
                {
                        /* Parent process closes up output side of pipe */
                        close(fd[1]);
                }
                .
                .
        }

如前所述,一旦管道建立,文件描述符可以像普通文件的描述符一样对待。

/*****************************************************************************
 Excerpt from "Linux Programmer's Guide - Chapter 6"
 (C)opyright 1994-1995, Scott Burkett
 ***************************************************************************** 
 MODULE: pipe.c
 *****************************************************************************/

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main(void)
{
        int     fd[2], nbytes;
        pid_t   childpid;
        char    string[] = "Hello, world!\n";
        char    readbuffer[80];

        pipe(fd);
        
        if((childpid = fork()) == -1)
        {
                perror("fork");
                exit(1);
        }

        if(childpid == 0)
        {
                /* Child process closes up input side of pipe */
                close(fd[0]);

                /* Send "string" through the output side of pipe */
                write(fd[1], string, (strlen(string)+1));
                exit(0);
        }
        else
        {
                /* Parent process closes up output side of pipe */
                close(fd[1]);

                /* Read in a string from the pipe */
                nbytes = read(fd[0], readbuffer, sizeof(readbuffer));
                printf("Received string: %s", readbuffer);
        }
        
        return(0);
}

通常,子进程中的描述符会被复制到标准输入或输出。 然后子进程可以 exec() 另一个程序,该程序会继承标准流。 让我们看看 dup() 系统调用


  SYSTEM CALL: dup();                                                           

  PROTOTYPE: int dup( int oldfd );                                              
    RETURNS: new descriptor on success                                          
             -1 on error: errno = EBADF (oldfd is not a valid descriptor)       
                                  EBADF (newfd is out of range)                 
                                  EMFILE (too many descriptors for the process) 

  NOTES: the old descriptor is not closed!  Both may be used interchangeably

虽然旧的描述符和新创建的描述符可以互换使用,但我们通常会先关闭一个标准流。 dup() 系统调用使用编号最小的未使用描述符作为新的描述符。

考虑以下情况

        .
        .
        childpid = fork();
        
        if(childpid == 0)
        {
                /* Close up standard input of the child */
                close(0);
                
                /* Duplicate the input side of pipe to stdin */
                dup(fd[0]);
                execlp("sort", "sort", NULL);
                .
        }

由于文件描述符 0 (stdin) 已被关闭,因此对 dup() 的调用将管道的输入描述符 (fd0) 复制到其标准输入。 然后我们调用 execlp(),用 sort 程序的文本段(代码)覆盖子进程的文本段(代码)。 由于新 exec 的程序会从其生成器继承标准流,因此它实际上继承了管道的输入端作为其标准输入! 现在,原始父进程发送到管道的任何内容都会进入 sort 工具。

还有另一个系统调用 dup2(),也可以使用。 这个特殊的调用起源于 UNIX 的 Version 7,并在 BSD 版本中延续下来,现在是 POSIX 标准要求的。


  SYSTEM CALL: dup2();                                                          

  PROTOTYPE: int dup2( int oldfd, int newfd );                                  
    RETURNS: new descriptor on success                                          
             -1 on error: errno = EBADF (oldfd is not a valid descriptor)       
                                  EBADF (newfd is out of range)                 
                                  EMFILE (too many descriptors for the process) 

  NOTES: the old descriptor is closed with dup2()!

使用这个特殊的调用,我们将 close 操作和实际的描述符复制操作都封装在一个系统调用中。 此外,它保证是原子的,这本质上意味着它永远不会被到达的信号中断。 整个操作将在控制权返回内核进行信号分派之前完成。 使用原始的 dup() 系统调用,程序员必须在调用它之前执行 close() 操作。 这导致了两个系统调用,并且在它们之间经过的短暂时间内存在很小的漏洞。 如果信号在该短暂的瞬间到达,描述符复制将失败。 当然,dup2() 为我们解决了这个问题。

考虑以下情况

        .
        .
        childpid = fork();
        
        if(childpid == 0)
        {
                /* Close stdin, duplicate the input side of pipe to stdin */
                dup2(0, fd[0]);
                execlp("sort", "sort", NULL);
                .
                .
        }


next up previous contents
下一节: 6.2.3 简易管道 上一层: 6.2 半双工 UNIX 管道 前一节: 6.2.1 基本概念

转换于
1996 年 3 月 29 日星期五 14:43:04 EST