天天看點

管道的核心實作

一.概述:

     管道是程序間通信的手段之一,它實際上是一個存在于記憶體的特殊檔案,而這個檔案要通過兩個已經打開的檔案才能進行操作,這兩個檔案分别指向管道的兩端。

            管道是通過在記憶體中開辟一個緩存區來實作程序間的通信的,這個緩存區的大小是固定的。在linux中,這個緩存區的大小為一頁,即4k。但固定的大小會帶來問題,當緩存區已經被write操作寫滿時,之後的write操作會阻塞,等待緩存區内的某些資料被讀取,以便騰出空間給寫操作調用。

            在linux中,管道并沒有專門的資料結構,而是通過file結構和inode共同實作。兩個file結構指向一個inode,而這個inode對應了記憶體的某個區域。

<a href="http://s5.51cto.com/wyfs02/M02/7E/D5/wKioL1cKLCPwOnoaAAB-fvSi5wU217.png" target="_blank"></a>

注:一個file中的f_op隻對應寫或者讀操作中的一個。

          而管道建立的緩存區是一個環形緩存區,即當用到緩存區的尾部時,下一個可用區間為緩存區的首部。緩存區一般采用的是數組形式,即申請的是一個線性的位址空間。而形成環狀用的是    模    緩存區長度。而通路這個緩存區的方式是一種典型的“生産者—消費者模型”,即當生産者有大量的資料要寫時,而緩存區的大小隻有1k,是以當緩存區被寫滿時,生産者必須等待,等待消費者讀取資料,以便騰出空間讓消費者寫。而當消費者發現緩存區内并沒有資料時,消費者必須等待,等待生産者往緩存區内寫資料來提供消費者讀資料。

          那麼又如何判斷是“空”還是“滿”呢。當read和write指向環形緩存區的同一位置時為空或滿。為了差別空和滿,規定read和write重疊時為空,而當write比read快,追到距離read還有一個元素間隔時,就認為是滿。

         摘:(不明覺厲)        

 并發通路

考慮到在不同環境下,任務可能對環形緩沖區的通路情況不同,需要對并發通路的情況進行分析。

在單任務環境下,隻存在一個讀任務和一個寫任務,隻要保證寫任務可以順利的完成将資料寫入,而讀任務可以及時的将資料讀出即可。如果有競争發生,可能會出現如下情況:

Case1:假如寫任務在“寫指針加1,指向下一個可寫空位置”執行完成時被打斷,此時寫指針write指向非法位置。當系統排程讀任 務執行時,如果讀任務需要讀多個資料,那麼不但應該讀出的資料被讀出,而且當讀指針被調整為0是,會将以前已經讀出的資料重複讀出。

Case2:假設讀任務進行讀操作,在“讀指針加1”執行完時被打斷,此時read所處的位置是非法的。當系統排程寫任務執行時,如果 寫任務要寫多個資料,那麼當寫指針指到尾部時,本來緩沖區應該為滿狀态,不能再寫,但是由于讀指針處于非法位置,在讀任務執行前,寫任務會任務緩沖區為 空,繼續進行寫操作,将覆寫還沒有來的及讀出的資料。

二.用管道實作程序間的通信:

代碼:

#include&lt;stdio.h&gt;

#include&lt;string.h&gt;

#include&lt;unistd.h&gt;

#include&lt;sys/types.h&gt;

#include&lt;stdlib.h&gt;

int main()

{

    //.....建立管道

    int pipefd[2] ={-1,-1};

    int ret = pipe(pipefd);

    if(ret == -1)

    {

        perror("pipe");

        return -1;

    }

    //....建立子程序

    pid_t id = fork();

    if(id &lt; 0)

    { 

        perror("fork");

    else if(id == 0)

         //....子程序關閉讀端

         close(pipefd[0]);

         char* buf = "i'm  child";

         int count = 5;

         while(count--)

         {

             write(pipefd[1],buf,strlen(buf));

             sleep(1);

         }

    else 

        //....父程序關閉寫端

        close(pipefd[1]);

        char buf[1024];

        int count = 5;

        while(count--)

        {

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

              ssize_t size = read(pipefd[0],buf,sizeof(buf) - 1);

              buf[size] = '\0';

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

        }

     }

}

