進程通信之管道

        在談管道之前我們先了解一下什麼是進程間通信。

        每個進程各自有不同的用戶地址空間,任何一個進程的全局變量在另一個進程中都看不到,所以進程間要交換數據必須通過內核,在內核中開闢一塊緩衝區,進程1把數據從用戶空間拷貝到內核,進程2再從內核緩衝區把數據讀走,內核提供的這種機制稱爲進程間通信(IPC,InterProcess Communication)。如下圖所示

                                

簡單說進程間通信的本質是讓不同進程看到一份共同的資源,這份共同資源一般由操作系統提供。

一、匿名管道(pipe)

        管道是一種最基本的IPC機制,由pipe函數創建:

         #include<unistd.h>

         #int pipe<int filedes[2]>;

        調用pipe函數時在內核中開闢一塊緩衝區(稱爲管道)用於通信,它有一個讀端一個寫端,然後通過filedes參數傳出給用戶程序兩個文件描述符,filedes[0]指向管道的讀端,filedes[1]指向管道的寫端(就像0是標準輸入,1是標準輸出)。所以管道在用戶程序看起來就像一個打開的文件,通過read(filedes[0];或者write(filedes[1]);向這個文件讀寫數據其實是在讀寫內核緩衝區。pipe函數調用成功返回0,調用失敗返回-1。

開闢了管道之後的兩個進程間的通信步驟如下

1、父進程調用pipe開闢管道,得到兩個文件描述符指向管道的兩端。

2、父進程調用fork創建子進程,那麼子進程也有兩個文件描述符指向同一管道。

3、父進程關閉管道讀端,子進程關閉管道寫端。父進程可以往管道里讀,管道是用環形隊列實現的,數據從寫端流入從讀端流出,這樣就實現了進程間通信。

使用管道的一些限制:

1、兩個進程通過一個管道只能實現單向通信,但也可以創建雙管道實現雙向通信。

2、管道只能實現有血緣關係的進程間通信。

3、管道依賴文件系統,它的生命週期隨進程,即進程退出,管道不存在。

4、管道的數據更新基於字節流。

5、管道內部完成同步機制,保證數據的一致性。

使用管道需要注意以下4種特殊情況:

1、如果所有指向管道寫端的文件描述符都關閉了(管道的引用計數等於0),而仍然有進程從管道的讀端讀數據,那麼管道中剩餘的數據都被讀取後,再次read會返回0,就像讀到文件末尾一樣。代碼如下:

  1  #include<stdio.h>
  2  #include<unistd.h>
  3  #include<errno.h>
  4  #include<string.h>
  5  #include<sys/wait.h>
  6   int main()
  7   {
  8         int _pipe[2];
  9         int ret=pipe(_pipe);
 10         if(ret==-1)
 11         {
 12              printf("create pipe error!errno code is:%d\n",errno);
 13              return 1;
 14
 15         }
 16         pid_t id=fork();
 17         if(id<0)
 18         {
 19             printf("fork error!");
 20             return 2;
 21         }
 22         else if(id==0){//child
 23             close(_pipe[0]);
 24             int i=0;
 25             char* _mesg_c=NULL;
 26             while(i<10){
 27                 _mesg_c="i an child";
 28                 write(_pipe[1],_mesg_c,strlen(_mesg_c)+1);
 29                 sleep(1);

 30                 i++;
 31             }
 32             close(_pipe[1]);
 33         }else{//father
 34         close(_pipe[1]);
 35         char _mesg[100];
 36         int j=0;
 37         while(j<100)
 38         {
 39             memset(_mesg,'\0',sizeof(_mesg));

 40             int ret=read(_pipe[0],_mesg,sizeof(_mesg));
 41             printf("%s:code is%d\n",_mesg,ret);
 42            j++;
 43         }
 44         if(waitpid(id,NULL,0)<0)
 45         {  
 46             return 3;
 47         }
 48         }
 49         return 0;
 50         }
2、如果指向管道寫端的文件描述符沒關閉(管道寫端的引用計數大於0),而持有管道寫端的進程也沒有向管道中寫數據,這時進程從數據從管道讀數據,那麼管道中剩餘的數據都被讀取後,再次read會阻塞,直到管道中有數據可讀纔讀取數據並返回。代碼如下:

 1  #include<stdio.h>
  2  #include<unistd.h>
  3  #include<errno.h>
  4  #include<string.h>
  5  #include<sys/wait.h>
  6   int main()
  7   {
  8         int _pipe[2];
  9         int ret=pipe(_pipe);
 10         if(ret==-1)
 11         {
 12              printf("create pipe error!errno code is:%d\n",errno);
 13              return 1;
 14
 15         }
 16         pid_t id=fork();
 17         if(id<0)
 18         {
 19             printf("fork error!");
 20             return 2;
 21         }
 22         else if(id==0){//child
 23             close(_pipe[0]);
 24             int i=0;
 25             char* _mesg_c=NULL;
 26             while(i<20){
 27                 if(i<10){
 28                 _mesg_c="i an child";
 29                 write(_pipe[1],_mesg_c,strlen(_mesg_c)+1);
 30                 }
 31                 sleep(1);
 32                 i++;
 33             }
 34             close(_pipe[1]);
 35         }else{//father
 36         close(_pipe[1]);
 37         char _mesg[100];
 38         int j=0;
 39         while(j<20)
 40         {
 41             memset(_mesg,'\0',sizeof(_mesg));
 42             int ret=read(_pipe[0],_mesg,sizeof(_mesg));
 43             printf("%s:code is%d\n",_mesg,ret);
 44             j++;
 45         }
 46         if(waitpid(id,NULL,0)<0)
 47         {
 48             return 3;
 49         }
 50         }
 51         return 0;
 52         }
3、如果所有指向管道讀端的文件描述符都關閉了(管道讀端的引用計數等於0),這時有進程管道的寫端write,那麼進程會收到信號SIGPIPE,通常會導致進程異常終止。

 1  #include<stdio.h>
  2  #include<unistd.h>
  3  #include<errno.h>
  4  #include<string.h>
  5  #include<sys/wait.h>
  6   int main()
  7   {
  8         int _pipe[2];
  9         int ret=pipe(_pipe);
 10         if(ret==-1)
 11         {
 12              printf("create pipe error!errno code is:%d\n",errno);
 13              return 1;
 14         }
 15         pid_t id=fork();
 16         if(id<0)
 17         {
 18             printf("fork error!");
 19             return 2;
 20         }
 21         else if(id==0){//child
 22             close(_pipe[0]);
 23             int i=0;
 24             char* _mesg_c=NULL;
 25             while(i<20){
 26                 if(i<10){
 27                 _mesg_c="i an child";
 28                 write(_pipe[1],_mesg_c,strlen(_mesg_c)+1);
 29                 }
 30                 sleep(1);
 31                 i++;
 32             }
 33         }else{//father
 34         close(_pipe[1]);
 35         char _mesg[100];
 36         int j=0;
 37         while(j<3)
 38         {
 39             memset(_mesg,'\0',sizeof(_mesg));
 40             int ret=read(_pipe[0],_mesg,sizeof(_mesg));
 41             printf("%s:code is%d\n",_mesg,ret);
 42             j++;
 43         }
 44         close(_pipe[0]);
 45         sleep(10);
 46         if(waitpid(id,NULL,0)<0)
 47         {
 48             return 3;
 49         }
 50         }
 51         return 0;
 52         }
4、如果有指向管道讀端的文件描述符沒關閉(管道讀端的引用計數大於0),而持有管道讀端的進程也沒有從管道中數據,這時有管道寫端寫數據,那麼在管道被·寫滿時再次write會阻塞,直到管道中有空位置了才寫入數據並返回。

 1  #include<stdio.h>
  2  #include<unistd.h>
  3  #include<errno.h>
  4  #include<string.h>
  5  #include<sys/wait.h>
  6   int main()
  7   {
  8         int _pipe[2];
  9         int ret=pipe(_pipe);
 10         if(ret==-1)
 11         {
 12              printf("create pipe error!errno code is:%d\n",errno);
 13              return 1;
 14         }
 15         pid_t id=fork();
 16         if(id<0)
 17         {
 18             printf("fork error!");
 19             return 2;
 20         }
 21         else if(id==0){//child
 22             close(_pipe[0]);
 23             int i=0;
 24             char* _mesg_c=NULL;
 25             while(1){
 26                 _mesg_c="i an child";
 27                 write(_pipe[1],_mesg_c,strlen(_mesg_c)+1);
 28                 i++;
 29                 printf("%d\n",i);
 30             }
 31         }else{//father
 32         close(_pipe[1]);
 33         sleep(1);
 34         if(waitpid(id,NULL,0)<0)
 35         {
 36             return 3;
 37         }
 38         }
 39         return 0;
 40         }
二、命名管道(FIFO)

概念

        管道的一個不足之處就是沒有名字,因此,只能用於具有血緣關係的進程間通信,在命名管道提出後,該限制得到了克服,命名管道不同於管道之處在於它提供一個路徑名與之關聯,以命名管道的文件形式存儲於文件系統中。命名管道是一個設備文件,因此,即使進程與創建命名管道的進程不存在血緣關係,只要可以訪問該路徑,就能通過命名管道互相通信。需注意:命名管道總是按照先進先出的原則工作,第一個被寫入的數據將從管道中讀出。

 命名管道的創建與讀寫

        Linux下有兩種方式創建命名管道,一是在shell下交互地建立一個命名管道,二是在程序中使用系統函數建立命名管道。shell方式下可使用mknod或mkfifo命令,下面使用mknod創建了一個命名管道:

mknod namedpipe

       創建命名管道的系統函數有兩個:mknod和mkfifio。兩函數均定義在頭文件sys/stat.h,函數原型如下:

       #include<sys/types.h>

       #include<sys/stat.h>

       int mknod(const char* path,mode_t,dev_t dev);

       int mkfifo(const char* path,mode_t mode);

       函數mknod參數中path爲創建的命名管道的全路徑名:mod爲創建的命名管道的模式u,指明其存取權限:dev爲設備值,該值取決於文件創建的種類,它只在創建設備文件時纔會用到。這兩個函數調用成功都返回0,失敗都返回-1。下面使用mknod函數創建了一個命名管道:

umask(0);

if(mknod("/tmp/fifo",S_IFIFO|0666)==-1)

{

perror("mkfifo error");

exit(1);

}

        函數mkfifo前兩個參數的含義和mknod相同。下面是使用mkfifo的示例代碼:

umask(0);

if(mkfifo("/tmp/fifo",S_IFIF0|0666)==-1)

{

perror("mkfifo error!");

exit(1);

}

        "S_IFIFO|0666"指明創建一個命名管道且存取權限爲0666,即創建者、與創建者同組的用戶、其他用戶對命名管道的訪問權限是可讀可寫的。

         命名管道創建後就可以使用了,命名管道和管道的使用方法基本相同。只是使用命名管道時,必須先調用open()將其打開。因爲命名管道是一個存在與硬盤上的文件,而管道是存在於內存中的特殊文件。

           需注意:調用open()打開命名管道的進程可能會阻塞。但如果同時用讀寫方式(O_RDWR)打開,則一定不會導致阻塞;如果以只讀方式(O_PDONLY)打開,則調用open()函數的進程將會被阻塞直到有寫方式打開管道;同樣以寫方式(O_WRONLY)打開也會阻塞直到有讀寫方式打開管道。

總結

        文件系統中的路徑名是全局的,各進程都可以訪問,因此可以用文件系統中的路徑名來標識一個IPC通道。

        在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文件,mkfifo函數的作用是在文件系統中創建一個文件,該文件用於提供FIFO功能,即命名管道。前面講的那些管道沒有名字,因此它們被稱爲(匿名)管道。對文件系統來說,匿名管道只能實現有血緣關係的進程間通信。而命名管道是一個可見文件,因此,他可以用於任何兩個進程間的通信。

fifo read端:

#include<stdio.h>

#include<sys/types.h>

#include<sys/stat.h>

#include<unistd.h>

#include<fcntl.h>

#include<string.h>

#define _PATH_ "/tmp/file.tmp"

#define _SIZE_ 100

int main()

{

     int fd=open(_PATH_,O_PDONLY);

     if(fd<0){

             printf("open file error!\n");

             return 1;

           }

           char but[_SIZE_];

           memset(buf,'\0',sizeof(buf)); 

           while(1){

                   int ret=read(fd,buf,sizeof(buf));

                   if(ret<=0)//error or end of file

                   {

                          printf("read end or error!\n");

                          break;   

                   }

                   printf("%s\n",buf);

                   if(strncmp(buf,"quit",4)==0){

                               break;

                        }

                 }

           close(fd);

                  return 0;

}

fifo write端

#include<stdio.h>

#include<sys/types.h>

#include<sys/stst.h>

#include<unistd.h>

#include<string.h>

#include<fcntl.h>

#define _PATH_ "/tmp/file.tmp"

#define _SIZE_ 100

int main()

{

      int ret=mkfifo(_PATH_,0666|S_IFIFO);

      if(ret==-1){

             printf("mkfifo error\n");

             return 1;
       }

       int fd=open(_PATH_,O_WRONLY);

        if(if<0){

              printf("open error\n");

          }

          char buf[_SIZE_];

           memset(but,'\0',sizeof(buf));

           while(1){

                     scanf("%s",buf);

                      int ret=write(fd,buf,strlen(buf)+1);

                      if(ret<0){

                              printf("write error\n");

                               break;

                       }

                       if(strncmp(but,"quit",4)==0){

                                    break;

                         }

                  }

             close(fd);

                   return 0;

}

發佈了53 篇原創文章 · 獲贊 3 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章