进程间的通信(二)命名管道fifo

 

在linux下我们先通过一个实例来说明一下mkfifo 的作用, 然后再看mkfifo 的使用详解

# mkfifo myfifo
# ping
edu.cn >> myfifo

另开一个终端:
# cat myfifo

看到效果了吧

mkfifo 命令

用途

制作先进先出(FIFO)特别文件。
语法

mkfifo [ -m Mode ] File …
描述

根据指定的顺序,mkfifo 命令创建由 File 参数指定的 FIFO 特别文件。如果没有指定 -m Mode 标志,则 FIFO 文件的文件方式是通过文件方式创建所修改的包含 OR 的 S_IRUSR、S_IWUSR、S_IRGRP、S_IWGRP、S_IROTH 和 S_IWOTH 许可权的一位宽度(请参阅 umask 命令)。

mkfifo 命令与 mkfifo 子例程运行相似。
标志

-m Mode     设置新创建的 FIFO 文件的文件许可权位的值为指定的方式值。 Mode 变量与为 chmod 命令定义的方式操作数相同。如果使用了字符 +(加号)和 -(减号),则它们是相对于初始值 a=rw 来解释的(即具有许可权 rw-rw-rw-)。
退出状态

这条命令返回以下退出值:
0     成功创建所有指定的 FIFO 特别文件。
>0     发生错误。
示例

   1. 要使用许可权 prw-r–r– 创建 FIFO 特别文件,请输入:
      mkfifo  -m 644 /tmp/myfifo
      此命令使用所有者的读/写许可权以及组和其他用户的读许可权来创建 /tmp/myfifo 文件。
   2. 使用 -(减号)操作符创建一个 FIFO 特别文件以设置 prw-r—– 许可权,请输入:
      mkfifo  -m g-w,o-rw /tmp/fifo2
      此命令创建 /tmp/fifo2 文件,删除组的写权限和其他用户的所有许可权。

          注:如果多于一个的文件是用 -(减号)操作符创建的,那么用顿号分隔每个方式说明符,中间不用空格。

文件

/usr/bin/mkfifo     包含 mkfifo 命令。

 

 

Linux下进程间通信:命名管道-mkfifo

由 lgb 于 星期日, 2010/09/12 - 21:24 发表 IPC Linux mkfifo mknode 命名管道 进程间通信

摘要:进程间通信的方法有很多,FIFO与管道是最古老,也是相对来说更简单的一个通信机制。FIFO相对管道有一个优势,就是FIFO只要求两个进程是同一主机的,而不要求进程之间存在亲缘关系。FIFO是存在于文件系统的文件,可以使用诸如open、read、write等函数来操作。本文总结网络和APUE关于FIFO讨论,同时参考了Linux系统手册。

目录  [隐藏]
FIFO(命名管道)概述 mkfifo函数 命名管道读写规则 从FIFO中读取数据 从FIFO中写入数据 FIFO示例

FIFO(命名管道)概述

FIFO是一种进程通信机制,它突破通常管道无法进行无关进程之间的通信的限制,使得同一主机内的所有的进程都可以通信。FIFO是一个文件类型,stat结构中st_mode指明一个文件结点是不是一个FIFO,可以使用宏S_ISFIFO来测试这一点。

当一个FIFO存在于文件系统里时,我们只需要在想进行通信的进程内打开这个文件就可以了。当然FIFO作为一个特殊的文件,它有一些不同普通文件特性,下面会详细详述它的读写规则,这些相对精通文件来有一定的区别。

我们可以使用open、read、write来操作FIFO文件,从而实现进程间通信的目的。在shell环境下,也可以直接使用FIFO,这时往往与重写向有一些关联,一般系统都提供mkfifo实用程序来创建一个FIFO文件,这个程序实际上使用mkfifo系统调用来完成这个事。

mkfifo函数

mkfifo创建一个指定名字的FIFO,它的函数原型如下:

#include
int  mkfifo ( const   char *  pathname, mode_t mode ) ;
返回值:成功, 0;失败, - 1

参数pathname指出想要创建的FIFO路径,参数mode指定创建的FIFO访问模式。这个访问会与当前进程的umask进程运算,以产生实际应用的权限模式。

mkfifo返回-1时表示创建过程中遇到某种错误,此时会设置errno,用户可以检测errno来取得进一步信息:

EACCES: 路径所在的目录不允许执行权限EEXIST:路径已经存在,这时包括路径是一个符号链接,无论它是悬空还没有悬空。ENAMETOOLONG:要么全部的文件名大于PATH_MAX,要么是单独的文件名大于NAME_MAX。在GNU系统里没有这个文件名长度的限制,但在其它系统里可能存在。ENOENT:目录部分不存在,或者是一个悬空链接。ENOTDIR:目录部分不一个目录。EROFS:路径指向一个只读的文件系统。

命名管道读写规则

FIFO又叫命名管道,事实上它与管道确实在下许多相似之处,下面关于规则的讨论很体现这个相似。

