Linux下進程間通信方式——pipe(管道)


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



不同進程間的通信本質:進程之間可以看到一份公共資源;而提供這份資源的形式或者提供者不同,造成了通信方式不同,而 pipe就是提供這份公共資源的形式的一種。

2.匿名管道

2.1管道的創建

管道是由調用pipe函數來創建

#include <unistd.h>
int pipe (int fd[2]);
                         //返回:成功返回0,出錯返回-1  

  fd參數返回兩個文件描述符,fd[0]指向管道的讀端,fd[1]指向管道的寫端。fd[1]的輸出是fd[0]的輸入。

2.2管道如何實現進程間的通信

(1)父進程創建管道,得到兩個件描述符指向管道的兩端

(2)父進程fork出子進程,子進程也有兩個文件描述符指向同管道。

(3)父進程關閉fd[0],子進程關閉fd[1],即子進程關閉管道讀端,父進程關閉管道寫端(因為管道只支持單向通信)。子進程可以往管道中寫,父進程可以從管道中讀,管道是由環形隊列實現的,數據從寫端流入從讀端流出,這樣就實現了進程間通信。

2.3如和用代碼實現管道通信

2.3如和用代碼實現管道通信

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<errno.h>
int main()
{
    int fd[2];
    int ret=pipe(fd);
    if(ret==-1)
    {
        perror("pipe error\n");
        return -1;
    }
    pid_t id=fork();
    if(id==0)
    {
        int i=0;
        close(fd[0]);
        char* child="I am child!";
        while(i<5)
        {
            write(fd[1],child,strlen(child)+1);
            sleep(2);
            i++;
        }
    }
    else if(id>0)
    {
        close(fd[1]);
        char msg[100];
        int j=0;
        while(j<5)
        {
            memset(msg,'\0',sizeof(msg));
            ssize_t s=read(fd[0],msg,sizeof(msg));
            if(s>0)
            {
                msg[s-1]='\0';
            }
            printf("%s\n",msg);
            j++;
        }
    }
    else
    {
        perror("fork error\n");
        return -1;
    }
    return 0;
}

  

運行結果:每隔2秒打印一次I am child! 並且打印了五次。

 

2.4管道讀取數據的四種的情況

(1)讀端不讀(fd[0]未關閉),寫端一直寫 

 

 

(2)寫端不寫(fd[1]未關閉),但是讀端一直讀 

(3)讀端一直讀,且fd[0]保持打開,而寫端寫了一部分數據不寫了,並且關閉fd[1]。 

如果一個管道讀端一直在讀數據,而管道寫端的引⽤計數⼤於0決定管道是否會堵塞,引用計數大於0,只讀不寫會導致管道堵塞。

(4)讀端讀了一部分數據,不讀了且關閉fd[0],寫端一直在寫且f[1]還保持打開狀態。

 

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<errno.h>
int main()
{
    int fd[2];
    int ret=pipe(fd);
    if(ret==-1)
    {
        perror("pipe error\n");
        return -1;
    }
    pid_t id=fork();
    if(id==0)
    {
        int i=0;
        close(fd[0]);
        char *child="I am child!";
        while(i<10)
        {
            write(fd[1],child,strlen(child)+1);
            sleep(2);
            i++;
        }
    }
    else if(id>0)
    {
        close(fd[1]);
        char msg[100];
        int status=0;
        int j=0;
        while(j<5)
        {
            memset(msg,'\0',sizeof(msg));
            ssize_t s=read(fd[0],msg,sizeof(msg));
            if(s>0)
            {
                msg[s-1]='\0';
            }
            printf("%s %d\n",msg,j);
            j++;
        }
		//寫方還在繼續,而讀方已經關閉它的讀端  
        close(fd[0]);
        pid_t ret=waitpid(id,&status,0);
        printf("exitsingle(%d),exit(%d)\n",status&0xff,(status>>8)&0xff);
		//低八位存放該子進程退出時是否收到信號  
        //此低八位子進程正常退出時,退出碼是多少 
    }
    else
    {
        perror("fork error\n");
        return -1;
    }
    return 0;
}

  運行結果:

 使用kill -l 查看13號信號,可以知道13號信號代表SIGPIPE。

 

總結:
如果一個管道的寫端一直在寫,而讀端的引⽤計數是否⼤於0決定管道是否會堵塞,引用計數大於0,只寫不讀再次調用write會導致管道堵塞;
如果一個管道的讀端一直在讀,而寫端的引⽤計數是否⼤於0決定管道是否會堵塞,引用計數大於0,只讀不寫再次調用read會導致管道堵塞;
而當他們的引用計數等於0時,只寫不讀會導致寫端的進程收到一個SIGPIPE信號,導致進程終止,只寫不讀會導致read返回0,就像讀到件末尾樣。

 

2.5管道特點

1.管道只允許具有血緣關系的進程間通信,如父子進程間的通信。

2.管道只允許單向通信。

3.管道內部保證同步機制,從而保證訪問數據的一致性。

4.面向字節流

5.管道隨進程,進程在管道在,進程消失管道對應的端口也關閉,兩個進程都消失管道也消失。

2.6管道容量大小

測試管道容量大小只需要將寫端一直寫,讀端不讀且不關閉fd[0],即可。 
測試代碼:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
int main()
{
    int fd[2];
    int ret = pipe(fd);
    if (ret == -1)
    {
        perror("pipe error\n");
        return -1;
    }
    pid_t id = fork();
    if (id == 0)
    {//child
        int i = 0;
        close(fd[0]);
        char *child = "I am  child!";
        while (++i)
        {
            printf("pipe capacity: %d\n", i*(strlen(child) + 1));
          //printf要寫在write前面否則會因為write寫滿了而阻塞就不會進行下面的代碼了,會使得輸出計算少一次
            write(fd[1], child, strlen(child) + 1);
           
        }
        close(fd[1]);
    }
    else if (id>0)
    {//father
        close(fd[1]);//父進程的讀端不能關閉,如果關閉了子進程寫端會因為異常而退出
        waitpid(id, NULL, 0);
    }
    else
    {//error
        perror("fork error\n");
        return -1;
    }
    return  0;
}    

  

  可以看到寫到65520之后管道堵塞了,而65536即為64K大小即為管道的容量

原理是:我們寫端每次寫入的數據大小是13,統計我們可以進行多少次寫入,寫入次數*13就是管道容量,因為65533+13=65546>65536所以就不能繼續輸入了,有因為內存對齊問題,所以我們可以知道容量一定是64k

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM