linux內核剖析(八)進程間通訊之-管道

管道


管道是一種兩個進程間進行單向通訊的機制。html

由於管道傳遞數據的單向性,管道又稱爲半雙工管道。linux

管道的這一特色決定了器使用的侷限性。管道是Linux支持的最初Unix IPC形式之一,具備如下特色:shell

  • 數據只能由一個進程流向另外一個進程(其中一個讀管道,一個寫管道);若是要進行雙工通訊,須要建 立兩個管道。編程

  • 管道只能用於父子進程或者兄弟進程間通訊。,也就是說管道只能用於具備親緣關係的進程間通訊。數組

    除了以上侷限性,管道還有其餘一些不足,如管道沒有名字(匿名管道),管道的緩衝區大小是受限制的。管道所傳輸的是無格式的字節流。這就須要管道輸入方和輸出方事先約定好數據格式。雖然有那麼多不足,但對於一些簡單的進程間通訊,管道仍是徹底能夠勝任的。數據結構

信號和消息的區別


咱們知道,進程間的信號通訊機制在傳遞信息時是以信號爲載體的,但管道通訊機制的信息載體是消息。那麼信號和消息之間的區別在哪裏呢?
  • 1
  • 2

首先,在數據內容方面,信號只是一些預約義的代碼,用於表示系統發生的某一情況;消息則爲一組連續語句或符號,不過量也不會太大。在做用方面,信號擔任進程間少許信息的傳送,通常爲內核程序用來通知用戶進程一些異常狀況的發生;消息則用於進程間交換彼此的數據。app

在發送時機方面,信號能夠在任什麼時候候發送;信息則不能夠在任什麼時候刻發送。在發送者方面,信號不能肯定發送者是誰;信息則知道發送者是誰。在發送對象方面,信號是發給某個進程;消息則是發給消息隊列。在處理方式上,信號能夠不予理會;消息則是必須處理的。在數據傳輸效率方面,信號不適合進大量的信息傳輸,由於它的效率不高;消息雖然不適合大量的數據傳送,但它的效率比信號強,所以適於中等數量的數據傳送。函數

管道-流管道-命名管道的區別


咱們知道,命名管道和管道均可以在進程間傳送消息,但它們也是有區別的。工具

管道這種通信方式有兩種限制,測試

  • 一是半雙工的通訊,數據只能單向流動

  • 二是隻能在具備親緣關係的進程間使用。

進程的親緣關係一般是指父子進程關係。

流管道s_pipe去除了第一種限制,能夠雙向傳輸。

管道可用於具備親緣關係進程間的通訊,命名管道name_pipe克服了管道沒有名字的限制,所以,除具備管道所具備的功能外,它還容許無親緣關係進程間的通訊;

管道技術只能用於鏈接具備共同祖先的進程,例如父子進程間的通訊,它沒法實現不一樣用戶的進程間的信息共享。再者,管道不能常設,當訪問管道的進程終止時,管道也就撤銷。這些限制給它的使用帶來很多限制,可是命名管道卻克服了這些限制。

命名管道也稱爲FIFO,是一種永久性的機構。FIFO文件也具備文件名、文件長度、訪問許可權等屬性,它也能像其它Linux文件那樣被打開、關閉和刪除,因此任何進程都能找到它。換句話說,即便是不一樣祖先的進程,也能夠利用命名管道進行通訊。

若是想要全雙工通訊,那最好使用Sockets API(linux並不支持s_pipe流管道)。下面咱們分別介紹經過管道命令解析管道的技術模型,而後詳細說明用來進行管道編程的編程接口和系統級命令。

管道技術模型


管道技術是Linux操做系統中從來已久的一種進程間通訊機制。

全部的管道技術,不管是半雙工的匿名管道,仍是命名管道,它們都是利用FIFO排隊模型來指揮進程間的通訊。

對於管道,咱們能夠形象地把它們看成是鏈接兩個實體的一個單向鏈接器。

使用管道進行通訊時,兩端的進程向管道讀寫數據是經過建立管道時,系統設置的文件描述符進行的。從本質上說,管道也是一種文件,但它又和通常的文件有所不一樣,能夠克服使用文件進行通訊的兩個問題,這個文件只存在內存中。

經過管道通訊的兩個進程,一個進程向管道寫數據,另一個從中讀數據。寫入的數據每次都添加到管道緩衝區的末尾,讀數據的時候都是從緩衝區的頭部讀出數據的。

管道命令詳解


參見

linux shell 管道命令(pipe)使用及與shell重定向區別

例如,請看下面的命令

管道符號,是unix功能強大的一個地方,符號是一條豎線:」|」,

用法: command 1 | command 2

他的功能是把第一個命令command 1執行的結果做爲command 2的輸入傳給command 2

