初識Linux · 匿名管道

前言:

在引入管道之前,我們先討論一些關于進程通信的問題。

首先,為什么進程需要通信?進程具有獨立性,但進程由內核數據結構和代碼數據組成,進程通信是為了協同工作,協同的本質是通過數據流動實現的。因此,第二個問題是,進程如何進行通信?

進程間通過數據進行通信,A進程發送數據,B進程接收數據。那么,數據流通的平臺是什么呢?此時,管道作為信息的載體,確保兩個進程能夠通信。對于進程間的通信,常見的方式有消息隊列、共享內存、信號量,這些將在后續介紹。

使用管道進行通信可以直接復用內核代碼,這不僅簡化了操作,還降低了成本。

那么,管道究竟是什么呢?

兩個進程要進行通信,必須訪問同一份資源或同一塊內存空間。因此,管道實際上是操作系統區和區之間開辟的一塊共享區資源。

管道分為匿名管道和命名管道。我們從匿名管道開始介紹,接下來會涉及進程池的小項目,最后介紹命名管道,這是我們介紹管道的順序。那么,讓我們直接進入主題吧!


為什么需要匿名管道?初識Linux · 匿名管道通過這張圖,我們可以簡單理解為什么需要管道。

假設有兩個進程,A進程將文件輸入到內核級文件緩沖區,然后數據通過操作系統到達磁盤,B進程通過read方法讀取A進程write的數據,這個過程似乎沒有問題。

但為什么我們不能直接讓A進程的數據直接傳給B進程呢?

這樣做不需要重新設計一個通信端口,太麻煩了。我們可以通過fork函數和close函數實現這一功能:

