歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> Linux基礎篇 進程通信——管道

Linux基礎篇 進程通信——管道

日期:2017/3/1 11:42:20   编辑:關於Linux

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

linux下進程間通信的幾種主要?手段簡介:
1 管道(Pipe)及有名管道(named pipe):管道可?用於具有親緣關系進程間的通信,有名管道克服了管道沒有名字的限制,因此,除具有管道所具有的功能外,它還允許無親緣關系進程間的通信;
2 信號(Signal):信號是比較復雜的通信?方式,用於通知接受進程有某種事件發?生,除了用於進程間通信外,進程還可以發送信號給進程本?身;linux除了?支持Unix早期信號語義函數sigal外,還支持語義符合Posix.1標准的信號函數sigaction(實際上,該函數是基於BSD的,BSD為了實現可靠信號機制,又能夠統一對外接口,?用sigaction函數重新實現了signal函數);
3 報?文(Message)隊列(消息隊列):消息隊列是消息的鏈接表,包括Posix消息隊列system V消息隊列。有足夠權限的進程可以向隊列中添加消息,被賦予讀權限的進程則可以讀走隊列中的消息。消息隊列克服了信號承載信息量少,管道只能承載無格式字節流以及緩沖區?大?小受限等缺點。
4 共享內存:使得多個進程可以訪問同一塊內存空間,是最快的可用IPC形式。是針對其他通信機制運行效率較低而設計的。往往與其它通信機制,如信號量結合使?用,來達到進程間的同步及互斥。
5 信號量(semaphore):主要作為進程間以及同一進程不同線程之間的同步手段。
6 套接口(Socket):更為一般的進程間通信機制,可用於不同機器之間的進程間通信。起初是由Unix系統的BSD分支開發出來的,但現在一般可以移植到其它類Unix系統上:Linux和System V的變種都支持套接字。

本篇文章先來學習下管道個命名管道。
管道是?一種最基本的IPC機制,由pipe函數創建:

#include 
int pipe(int filedes[2]);

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

我們來演示下Pipe的通信功能,首先
1. 父進程調?用pipe開辟管道,得到兩個文件描述符指向管道的兩端。
2. 父進程調?用fork創建?子進程,那麼子進程也有兩個文件描述符指向同一管道。
3. 父進程關閉管道讀端,子進程關閉管道寫端。父進程可以往管道裡寫,子進程可以從管道裡讀,管道是用環形隊列實現的,數據從寫端流入從讀端流出,這樣就實現了進程間通信。
代碼如下:

#include
  2 #include
  3 #include
  4 #include
  5 
  6 int main()
  7 {
  8     int _pipe[2];
  9     int ret=pipe(_pipe);
 10     if(ret==-1)//
 11     {
 12         printf("creat 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     {
 23         close(_pipe[0]);
 24         int i=0;
 25         char* _mesg_c=NULL;
 26         while(i<100)
 27         {
 28             _mesg_c="i im coder";
 29             write(_pipe[1],_mesg_c,strlen(_mesg_c)+1);
 30             sleep(1);
 31             i++;
 32         }
 33     }
 34   else{//father
 35        close(_pipe[1]);
 36        char _mesg[100];
 37        int j=0;
 38        while(j<100)
 39        {
 40            memset(_mesg,'\0',sizeof(_mesg));
 41            read(_pipe[0],_mesg,sizeof(_mesg));
 42            printf("%s\n",_mesg);
43            j++;
 44        }
 45         }
 46 
 47     return 0;
 48 }

運行結果如下:
這裡寫圖片描述
使?用管道有一些限制:
兩個進程通過一個管道只能實現單向通信。比如上面的例子,父進程寫子進程讀,如果有時候也需要子進程寫父進程讀,就必須另開一個管道。
管道的讀寫端通過打開的?文件描述符來傳遞,因此要通信的兩個進程必須從它們的公共祖先那?裡繼承管道?文件描述符。上?面的例子是父進程把?文件描述符傳給子進程之後父子進程之間通信,也可以父進程fork兩次,把?文件描述符傳給兩個子進程,然後兩個子進程之間通信, 總之需要通過fork傳遞文件描述符使兩個進程都能訪問同一管道,它們才能通信。 也就是說,管道通信是需要進程之間有關系。
使?用管道需要注意以下4種特殊情況(假設都是阻塞I/O操作,沒有設置O_NONBLOCK標志):
1. 如果所有指向管道寫端的文件描述符都關閉了(管道寫端的引?用計數等於0),而仍然有進程 從管道的讀端讀數據,那麼管道中剩余的數據都被讀取後,再次read會返回0,就像讀到文件末尾一樣。
代碼和上面的代碼類似,只是修改子進程中寫入的循環條件,部分代碼如下:

 else if(id==0)//child
 22     {
 23         close(_pipe[0]);
 24         int i=0;
 25         char* _mesg_c=NULL;
 26         while(i<10)
 27         {
 28             _mesg_c="i im coder";
 29             write(_pipe[1],_mesg_c,strlen(_mesg_c)+1);
 30             sleep(1);
 31             i++;
 32         }
            close(_pipe[1]);
 33     }

運行結果如下:
這裡寫圖片描述
2. 如果有指向管道寫端的文件描述符沒關閉(管道寫端的引用計數?大於0),而持有管道寫端的 進程也沒有向管道中寫數據,這時有進程從管道讀端讀數據,那麼管道中剩余的數據都被讀取後,再次read會阻塞,直到管道中有數據可讀了才讀取數據並返回。
3. 如果所有指向管道讀端的文件描述符都關閉了(管道讀端的引?用計數等於0),這時有進程向管道的寫端write,那麼該進程會收到信號SIGPIPE,通常會導致進程異常終止。

#include 
  2 #include 
  3 #include 
  4 #include 
  5 #include 
  6 void fun()
  7 {
  8     printf("zhongzhi\n");
  9 }
 10 int main()
 11 {
 12     signal(13,fun);
 13 int _pipe[2];
 14 int ret = pipe(_pipe);
 15 if(ret == -1){
 16 printf("create pipe error! errno code is : %d\n",errno);
 17 return 1;
 18 }
 19 pid_t id = fork();
 20 if( id < 0 ){
 21 printf("fork error!");
 22 return 2;
23 }else if( id == 0 ){ //child 
 24 close(_pipe[0]);
 25 int i =0;
 26 char *_mesg_c=NULL;
 27 while(i<20){
 28 if( i < 10 ){
 29 _mesg_c="i am child!";
 30 write(_pipe[1], _mesg_c, strlen(_mesg_c)+1);
 31 }
 32 sleep(1);
 33 i++;
 34 }
 35 }else{ //father 
 36 close(_pipe[1]);
 37 char _mesg[100];
 38 int j = 0;
 39 while(j<3){
 40 memset(_mesg, '\0', sizeof(_mesg));
 41 int ret = read(_pipe[0], _mesg, sizeof(_mesg));
 42 printf("%s : code is : %d\n",_mesg, ret);
 43 j++;
44 }
 45 close(_pipe[0]);
 46 sleep(10);
 47 if ( waitpid(id, NULL, 0)< 0)
 48 {
 49 return 3;
 50 }
 51 }
 52 return 0;
 53 }

運行結果如下:
這裡寫圖片描述vcHLv8u3/qGjRklGT7K7zazT2rnctcDWrrSm1NrT2sv8zOG5qdK7uPbCt762w/vT69auudjBqqOs0tRGSUZPtcTOxLz+0M7KvbTmtKLT2s7EvP7Ptc2z1tCho8P8w/u53LXAysfSu7j2yeixuM7EvP6jrNLytMujrLy0yrm9+LPM0+u0tL2oRklGT7XEvfizzLK7tObU2sfX1LW52M+1o6zWu9Kqv8nS1LfDzsq4w8K3vrajrL7NxNy5u82ouf1GSUZPz+C7pc2o0MWho9a1tcPXotLitcTKx6OsRklGTyhmaXJzdCBpbnB1dCBmaXJzdCBvdXRwdXQp19zKx7C01dXPyL34z8iz9rXE1K3U8rmk1/ejrLXa0ru49rG70LTI67XEyv2+3b2rP8rXz8i007nctcDW0LbBs/ahozxiciAvPg0KMiDD/MP7udy1wLXEtLS9qNPrtsHQtDxiciAvPg0KTGludXjPwtPQwb3W1re9yr20tL2ow/zD+7nctcCho9K7ysfU2lNoZWxsz8K9u7ultdi9qMGi0ru49sP8w/u53LXAo6y2/srH1NqzzNDy1tDKuT/Tw8+1zbO6r8r9vag/waLD/MP7udy1wKGjU2hlbGy3vcq9z8K/ycq508Nta25vZLvybWtmaWZvw/zB7qOsz8LD5sP8we7KudPDbWtub2S0tL2owcvSu7j2w/zD+7nctcCjum1rbm9kIG5hbWVkcGlwZbS0vajD/MP7udy1wLXEz7XNs7qvyv3T0MG9uPajum1rbm9kus1ta2ZpZm+ho8G9uPa6r8r9vvm2qNLl1NrNtz/OxLz+c3lzL3N0YXQuaKOsPGJyIC8+DQq6r8r91K3Qzcjnz8KjujwvcD4NCjxwcmUgY2xhc3M9"brush:java;"> #include #include int mknod(const char *path,mode_t mod,dev_t dev); int mkfifo(const char *path,mode_t mode);

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

umask(0);
if (mknod("/tmp/fifo",S_IFIFO | 0666) == -1)
{
perror("mkfifo error");
exit(1);
}
umask(0);
if (mkfifo("/tmp/fifo",S_IFIFO|0666) == -1)
{
perror("mkfifo error!");
exit(1);
}

“S_IFIFO|0666”指明創建?一個命名管道且存取權限為0666,即創建者、與創建者同組的?用戶、其他用戶對該命名管道的訪問權限都是可讀可寫( 這?裡要注意umask對生成的管道?文件權限的影響 )。命名管道創建後就可以使用了,命名管道和管道的使用方法基本是相同的。只是使?用命名管道時,必須先調用open()將其打開。因為命名管道是一個存在於硬盤上的?文件,而管道是存在於內存中的特殊?文件。 需要注意的是,調用open()打開命名管道的進程可能會被阻塞。但如果同時用讀寫方式(O_RDWR)打開,則?一定不會導致阻塞;如果以只讀方式(O_RDONLY)打開,則調?用open()函數的進程將會被阻塞直到有寫方打開管道;同樣以寫?方式(O_WRONLY)打開也會阻塞直到有讀?方式打開管道。
3、總結
mkfifo函數的作?用是在?文件系統中創建?一個?文件,該?文件?用於提供FIFO功能,即命名管道。
前邊講的那些管道都沒有名字,因此它們被稱為匿名管道,或簡稱管道。對?文件系統來說,
匿名管道是不可見的,它的作?用僅限於在?父進程和?子進程兩個進程間進?行通信。?而命名管
道是?一個可見的?文件,因此,它可以?用於任何兩個進程之間的通信,不管這兩個進程是不
是?父?子進程,也不管這兩個進程之間有沒有關系。
下面我們通過程序演示下FIFO的功能
FIFO server端:

 1 #include
  2 #include
  3 #include
  4 #include
  5 #include
  6 #include
  7 
  8 #define _PATH_ "./file.tmp"
  9 #define _SIZE_ 100
 10 
 11 int main()
 12 {
 13     int ret=mkfifo(_PATH_,0666|S_IFIFO);
 14     printf("%d\n",ret);
 15     if(ret==-1)
 16     {
 17         printf("mkfifo error\n");
 18         return 1;
 19     }
 20     int fd=open(_PATH_,O_WRONLY);
 21     if(fd<0)
 22     {
23         printf("open file error!\n");
 24         return 1;
 25     }
 26     char buf[_SIZE_];
 27     memset(buf,'\0',sizeof(buf));
 28     while(1)
 29     {
 30         scanf("%s",buf);
 31         int ret=write(fd,buf,sizeof(buf));
 32         if(ret<=0)
 33         {
 34             printf("raed end or error!\n");
 35             break;
 36         }
 37         printf("%s\n",buf);
 38         if(strncmp(buf,"quit",4)==0)
 39         {
 40             break;
 41         }
 42     }
 43     close(fd);
 44     return 0;
 45 }

FIFO client端:

 1 #include
  2 #include
  3 #include
  4 #include
  5 #include
  6 #include
  7 
  8 #define _PATH_ "./file.tmp"
  9 #define _SIZE_ 100
 10 
 11 int main()
 12 {
 13     int fd=open(_PATH_,O_RDONLY);
 14     if(fd<0)
 15     {
 16         printf("open file error!\n");
 17         return 1;
 18     }
 19     char buf[_SIZE_];
 20     memset(buf,'\0',sizeof(buf));
 21     while(1)
 22     {
 23         int ret=read(fd,buf,sizeof(buf));
 24         if(ret<=0)
 25         {
 26             printf("raed end or error!\n");
 27             break;
 28         }
 29         printf("%s\n",buf);
 30         if(strncmp(buf,"quit",4)==0)
 31         {
 32             break;
 33         }
 34     }
 35     close(fd);
 36     return 0;
 37 }

運行結果如下:
這裡寫圖片描述

Copyright © Linux教程網 All Rights Reserved