天天看點

深入了解Linux程序間通信(IPC)-- FIFO

1.2. 有名管道概述及相關API應用

1.2.1 有名管道相關的關鍵概念

管道應用的一個重大限制是它沒有名字,是以,隻能用于具有親緣關系的程序間通信,在有名管道(named pipe或FIFO)提出後,該限制得到了克服。FIFO不同于管道之處在于它提供一個路徑名與之關聯,以FIFO的檔案形式存在于檔案系統中。這樣,即使與FIFO的建立程序不存在親緣關系的程序,隻要可以通路該路徑,就能夠彼此通過FIFO互相通信(能夠通路該路徑的程序以及FIFO的建立程序之間),是以,通過FIFO不相關的程序也能交換資料。值得注意的是,FIFO嚴格遵循先進先出(first in first out),對管道及FIFO的讀總是從開始處傳回資料,對它們的寫則把資料添加到末尾。它們不支援諸如lseek()等檔案定位操作。

1.2.2有名管道的建立

#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,如close、read、write等等。

1.2.3有名管道的打開規則

有名管道比管道多了一個打開操作:open。

FIFO的打開規則:

如果目前打開操作是為讀而打開FIFO時,若已經有相應程序為寫而打開該FIFO,則目前打開操作将成功傳回;否則,可能阻塞直到有相應程序為寫而打開該FIFO(目前打開操作設定了阻塞标志);或者,成功傳回(目前打開操作沒有設定阻塞标志)。

如果目前打開操作是為寫而打開FIFO時,如果已經有相應程序為讀而打開該FIFO,則目前打開操作将成功傳回;否則,可能阻塞直到有相應程序為讀而打開該FIFO(目前打開操作設定了阻塞标志);或者,傳回ENXIO錯誤(目前打開操作沒有設定阻塞标志)。

1.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錯誤,提醒以後再寫;

對FIFO讀寫規則的驗證:

下面提供了兩個對FIFO的讀寫程式,适當調節程式中的很少地方或者程式的指令行參數就可以對各種FIFO讀寫規則進行驗證。

程式1:寫FIFO的程式

#include <sys/types.h>

#include <sys/stat.h>

#include <errno.h>

#include <fcntl.h>

#define FIFO_SERVER "/tmp/fifoserver"

main(int argc,char** argv)

//參數為即将寫入的位元組數

{

int fd;

char w_buf[4096*2];

int real_wnum;

memset(w_buf,0,4096*2);

if((mkfifo(FIFO_SERVER,O_CREAT|O_EXCL)<0)&&(errno!=EEXIST))

printf("cannot create fifoserver\n");

if(fd==-1)

if(errno==ENXIO)

printf("open error; no reading process\n");

fd=open(FIFO_SERVER,O_WRONLY|O_NONBLOCK,0);

//設定非阻塞标志

//fd=open(FIFO_SERVER,O_WRONLY,0);

//設定阻塞标志

real_wnum=write(fd,w_buf,2048);

if(real_wnum==-1)

{

if(errno==EAGAIN)

printf("write to fifo error; try later\n");

}

else

printf("real write num is %d\n",real_wnum);

real_wnum=write(fd,w_buf,5000);

//5000用于測試寫入位元組大于4096時的非原子性

//real_wnum=write(fd,w_buf,4096);

//4096用于測試寫入位元組不大于4096時的原子性

if(real_wnum==-1)

if(errno==EAGAIN)

printf("try later\n");

}

程式2:與程式1一起測試寫FIFO的規則,第一個指令行參數是請求從FIFO讀出的位元組數

#include <sys/types.h>

#include <sys/stat.h>

#include <errno.h>

#include <fcntl.h>

#define FIFO_SERVER "/tmp/fifoserver"

main(int argc,char** argv)

{

char r_buf[4096*2];

int fd;

int r_size;

int ret_size;

r_size=atoi(argv[1]);

printf("requred real read bytes %d\n",r_size);

memset(r_buf,0,sizeof(r_buf));

fd=open(FIFO_SERVER,O_RDONLY|O_NONBLOCK,0);

//fd=open(FIFO_SERVER,O_RDONLY,0);

//在此處可以把讀程式編譯成兩個不同版本:阻塞版本及非阻塞版本

if(fd==-1)

{

printf("open %s for read error\n");

exit();

}

while(1)

{

memset(r_buf,0,sizeof(r_buf));

ret_size=read(fd,r_buf,r_size);

if(ret_size==-1)

if(errno==EAGAIN)

printf("no data avlaible\n");

printf("real read bytes %d\n",ret_size);

sleep(1);

}

pause();

unlink(FIFO_SERVER);

}

程式應用說明:

把讀程式編譯成兩個不同版本:

  • 阻塞讀版本:br
  • 以及非阻塞讀版本nbr

把寫程式編譯成兩個四個版本:

  • 非阻塞且請求寫的位元組數大于PIPE_BUF版本:nbwg
  • 非阻塞且請求寫的位元組數不大于PIPE_BUF版本:版本nbw
  • 阻塞且請求寫的位元組數大于PIPE_BUF版本:bwg
  • 阻塞且請求寫的位元組數不大于PIPE_BUF版本:版本bw

下面将使用br、nbr、w代替相應程式中的阻塞讀、非阻塞讀

驗證阻塞寫操作:

  1. 當請求寫入的資料量大于PIPE_BUF時的非原子性:
    • nbr 1000
    • bwg
  2. 當請求寫入的資料量不大于PIPE_BUF時的原子性:
    • nbr 1000
    • bw

