【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的詳細內容參考: