Linux處理序間通訊——使用具名管道

來源:互聯網
上載者:User
在前一篇文章——Linux處理序間通訊——使用匿名管道中,我們看到了如何使用匿名管道來在進程之間傳遞資料,同時也看到了這個方式的一個缺陷,就是這些進程都由一個共同的祖先進程啟動,這給我們在不相關的的進程之間交換資料帶來了不方便。這裡將會介紹進程的另一種通訊方式——具名管道,來解決不相關進程間的通訊問題。一、什麼是具名管道具名管道也被稱為FIFO檔案,它是一種特殊類型的檔案,它在檔案系統中以檔案名稱的形式存在,但是它的行為卻和之前所講的沒有名字的管道(匿名管道)類似。由於Linux中所有的事物都可被視為檔案,所以對具名管道的使用也就變得與檔案操作非常的統一,也使它的使用非常方便,同時我們也可以像平常的檔案名稱一樣在命令中使用。二、建立具名管道我們可以使用兩下函數之一來建立一個具名管道,他們的原型如下:
#include <sys/types.h>#include <sys/stat.h>int mkfifo(const char *filename, mode_t mode);int mknod(const char *filename, mode_t mode | S_IFIFO, (dev_t)0);
這兩個函數都能建立一個FIFO檔案,注意是建立一個真實存在於檔案系統中的檔案,filename指定了檔案名稱,而mode則指定了檔案的讀寫權限。mknod是比較老的函數,而使用mkfifo函數更加簡單和規範,所以建議在可能的情況下,盡量使用mkfifo而不是mknod。三、訪問具名管道
1、開啟FIFO檔案與開啟其他檔案一樣,FIFO檔案也可以使用open調用來開啟。注意,mkfifo函數只是建立一個FIFO檔案,要使用具名管道還是將其開啟。但是有兩點要注意,1、就是程式不能以O_RDWR模式開啟FIFO檔案進行讀寫操作,而其行為也未明確定義,因為如一個管道以讀/寫方式開啟,進程就會讀回自己的輸出,同時我們通常使用FIFO只是為了單向的資料傳遞。2、就是傳遞給open調用的是FIFO的路徑名,而不是正常的檔案。開啟FIFO檔案通常有四種方式,
open(const char *path, O_RDONLY);//1open(const char *path, O_RDONLY | O_NONBLOCK);//2open(const char *path, O_WRONLY);//3open(const char *path, O_WRONLY | O_NONBLOCK);//4
在open函數的調用的第二個參數中,你看到一個陌生的選項O_NONBLOCK,選項O_NONBLOCK表示非阻塞,加上這個選項後,表示open調用是非阻塞的,如果沒有這個選項,則表示open調用是阻塞的。open調用的阻塞是什麼一回事呢?很簡單,對於以唯讀方式(O_RDONLY)開啟的FIFO檔案,如果open調用是阻塞的(即第二個參數為O_RDONLY),除非有一個進程以寫方式開啟同一個FIFO,否則它不會返回;如果open調用是非阻塞的的(即第二個參數為O_RDONLY | O_NONBLOCK),則即使沒有其他進程以寫方式開啟同一個FIFO檔案,open調用將成功並立即返回。對於以唯寫方式(O_WRONLY)開啟的FIFO檔案,如果open調用是阻塞的(即第二個參數為O_WRONLY),open調用將被阻塞,直到有一個進程以唯讀方式開啟同一個FIFO檔案為止;如果open調用是非阻塞的(即第二個參數為O_WRONLY | O_NONBLOCK),open總會立即返回,但如果沒有其他進程以唯讀方式開啟同一個FIFO檔案,open調用將返回-1,並且FIFO也不會被開啟。四、使用FIFO實現進程間的通訊說了這麼多,下面就用一個例子程式來說明一下,兩個進程如何通過FIFO實現通訊吧。這裡有兩個源檔案,一個fifowrite.c,它在需要時建立管道,然後向管道寫入資料,資料由檔案Data.txt提供,大小為10M,內容全是字元‘0’。另一個源檔案為fiforead.c,它從FIFO中讀取資料,並把讀到的資料儲存到另一個檔案DataFormFIFO.txt中。為了讓程式更加簡潔,忽略了有些函數調用是否成功的檢查。fifowrite.c的原始碼如下:
#include <unistd.h>#include <stdlib.h>#include <fcntl.h>#include <limits.h>#include <sys/types.h>#include <sys/stat.h>#include <stdio.h>#include <string.h>int main(){const char *fifo_name = "/tmp/my_fifo";int pipe_fd = -1;int data_fd = -1;int res = 0;const int open_mode = O_WRONLY;int bytes_sent = 0;char buffer[PIPE_BUF + 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());//以唯寫阻塞方式開啟FIFO檔案,以唯讀方式開啟資料檔案pipe_fd = open(fifo_name, open_mode);data_fd = open("Data.txt", O_RDONLY);printf("Process %d result %d\n", getpid(), pipe_fd);if(pipe_fd != -1){int bytes_read = 0;//向資料檔案讀取資料bytes_read = read(data_fd, buffer, PIPE_BUF);buffer[bytes_read] = '\0';while(bytes_read > 0){//向FIFO檔案寫資料res = write(pipe_fd, buffer, bytes_read);if(res == -1){fprintf(stderr, "Write error on pipe\n");exit(EXIT_FAILURE);}//累加寫的位元組數,並繼續讀取資料bytes_sent += res;bytes_read = read(data_fd, buffer, PIPE_BUF);buffer[bytes_read] = '\0';}close(pipe_fd);close(data_fd);}elseexit(EXIT_FAILURE);printf("Process %d finished\n", getpid());exit(EXIT_SUCCESS);}
源檔案fiforead.c的代碼如下:
#include <unistd.h>#include <stdlib.h>#include <stdio.h>#include <fcntl.h>#include <sys/types.h>#include <sys/stat.h>#include <limits.h>#include <string.h>int main(){const char *fifo_name = "/tmp/my_fifo";int pipe_fd = -1;int data_fd = -1;int res = 0;int open_mode = O_RDONLY;char buffer[PIPE_BUF + 1];int bytes_read = 0;int bytes_write = 0;//清空緩衝數組memset(buffer, '\0', sizeof(buffer));printf("Process %d opening FIFO O_RDONLY\n", getpid());//以唯讀阻塞方式開啟管道檔案,注意與fifowrite.c檔案中的FIFO同名pipe_fd = open(fifo_name, open_mode);//以唯寫方式建立儲存資料的檔案data_fd = open("DataFormFIFO.txt", O_WRONLY|O_CREAT, 0644);printf("Process %d result %d\n",getpid(), pipe_fd);if(pipe_fd != -1){do{//讀取FIFO中的資料,並把它儲存在檔案DataFormFIFO.txt檔案中res = read(pipe_fd, buffer, PIPE_BUF);bytes_write = write(data_fd, buffer, res);bytes_read += res;}while(res > 0);close(pipe_fd);close(data_fd);}elseexit(EXIT_FAILURE);printf("Process %d finished, %d bytes read\n", getpid(), bytes_read);exit(EXIT_SUCCESS);}
運行結果如下:分析:兩個程式都使用阻塞模式的FIFO,為了讓大家更清楚地看清楚阻塞究竟是怎麼一回事,首先我們運行fifowrite.exe,並把它放到後台去運行。這時調用jobs命令,可以看到它確實在後台運行著,過了5秒後,再調用jobs命令,可以看到進程fifowrite.exe還沒有結束,它還在繼續運行。因為fifowrite.exe進程的open調用是阻塞的,在fiforead.exe還沒有運行時,也就沒有其他的進程以讀方式開啟同一個FIFO,所以它就一直在等待,open被阻塞,沒有返回。然後,當我們進程fiforead.exe運行時(為了查看效能,在time命令中運行),fifowrite.exe中的open調用返回,進程開始繼續工作,然後結束進程。而fiforead.exe的open調用雖然也是阻塞模式,但是fifowrite.exe早已運行,即早有另一個進程以寫方式開啟同一個FIFO,所以open調用立即返回。從time中的輸出來看,管道的傳遞效率是非常高的,因為fiforead.exe既要讀取資料,還要寫資料到檔案DataFormFIFO.txt中,10M的資料只用了0.1秒多一點。此外,如果此時,你在shell中輸入如下命令,ls -l /tmp/my_fifo,可以看到如下結果:證明FIFO檔案確實是存在於檔案系統中的檔案,檔案屬性的第一個字元為‘p',表示該檔案是一個管道。五、具名管道的安全問題前面的例子是兩個進程之間的通訊問題,也就是說,一個進程向FIFO檔案寫資料,而另一個進程則在FIFO檔案中讀取資料。試想這樣一個問題,只使用一個FIFO檔案,如果有多個進程同時向同一個FIFO檔案寫資料,而只有一個讀FIFO進程在同一個FIFO檔案中讀取資料時,會發生怎麼樣的情況呢,會發生資料區塊的相互交錯是很正常的?而且個人認為多個不同進程向一個FIFO讀進程發送資料是很普通的情況。為瞭解決這一問題,就是讓寫操作的原子化。怎樣才能使寫操作原子化呢?答案很簡單,系統規定:在一個以O_WRONLY(即阻塞方式)開啟的FIFO中, 如果寫入的資料長度小於等待PIPE_BUF,那麼或者寫入全部位元組,或者一個位元組都不寫入。如果所有的寫請求都是發往一個阻塞的FIFO的,並且每個寫記請求的資料長度小於等於PIPE_BUF位元組,系統就可以確保資料決不會交錯在一起。六、具名管道與匿名管道的對比使用匿名管道,則通訊的進程之間需要一個父子關係,通訊的兩個進程一定是由一個共同的祖先進程啟動。但是匿名管道沒有上面說到的資料交叉的問題。
與使用匿名管道相比,我們可以看到fifowrite.exe和fiforead.exe這兩個進程是沒有什麼必然的聯絡的,如果硬要說他們具有某種聯絡,就只能說是它們都訪問同一個FIFO檔案。它解決了之前在匿名管道中出現的通訊的兩個進程一定是由一個共同的祖先進程啟動的問題。但是為了資料的安全,我們很多時候要採用阻塞的FIFO,讓寫操作變成原子操作。
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.