linux下使用FIFO進行程序間通訊
無名管道應用的一個重大限制是它沒有名字,因此,只能用於具有親緣關係的程序間通訊,在有名管道(named pipe或FIFO)提出後,該限制得到了克服。FIFO不同於管道之處在於它提供一個路徑名與之關聯,以FIFO的檔案形式存在於檔案系統中。這樣,即使與FIFO的建立程序不存在親緣關係的程序,只要可以訪問該路徑,就能夠彼此通過FIFO相互通訊(能夠訪問該路徑的程序以及FIFO的建立程序之間),因此,通過FIFO不相關的程序也能交換資料。值得注意的是,FIFO嚴格遵循先進先出(first in first out),對管道及FIFO的讀總是從開始處返回資料,對它們的寫則把資料新增到末尾。它們不支援諸如lseek()等檔案定位操作。
管道的緩衝區是有限的
管道所傳送的是無格式位元組流,這就要求管道的讀出方和寫入方必須事先約定好資料的格式,比如多少位元組算作一個訊息(或命令、或記錄)等等
FIFO的開啟規則:
- 如果當前開啟操作是為讀而開啟FIFO時,若已經有相應程序為寫而開啟該FIFO,則當前開啟操作將成功返回;否則,可能阻塞直到有相應程序為寫而開啟該FIFO(當前開啟操作設定了阻塞標誌);或者,成功返回(當前開啟操作沒有設定阻塞標誌)。
- 如果當前開啟操作是為寫而開啟FIFO時,如果已經有相應程序為讀而開啟該FIFO,則當前開啟操作將成功返回;否則,可能阻塞直到有相應程序為讀而開啟該FIFO(當前開啟操作設定了阻塞標誌);或者,返回ENXIO錯誤(當前開啟操作沒有設定阻塞標誌)。
總之就是一句話,一旦設定了阻塞標誌,呼叫mkfifo建立好之後,那麼管道的兩端讀寫必須分別開啟,有任何一方未開啟,則在呼叫open的時候就阻塞。
從FIFO中讀取資料:
約定:如果一個程序為了從FIFO中讀取資料而阻塞開啟FIFO,那麼稱該程序內的讀操作為設定了阻塞標誌的讀操作。(意思就是我現在要開啟一個有名管道來讀資料!)
如果有程序寫開啟FIFO,且當前FIFO內沒有資料(可以理解為管道的兩端都建立好了,但是寫端還沒開始寫資料!)
- 則對於設定了阻塞標誌的讀操作來說,將一直阻塞(就是block住了,等待資料。它並不消耗CPU資源,這種程序的同步方式對CPU而言是非常有效率的。)
- 對於沒有設定阻塞標誌讀操作來說則返回-1,當前errno值為EAGAIN,提醒以後再試。
對於設定了阻塞標誌的讀操作說(見上面的約定)
造成阻塞的原因有兩種
- FIFO內有資料,但有其它程序在讀這些資料(對於各個讀程序而言,這根有名管道是臨界資源,大家得互相謙讓,不能一起用。)
- FIFO內沒有資料。解阻塞的原因則是FIFO中有新的資料寫入,不論信寫入資料量的大小,也不論讀操作請求多少資料量。
讀開啟的阻塞標誌只對本程序第一個讀操作施加作用,如果本程序內有多個讀操作序列,則在第一個讀操作被喚醒並完成讀操作後,其它將要執行的讀操作將不再阻塞,即使在執行讀操作時,FIFO中沒有資料也一樣,此時,讀操作返回0。
注:如果FIFO中有資料,則設定了阻塞標誌的讀操作不會因為FIFO中的位元組數小於請求讀的位元組數而阻塞,此時,讀操作會返回FIFO中現有的資料量。
向FIFO中寫入資料:
約定:如果一個程序為了向FIFO中寫入資料而阻塞開啟FIFO,那麼稱該程序內的寫操作為設定了阻塞標誌的寫操作。(意思就是我現在要開啟一個有名管道來寫資料!)
對於設定了阻塞標誌的寫操作:
- 當要寫入的資料量不大於PIPE_BUF時,linux將保證寫入的原子性。如果此時管道空閒緩衝區不足以容納要寫入的位元組數,則進入睡眠,直到當緩衝區中能夠容納要寫入的位元組數時,才開始進行一次性寫操作。(PIPE_BUF ==>> /usr/include/linux/limits.h)
- 當要寫入的資料量大於PIPE_BUF時,linux將不再保證寫入的原子性。FIFO緩衝區一有空閒區域,寫程序就會試圖向管道寫入資料,寫操作在寫完所有請求寫的資料後返回。
對於沒有設定阻塞標誌的寫操作:
- 當要寫入的資料量大於PIPE_BUF時,linux將不再保證寫入的原子性。在寫滿所有FIFO空閒緩衝區後,寫操作返回。
- 當要寫入的資料量不大於PIPE_BUF時,linux將保證寫入的原子性。如果當前FIFO空閒緩衝區能夠容納請求寫入的位元組數,寫完後成功返回;如果當前FIFO空閒緩衝區不能夠容納請求寫入的位元組數,則返回EAGAIN錯誤,提醒以後再寫;
簡單描述下上面設定了阻塞標誌的邏輯
設定了阻塞標誌
if (buf_to_write <= PIPE_BUF) //寫入的資料量不大於PIPE_BUF時 then if ( buf_to_write > system_buf_left ) //保證寫入的原子性,要麼一次性把buf_to_write全都寫完,要麼一個位元組都不寫! then block ; until ( buf_to_write <= system_buf_left ); goto write ; else write ; fi else write ; //不管怎樣,就是不斷寫,知道把緩衝區寫滿了才阻塞 fi
管道寫端 pipe_read.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
//pipe_read.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; 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; printf("%d\n",bytes); }while(res > 0); close(pipe_fd); } else { exit(EXIT_FAILURE); } printf("Process %d finished, %d bytes read\n", getpid(), bytes); exit(EXIT_SUCCESS); } |
管道讀端 pipe_write.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
//pipe_write.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 #define TEN_MEG (1024 * 100) int main() { int pipe_fd; int res; int open_mode = O_WRONLY; int bytes = 0; char buffer[BUFFER_SIZE + 1]; 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()); pipe_fd = open(FIFO_NAME, open_mode); printf("Process %d result %d\n", getpid(), pipe_fd); //sleep(20); if (pipe_fd != -1) { while (bytes < TEN_MEG) { res = write(pipe_fd, buffer, BUFFER_SIZE); if (res == -1) { fprintf(stderr, "Write error on pipe\n"); exit(EXIT_FAILURE); } bytes += res; printf("%d\n",bytes); } close(pipe_fd); } else { exit(EXIT_FAILURE); } printf("Process %d finish\n", getpid()); exit(EXIT_SUCCESS); } |