進程間通信-管道(PIPE)和有名管道(FIFO)

  前面我們學習了一下進程,我們知道多,進程間的地址空間相對獨立。進程與進程間不能像線程間通過全局變量通信。 如果想進程間通信,就需要其他機制。
    
    常用的進程間通信方式有這幾種

A.傳統的進程間通信方式
無名管道(pipe)、有名管道(fifo)和信號(signal)

B.System v IPC對象
共享內存(share memory)、消息隊列(message queue)和信號燈(semaphore)

C.BSD
套接字(socket)

一、無名管道(pipe)

1.1管道的介紹

A.管道是半雙工的,數據只能向一個方向流動;需要雙方通信時,需要建立起兩個管道

B.只能用於父子進程或者兄弟進程之間(具有親緣關係的進程);

C.單獨構成一種獨立的文件系統:管道對於管道兩端的進程而言,就是一個文件,但它不是普通的文件,它不屬於某種文件系統,而是自立門戶,單獨構成一種文件系統,並且只存在與內存中

D.數據的讀出和寫入:一個進程向管道中寫的內容被管道另一端的進程讀出。寫入的內容每次都添加在管道緩衝區的末尾,並且每次都是從緩衝區的頭部讀出數據。

1.2管道的創建



解釋如下 :



從以上我們可以知道:

管道是基於文件描述符的通信方式。當一個管道建立時,它會創建兩個文件描述符fd[0]和fd[1]。其中fd[0]固定用於讀管道,而fd[1]固定用於寫管道,一般文件I/O的函數都可以用來操作管道(lseek除外)。



我們來測試一下管道的大小:

案例一、



單獨創建一個無名管道,並沒有實際的意義。我們一般是在一個進程在由pipe()創建管道後,一般再由fork一個子進程,然後通過管道實現父子進程間的通信(因此也不難推出,只要兩個進程中存在親緣關係,這裏的親緣關係指的是具有共同的祖先,都可以採用管道方式來進行通信)。

1.3無名管道的讀寫規則探究

A.從管道中讀取數據

<1>寫端不存在時,此時則認爲已經讀到了數據的末尾,讀函數返回的讀出字節數爲0;


  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <stdlib.h>

  4. int main()
  5. {
  6.     int n;
  7.     int fd[2];
  8.     int count = 0;
  9.     char buf[100] = {0};

  10.     if(pipe(fd) < 0)
  11.     {
  12.         perror("Fail to create pipe");
  13.         exit(EXIT_FAILURE);
  14.     }
  15.     
  16.     close(fd[1]);
  17.     
  18.     if((= read(fd[0],buf,sizeof(buf))) < 0)
  19.     {
  20.         perror("Fail to read pipe");
  21.         exit(EXIT_FAILURE);
  22.     }

  23.     printf("Rread %d bytes : %s.\n",n,buf);

  24.     return 0;
  25. }

運行結果:



<2>寫端存在時,如果請求的字節數目大於PIPE_BUF(ubuntu操作系統爲65536),則返回管道中現有的數據字節數,如果請求的字節數目不大於PIPE_BUF,則放回管道中現有數據字節數(此時,管道中數據量小於請求的數據量);或者返回請求的字節數(此時,管道中數據量不小於請求的數據量)

案例二、父進程向管道中寫數據,子進程從管道中讀取數據


