1.什么是管道?

  管道分为无名管道和命名管道,本文中如无特殊说明均指无名管道。

  管道是Linux支持的最初Unix IPC形式之一,具有以下特点:

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

B.只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程);

C.单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中。

D.数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。

2.如何创建管道?

2.1 包含头文件

#include <unistd.h>

int pipe(int pipefd[2]);

返回值:成功,返回0,否则返回-1。

参数数组包含pipe使用的两个文件的描述符:fd[0]:读端,fd[1]:写端。

3.管道数据的读写

  管道两端可分别用描述字fd[0]以及fd[1]来描述,需要注意的是,管道的两端是固定了任务的。即一端只能用于读,由描述字fd[0]表示,称其为管道读端;另一端则只能用于写,由描述字fd[1]来表示,称其为管道写端。如果试图从管道写端读取数据,或者向管道读端写入数据都将导致错误发生。一般文件的I/O函数都可以用于管道,如close、read、write等。

从管道中读取数据:

  如果管道的写端不存在,则认为已经读到了数据的末尾,读函数返回的读出字节数为0;

当管道的写端存在时,如果请求的字节数目大于PIPE_BUF,则返回管道中现有的数据字节数,如果请求的字节数目不大于PIPE_BUF,则返回管道中现有数据字节数(此时,管道中数据量小于请求的数据量);或者返回请求的字节数(此时,管道中数据量不小于请求的数据量)



内核代码pipe.c里没有pipe的接口,而是定义成了系统调用接口sys_pipe,。内核里有如下定义


SYSCALL_DEFINE1(pipe, int __user *, fildes)

{

  return sys_pipe2(fildes, 0);

}


4.实例1

#include<unistd.h>

#include<memory.h>

#include<errno.h>

#include<stdio.h>

#include<stdlib.h>

int main()

{

  pid_t pid;

  int r_num; 

  int pipe_fd[2];

  char buf_r[100];

  char* p_wbuf;


  memset(buf_r,0,sizeof(buf_r));

  if(pipe(pipe_fd)<0)

  {

    printf("pipe create error\n");

    return -1;

  }


  if((pid=fork())==0)

  {

    printf("\n");

    close(pipe_fd[1]);

    sleep(1);

    if((r_num=read(pipe_fd[0],buf_r,100))>0)

    {

      printf("%d numbers read from pipe is:\n %s\n",r_num,buf_r);

    }


    close(pipe_fd[0]);

    exit(0);

  }


  else if(pid>0)

  {

    close(pipe_fd[0]);

    if(write(pipe_fd[1],"Hello",5)!=-1)

      printf("parent write success!\n");

    if(write(pipe_fd[1]," PIPE",5)!=-1)

      printf("parent wirte2 succes!\n");

    close(pipe_fd[1]);

    sleep(3);

    waitpid(pid,NULL,0);

    exit(0);

  }

}

4.1函数解释

  函数首先创建了一个管道,接着用fork创建了一个进程,在子进程中首先关闭管道的写端,再从管道中读取数据,接着关闭管道读端,最后子进程退出;在父进程中,首先关闭管道的读端,接着向管道中写入两次数据,接着关闭管道写端,等待子进程退出,最后退出程序;

4.2 fork()函数

  fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。

fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:

1)在父进程中,fork返回新创建子进程的进程ID;

2)在子进程中,fork返回0;

3)如果出现错误,fork返回一个负值;

4.3程序的编译、运行

[wss@localhost pipe]$ls

pipe.c

[wss@localhost pipe]$gcc pipe.c -o pipe

[wss@localhost pipe]$ls

pipe pipe.c

[wss@localhost pipe]$./pipe

parent write success!

parent wirte2 succes!

10 numbers read from pipe is:

Hello PIPE

[wss@localhost pipe]$


5.实例2

这个例子是官方给的标准例子,程序首先创建一个管道,然后创建一个子进程,之后父进程关闭管道的读端,然后向管道写入命令行传入的参数,然后关闭管道的写端,父进程退出;在子进程中,首先关闭管道写端,接着读管道,数据读取完成,程序退出;

#include <sys/wait.h>

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <string.h>

int main(int argc, char *argv[])

{

  int pipefd[2];

  pid_t cpid;

  char buf;

  if (argc != 2)

  {

    fprintf(stderr, "Usage: %s <string>\n", argv[0]);

    exit(EXIT_FAILURE);

  }

  if (pipe(pipefd) == -1)

  {

    perror("pipe");

    exit(EXIT_FAILURE);

  }

  cpid = fork();

  if (cpid == -1)

  {

    perror("fork");

    exit(EXIT_FAILURE);

  }

  if (cpid == 0)

   {                   /* Child reads from pipe */

    close(pipefd[1]);         /* Close unused write end */

    while (read(pipefd[0], &buf, 1) > 0)

    write(STDOUT_FILENO, &buf, 1);

    write(STDOUT_FILENO, "\n", 1);

    close(pipefd[0]);

    _exit(EXIT_SUCCESS);

  }

  else

  {                  /* Parent writes argv[1] to pipe */

    close(pipefd[0]);       /* Close unused read end */

    write(pipefd[1], argv[1], strlen(argv[1]));

    close(pipefd[1]);        /* Reader will see EOF */

    wait(NULL);           /* Wait for child */

    exit(EXIT_SUCCESS);

  }

}

5.1程序的编译运行

[wss@localhost pipe2]$ls

pipe2.c

[wss@localhost pipe2]$gcc pipe2.c -o pipe2

[wss@localhost pipe2]$ls

pipe2 pipe2.c

[wss@localhost pipe2]$./pipe2 Hello-pipe

Hello-pipe

[wss@localhost pipe2]$


在Linux内核的实现中,read默认是阻塞的,write默认是非阻塞的,当然,用户也可以通过fcntl来改变文件的读写模式。