#Linux#進程間通信# 管道(pipe)-匿名管道pipe

我們通常把一個進程連接到另一個進程的一個數據流稱爲一個“管道”,通常是用作把一個進程的輸出通過管道連接到另一個進程的輸入。管道本質上是內核的一塊緩存內核使用環形隊列機制,藉助內核緩衝區(4k)實現。

  • 管道是Unix中最古老的進程間通信的形式;
  • 我們把從一個進程連接到另一個進程的一個數據流稱爲一個“管道”;
  • 管道的實質就是操作系統所提供的一塊內存;

每個管道只有一個頁面作爲緩衝區,該頁面是按照環形緩衝區的方式來使用的。這種訪問方式是典型的“生產者——消費者”模型。當“生產者”進程有大量的數據需要寫時,而且每當寫滿一個頁面就需要進行睡眠等待,等待“消費者”從管道中讀走一些數據,爲其騰出一些空間。相應的,如果管道中沒有可讀數據,“消費者” 進程就要睡眠等待,具體過程如下圖所示:

管道是由內核管理的一個緩衝區,相當於我們放入內存中的一個紙條。管道的一端連接一個進程的輸出。這個進程會向管道中放入信息。管道的另一端連接一個進程的輸入,這個進程取出被放入管道的信息。一個緩衝區不需要很大(內核緩衝區(4k)),它被設計成爲環形的數據結構,以便管道可以被循環利用。當管道中沒有信息的話,從管道中讀取的進程會等待,直到另一端的進程放入信息。當管道被放滿信息的時候,嘗試放入信息的進程會等待,直到另一端的進程取出信息。當兩個進程都終結的時候,管道也自動消失。

匿名管道

匿名管道是基於文件描述符的通信方式。實現兩個進程間的通信時必須通過fork創建子進程,實現父子進程之間的通信。

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

  • 1. 父進程調用pipe函數創建管道,得到兩個文件描述符fd[0]、fd[1]指向管道的讀端和寫端。
  • 2. 父進程調用fork創建子進程,那麼子進程也有兩個文件描述符指向同一管道。
  • 3. 父進程關閉管道讀端,子進程關閉管道寫端。父進程可以向管道中寫入數據,子進程將管道中的數據讀出。由於管道是利用環形隊列實現的,數據從寫端流入管道,從讀端流出,這樣就實現了進程間通信。

匿名管道讀寫規則:

  • 當沒有數據可讀時:O_NONBLOCK disable:read調用阻塞,即進程暫停執行,一直等到有數據來到爲止;O_NONBLOCK enable:read調用返回-1,errno值爲EAGAIN;(基於不同版本linux內核返回值可能存在差異,Linux5.4.6返回值-EAGAIN)
  • 當管道滿的時候:O_NONBLOCK disable:write 調用阻塞,直到有進程讀走數據;O_NONBLOCK enable:調用返回 -1,error 值爲EAGAIN;(基於不同版本linux內核返回值可能存在差異,Linux5.4.6返回值-EAGAIN)
  • 如果所有的管道寫端對應的文件描述符被關閉,則read返回0;(基於不同版本linux內核返回值可能存在差異,Linux5.4.6返回當前已讀取的字節大小
  • 如果所有管道讀端對應的文件描述符被關閉,則write操作會產生信號SIGPIPE,進而可能導致write進程退出;(基於不同版本linux內核返回值可能存在差異,Linux5.4.6返回值-EPIPE
  • 當要寫入的數據量不大於PIPE_BUF時,linux將保證寫入的原子性;
  • 當要寫入的數據量大於PIPE_BUF時,linux將不在保證寫入的原子性;


匿名管道特點:

  • 只能用於具有公共祖先的進程(具有親緣關係的進程)之間進行通信;通常,一個管道由一個進程創建,然後該進程調用fork,此後父子進程之間就可應用該管道。
  • 管道提供流式服務(管道所傳送的數據是無格式的,這要求管道的讀出方與寫入方必須事先約定好數據的格式,如多少字節算一個消息等)。
  • 寫入管道中的數據遵循先入先出的規則。
  • 一般而言,進程退出,管道釋放,所以管道的生命週期隨進程。
  • 一般而言,內核會對管道操作進行同步與互斥(寫滿就不寫,讀完就不讀)。
  • 管道是半雙工的(一邊進一遍出),數據只能向一個方向流動;需要雙方通信時,需要建立兩個管道。
     
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章