从FIFO中读取数据

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

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

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

FIFO的长度是需要考虑的一个很重要因素。系统对任一时刻在一个FIFO中可以存在的数据长度是有限制的。它由#define PIPE_BUF定义,在头文件limits.h中。在Linux和许多其他类UNIX系统中,它的值通常是4096字节,Red Hat Fedora9下是4096,但在某些系统中它可能会小到512字节。

虽然对于只有一个FIFO写进程和一个FIFO的读进程而言,这个限制并不重要,但只使用一个FIFO并允许多个不同进程向一个FIFO读进程发送请求的情况是很常见的。如果几个不同的程序尝试同时向FIFO写数据,能否保证来自不同程序的数据块不相互交错就非常关键了à也就是说,每个写操作必须“原子化”。

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

当要写入的数据量不大于PIPE_BUF时,Linux将保证写入的原子性。如果此时管道空闲缓冲区不足以容纳要写入的字节数,则进入睡眠,直到当缓冲区中能够容纳要写入的字节数时,才开始进行一次性写操作。即写入的数据长度小于等于PIPE_BUF时,那么或者写入全部字节,或者一个字节都不写入,它属于一个一次性行为,具体要看FIFO中是否有足够的缓冲区。当要写入的数据量大于PIPE_BUF时,Linux将不再保证写入的原子性。FIFO缓冲区一有空闲区域,写进程就会试图向管道写入数据,写操作在写完所有请求写的数据后返回。

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

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

本段给出使用FIFO一个示例,它体现了两个使用FIFO的典型情景。

创建FIFO

#include
#include
#include
#include
int  main ( )
{
          int  res   =  mkfifo ( "/tmp/my_fifo", 0777 ) ;
          if (res   ==  0 )
          {
                  printf ( "FIFO created\n" ) ;
          }
          exit ( EXIT_SUCCESS ) ;
}

使用FIFO

#include
#include
#include

FIFO   "/tmp/my_fifo"
//本程序从一个FIFO读数据,并把读到的数据打印到标准输出
//如果读到字符“Q”,则退出
int  main ( int  argc,   char **  argv )
{
          char  buf_r [100 ] ;
          int  fd ;
          int  nread ;
          if ( (mkfifo (FIFO, O_CREAT )   <  0 )   &&   ( errno   ! =  EEXIST ) )
          {
                  printf ( "不能创建FIFO\n" ) ;
                  exit (1 ) ;
          }

          printf ( "准备读取数据\n" ) ;
        fd   =  open (FIFO, O_RDONLY, 0 ) ;
          if (fd   ==   -1 )
          {
                  perror ( "打开FIFO" ) ;
                  exit (1 ) ;
          }

          while (1 )
          {
                  if ( (nread   =  read (fd, buf_r, 100 ) )   ==   -1 )
                  {
                          if ( errno   ==  EAGAIN )   printf ( "没有数据\n" ) ;
                  }

                  //假设取到Q的时候退出
                  if (buf_r [ 0 ] == 'Q' )   break ;

                buf_r [nread ] = 0 ;
                  printf ( "从FIFO读取的数据为:%s\n", buf_r ) ;
                sleep (1 ) ;
          }

}
 
 
管道没有名字,所以只能在具有血缘关系的进程间使用,而在无名管道发展出来的有名管道FIFO,则有路径名与之相关联,以一种特殊设备文件形式存在于文件系统中,从而允许无亲缘关系的进程访问FIFO,下面看FIFO的详细操作

1.FIFO的建立

FIFO是存在于文件系统的文件节点,所以我们可以建立文件节点的mknod系统用来建立它,也可以mkfifo系统调用

mkfifo说明:

#include

#include

int mkfifo(char *path,mode_t mode);

说明:path:路径名,mode:指定文件存取权标志,mkfifo()会依参数pathname建立特殊的FIFO文件,该文件必须不存在,系统调用已经指定O_CREATE|O_EXCL

返回:若成功则返回0,否则返回-1,错误原因存于errno中。
错误代码
EACCESS 参数pathname所指定的目录路径无可执行的权限
EEXIST 参数pathname所指定的文件已存在。
ENAMETOOLONG 参数pathname的路径名称太长。
ENOENT 参数pathname包含的目录不存在
ENOSPC 文件系统的剩余空间不足
ENOTDIR 参数pathname路径中的目录存在但却非真正的目录。
EROFS 参数pathname指定的文件存在于只读文件系统内。

 

2.FIFO使用

创建后,在读写前,要先打开它,用open系统调用

当使用open()来打开 FIFO文件时,O_NONBLOCK旗标会有影响
1、当使用O_NONBLOCK 旗标时,打开FIFO 文件来读取的操作会立刻返回,但是若还没有其他进程打开FIFO 文件来读取,则写入的操作会返回ENXIO 错误代码。
2、没有使用O_NONBLOCK 旗标时,打开FIFO 来读取的操作会等到其他进程打开FIFO文件来写入才正常返回。同样地,打开FIFO文件来写入的操作会等到其他进程打开FIFO 文件来读取后才正常返回。