結果截圖:

<a href="http://s5.51cto.com/wyfs02/M00/7E/D5/wKioL1cKSNjzpL_vAAAhTouD87o131.png" target="_blank"></a>

三.使用管道會出現的四種情況(假設都是阻塞I/O操作,沒有設定O_NONBLOCK标志):

1:當所有寫端都關閉時,但還有程序要從管道中讀取資料,那麼當管道中的資料讀完後,再次read時,就會傳回0,就像讀到檔案末尾一樣。

代碼如下:

        //....子程序關閉寫端

              if(size &gt; 0)

              {

                    buf[size] = '\0';

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

              }

              else

                  printf("read error");

         //....父程序關閉讀端

         char* buf = "i'm  father";

         int i = 0;

             if(i == 3)

             {

                 printf("i just want to sleep");

                 break;

             }

             i++;

執行結果:

<a href="http://s1.51cto.com/wyfs02/M01/7E/D6/wKioL1cKU07QXLwKAAAmTbW1SV0167.png" target="_blank"></a>

2.如果指向管道寫端的檔案辨別符沒關閉,但寫端也不向管道中寫資料時,如果此時有程序從管道中讀資料,那麼當緩存區内的資料被讀完之後,再次read,讀程序會進入阻塞狀态,直到緩存區内有資料才進行讀資料。

                 printf("i just want to sleep\n");

                 sleep(5);

<a href="http://s3.51cto.com/wyfs02/M01/7E/D9/wKiom1cKVLSRRYsfAAAoS7S6njQ365.png" target="_blank"></a>

3.如果所有指向管道的讀端都關閉了,但還有程序要往管道中寫資料,那麼程序就會收到信号SIGPIPE,通常會導緻程序異常終止。(因為此時再寫也沒用,又沒程序要讀)

        //....子程序關閉讀端

        close(pipefd[0]);

        char buf[] = "i'm  child";

              write(pipefd[1],buf,strlen(buf));

              sleep(1);

         //....父程序關閉寫端

         close(pipefd[1]);

         char buf[1024];

             {

                   close(pipefd[0]);

                   break;

             }

             i++;

            int ret = 0;

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

            ret =  read(pipefd[0],buf,sizeof(buf) - 1);

            buf[ret] = '\0';

            if(ret &gt; 0)            

            { 

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

                 fflush(stdout);

            }

         int status = 0;

         pid_t pid = waitpid(id, &amp;status, 0);

         printf("pid: [%d]  signal:[%d]\n",id,status|0xff);

         fflush(stdout);

<a href="http://s1.51cto.com/wyfs02/M01/7E/DA/wKiom1cK-O-DrCSuAAA-zZpM7AM436.png" target="_blank"></a>

4.如果指向管道讀端的檔案辨別符沒有關閉,但又不讀緩存區内的内容,那麼如果此時還有程序要往緩存區内寫,而緩存區已被寫滿時,寫程序會阻塞,直到緩存區内有空位置之後才能寫。

                   printf("i just want to sleep\n");

                   fflush(stdout);

                   sleep(5);

             ret =  read(pipefd[0],buf,sizeof(buf) - 1);

            if(ret &gt; 0)

            { 

<a href="http://s1.51cto.com/wyfs02/M01/7E/DA/wKiom1cK-7bCytpqAABTg_uTl1I409.png" target="_blank"></a>

本文轉自 ye小灰灰  51CTO部落格,原文連結:http://blog.51cto.com/10704527/1762492,如需轉載請自行聯系原作者

繼續閱讀