1. 程式人生 > >【Linux】程序間通訊-命名管道FIFO

【Linux】程序間通訊-命名管道FIFO

命名管道概述

如果我們要在不相關的程序間交換資料,那麼使用FIFO檔案將會十分方便。

FIFO檔案通常也稱為命名管道(named pipe)。命名管道是一種特殊型別的檔案,它在檔案系統中以檔名的形式存在。

建立命名管道

建立命名管道一般有兩種方式:

命令列方式

一個比較舊的方式是:

mknod filename p

這個命令並未出現在X/Open規範的命令列表中,所以可能並不是所有的類Unix系統都可以這樣做。

推薦的做法是:

mkfifo filename

函式呼叫方式

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
int mknod(const char *pathname, mode_t mode | S_FIFO, (dev_t)0);
函式說明
    mkfifo()會依引數pathname建立特殊的FIFO檔案,該檔案必須不存在,而引數mode為該檔案的許可權(mode%~umask),因此 umask值也會影響到FIFO檔案的許可權。Mkfifo()建立的FIFO檔案其他程序都可以用讀寫一般檔案的方式存取。當使用open()來開啟 FIFO檔案時,O_NONBLOCK旗標會有影響
    1、當使用O_NONBLOCK 旗標時,開啟FIFO 檔案來讀取的操作會立刻返回,但是若還沒有其他程序開啟FIFO 檔案來讀取,則寫入的操作會返回ENXIO 錯誤程式碼。
    2、沒有使用O_NONBLOCK 旗標時,開啟FIFO 來讀取的操作會等到其他程序開啟FIFO檔案來寫入才正常返回。同樣地,開啟FIFO檔案來寫入的操作會等到其他程序開啟FIFO 檔案來讀取後才正常返回。
返回值
    若成功則返回0,否則返回-1,錯誤原因存於errno中。
錯誤程式碼
    EACCESS 引數pathname所指定的目錄路徑無可執行的許可權
    EEXIST 引數pathname所指定的檔案已存在。
    ENAMETOOLONG 引數pathname的路徑名稱太長。
    ENOENT 引數pathname包含的目錄不存在
    ENOSPC 檔案系統的剩餘空間不足
    ENOTDIR 引數pathname路徑中的目錄存在但卻非真正的目錄。
    EROFS 引數pathname指定的檔案存在於只讀檔案系統內。

程式碼演示

下面程式碼演示了mkfifo函式的用法:

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>

int main()
{
    int res = mkfifo("~/Test/PipeTest/my_fifo",0777);
    if(!res)
	printf("FIFO created\n");

    exit(EXIT_SUCCESS);
}
執行結果:


linux中ls命令的-F選項是列出檔案的型別。

訪問FIFO檔案

同樣有兩種方式訪問FIFO檔案。

命令列方式

首先用cat命令讀取剛才建立的FIFO檔案:

cat < /tmp/my_fifo

這個時候,cat命令將一直掛起,直到終端或者有資料傳送到FIFO中。

然後嘗試向FIFO中寫資料(在另外一個終端執行這個命令)

echo "FIFO test" > /tmp/my_fifo

這個時候cat將會輸出內容。

函式呼叫方式

首先需要注意的是:

與通過pipe呼叫建立管道不同,FIFO是以命名檔案的形式存在,而不是開啟的檔案描述符,所以在對它進行讀寫操作之前必須先開啟它。

1、使用open函式開啟FIFO檔案

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);

開啟FIFO檔案和普通檔案的區別有2點:

第一個是不能以O_RDWR模式開啟FIFO檔案進行讀寫操作。這樣做的行為是未定義的。

因為我們通常使用FIFO只是為了單向傳遞資料,所以沒有必要使用這個模式。

如果確實需要在程式之間雙向傳遞資料,最好使用一對FIFO或管道,一個方向使用一個。或者採用先關閉在重新開啟FIFO的方法來明確改變資料流的方向。

