Linux 进程间通信之管道(pipe),(fifo)

Posted mingfeng002

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux 进程间通信之管道(pipe),(fifo)相关的知识,希望对你有一定的参考价值。

 无名管道(pipe)

管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;

定义函数: int pipe(int filedes[2])

filedes[0]为管道里的读取端

filedes[1]则为管道的写入端。

实现机制:

管道是由内核管理的一个缓冲区,相当于我们放入内存中的一个纸条。管道的一端连接一个进程的输出。这个进程会向管道中放入信息。管道的另一端连接一个进程的输入,这个进程取出被放入管道的信息。一个缓冲区不需要很大,它被设计成为环形的数据结构,以便管道可以被循环利用。当管道中没有信息的话,从管道中读取的进程会等待,直到另一端的进程放入信息。当管道被放满信息的时候,尝试放入信息的进程会等待,直到另一端的进程取出信息。当两个进程都终结的时候,管道也自动消失。

 

 

 

 

 

从原理上,管道利用fork机制建立,从而让两个进程可以连接到同一个PIPE上。最开始的时候,上面的两个箭头都连接在同一个进程Process 1上(连接在Process 1上的两个箭头)。当fork复制进程的时候,会将这两个连接也复制到新的进程(Process 2)。随后,每个进程关闭自己不需要的一个连接 (两个黑色的箭头被关闭; Process 1关闭从PIPE来的输入连接,Process 2关闭输出到PIPE的连接),这样,剩下的红色连接就构成了如上图的PIPE

 

一、管道读写注意点

1.必须在系统调用fork之前调用pipe,否则子进程不会继承文件描述符

2.只有在管道读端存在时,向管道写入才有意义;否则,会收到内核中的出错信号:SIFPIPE只有在管道读端存在时,向管道写入才有意义;否则,会收到内核中的出错信号:SIFPIPE

3.向管道写入数据时不保证写入的原子性,管道缓冲区一有空闲区域,写进程就试图向其写入内容。若读进程不读取管道中的内容,则写进程会一直阻塞。

4.父子进程在运行时,它们的先后顺序得不到保证。因此在这里,为保证父进程关闭读描述符,可向子进程加入sleep(2)。

二、实例

1.无名管道

/*pipe_rw.c*/
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
    int pipe_fd[2];
    pid_t pid;
    char buf_r[100];
    char* p_wbuf;
    int r_num;
    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");
        /*关闭子进程管道写端。睡眠2秒,确保父进程已相应地关闭了管道读端*/
        close(pipe_fd[1]);
        sleep(2);
        /*子进程读取管道内容*/
        if((r_num=read(pipe_fd[0],buf_r,100))>0){
            printf(   "%d numbers read from the pipe is %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 write1 success!\\n");
        if(write(pipe_fd[1]," Pipe",5)!=-1)
            printf("parent write2 success!\\n");
        /*关闭父进程写端并睡眠3秒,让子进程读数据*/
        close(pipe_fd[1]);
        sleep(3);
        /*收集子进程退出信息*/
        waitpid(pid,NULL,0);
        exit(0);
    }
}

运行结果如下:

[root@localhost ipc]# ./pipe_rw 
parent write1 success! parent write2 success! 10 numbers read from the pipe is Hello Pipe

2.命名管道(named PIPE)

由于基于fork机制,所以管道只能用于父进程和子进程之间,或者拥有相同祖先的两个子进程之间 (有亲缘关系的进程之间)。为了解决这一问题,Linux提供了FIFO方式连接进程。FIFO又叫做命名管道(named PIPE)。

FIFO (First in, First out)为一种特殊的文件类型,它在文件系统中有对应的路径。当一个进程以读(r)的方式打开该文件,而另一个进程以写(w)的方式打开该文件,那么内核就会在这两个进程之间建立管道,所以FIFO实际上也由内核管理,不与硬盘打交道。之所以叫FIFO,是因为管道本质上是一个先进先出的队列数据结构,最早放入的数据被最先读出来,从而保证信息交流的顺序。FIFO只是借用了文件系统(file system,命名管道是一种特殊类型的文件,因为Linux中所有事物都是文件,它在文件系统中以文件名的形式存在。)来为管道命名。写模式的进程向FIFO文件中写入,而读模式的进程从FIFO文件中读出。当删除FIFO文件时,管道连接也随之消失。FIFO的好处在于我们可以通过文件的路径来识别管道,从而让没有亲缘关系的进程之间建立连接