點擊(此處)摺疊或打開

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <errno.h>
  4. #include <string.h>

  5. #define N 10
  6. #define MAX 100

  7. int child_read_pipe(int fd)
  8. {
  9.     char buf[N];
  10.     int n = 0;

  11.     while(1)
  12.     {
  13.         n = read(fd,buf,sizeof(buf));
  14.         buf[n] = '\0';

  15.         printf("Read %d bytes : %s.\n",n,buf);

  16.         if(strncmp(buf,"quit",4) == 0)
  17.             break;
  18.     }

  19.     return 0;
  20. }

  21. int father_write_pipe(int fd)
  22. {
  23.     char buf[MAX] = {0};
  24.     
  25.     while(1)
  26.     {
  27.         printf(">");
  28.         fgets(buf,sizeof(buf),stdin);
  29.         buf[strlen(buf)-1] = '\0';
  30.         write(fd,buf,strlen(buf));
  31.         usleep(500);
  32.         if(strncmp(buf,"quit",4) == 0)
  33.             break;
  34.     }

  35.     return 0;
  36. }

  37. int main()
  38. {
  39.     int pid;
  40.     int fd[2];

  41.     if(pipe(fd) < 0)
  42.     {
  43.         perror("Fail to pipe");
  44.         exit(EXIT_FAILURE);
  45.     }

  46.     if((pid = fork()) < 0)
  47.     {
  48.         perror("Fail to fork");
  49.         exit(EXIT_FAILURE);

  50.     }else if(pid == 0){

  51.         close(fd[1]);
  52.         child_read_pipe(fd[0]);

  53.     }else{
  54.         
  55.         close(fd[0]);
  56.         father_write_pipe(fd[1]);
  57.     }
  58.     
  59.     exit(EXIT_SUCCESS);
  60. }

運行結果:



從以上驗證我們可以看到:
<1>當寫端存在時,管道中沒有數據時,讀取管道時將阻塞
<2>當讀端請求讀取的數據大於管道中的數據時,此時讀取管道中實際大小的數據
<3>當讀端請求讀取的數據小於管道中的數據時,此時放回請求讀取的大小數據

B.向管道中寫入數據:

向管道中寫入數據時,linux將不保證寫入的原子性,管道緩衝區一有空閒區域,寫進程就會試圖向管道寫入數據。當管道滿時,讀進程不讀走管道緩衝區中的數據,那麼寫操作將一直阻塞。

注意:只有管道的讀端存在時,向管道中寫入數據纔有意義。否則,向管道中寫入數據的進程將收到內核傳來的SIGPIPE信號,應用程序可以處理該信號,也可以忽略(默認動作則是使應用程序終止)。


  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <errno.h>
  4. #include <string.h>

  5. int main()
  6. {
  7.     int pid;
  8.     int n;
  9.     int fd[2];
  10.     char buf[1000 * 6] = {0};

  11.     if(pipe(fd) < 0)
  12.     {
  13.         perror("Fail to pipe");
  14.         exit(EXIT_FAILURE);
  15.     }

  16.     if((pid = fork()) < 0)
  17.     {
  18.         perror("Fail to fork");
  19.         exit(EXIT_FAILURE);

  20.     }else if(pid == 0){
  21.         
  22.         close(fd[1]);
  23.         sleep(5);
  24.         close(fd[0]);
  25.         printf("Read port close.\n");
  26.         sleep(3);

  27.     }else{

  28.         close(fd[0]);
  29.         
  30.         while(1)
  31.         {
  32.             n = write(fd[1],buf,sizeof(buf));
  33.             printf("Write %d bytes to pipe.\n",n);
  34.         }
  35.     
  36.     }

  37.     exit(EXIT_SUCCESS);
  38. }

運行結果:



探究發現,當管道數據滿時,此時再向管道寫數據,寫端將阻塞。當讀端不存在時,寫端寫數據,內核將向其發送SIGPIPE信號,默認是終止進程。

案例3:父進程讀取文件的內容,寫到無名管道,子進程從管道中讀取內容寫到另一個文件。
//思考:父進程什麼時候結束,子進程什麼時候結束?


