FIFO管道

Posted tianzeng

tags:

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

为了解决非亲属进程间通信这一问题,Linux提供了FIFO方式连接进程。FIFO又叫做命名管道(named PIPE)

FIFO (First in, First out)为一种特殊的文件类型,它在文件系统中有对应的路径。当一个进程以读(r)的方式打开该文件,而另一个进程以写(w)的方式打开该文件,那么内核就会在这两个进程之间建立管道,所以FIFO实际上也由内核管理,不与硬盘打交道
#include < sys/types.h >
#include < sys/stat.h >
int mknod(const char *path,mode_t mod,dev_t dev); 
int mkfifo(const char *path,mode_t mode);
函数mknod参数中path为创建的命名管道的全路径名:mod为创建的命名管道的模式,指明其存取权限;dev为设备值,该值取决于文件创建的种类,它只在创建设备文件时才会用到。这两个函数调用成功都返回0,失败都返回-1。

之所以叫FIFO,是因为管道本质上是一个先进先出的队列数据结构,最早放入的数据被最先读出来,从而保证信息交流的顺序。FIFO只是借用了文件系统(file system,命名管道是一种特殊类型的文件,因为Linux中所有事物都是文件,它在文件系统中以文件名的形式存在)来为管道命名。写模式的进程向FIFO文件中写入,而读模式的进程从FIFO文件中读出。当删除FIFO文件时,管道连接也随之消失

0x1: 有名管道的操作规则

1. 有名管道的打开规则

1. 如果当前打开操作是为读而打开FIFO时
    1) 若已经有相应进程为写而打开该FIFO,则当前打开操作将成功返回
    2) 否则,可能阻塞直到有相应进程为写而打开该FIFO(当前打开操作设置了阻塞标志)
    3) 或者,成功返回(当前打开操作没有设置阻塞标志)
2. 如果当前打开操作是为写而打开FIFO时
    1) 如果已经有相应进程为读而打开该FIFO,则当前打开操作将成功返回
    2) 否则,可能阻塞直到有相应进程为读而打开该FIFO(当前打开操作设置了阻塞标志)
    3) 或者,返回ENXIO错误(当前打开操作没有设置阻塞标志)

2. 有名管道的读写规则

1. 从FIFO中读取数据: 如果一个进程为了从FIFO中读取数据而阻塞打开FIFO,那么称该进程内的读操作为设置了阻塞标志的读操作。
    1) 如果有进程写打开FIFO,且当前FIFO内没有数据,则对于设置了阻塞标志的读操作来说,将一直阻塞。对于没有设置阻塞标志读操作来说则返回-1,当前errno值为EAGAIN,提醒以后再试 
    2) 对于设置了阻塞标志的读操作说,造成阻塞的原因有两种,解阻塞的原因则是FIFO中有新的数据写入,不论信写入数据量的大小,也不论读操作请求多少数据量 
        2.1) 当前FIFO内有数据,但有其它进程在读这些数据
        2.2) 另外就是FIFO内没有数据 
    3) 读打开的阻塞标志只对本进程第一个读操作施加作用,如果本进程内有多个读操作序列,则在第一个读操作被唤醒并完成读操作后,其它将要执行的读操作将不再阻塞,即使在执行读操作时,FIFO中没有数据也一样(此时,读操作返回0)
    4) 如果没有进程写打开FIFO,则设置了阻塞标志的读操作会阻塞 
 
2. 向FIFO中写入数据: 如果一个进程为了向FIFO中写入数据而阻塞打开FIFO,那么称该进程内的写操作为设置了阻塞标志的写操作 
    1) 对于设置了阻塞标志的写操作 
        1.1) 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果此时管道空闲缓冲区不足以容纳要写入的字节数,则进入睡眠,直到当缓冲区中能够容纳要写入的字节数时,才开始进行一次性写操作 
        1.2) 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。FIFO缓冲区一有空闲区域,写进程就会试图向管道写入数据,写操作在写完所有请求写的数据后返回 
    2) 对于没有设置阻塞标志的写操作 
        2.1) 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。在写满所有FIFO空闲缓冲区后,写操作返回 
        2.2) 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果当前FIFO空闲缓冲区能够容纳请求写入的字节数,写完后成功返回;如果当前FIFO空闲缓冲区不能够容纳请求写入的字节数,则返回EAGAIN错误,提醒以后再写 

3.有名管道的使用

使用命名管道时,必须先调用open()将其打开。因为命名管道是一个存在于硬盘上的文件,而管道是存在于内存中的特殊文件。需要注意的是,调用open()打开命名管道的进程可能会被阻塞。但如果同时用读写方式(O_RDWR)打开,