第二是對標誌位的O_NONBLOCK選項的用法。

使用這個選項不僅改變open呼叫的處理方式,還會改變對這次open呼叫返回的檔案描述符進行的讀寫請求的處理方式。

O_RDONLY、O_WRONLY和O_NONBLOCK標誌共有四種合法的組合方式:

  • flags=O_RDONLY:open將會呼叫阻塞,除非有另外一個程序以寫的方式開啟同一個FIFO,否則一直等待。
  • flags=O_WRONLY:open將會呼叫阻塞,除非有另外一個程序以讀的方式開啟同一個FIFO,否則一直等待。
  • flags=O_RDONLY|O_NONBLOCK:如果此時沒有其他程序以寫的方式開啟FIFO,此時open也會成功返回,此時FIFO被讀開啟,而不會返回錯誤。
  • flags=O_WRONLY|O_NONBLOCK:立即返回,如果此時沒有其他程序以讀的方式開啟,open會失敗開啟,此時FIFO沒有被開啟,返回-1。

測試程式碼:

#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>

#define FIFO_NAME "/tmp/my_fifo"

int main(int argc,char *argv[])
{
        int res,i;
        int open_mode=0;
        if(argc < 2){
                fprintf(stderr,"Usage:%s<some combination of \
                        O_RDONLY,O_WRONLY,O_NONBLOCK\n",*argv);
                exit(EXIT_FAILURE);
        }
        argv++;
        if(strncmp(*argv,"O_RDONLY",8)==0)open_mode|=O_RDONLY;
        if(strncmp(*argv,"O_WRONLY",8)==0)open_mode|=O_WRONLY;
        if(strncmp(*argv,"O_NONBLOCK",10)==0)open_mode|=O_NONBLOCK;
       
	for(i = 1;i < argc;++i)
	{
       		argv++;
        	if(*argv){
                	if(strncmp(*argv,"O_RDONLY",8)==0)open_mode|=O_RDONLY;
                	if(strncmp(*argv,"O_WRONLY",8)==0)open_mode|=O_WRONLY;
                	if(strncmp(*argv,"O_NONBLOCK",10)==0)open_mode|=O_NONBLOCK;
        	}
	}
        if(access(FIFO_NAME,F_OK)==-1){
                res=mkfifo(FIFO_NAME,0777);
                if(res!=0){
                        fprintf(stderr,"Could not create fifo %s\n",FIFO_NAME);
                        exit(EXIT_FAILURE);
                }
        }
        printf("process %d open FIFO with %d\n",getpid(),open_mode);
        res=open(FIFO_NAME,open_mode);
        printf("process %d result %d\n",getpid(),res);
        sleep(5);
        if(res!=-1)close(res);
        printf("process %d finished\n",getpid());
        exit(EXIT_SUCCESS);
}

執行結果:

對FIFO檔案進行讀寫操作

open函式呼叫中的引數標誌O_NONBLOCK會影響FIFO的讀寫操作。

規則如下:

  • 對一個空的阻塞的FIFO的read呼叫將等待,直到有資料可以讀的時候才繼續執行/
  • 對一個空的非阻塞的FIFO的read呼叫立即返回0位元組。
  • 對一個完全阻塞的FIFO的write呼叫將等待,直到資料可以被寫入時才開始執行。
    • 系統規定:如果寫入的資料長度小於等於PIPE_BUF位元組,那麼或者寫入全部位元組,要麼一個位元組都不寫入。