注意:

  1. 管道命令只處理前一個命令正確輸出,不處理錯誤輸出

  2. 管道命令右邊命令,必須可以接收標準輸入流命令才行。

例如:
ls -l | more

該命令列出當前目錄中的任何文檔,並把輸出送給more命令做爲輸入,more命令分頁顯示文件列表。

管道命令與重定向區別


區別是:

  • 左邊的命令應該有標準輸出 | 右邊的命令應該接受標準輸入

  • 左邊的命令應該有標準輸出 > 右邊只能是文件

  • 左邊的命令應該須要標準輸入 < 右邊只能是文件

  • 管道觸發兩個子進程執行」|」兩邊的程序;而重定向是在一個進程內執行

重定向與管道在使用時候不少時候能夠通用

其實,在shell裏面,常常是條條大路通羅馬的。

通常若是是命令間傳遞參數,仍是管道的好,若是處理輸出結果須要重定向到文件,仍是用重定向輸出比較好。

前面的例子實際上就是在兩個命令之間創建了一根管道(有時咱們也將之稱爲命令的流水線操做)。

第一個命令ls執行後產生的輸出做爲了第二個命令more的輸入。

這是一個半雙工通訊,由於通訊是單向的。兩個命令之間的鏈接的具體工做,是由內核來完成的。

固然內核也爲咱們提供了一套接口(系統調用),除了命令以外,應用程序也可使用管道進行鏈接。

管道編程技術


參考 http://www.cppblog.com/jackdongy/archive/2013/01/07/197055.html

http://blog.chinaunix.net/uid-26495963-id-3066282.html

管道的接口


無名管道pipe