驗證非阻塞寫操作:

  1. 當請求寫入的資料量大于PIPE_BUF時的非原子性:
    • nbr 1000
    • nbwg
  2. 請求寫入的資料量不大于PIPE_BUF時的原子性:
    • nbr 1000
    • nbw

不管寫打開的阻塞标志是否設定,在請求寫入的位元組數大于4096時,都不保證寫入的原子性。但二者有本質差別:

對于阻塞寫來說,寫操作在寫滿FIFO的空閑區域後,會一直等待,直到寫完所有資料為止,請求寫入的資料最終都會寫入FIFO;

而非阻塞寫則在寫滿FIFO的空閑區域後,就傳回(實際寫入的位元組數),是以有些資料最終不能夠寫入。

對于讀操作的驗證則比較簡單,不再讨論。

1.2.5有名管道應用執行個體

在驗證了相應的讀寫規則後,應用執行個體似乎就沒有必要了。

1.3.小結

管道常用于兩個方面:(1)在shell中時常會用到管道(作為輸入輸入的重定向),在這種應用方式下,管道的建立對于使用者來說是透明的;(2)用于具有親緣關系的程序間通信,使用者自己建立管道,并完成讀寫操作。

FIFO可以說是管道的推廣,克服了管道無名字的限制,使得無親緣關系的程序同樣可以采用先進先出的通信機制進行通信。

管道和FIFO的資料是位元組流,應用程式之間必須事先确定特定的傳輸"協定",采用傳播具有特定意義的消息。

要靈活應用管道及FIFO,了解它們的讀寫規則是關鍵。

附1:kill -l 的運作結果,顯示了目前系統支援的所有信号:

1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL

5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE

9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2

13) SIGPIPE 14) SIGALRM 15) SIGTERM 17) SIGCHLD

18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN

22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ

26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO

30) SIGPWR 31) SIGSYS 32) SIGRTMIN 33) SIGRTMIN+1

34) SIGRTMIN+2 35) SIGRTMIN+3 36) SIGRTMIN+4 37) SIGRTMIN+5

38) SIGRTMIN+6 39) SIGRTMIN+7 40) SIGRTMIN+8 41) SIGRTMIN+9

42) SIGRTMIN+10 43) SIGRTMIN+11 44) SIGRTMIN+12 45) SIGRTMIN+13

46) SIGRTMIN+14 47) SIGRTMIN+15 48) SIGRTMAX-15 49) SIGRTMAX-14

50) SIGRTMAX-13 51) SIGRTMAX-12 52) SIGRTMAX-11 53) SIGRTMAX-10

54) SIGRTMAX-9 55) SIGRTMAX-8 56) SIGRTMAX-7 57) SIGRTMAX-6

58) SIGRTMAX-5 59) SIGRTMAX-4 60) SIGRTMAX-3 61) SIGRTMAX-2

62) SIGRTMAX-1 63) SIGRTMAX

除了在此處用來說明管道應用外,接下來的專題還要對這些信号分類讨論。

附2:對FIFO打開規則的驗證(主要驗證寫打開對讀打開的依賴性)

#include <sys/types.h>

#include <sys/stat.h>

#include <errno.h>

#include <fcntl.h>

#define FIFO_SERVER "/tmp/fifoserver"

int handle_client(char*);

main(int argc,char** argv)

{

int r_rd;

int w_fd;

pid_t pid;

if((mkfifo(FIFO_SERVER,O_CREAT|O_EXCL)<0)&&(errno!=EEXIST))

printf("cannot create fifoserver\n");

handle_client(FIFO_SERVER);

}

int handle_client(char* arg)

{

int ret;

ret=w_open(arg);

switch(ret)

{

case 0:

{

printf("open %s error\n",arg);

printf("no process has the fifo open for reading\n");

return -1;

}

case -1:

{

printf("something wrong with open the fifo except for ENXIO");

return -1;

}

case 1:

{

printf("open server ok\n");

return 1;

}

default:

{

printf("w_no_r return ----\n");

return 0;

}

}

unlink(FIFO_SERVER);

}

int w_open(char*arg)

//0 open error for no reading

//-1 open error for other reasons

//1 open ok

{

if(open(arg,O_WRONLY|O_NONBLOCK,0)==-1)

{ if(errno==ENXIO)

{

return 0;

}

else

return -1;

}

return 1;

}

1.4. 參考資料

  • UNIX網絡程式設計第二卷:程序間通信,作者:W.Richard Stevens,譯者:楊繼張,清華大學出版社。豐富的UNIX程序間通信執行個體及分析,對Linux環境下的程式開發有極大的啟發意義。
  • linux核心源代碼情景分析(上、下),毛德操、胡希明著,浙江大學出版社,當要驗證某個結論、想法時,最好的參考資料;
  • UNIX環境進階程式設計,作者:W.Richard Stevens,譯者:尤晉元等,機械工業出版社。具有豐富的程式設計執行個體,以及關鍵函數伴随Unix的發展曆程。
  • http://www.linux.org.tw/CLDP/gb/Secure-Programs-HOWTO/x346.html 點明linux下sigaction的實作基礎,linux源碼../kernel/signal.c更說明了問題;
  • pipe手冊,最直接而可靠的參考資料
  • fifo手冊,最直接而可靠的參考資料

繼續閱讀