函数原型:

 

#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *filename, mode_t mode);
int mknode(const char *filename, mode_t mode | S_IFIFO, (dev_t) 0 );

其中pathname是被创建的文件名称,mode表示将在该文件上设置的权限位和将被创建的文件类型(在此情况下为S_IFIFO),dev是当创建设备特殊文件时使用的一个值。因此,对于先进先出文件它的值为0。

FIFO读写规则

1.从FIFO中读取数据: 约定:如果一个进程为了从FIFO中读取数据而阻塞打开了FIFO,那么称该进程内的读操作为设置了阻塞标志的读操作

2.从FIFO中写入数据: 约定:如果一个进程为了向FIFO中写入数据而阻塞打开FIFO,那么称该进程内的写操作为设置了阻塞标志的写操作。

 

一旦创建了了FIFO,就可open去打开它,可以使用open,read,close等去操作FIFO 当打开FIFO时,非阻塞标志(O_NONBLOCK)将会对读写产生如下影响:

1、没有使用O_NONBLOCK:访问要求无法满足时进程将阻塞。如试图读取空的FIFO,将导致进程阻塞;

2、使用O_NONBLOCK:访问要求无法满足时不阻塞,立即出错返回,errno是ENXIO;

 

/*fifo_write.c*/
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define FIFO_SERVER "/tmp/myfifo"

main(int argc,char** argv)
{
    int fd;
    char w_buf[100];
    int nwrite;
    
    if(fd==-1)
        if(errno==ENXIO)
            printf("open error; no reading process\\n");
    /*打开有名管道,并设置为非阻塞*/
    fd=open(FIFO_SERVER,O_WRONLY|O_NONBLOCK,0);
    if(argc==1)
        printf("Please send something\\n");
    strcpy(w_buf,argv[1]);
    /*向管道写入字符串*/
    if((nwrite=write(fd,w_buf,100))==-1)
    {
        if(errno==EAGAIN)
            printf("The FIFO has not been read yet.Please try later\\n");
    }
    else 
        printf("write %s to the FIFO\\n",w_buf);
}


/*fifo_read.c*/

#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define FIFO "/tmp/myfifo"

main(int argc,char** argv)
{
    char buf_r[100];
    int  fd;
    int  nread;
    
    /*创建有名管道,并设置相应的权限*/
    if((mkfifo(FIFO,O_CREAT|O_EXCL)<0)&&(errno!=EEXIST))
        printf("cannot create fifoserver\\n");
    printf("Preparing for reading bytes...\\n");
    
    memset(buf_r,0,sizeof(buf_r));
    /*打开有名管道,并设置非阻塞标志*/
    fd=open(FIFO,O_RDONLY|O_NONBLOCK,0);
    if(fd==-1)
    {
        perror("open");
        exit(1);    
    }
    while(1)
    {
        memset(buf_r,0,sizeof(buf_r));
        /*读取管道中的字符串*/
        if((nread=read(fd,buf_r,100))==-1){
            if(errno==EAGAIN)
                printf("no data yet\\n");
        }
        printf("read %s from FIFO\\n",buf_r);
        sleep(1);
    }    
    pause();
    unlink(FIFO);//这个谁mkfifo创建谁释放 
}

运行结果:

终端1:

[root@localhost ipc]# ./fifo_write 123123  
write 123123 to the FIFO  

终端2:

[root@localhost ipc]# ./fifo_read
Preparing for reading bytes...
read  from FIFO
read  from FIFO
read  from FIFO
read  from FIFO
read  from FIFO
read 123123 from FIFO
read  from FIFO

 

总结:

      1.read没有数据read阻塞,read读取后数据被删除

      2.数据有序,安装写入的顺序

      3.打开的描述符号可以读写(two-way)

      4.管道文件关闭后,数据不持久

      5.管道的数据在内核缓冲

      6.有名管道的名字仅仅是内核识别是否放回同一个fd的标识

以上是关于Linux 进程间通信之管道(pipe),(fifo)的主要内容,如果未能解决你的问题,请参考以下文章

简述Linux进程间通信之管道pipe(下)

Linux进程通信之匿名管道

Linux之进程间通信

多进程编程之进程间通信-管道和消息队列

Linux之进程间通信

Linux之------进程间通信