int main(){         pid_t id = fork();         if(id == 0)         {                 //子進程準備work...         }         //父進程準備work...                 return 0; }

在實現這一功能之前,我們需要了解管道通信的文件描述符是如何工作的?

初識Linux · 匿名管道先看一段代碼:

#include <iostream> #include <sys> #include <unistd.h> using namespace std; int main(){         pid_t id = fork();         if (id == 0)         {                 std::cout 

我們思考一個現象,為什么父子進程默認都打印在文件描述符1上?

當進程啟動時,默認打開三個流,但我們是否思考過為什么會這樣?前文提到過歷史原因的存在。當我們啟動linux機器時,bash進程已經啟動,此時bash進程的三個流已經打開,我們后面啟動的所有進程都是bash進程的子進程,子進程的三個流也默認打開了。那么,如果我們關閉子進程的0、1、2呢?

#include <iostream> #include <sys> #include <unistd.h> using namespace std; int main(){         pid_t id = fork();         if (id == 0)         {                 close(0);                 close(1);                 close(2);                 std::cout 

初識Linux · 匿名管道現象是父進程可以正常打印,因此關閉文件描述符不會影響父進程。我們利用這一點可以實現管道單向通信的功能。為什么實現的是單向通信呢?因為如果是雙向通信,父進程和子進程的數據都在管道中,讀取時不經過一些操作肯定會出錯,所以我們先簡單看看單向通信。

為什么我們能得出結論是子進程能繼承父進程的文件描述符表?為了實現單向管道通信,我們需要關閉文件描述符。


什么是管道?在實現管道時,我們需要用到的函數是pipe:

初識Linux · 匿名管道對于該函數,我們不使用那個結構體,而是使用int pipe(int pipefd[2])即可,結構體暫時先不管。對于pipefd[2],這是一個輸出型參數,管道開辟成功后,fd[1]是管道的寫入文件描述符,fd[0]是文件描述符的讀端。

為什么管道稱為匿名管道?因為我們獲得該文件描述符甚至不需要文件名或文件路徑,所以稱為匿名管道。

初識Linux · 匿名管道這是創建管道的最開始狀態,最后需要我們手動關閉幾個文件描述符。至于為什么是單向的,為什么要關閉,是否可以不關閉等問題,這里不做討論,因為上文已經介紹了。

我們今天的重點是放在如何實現上。


如何實現管道?通過前文的介紹,我們知道了基本操作是需要我們創建管道,使用pipe函數,創建好管道后,我們需要手動關閉兩個文件描述符,因為子進程會繼承父進程的文件描述符表,所以對于父進程來說,我們同樣需要關閉對應的文件描述符表。

對于0和1是讀還是寫,我們結合形狀吧,0是張開了嘴巴,所以是讀取,1是另一個。

我們從三個部分開始,第一個是創建管道,第二個是子進程寫入數據,第三個是父進程讀取數據。

初識Linux · 匿名管道如果成功創建了管道,返回的就是0,如果不等于0我們就可以cerr了。

    int pipefd[2];         int n = pipe(pipefd);         if(n)         {                 std::cerr 

創建管道部分,如果返回值不是0的話,也就是創建失敗了,所以我們打印出具體的錯誤信息,使用到的是前面學習到的errnostrerror,一個是錯誤碼,一個是錯誤碼對應的字符串,然后打印出0和1對應的文件描述符,就算是管道創建成功了。

現在就是子進程的寫入數據部分,我們寫對應的代碼之前,簡單思考一下大體的寫入思路是什么樣的?

首先是創建子進程,創建之后,關閉不需要的fd,然后子進程開始work,對應的工作做完之后,關閉掉對應的文件描述符,然后子進程退出,父進程回收即可,這個過程文件描述符肯定都是要關閉的,因為管道這個內存是一個引用計數的空間,所以如果不關閉,導致的結果就是內存泄漏,畢竟是空間都沒有釋放。

整體代碼為:

    //2.創建子進程         pid_t id = fork();         if(id == 0)         {                 //子進程開始準備工作                 std::cout 

然后就是子進程的subProcessWrite函數了:

std::string getOtherMessage(){         //消息次數         static int cnt = 0;         std::string message = std::to_string(cnt);         cnt++;         //子進程的pid         pid_t self_id  = getpid();         std::string stringpid = std::to_string(self_id);         std::string info = "messageid: ";         message += message;         message += " My pid is :";         message += stringpid;         return message; } void SubProcessWrite(int wfd){         int pipesize = 0;         std::string message = "Father,I am your son process! ";         char charactor = 'A';         while(true)         {                 std::cout 

寫入數據的同時通過cerr打印到顯示器上,并且寫入的時候我們通過函數GetOtherMessage獲取到子進程的Pid和寫入了多少次的字符串。

這是子進程的寫入函數部分。

子進程寫入完畢之后是父進程開始讀取數據:

void ProcessFatherRead(int rfd){         char inbuffer[SIZE];         while(true)         {                 //休眠一會兒開始讀取                 sleep(2);                 std::cout 

父進程使用函數read,這里不妨溫習一下read函數:

初識Linux · 匿名管道返回值是ssize_t,讀取count個字符,讀取到buf數組里面。

初識Linux · 匿名管道如果返回值是0,代表讀取到了文件的末尾,如果返回的是-1代表read出錯了,> 0的代表的是success。

然后是主函數的父進程開始讀取數據部分函數,大體思路仍然先關閉掉不需要的文件描述符,讀取完之后,需要等待子進程退出,為了收集子進程的退出信息,并且我們可以打印出來:

    //3.父進程開始讀取          std::cout 

初識Linux · 匿名管道目前看來是正常寫入,但是父進程是否讀取到了我們并不知道,所以我們打算讓子進程write到一定程度的時候break

    while(true)         {                 std::cout 

初識Linux · 匿名管道此時,子進程退出之后,子進程的狀態成功變成了僵尸狀態,我們將父進程的sleep時間縮短,準備讓父進程進行回收子進程。

匿名管道的介紹到這里就結束了,后面等著二刷。

感謝閱讀!

? 版權聲明
THE END
喜歡就支持一下吧
點贊7 分享