建立管道pipe

  1. 函數原型`int pipe(int filedes[2]);

    • pipe()會創建管道,並將文件描述詞由參數 filedes 數組返回。

    • filedes[0]爲管道里的讀取端,因此pipe用read調用的。

    • filedes[1]則爲管道的寫入端。使用write進行寫入操做。

  2. 返回值

    • 若成功則返回零,不然返回-1,錯誤緣由存於 errno 中。
  3. 錯誤代碼

    • EMFILE 進程已用完文件描述詞最大量

    • ENFILE 系統已無文件描述詞可用。

    • EFAULT 參數 filedes 數組地址不合法。

當調用成功時,函數pipe返回值爲0,不然返回值爲-1。成功返回時,數組fds被填入兩個有效的文件描述符。數組的第一個元素中的文件描述符供應用程序讀取之用,數組的第二個元素中的文件描述符能夠用來供應用程序寫入。

關閉管道close

  • 關閉管道只是將兩個文件描述符關閉便可,可使用普通的close函數逐個關閉。

若是管道的寫入端關閉,可是還有進程嘗試從管道讀取的話,將被返回0,用來指出管道已不可用,而且應當關閉它。若是管道的讀出端關閉,可是還有進程嘗試向管道寫入的話,試圖寫入的進程將收到一個SIGPIPE信號,至於信號的具體處理則要視其信號處理程序而定了。

dup函數和dup2函數


dup和dup2也是兩個很是有用的調用,它們的做用都是用來複制一個文件的描述符。

它們常常用來重定向進程的stdin、stdout和stderr。

這兩個函數的原型以下所示:

#include <unistd.h> int dup( int oldfd ); int dup2( int oldfd, int targetfd )

 

dup函數

利用函數dup,咱們能夠複製一個描述符。傳給該函數一個既有的描述符,它就會返回一個新的描述符,這個新的描述符是傳給它的描述符的拷貝。這意味着,這兩個描述符共享同一個數據結構。

例如,若是咱們對一個文件描述符執行lseek操做,獲得的第一個文件的位置和第二個是同樣的。下面是用來講明dup函數使用方法的代碼片斷:

int fd1, fd2;
fd2 = dup( fd1 );

 

須要注意的是,咱們能夠在調用fork以前創建一個描述符,這與調用dup創建描述符的效果是同樣的,子進程也一樣會收到一個複製出來的描述符。

dup2函數

dup2函數跟dup函數類似,但dup2函數容許調用者規定一個有效描述符和目標描述符的id。

dup2函數成功返回時,目標描述符(dup2函數的第二個參數)將變成源描述符(dup2函數的第一個參數)的複製品,換句話說,兩個文件描述符如今都指向同一個文件,而且是函數第一個參數指向的文件。

下面咱們用一段代碼加以說明:

int oldfd; oldfd = open("app_log", (O_RDWR | O_CREATE), 0644 ); dup2( oldfd, 1 ); close( oldfd ); 

 

咱們打開了一個新文件,稱爲「app_log」,並收到一個文件描述符,該描述符叫作fd1。咱們調用dup2函數,參數爲oldfd和1,這會致使用咱們新打開的文件描述符替換掉由1表明的文件描述符(即stdout,由於標準輸出文件的id爲1)。任何寫到stdout的東西,如今都將改成寫入名爲「app_log」的文件中。須要注意的是,dup2函數在複製了oldfd以後,會當即將其關閉,但不會關掉新近打開的文件描述符,由於文件描述符1如今也指向它。

命名管道mkfifo


mkfifo函數的做用是在文件系統中建立一個文件,該文件用於提供FIFO功能,即命名管道。前邊講的那些管道都沒有名字,所以它們被稱爲匿名管道,或簡稱管道。對文件系統來講,匿名管道是不可見的,它的做用僅限於在父進程和子進程兩個進程間進行通訊。而命名管道是一個可見的文件,所以,它能夠用於任何兩個進程之間的通訊,無論這兩個進程是否是父子進程,也無論這兩個進程之間有沒有關係。Mkfifo函數的原型以下所示:

#include <sys/types.h> #include <sys/stat.h> int mkfifo( const char *pathname, mode_t mode );

 

mkfifo函數須要兩個參數,第一個參數(pathname)是將要在文件系統中建立的一個專用文件。第二個參數(mode)用來規定FIFO的讀寫權限。Mkfifo函數若是調用成功的話,返回值爲0;若是調用失敗返回值爲-1。下面咱們以一個實例來講明如何使用mkfifo函數建一個fifo,具體代碼以下所示:

int ret; ret = mkfifo( "/tmp/cmd_pipe", S_IFIFO | 0666 ); if (ret == 0) { // 成功創建命名管道 } else { // 建立命名管道失敗 }

 

在這個例子中,利用/tmp目錄中的cmd_pipe文件創建了一個命名管道(即fifo)。以後,就能夠打開這個文件進行讀寫操做,並以此進行通訊了。命名管道一旦打開,就能夠利用典型的輸入輸出函數從中讀取內容。舉例來講,下面的代碼段向咱們展現瞭如何經過fgets函數來從管道中讀取內容:

pfp = fopen( "/tmp/cmd_pipe", "r" ); ret = fgets( buffer, MAX_LINE, pfp );

 

咱們還能向管道中寫入內容,下面的代碼段向咱們展現了利用fprintf函數向管道寫入的具體方法:

pfp = fopen( "/tmp/cmd_pipe", "w+ ); ret = fprintf( pfp, "Here’s a test string!\n" );

 

對命名管道來講,除非寫入方主動打開管道的讀取端,不然讀取方是沒法打開命名管道的。Open調用執行後,讀取方將被鎖住,直到寫入方出現爲止。儘管命名管道有這樣的侷限性,但它仍不失爲一種有效的進程間通訊工具。

無名管道


無名管道爲創建管道的進程及其子孫提供一條以比特流方式傳送消息的通訊管道。

該管道再邏輯上被看做管道文件,在物理上則由文件系統的高速緩衝區構成,而不多啓動外設。

發送進程利用文件系統的系統調用write(fd[1],buf,size),把buf 中的長度爲size字符的消息送入管道入口fd[1]

接收進程則使用系統調用read(fd[0],buf,size)從管道出口fd[0]出口讀出size字符的消息置入buf中。

這裏,管道按FIFO(先進先出)方式傳送消息,且只能單向傳送消息(如圖)。

這裏寫圖片描述

無名管道pipe讀寫


管道用於不一樣進程間通訊。一般先建立一個管道,再經過fork函數建立一個子進程,該子進程會繼承父進程建立的管道。注意事項:必須在系統調用fork()前調用pipe(),不然子進程將不會繼承文件描述符。不然,會建立兩個管道,由於父子進程共享同一段代碼段,都會各自調用pipe(),即創建兩個管道,出現異常錯誤。

無名管道讀寫過程如圖所示

這裏寫圖片描述

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #define MAX_DATA_LEN 256 #define DELAY_TIME 1 int main(void) { pid_t pid; char buf[MAX_DATA_LEN]; const char *data="Pipe Test program"; int real_read, real_write; int pipe_fd[2]; memset((void*)buf, 0, sizeof(buf)); if(pipe(pipe_fd) < 0) { perror("Pipe create error...\n"); exit(1); } else { printf("Pipe create success...\n"); } if ((pid = fork()) < 0) { perror("Fork error!\n"); exit(1); } else if (pid == 0) { printf("I am the child process, PID = %d, PPID = %d", getpid(), getppid()); close(pipe_fd[1]); sleep(DELAY_TIME * 3); if ((real_read=read(pipe_fd[0],buf, MAX_DATA_LEN)) > 0) { printf("Child receive %d bytes from pipe: '%s'.\n", real_read, buf); } close(pipe_fd[0]); exit(0); } else { printf("I am the parent process, PID = %d, PPID = %d", getpid(), getppid()); close(pipe_fd[0]); sleep(DELAY_TIME); if ((real_write = write(pipe_fd[1], data, strlen(data))) > 0) { printf("Parent write %d bytes into pipe: '%s'.\n", real_write, data); } close(pipe_fd[1]); waitpid(pid,NULL,0); exit(0); } return EXIT_SUCCESS; } 

 

多進程管道讀寫


創建一個管道。同時,父進程生成子進程P1,P2,這兩個進程分別向管道中寫入各自的字符串,父進程讀出它們(如圖)。

#include < stdio.h> main( ) { int I,r,p1,p2,fd[2]; char buf[50],s[50]; pipe(fd); /*父進程創建管道*/ while((p1=fork()) = = -1); if(p1 = = 0 ) { lockf(fd[1],1,0); /*加鎖鎖定寫入端*/ sprinrf(buf, 」child process P1 is sending messages! \n」); printf(「child process P1! \n」); write(fd[1],buf, 50); /*把buf中的50個字符寫入管道*/ sleep(5); lockf(fd[1],0,0); /*釋放管道寫入端*/ exit(0); /*關閉P1*/ } else /*從父進程返回,執行父進程*/ { while((p2=fork()) = = -1); /*建立子進程P2,失敗時循環*/ if(p2 = = 0) /*從子進程P2返回,執行P2*/ { lockf(fd[1],1,0); / *鎖定寫入端*/ sprintf(buf, 」child process P2 is sending messages \n」); printf(「child process P2 ! \n」); write(fd[1],buf,50); /*把buf中字符寫入管道*/ sleep(5); /* 睡眠等待*/ lockf (fd[1],0,0); /*釋放管道寫入端*/ exit(0); /*關閉P2*/ } wait(0); if (r = read(fd[0],s 50) = = -1) printf(「can’t read pipe \n」); else printf(「%s\n」,s); wait(0); if(r = read(fd[0],s,50)= = -1) printf(「can’t read pipe \n」); else printf((「%s\n」,s); exit(0); } } 

 

使用dup函數實現指令流水


咱們的子進程把它的輸出重定向的管道的輸入,而後,父進程將它的輸入重定向到管道的輸出。這在實際的應用程序開發中是很是有用的一種技術。

#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main() { int pfds[2]; if ( pipe(pfds) == 0 ) { if ( fork() == 0 ) { close(1); dup2( pfds[1], 1 ); close( pfds[0] ); execlp( "ls", "ls", "-1", NULL ); } else { close(0); dup2( pfds[0], 0 ); close( pfds[1] ); execlp( "wc", "wc", "-l", NULL ); } } return 0; } 

 

命名管道


write端

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <errno.h> #include <unistd.h> #include <fcntl.h> #define FIFO "myfifo" #define BUFF_SIZE 1024 int main(int argc,char* argv[]) { char buff[BUFF_SIZE]; int real_write; int fd; if(argc <= 1) { printf("Usage: %s string\n", argv[0]); exit(1); } else { printf("%s at PID = %d\n", argv[0], getpid()); } sscanf(argv[1], "%s", buff); // 測試FIFO是否存在,若不存在,mkfifo一個FIFO if(access(FIFO, F_OK) == -1) { if((mkfifo(FIFO, 0666) < 0) && (errno != EEXIST)) { printf("Can NOT create fifo file!\n"); exit(1); } } // 調用open以只寫方式打開FIFO,返回文件描述符fd if((fd = open(FIFO, O_WRONLY)) == -1) { printf("Open fifo error!\n"); exit(1); } // 調用write將buff寫到文件描述符fd指向的FIFO中 if ((real_write = write(fd, buff, BUFF_SIZE)) > 0) { printf("Write into pipe: '%s'.\n", buff); exit(1); } close(fd); exit(0); } 

 

read端

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <errno.h> #include <unistd.h> #include <fcntl.h> #define FIFO "myfifo" #define BUFF_SIZE 1024 int main(int argc, char *argv[]) { char buff[BUFF_SIZE]; int real_read; int fd; printf("%s at PID = %d ", argv[0], getpid()); // access肯定文件或文件夾的訪問權限。即,檢查某個文件的存取方式 // 若是指定的存取方式有效,則函數返回0,不然函數返回-1 // 若不存在FIFO,則建立一個 if(access(FIFO, F_OK) == -1) { if((mkfifo(FIFO, 0666) < 0) && (errno != EEXIST)) { printf("Can NOT create fifo file!\n"); exit(1); } } // 以只讀方式打開FIFO,返回文件描述符fd if((fd = open(FIFO, O_RDONLY)) == -1) { printf("Open fifo error!\n"); exit(1); } // 調用read將fd指向的FIFO的內容,讀到buff中,並打印 while(1) { memset(buff, 0, BUFF_SIZE); if ((real_read = read(fd, buff, BUFF_SIZE)) > 0) { printf("Read from pipe: '%s'.\n",buff); } } close(fd); exit(0); } 
相關文章
相關標籤/搜索