點擊(此處)摺疊或打開

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <errno.h>
  5. #include <sys/stat.h>
  6. #include <sys/types.h>
  7. #include <fcntl.h>

  8. #define MAX 100

  9. int child_work(int pfd,char *fname)
  10. {
  11.     int n,fd;
  12.     char buf[MAX];

  13.     if((fd = open(fname,O_WRONLY | O_CREAT | O_TRUNC,0666)) < 0)
  14.     {
  15.         fprintf(stderr,"Fail to open %s : %s.\n",fname,strerror(errno));
  16.         return -1;
  17.     }

  18.     while( n = read(pfd,buf,sizeof(buf)) )
  19.     {
  20.         write(fd,buf,n);
  21.     }
  22.     
  23.     close(pfd);

  24.     return 0;
  25. }

  26. int father_work(int pfd,char *fname)
  27. {
  28.     int fd,n;
  29.     char buf[MAX];

  30.     if((fd = open(fname,O_RDONLY)) < 0)
  31.     {
  32.         fprintf(stderr,"Fail to open %s : %s.\n",fname,strerror(errno));
  33.         return -1;
  34.     }

  35.     while(= read(fd,buf,sizeof(buf)))
  36.     {
  37.         write(pfd,buf,n);
  38.     }
  39.     
  40.     close(pfd);

  41.     return 0;
  42. }

  43. int main(int argc,char *argv[])
  44. {
  45.     int pid;
  46.     int fd[2];

  47.     if(argc < 3)
  48.     {
  49.         fprintf(stderr,"usage %s argv[1] argv[2].\n",argv[0]);
  50.         exit(EXIT_FAILURE);
  51.     }

  52.     if(pipe(fd) < 0)
  53.     {
  54.         perror("Fail to pipe");
  55.         exit(EXIT_FAILURE);
  56.     }

  57.     if((pid = fork()) < 0)
  58.     {
  59.         perror("Fail to fork");
  60.         exit(EXIT_FAILURE);
  61.     
  62.     }else if(pid == 0){
  63.         
  64.         close(fd[1]);
  65.         child_work(fd[0],argv[2]);
  66.     
  67.     }else{
  68.     
  69.         close(fd[0]);
  70.         father_work(fd[1],argv[1]);
  71.         wait(NULL);
  72.     }

  73.     exit(EXIT_SUCCESS);
  74. }

二、有名管道

1.1有名管道的介紹

無名管道,由於沒有名字,只能用於親緣關係的進程間通信.。爲了克服這個缺點,提出了有名管道(FIFO)。

FIFO不同於無名管道之處在於它提供了一個路徑名與之關聯,以FIFO的文件形式存在於文件系統中,這樣,即使與FIFO的創建進程不存在親緣關係的進程,只要可以訪問該路徑,就能夠彼此通過FIFO相互通信,因此,通過FIFO不相關的進程也能交換數據。值的注意的是,FIFO嚴格遵循先進先出(first in first out),對管道及FIFO的讀總是從開始處返回數據,對它們的寫則把數據添加到末尾。它們不支持諸如lseek()等文件定位操作。

注意:有名管道的名字存在於文件系統中,內容存放在內存中。

1.2有名管道的創建



該函數的第一個參數是一個普通的路勁名,也就是創建後FIFO的名字。第二個參數與打開普通文件的open()函數中的mode參數相同。如果mkfifo的一個參數是一個已經存在路勁名時,會返回EEXIST錯誤,所以一般典型的調用代碼首先會檢查是否返回該錯誤,如果確實返回該錯誤,那麼只要調用打開FIFO的函數就可以了。

1.3有名管道的打開規則

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

FIFO的打開規則:

如果當前打開操作時爲讀而打開FIFO時,若已經有相應進程爲寫而打開該FIFO,則當前打開操作將成功返回;否則,可能阻塞到有相應進程爲寫而打開該FIFO(當前打開操作設置了阻塞標誌);或者,成功返回(當前打開操作沒有設置阻塞標誌)。

如果當前打開操作時爲寫而打開FIFO時,如果已經有相應進程爲讀而打開該FIFO,則當前打開操作將成功返回;否則,可能阻塞直到有相應進程爲讀而打開該FIFO(當前打開操作設置了阻塞標誌);或者,返回ENIO錯誤(當期打開操作沒有設置阻塞標誌)。


案例:

A.open for  write