注意這個限制的作用:
當只使用一個FIF並允許多個不同的程式向一個FIFO讀程序傳送請求的時候,為了保證來自不同程式的資料塊 不相互交錯,即每個操作都原子化,這個限制就很重要了。如果能夠包子所有的寫請求是發往一個阻塞的FIFO的,並且每個寫請求的資料長父小於等於PIPE_BUF位元組,系統就可以確保資料絕不會交錯在一起。通常將每次通過FIFO傳遞的資料長度限制為PIPE_BUF是一個好辦法。
  • 在非阻塞的write呼叫情況下,如果FIFO 不能接收所有寫入的資料,將按照下面的規則進行:
    • 請求寫入的資料的長度小於PIPE_BUF位元組,呼叫失敗,資料不能被寫入。
    • 請求寫入的資料的長度大於PIPE_BUF位元組,將寫入部分資料,返回實際寫入的位元組數,返回值也可能是0。

其中。PIPE_BUF是FIFO的長度,它在標頭檔案limits.h中被定義。在linux或其他類UNIX系統中,它的值通常是4096位元組。

在這裡需要注意的是:

有兩個程序去訪問FIFO管道時,Linux會安排好兩個程序之間的排程,使得兩個程序在可以執行的時候執行,在不能執行的時候阻塞。

程式例項

下面的程式用命名管道在兩個獨立的程序之間通訊,模擬了消費者和生產者程式。

生產者程式fork3.c:

//生產者程式
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>

#define FIFO_NAME "/tmp/my_fifo"
#define BUFFER_SIZE PIPE_BUF
#define TEN_MSG (1024 * 1024 * 10)

int main()
{
    int pipe_fd;
    int res;
    int open_mode = O_WRONLY;
    int bytes_sent = 0;
    char buffer[BUFFER_SIZE + 1];

    printf("Productor Program beginning...\n");

    //檢查FIFO檔案是否存在
    if(access(FIFO_NAME, F_OK) == -1){
        res = mkfifo(FIFO_NAME, 0777);
        if(res != 0){
            fprintf(stderr, "Could not create fifo %s\n", FIFO_NAME);
            exit(EXIT_FAILURE);
        }
    }
    printf("Process %d opening FIFO O_WRONLY\n", getpid());
    //開啟FIFO檔案
    pipe_fd = open(FIFO_NAME, open_mode);
    printf("Process %d result %d\n", getpid(), pipe_fd);

    if(pipe_fd != -1){
        while(bytes_sent < TEN_MSG){
            res = write(pipe_fd, buffer, BUFFER_SIZE); //向FIFO寫入資料
            if(res == -1){
                fprintf(stderr, "Write error on pipe\n");
                exit(EXIT_FAILURE);
            }
            bytes_sent += res;
        }
        (void)close(pipe_fd);
    }
    else{
        exit(EXIT_FAILURE);
    }
    
    printf("Process %d finished\n", getpid());
    exit(EXIT_SUCCESS);
}

消費者程式fork4.c:
//消費者程式

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>

#define FIFO_NAME "/tmp/my_fifo"
#define BUFFER_SIZE PIPE_BUF

int main()
{
    int pipe_fd;
    int res;

    int open_mode = O_RDONLY;
    char buffer[BUFFER_SIZE + 1];
    int bytes = 0;

    printf("COnsumer Program beginning...");
    memset(buffer,'\0', sizeof(buffer));

    printf("Process %d opeining FIFO O_RDONLY\n", getpid());
    pipe_fd = open(FIFO_NAME, open_mode);
    printf("Process %d result %d\n", getpid(), pipe_fd);

    if (pipe_fd != -1)
    {
        do{
            res = read(pipe_fd, buffer, BUFFER_SIZE);
            bytes += res;
        }while(res > 0);
        close(pipe_fd);
    }
    else
    {
        exit(EXIT_FAILURE);
    }

    printf("Process %d finished, %d bytes read\n", getpid(), bytes);
    exit(EXIT_SUCCESS);
}

執行結果:


可以發現讀程序只運行了不到0.1S的時間,卻讀取了10MB的資料。這說明管道在程式之間傳遞資料是很有效率的。

刪除FIFO檔案

FIFO檔案使用完畢之後需刪除,以免造成垃圾檔案。

#include <unistd.h>
int unlink(const char *pathname);

關於unlink的詳細內容參考: