Linux管道 - 系统调用pipe()函数实现

管道概念

1、管道是操作提供的一块操作系统内存;
2、管道是Unix中最古老的进程间通信方式;
3、我们把一个进程连接到另一进程的数据流称为管道。

管道通信原理

父进程和子进程之间,或者两个兄弟进程之间,可以通过系统调用建立起一个单向的通信管道。但是这种管道只能由父进程开建立,对于子进程来说是静态的,与生俱来的。管道两端的进程各自都将该管道视作一个文件。一个进程写,另一个进程读。并且,通过管道传递的内容遵循“先入先出”(FIFO)的原则。每个管道都是单向的,需要双向通信时就要建立两个管道。

管道是半双工通信,如果要实现全双工通信,就需要创建两个管道。

系统调用pipe()函数实现

管道机制的主体是系统调用pipe(),但是由pipe()所建立的管道的两端都在同一进程中,所以必须在fork的配合下,才能在父子进程之间或者两个子进程之间建立起进程间的通信管道。由于管道两端都是以(已打开)文件的形式出现在相关的进程中,在具体实现上也是作为匿名文件来实现的。所以pipe()的代码与文件系统密切相关。

  • 从键盘读取数据,写入管道,读取管道,写到屏幕
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>

int main()
{
  int fds[2];
  char buf[100];
  int len;

  if(pipe(fds) == -1)
  perror("make pipe");
  exit(-1);

//read from stdin
while(fgets(buf,100,stdin))
{
  len = strlen(buf);
   //read into pipe
   if(write(fds[1],buf,len)!=len)
     perror("write to pipe");
     break;
 }

memset(buf,0x00,sizeof(buf));

//read from pipe
if((len = read(fds[0],buf,100)) == -1)
{
  perrpr("read from pipe");
  break;
}

//write to stdout
if(write(1,buf,len) != len))
{
  perror("write to stdout");
  break;
}
}
注意:

使用管道有一些限制:

两个进程通过一个管道只能实现单向通信,比如上面的例子,父进程写子进程读,如果

有时候也需要子进程写父进程读,就必须另开一个管道。请读者思考,如果只开一个管道,

但是父进程不关闭读端,子进程也不关闭写端,双方都有读端和写端,为什么不能实现双向

通信?

管道的读写端通过打开的文件描述符来传递,因此要通信的两个进程必须从它们的公共

祖先那里继承管道文件描述符。上面的例子是父进程把文件描述符传给子进程之后父子进程

之间通信,也可以父进程fork两次,把文件描述符传给两个子进程,然后两个子进程之间通

信,总之需要通过fork传递文件描述符使两个进程都能访问同一管道,它们才能通信。

使用管道需要注意以下4种特殊情况(假设都是阻塞I/O操作,没有设置O_NONBLOCK标

志):

1.如果所有指向管道写端的文件描述符都关闭了(管道写端的引用计数等于0),而仍

然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0,就

像读到文件末尾一样。

2.如果有指向管道写端的文件描述符没关闭(管道写端的引用计数大于0),而持有管

道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数

据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。

3.如果所有指向管道读端的文件描述符都关闭了(管道读端的引用计数等于0),这时

有进程向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止。

在第33章信号会讲到怎样使SIGPIPE信号不终止进程。

4.如果有指向管道读端的文件描述符没关闭(管道读端的引用计数大于0),而持有管

道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时

再次write会阻塞,直到管道中有空位置了才写入数据并返回。

管道的这四种特殊情况具有普遍意义。

猜你喜欢

转载自blog.csdn.net/m0_37925202/article/details/79835102