下面练习,分别写两个程序,一个是服务器程序,不断从管道读取客户发送的信息;另一个是客户程序,在命令行输入信息并从管道发送:

客户程序(写管道)

view plain copy to clipboard print ?
  1. /*fifo_write.c*/  
  2. #include    
  3. #include    
  4. #include    
  5. #include    
  6. #include    
  7. #include    
  8. #include    
  9. /*FIFO管道路径*/  
  10. #define FIFO_SERVER "/tmp/myfifo"   
  11. main(int argc,char** argv)  
  12. {  
  13.     int fd = 0;  
  14.     char w_buf[100];  
  15.     int nwrite;  
  16.     /*打开FIFO管道*/  
  17.     fd=open(FIFO_SERVER,O_WRONLY|O_NONBLOCK,0);  
  18.     if(fd==-1)  
  19.         if(errno==ENXIO)  
  20.             printf("open error; no reading process/n");  
  21.     /*判断有没有参数输入*/  
  22.     if(argc==1)  
  23.         printf("Please send something/n");  
  24.     /*复制参数输入*/  
  25.     strcpy(w_buf,argv[1]);  
  26.     /*写到FIFO去*/  
  27.     if((nwrite=write(fd,w_buf,100))==-1)  
  28.     {  
  29.         if(errno==EAGAIN)  
  30.             printf("The FIFO has not been read yet.Please try later/n");  
  31.     }  
  32.     else   
  33.         /*输出写入的内容*/  
  34.         printf("write %s to the FIFO/n",w_buf);  
  35. }  
/*fifo_write.c*/#include #include #include #include #include #include #include /*FIFO管道路径*/#define FIFO_SERVER "/tmp/myfifo"main(int argc,char** argv){int fd = 0;char w_buf[100];int nwrite;/*打开FIFO管道*/fd=open(FIFO_SERVER,O_WRONLY|O_NONBLOCK,0);if(fd==-1)if(errno==ENXIO)printf("open error; no reading process/n");/*判断有没有参数输入*/if(argc==1)printf("Please send something/n");/*复制参数输入*/strcpy(w_buf,argv[1]);/*写到FIFO去*/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);}

服务程序(读管道)

view plain copy to clipboard print ?
  1. /*fifo_read.c*/  
  2. #include    
  3. #include    
  4. #include    
  5. #include    
  6. #include    
  7. #include    
  8. #include    
  9. /*定义FIFO路径*/  
  10. #define FIFO "/tmp/myfifo"   
  11. main(int argc,char** argv)  
  12. {  
  13.     char buf_r[100];  
  14.     int  fd;  
  15.     int  nread;  
  16.     /*创建FIFO管道*/  
  17.     if((mkfifo(FIFO,O_CREAT|O_EXCL)<0)&&(errno!=EEXIST))  
  18.         printf("cannot create fifoserver/n");  
  19.     printf("Preparing for reading bytes.../n");  
  20.       
  21.     memset(buf_r,0,sizeof(buf_r));  
  22.     /*打开FIFO管道,不阻塞方式*/  
  23.     fd=open(FIFO,O_RDONLY|O_NONBLOCK,0);  
  24.     if(fd==-1)  
  25.     {  
  26.         perror("open");  
  27.         exit(1);      
  28.     }  
  29.     while(1)  
  30.     {  
  31.         memset(buf_r,0,sizeof(buf_r));  
  32.         /*读管道,因为定义了非阻塞方式,故在此不会阻塞进程*/  
  33.         if((nread=read(fd,buf_r,100))==-1){  
  34.             if(errno==EAGAIN)  
  35.                 printf("no data yet/n");  
  36.         }  
  37.         printf("read %s from FIFO/n",buf_r);  
  38.         sleep(1);  
  39.     }     
  40.     pause();  
  41.     unlink(FIFO);  
  42. }  
/*fifo_read.c*/#include #include #include #include #include #include #include /*定义FIFO路径*/#define FIFO "/tmp/myfifo"main(int argc,char** argv){char buf_r[100];int fd;int nread;/*创建FIFO管道*/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));/*打开FIFO管道,不阻塞方式*/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);}

 

接下来进行编译,编译好后,在Linux中运行两个终端,分别运行以上两个程序,可以看到,运行fifo_read时,程序一直在每隔一秒读,然后我们在另一个终端输入:

$ ./fifo_write helloworld

可以看出fifo_read显示出“helloworld”,说明接受成功

 

FIFO的一些注意问题:

(1)管道数据的FIFO处理方式

首先放入管道的数据,在端口首先被读出

(2)管道数据的不可再现性

已读取的数据在管道里消失,不能再读

(3)管道长度的有限性

(4)SIGPIPE信号 如果一个进程试图写入到一个没有读取到进程的管道中哦你,系统内核产生SIGPIPE信号

 
 

你可能感兴趣的:(linux系统编程)