则一定不会导致阻塞;如果以只读方式(O_RDONLY)打开,则调用open()函数的进程将会被阻塞直到有写方打开管道;同样以写方式(O_WRONLY)打开也会阻塞直到有读方式打开管道。

实例(FIFO应用于父子进程间)

//fifo.c
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include "unpipc.h" #include "fifo.h" #include "client.h" #include "server.h" #define FIFO1 "/tmp/fifo.1" #define FIFO2 "/tmp/fifo.2" int main() { int readfd,writefd; pid_t cpid; if((mkfifo(FIFO1,FILE_MODE)<0)&&(errno!=EEXIST)) err_sys("can‘t create %s",FIFO1); if((mkfifo(FIFO2,FILE_MODE)<0)&&(errno!=EEXIST)) { unlink(FIFO1); err_sys("can‘t create %s",FIFO1); } if((cpid=fork())==0) { readfd=open(FIFO1,O_RDONLY,0); writefd=open(FIFO2,O_WRONLY,0); server(readfd,writefd); exit(0); } writefd=open(FIFO1,O_WRONLY,0); readfd=open(FIFO2,O_RDONLY,0); client(readfd,writefd); waitpid(cpid,NULL,0); close(readfd); close(writefd); unlink(FIFO1); unlink(FIFO2); exit(0); }
//client.h 
#include <string.h>
#include "unpipc.h"
//#include "my_err.h"
#define MAXLINE 4096

void client(int readfd,int writefd)
{
    char buff[MAXLINE];
    fgets(buff,MAXLINE,stdin);

    size_t len=strlen(buff);
    if(buff[len-1]==\n)
      len--;

    write(writefd,buff,len);

    ssize_t n;
    while((n=read(readfd,buff,MAXLINE))>0)
      write(STDOUT_FILENO,buff,n);
}
//server.h 
#include "unpipc.h"
#include "my_err.h"
#include <string.h>

void server(int readfd,int writefd)
{
    int fd;
    ssize_t n;
    char buff[MAXLINE+1];

    if((n=read(readfd,buff,MAXLINE))==0)
      err_quit("end-of-file while reading pathname");
    buff[n]=\0;

    if((fd=open(buff,O_RDONLY))<0)
    {
         snprintf(buff+n,sizeof(buff)-n,":can‘t open, %s\n",strerror(errno));
        n=strlen(buff);
        write(writefd,buff,n);
    }
    else
    {
        while((n=read(fd,buff,MAXLINE))>0)
          write(writefd,buff,n);

        close(fd);
    }
}

FIFO应用于无亲属关系的进程间:

//client.c
#include <sys/types.h> #include <sys/stat.h> #include "fifo.h" #include "client.h" int main() { int readfd,writefd; writefd=open(FIFO1,O_WRONLY,0); readfd=open(FIFO2,O_RDONLY,0); client(readfd,writefd); close(readfd); close(writefd); unlink(FIFO1); unlink(FIFO2); exit(0); }
//server.c
#include <sys/types.h> #include <sys/stat.h> #include "fifo.h" #include "unpipc.h" #include "server.h" int main() { int readfd,writefd; if((mkfifo(FIFO1,FILE_MODE)<0)&&(errno!=EEXIST)) err_sys("can‘t create %s",FIFO1); if((mkfifo(FIFO2,FILE_MODE)<0)&&(errno!=EEXIST)) { unlink(FIFO1); err_sys("can‘t create %s",FIFO2); } readfd=open(FIFO1,O_RDONLY,0); writefd=open(FIFO2,O_WRONLY,0); server(readfd,writefd); exit(0); }
//fifo.h
#include "unpipc.h" #define FIFO1 "/tmp/fifo.1" #define FIFO2 "/tmp/fifo.2"

 

总结:pipe与FIFO间的区别

1、对文件系统来说,匿名管道是不可见的,它的作用仅限于在父进程和子进程两个进程间进行通信。而命名管道是一个可见的文件

2、pipe只能是两个具有公共祖先的进程间进行通信,而FIFO可以用于任何两个进程之间的通信,不管这两个进程是不是父子进程,也不管这两个进程之间有没有关系。

3、pipe创建出来的管道没有名字,而FIFO是通过唯一路径来标识唯一的命名管道。

以上是关于FIFO管道的主要内容,如果未能解决你的问题,请参考以下文章

Python 子进程挂起命名管道

FIFO管道返回0而不是正确的整数?

Python 从命名管道/FIFO 中读取 JSON

读取管道后的bash返回码

Linux系统编程——进程间通信:命名管道(FIFO)

多处理是不是支持命名管道(FIFO)?