點擊(此處)摺疊或打開

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <errno.h>
  5. #include <sys/types.h>
  6. #include <sys/stat.h>
  7. #include <fcntl.h>

  8. int main(int argc,char *argv[])
  9. {
  10.     int fd;

  11.     if(argc < 2)
  12.     {
  13.         fprintf(stderr,"usage : %s argv[1].\n",argv[0]);
  14.         exit(EXIT_FAILURE);
  15.     }
  16.     
  17.     if(mkfifo(argv[1],0666) < 0 && errno != EEXIST)
  18.     {
  19.         fprintf(stderr,"Fail to mkfifo %s : %s.\n",argv[1],strerror(errno));
  20.         exit(EXIT_FAILURE);
  21.     }

  22.     if((fd = open(argv[1],O_WRONLY)) < 0)
  23.     {
  24.         fprintf(stderr,"Fail to open %s : %s.\n",argv[1],strerror(errno));
  25.         exit(EXIT_FAILURE);
  26.     }

  27.     printf("open for write success.\n");
  28.     
  29.     return 0;
  30. }

B.open for read


點擊(此處)摺疊或打開

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <errno.h>
  5. #include <sys/types.h>
  6. #include <sys/stat.h>
  7. #include <fcntl.h>

  8. int main(int argc,char *argv[])
  9. {
  10.     int fd;

  11.     if(argc < 2)
  12.     {
  13.         fprintf(stderr,"usage : %s argv[1].\n",argv[0]);
  14.         exit(EXIT_FAILURE);
  15.     }
  16.     
  17.     if(mkfifo(argv[1],0666) < 0 && errno != EEXIST)
  18.     {
  19.         fprintf(stderr,"Fail to mkfifo %s : %s.\n",argv[1],strerror(errno));
  20.         exit(EXIT_FAILURE);
  21.     }

  22.     if((fd = open(argv[1],O_RDONLY)) < 0)
  23.     {
  24.         fprintf(stderr,"Fail to open %s : %s.\n",argv[1],strerror(errno));
  25.         exit(EXIT_FAILURE);
  26.     }

  27.     printf("open for read success.\n");
  28.     
  29.     return 0;
  30. }

探究發現,如果open時沒有使用O_NONBLOCK參數,我們發現不論讀端還是寫端先打開,先打開者都會阻塞,一直阻塞到另一端打開。

讀者自己可以探究,如果open時使用了O_NONBLOCK參數,此時打開FIFO 又會是什麼情況?

1.4有名管道的讀寫規則

A.從FIFO中讀取數據

約定:如果一個進程爲了從FIFO中讀取數據而以阻塞的方式打開FIFO, 則稱內核爲該進程的讀操作設置了阻塞標誌

<1>如果有進程爲寫而打開FIFO,且當前FIFO內沒有數據,則對於設置了阻塞標誌的讀操作來說,將一直阻塞。對於沒有設置阻塞標誌讀操作來說返回-1,當前errno值爲EAGAIN,提醒以後再試。

<2>對於設置阻塞標誌的讀操作說,造成阻塞的原因有兩種:當前FIFO內有數據,但有其他進程正在讀這些數據;另外就是FIFO內沒有數據。解阻塞的原因則是FIFO中有新的數據寫入,不論寫入數據量的大小,也不論讀操作請求多少數據量。

<3>如果沒有進程寫打開FIFO,則設置了阻塞標誌的讀操作會阻塞

<4>如果寫端關閉,管道中有數據讀取管道中的數據,如果管道中沒有數據讀端將不會繼續阻塞,此時返回0。

注意:如果FIFO中有數據,則設置了阻塞標誌的讀操作不會因爲FIFO中的字節數小於請求讀的字節數而阻塞,此時,讀操作會返回FIFO中現有的數據量。

B.向FIFO中寫入數據

約定:如果一個進程爲了向FIFO中寫入數據而阻塞打開FIFO,那麼稱該進程內的寫操作設置了阻塞標誌。

對於設置了阻塞標誌的寫操作:

<1>當要寫入的數據量不大於PIPE_BUF時,linux將保證寫入的原子性。如果此時管道空閒緩衝區不足以容納要寫入的字節數,則進入睡眠,直到當緩衝區中能夠容納寫入的字節數時,纔開始進行一次性寫操作。

