Linux進程通訊——管道
處理序間通訊(IPC:Inner Proceeding Communication)
進程是作業系統實現程式獨佔系統啟動並執行假象的方法,是對處理器、主存、I/O裝置的抽象表示。每個進程都是一個獨立的資源嵌入式管理單元,每個進程所看到的是自己獨佔使用系統的假象,因此各個進程之間是不能夠直接的訪問對方進程的資源的,不同的進程之間進行資訊互動需要藉助作業系統提供的特殊的進程通訊機制。
進程之間的通訊,從物理上分,可以分為同主機的進程之間的通訊和不同主機間的進程之間的通訊。從通訊內容方式上分,可以分為資料互動、同步通訊、非同步通訊。
Linux系統進程之間的通訊方式大致如所示
集合上述兩種從物理和內容方式的劃分,可以這樣理解:
(1)同主機處理序間資料互動機制:無名管道(PIPE)、有名管道(FIFO)、訊息佇列(Message Queue)和共用記憶體(Shared Memory)。
(2)同主機處理序間同步通訊機制:訊號量(Semaphore)。
(3)同主機處理序間非同步通訊機制:訊號(Signal)。
(4)不同主機間進程資料互動機制:通訊端(Socket)、遠程調用RPC(Remote Procedure Call)。
管道通訊
管道又可以分為無名管道和具名管道,兩者的用途是不一樣的。
無名管道PIPE:主要用於具有親緣關係的進程之間的通訊,無名管道的通訊是單向的,只能由一段到另外一段;無名管道是臨時性的,完成通訊後將自動消失。一般採用先建立無名管道,再建立子進程,使子進程繼承父進程的管道檔案描述符,從而實現父子進程間的通訊;在非親緣關係管道之間,如果想利用無名管道進行通訊,則需要藉助另外的檔案描述符傳遞機制。
有名管道FIFO:有名管道是一個實際存在的特殊檔案,利用有名管道可以實現同主機任意進程之間的資料互動。
Shell中的無名管道
在Shell命令列中使用無名管道是到過管道符“ | ” 實現的,如下面的shell命令將cat命令的輸出通過管道傳遞給grep命令的輸入。
無名管道是一種特殊的檔案,這就意味著你可以向操作檔案一樣操作無名管道,無名管道在核心中對應的是一段特殊的記憶體空間,這段記憶體空間由作業系統進行管理,對使用者是不可見的,在使用者空間的應用程式中只能通過系統調用來訪問它。在這段記憶體空間中以迴圈隊列的方式來臨時儲存一個進程發往另外一個進程的資訊,並且在通訊完成後就會自動釋放相應的空間。
Linux C編程中的無名管道
1、建立無名管道
建立無名管道需要使用pipe(int _pipedes[2])函數,這個函數的參數是一個含有兩個元素的整型數組,如果執行成功,這個整形數組將分別儲存無名管道讀端的檔案描述符和寫端的檔案描述符,利用這兩個讀、寫檔案描述符,我們可以像讀寫檔案一樣,操作無名管道的讀寫。如pipe( )函數調用失敗將返回-1。
2、讀寫無名管道
下面的程式是在一個程式中完成,即一個進程既充當讀進程又充當寫進程。
#include<stdio.h>#include<unistd.h>#include<string.h>int main(){ int p[2]; char *str = "HelloWorld"; //待寫入無名管道的字串 char buf[128]; //讀取無名管道時的緩衝區 memset(buf,'\0',128); //清空緩衝區 if(pipe(p) == -1) { printf("function pipe() calls failed."); return -1; } write(p[1],str,strlen(str)); //寫無名管道 read(p[0],buf,strlen(str)); //讀無名管道 printf("%s\n",buf); //列印讀取的內容 return 0;}下面的程式類比了具有親緣關係的父子進程之間的通訊,子進程向父進程發送字串str,遵循上面的先建立管道再建立子進程,這樣子進程會複製父進程的管道檔案描述符。
#include<stdio.h>#include<unistd.h>#include<string.h>int main(){ int p[2]; int pid; char *str = "HelloWorld"; char buf[128]; memset(buf,'\0',128); if(pipe(p) == -1) { printf("function pipe() calls failed."); return -1; } if((pid=fork()) == -1) //建立一個子進程 { printf("function fork() calls failed.\n"); return -1; } else if(pid == 0) //在子進程中 { printf("In sub : pid=%d\n",getpid()); write(p[1],str,strlen(str)); //向無名管道中寫入str }else { //在父進程中 printf("In father : pid=%d\n",getpid()); read(p[0],buf,strlen(str)); //讀取無名管道 printf("In father : buf=%s\n",buf); }}上述程式的輸出結果:
注意:讀寫無名管道的read和write方法預設是阻塞式的讀寫,當讀取管道,但是這時管道中沒有資料,也沒有進程向寫端寫資料的時候,讀操作會發生阻塞;同理如果,向管道中寫資料,但是這時管道已經滿了(管道是一個特殊的記憶體空間檔案,預設大小是PIPE_BUF,一般是4096位元組),寫操作將發生阻塞。另外如果,寫管道時,讀端已經關閉,那麼寫操作將收到SIGPIPE訊號,write函數返回-1.
具名管道
具名管道是實際存在的一種檔案,在shell中,我們可以使用mknod命令建立一個具名管道,下面是mknod的用法,並且利用mknod建立了一個p_file檔案,可以發現這個檔案的檔案類型是p
Linux C編程中的具名管道
1、建立具名管道
在編程中,可以使用mkfifo(char *path, _mode_t _mode)函數建立一個具名管道,mkfifo有兩個參數,第一個是指定要建立的具名管道的名字,第二個是產生的具名管道檔案的模式。
2、讀寫具名管道
和無名管道一樣,具名管道的實質仍然是一段核心空間管理的記憶體,但是在使用write和read之前需要先使用open函數開啟具名管道檔案
下面的程式是兩個沒有親緣關係的進程之間利用具名管道進行通訊
寫進程源碼如下
#include<stdio.h>#include<unistd.h>#include<string.h>#include<fcntl.h>int main(){int fd;int res;char *str = "HelloWorld";res = mkfifo("myfifo",0766);if(res != 0){printf("function nkfifo() calls failed.");return -1;}fd = open("myfifo",O_WRONLY); //create fifo file named "myfifo"if(fd == -1){printf("function open() calls failed.");return -1;}printf("This proceeding id =%d, fifo file file ID=%d\n",getpid(),fd);res = write(fd,str,strlen(str)); //write to fifo file, This will be blocking if no readif(res == -1){printf("function write() calls failed.");return -1;}printf("write ok.\n");close(fd);return 0;}讀取管道程式的源碼如下
#include<stdio.h>#include<unistd.h>#include<string.h>#include<fcntl.h>int main(){int fd;int res;char buf[4096];memset(buf,'\0',4096);fd = open("myfifo",O_RDONLY); //open fifo fileif(fd == -1){printf("function open() calls failed.");return -1;}printf("This proceeding id=%d, fd=%d\n",getpid(),fd);read(fd,buf,sizeof(buf)); //read fifo into buffer, This is a blocking methodprintf("read : buf=%s\n",buf);close(fd);return 0;}需要先運行寫進程,因為需要建立管道檔案,write方法將被阻塞,因為沒有讀關聯到管道;繼續在另外的終端,運行讀取進程,讀進程可以讀取到寫進程發送的資料,並且寫進程的阻塞將會解除。
總結:
無名管道主要用於具有親緣關係的父子進程之間的通訊,是臨時性的,需要先建立管道,再建立子進程;管道都是單向的,若要實現雙向通訊,則需要兩個管道。具名管道是實際存在的檔案,使用前需要先開啟,管道預設的read和write操作都是阻塞式的。