linux c编程:FIFO

前面介绍的pipe属于匿名管道

管道的主要局限性正体现在它的特点上:

  • 只支持单向数据流; 
  • 只能用于具有亲缘关系的进程之间; 
  • 没有名字; 
  • 管道的缓冲区是有限的(管道制存在于内存中,在管道创建时,为缓冲区分配一个页面大小); 
  • 管道所传送的是无格式字节流,这就要求管道的读出方和写入方必须事先约定好数据的格式,比如多少字节算作一个消息(或命令、或记录)等等;

如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道,是一种特殊类型的文件。

二,命名管道FIFO

2.1 有名管道相关的关键概念

管 道应用的一个重大限制是它没有名字,因此,只能用于具有亲缘关系的进程间通信,在有名管道(named pipeFIFO)提出后,该限制得到了克服。FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。这样,即 使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信(能够访问该路径的进程以及FIFO的创建进程之 间),因此,通过FIFO不相关的进程也能交换数据。值得注意的是,FIFO严格遵循先进先出(first in first out),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如lseek()等文件定位操作。

2.2有名管道的创建

命名管道可以从命令行上创建,命令行方法是使用下面这个命令: 

$ mkfifo filename 

命名管道也可以从程序里创建,相关函数有:

#include <sys/types.h>
   #include <sys/stat.h>
   int mkfifo(const char * pathname, mode_t mode)

该函数的第一个参数是一个普通的路径名,也就是创建 后FIFO的名字。第二个参数与打开普通文件的open()函数中的mode 参数相同。 如果mkfifo的第一个参数是一个已经存在的路径名时,会返回EEXIST错误,所以一般典型的调用代码首先会检查是否返回该错误,如果确实返回该错 误,那么只要调用打开FIFO的函数就可以了。一般文件的I/O函数都可以用于FIFO,如closereadwrite等等。

2.3有名管道的打开规则(与匿名管道一样)

FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一量这些工作完成之后,它们具有相同的语义。

man帮助说明:The only difference between pipes and FIFOs is the manner in which they are created and opened. Once these tasks have been accomplished, I/O on pipes and FIFOs has exactly the same semantics

有名管道比管道多了一个打开操作:open

FIFO的打开规则:

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

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

2.4有名管道的读写规则

FIFO中读取数据:

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

  • 如果有进程写打开FIFO,且当前FIFO内没有数据,则对于设置了阻塞标志的读操作来说,将一直阻塞。对于没有设置阻塞标志读操作来说则返回-1,当前errno值为EAGAIN,提醒以后再试。 
  • 对于设置了阻塞标志的读操作说,造成阻塞的原因有两种:当前FIFO内有数据,但有其它进程在读这些数据;另外就是FIFO内没有数据。解阻塞的原因则是FIFO中有新的数据写入,不论信写入数据量的大小,也不论读操作请求多少数据量。 
  • 读打开的阻塞标志只对本进程第一个读操作施加作用,如果本进程内有多个读操作序列,则在第一个读操作被唤醒并完成读操作后,其它将要执行的读操作将不再阻塞,即使在执行读操作时,FIFO中没有数据也一样(此时,读操作返回0)。 
  • 如果没有进程写打开FIFO,则设置了阻塞标志的读操作会阻塞。

注:如果FIFO中有数据,则设置了阻塞标志的读操作不会因为FIFO中的字节数小于请求读的字节数而阻塞,此时,读操作会返回FIFO中现有的数据量。

FIFO中写入数据:

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

对于设置了阻塞标志的写操作:

  • 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果此时管道空闲缓冲区不足以容纳要写入的字节数,则进入睡眠,直到当缓冲区中能够容纳要写入的字节数时,才开始进行一次性写操作。 
  • 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。FIFO缓冲区一有空闲区域,写进程就会试图向管道写入数据,写操作在写完所有请求写的数据后返回。

对于没有设置阻塞标志的写操作:

  • 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。在写满所有FIFO空闲缓冲区后,写操作返回。 
  • 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果当前FIFO空闲缓冲区能够容纳请求写入的字节数,写完后成功返回;如果当前FIFO空闲缓冲区不能够容纳请求写入的字节数,则返回EAGAIN错误,提醒以后再写;

来看一个具体的实现:

write的函数:

#include <stdio.h>

#include <unistd.h>

#include <stdlib.h>

#include <sys/stat.h>

#include <sys/types.h>

#include <fcntl.h>

 

int main(int argc, char **argv)

{

    int infd;

    int fd;

    char buf[1024*4];

    int n = 0;

    char *path="/home/zhf/c_prj/test.c";

    char *path1="/home/zhf/c_prj/tmpfifo";

    infd = open(path,O_RDONLY);

    if(infd == -1){

        perror("open error");

        exit(EXIT_FAILURE);

    }

 

    if(mkfifo(path1,0644) == -1){

        perror("mkfifo error");

        exit(EXIT_FAILURE);

    }

    fd = open(path1,O_WRONLY);

    if(fd == -1){

        perror("open fifo error");

        exit(EXIT_FAILURE);

    }

    while((n = read(infd,buf,1024*4))){

        write(fd,buf,n);

    }

    close(infd);

    close(fd);

    printf("write success\n");

    return 0;

}

read的函数:

#include <stdio.h>

#include <unistd.h>

#include <stdlib.h>

#include <sys/stat.h>

#include <sys/types.h>

#include <fcntl.h>

 

int main(int argc, char **argv)

{

    int outfd;

    int fd;

    char buf[1024*4];

    int n = 0;

    char *path="/home/zhf/c_prj/tmp.txt";

    char *path1="/home/zhf/c_prj/tmpfifo";

    outfd = open(path,O_WRONLY | O_CREAT | O_TRUNC);

    if(outfd == -1){

        perror("open error");

        exit(EXIT_FAILURE);

    }

    fd = open(path1,O_RDONLY);

    if(fd == -1){

        perror("open fifo error");

        exit(EXIT_FAILURE);

    }

    while((n = read(fd,buf,1024*4))){

        write(outfd,buf,n);

    }

    close(outfd);

    close(fd);

    printf("read success\n");

    return 0;

}

运行步骤:

首先在write函数中打开/home/zhf/c_prj/test.c文件并且建立tmpfifo文件。以写的方式打开FIFO文件

test.c中读取数据存入buf数组中,并继续写入tmpfifo文件中

read函数中打开/home/zhf/c_prj/tmp.txt以及tmpfifo文件,以读的方式打开FIFO文件。并将FIFO文件的数据写入tmp.txt

运行结果

 

posted @ 2018-10-27 19:43  red_leaf_412  阅读(3451)  评论(0编辑  收藏  举报