<2>當要寫入的數據量大於PIPE_BUF時,Linux將不再保證寫入的原子性。FIFO緩衝區一有空閒區域,寫進程就會試圖向管道寫入數據,寫操作在寫完所有請求寫的數據後返回。

對於沒有設置阻塞標誌的寫操作:

<1>當要寫入的數據量大於PIPE_BUF時,linux將不再保證寫入的原子性。在寫滿所有FIFO空閒緩衝區後,寫操作返回。

<2>當要寫入的數據量不大於PIPE_BUF時,linux將保證寫入的原子性。如果當前FIFO空閒緩衝區能夠容納請求寫入的字節數,寫完後成功返回;如果當前FIFO空閒緩衝區不能夠容納請求寫入的字節數,則返回EAGAIN錯誤,提醒以後再寫。

注意:只有讀端存在,寫端纔有意義。如果讀端不在,寫端向FIFO寫數據,內核將向對應的進程發送SIGPIPE信號(默認終止進程);

案例一、

write to FIFO


  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <errno.h>
  5. #include <sys/types.h>
  6. #include <sys/stat.h>
  7. #include <fcntl.h>

  8. #define MAX 655360

  9. int main(int argc,char *argv[])
  10. {
  11.     int n,fd;
  12.     char buf[MAX];

  13.     if(argc < 2)
  14.     {
  15.         fprintf(stderr,"usage : %s argv[1].\n",argv[0]);
  16.         exit(EXIT_FAILURE);
  17.     }
  18.     
  19.     if(mkfifo(argv[1],0666) < 0 && errno != EEXIST)
  20.     {
  21.         fprintf(stderr,"Fail to mkfifo %s : %s.\n",argv[1],strerror(errno));
  22.         exit(EXIT_FAILURE);
  23.     }

  24.     if((fd = open(argv[1],O_WRONLY )) < 0)
  25.     {
  26.         fprintf(stderr,"Fail to open %s : %s.\n",argv[1],strerror(errno));
  27.         exit(EXIT_FAILURE);
  28.     }

  29.     printf("open for write success.\n");

  30.     while(1)
  31.     {
  32.         printf(">");
  33.         scanf("%d",&n);

  34.         n = write(fd,buf,n);
  35.         printf("write %d bytes.\n",n);
  36.     }
  37.     
  38.     exit(EXIT_SUCCESS);
  39. }

read from FIFO


點擊(此處)摺疊或打開

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <errno.h>
  5. #include <sys/types.h>
  6. #include <sys/stat.h>
  7. #include <fcntl.h>

  8. #define MAX 655360

  9. int main(int argc,char *argv[])
  10. {
  11.     int fd,n;
  12.     char buf[MAX];

  13.     if(argc < 2)
  14.     {
  15.         fprintf(stderr,"usage : %s argv[1].\n",argv[0]);
  16.         exit(EXIT_FAILURE);
  17.     }
  18.     
  19.     if(mkfifo(argv[1],0666) < 0 && errno != EEXIST)
  20.     {
  21.         fprintf(stderr,"Fail to mkfifo %s : %s.\n",argv[1],strerror(errno));
  22.         exit(EXIT_FAILURE);
  23.     }

  24.     if((fd = open(argv[1],O_RDONLY )) < 0)
  25.     {
  26.         fprintf(stderr,"Fail to open %s : %s.\n",argv[1],strerror(errno));
  27.         exit(EXIT_FAILURE);
  28.     }
  29.     
  30.     printf("open for read success.\n");

  31.     while(1)
  32.     {
  33.         printf(">");
  34.         scanf("%d",&n);
  35.         
  36.         n = read(fd,buf,n);
  37.     
  38.         printf("Read %d bytes.\n",n);

  39.     }

  40.     exit(EXIT_SUCCESS);
  41. }

讀者可以將這兩個程序運行,然後輸入read和write   FIFO大小就可以看到效果。
發佈了12 篇原創文章 · 獲贊 23 · 訪問量 32萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章