Pipe

讀取直到管道關閉

  • March 10, 2022

我現在正在做作業以介紹作業系統,我很開心,但同時也很困惑。我現在正在研究管道;我在下面的程式碼。

最初,我的程式碼如下所示:

// Child process - write
if (fork() == 0) {
   fprintf(stderr, "Child\r\n");
   close(1);
   dup(p[1]);
   close(p[0]);
   close(p[1]);
   runcmd(pcmd->left);
// Parent process - read
} else {
   wait(0);
   close(0);
   dup(p[0]);
   close(p[0]);
   close(p[1]);
   fprintf(stderr, "Parent\r\n");
   runcmd(pcmd->right);
}

我對此的思考過程是,父母會等到孩子被終止,然後從管道中讀取,就是這樣。我在我們的討論頁面上將此程式碼發布給了我的講師,他告訴我該程式碼存在幾個問題,其中之一是:

  1. 如果子程序在足夠長的輸入上執行以阻塞管道,則父程序可能會無限掛起。

他提到,因此(關於wc)正確的實現將是使用阻塞讀取命令,該命令將在管道上等待直到數據可用,然後開始讀取直到管道關閉。

當管道中有數據時,我試圖四處尋找某種方式從管道中“讀取”,但不確定如何繞過它。最後,為了嘗試解決在阻塞管道上可能永遠等待的問題,我讓父子程序同時並行執行,但這可能意味著讀取過程可能首先終止而不是讀入寫入前的所有數據都已完成。我將如何解決這個問題?

   int p[2];
   pipe(p);
   // Child process - read
   if (fork() == 0) {
       fprintf(stderr, "Start child\r\n");
       close(0);
       dup(p[0]);
       close(p[0]);
       close(p[1]);
       fprintf(stderr, "Child\r\n");
       runcmd(pcmd->right);
   // Parent process - write
   } else {
       fprintf(stderr, "Start parent\r\n");
       close(1);
       dup(p[1]);
       close(p[0]);
       close(p[1]);
       fprintf(stderr, "Parent\r\n");
       runcmd(pcmd->left);
  }

編輯:我也嘗試了該read命令,但不確定如何實際使用它,因為它需要緩衝區,以及要讀取的預期大小(?)。當您不知道傳入數據的大小時,我不確定如何檢索其中任何一個。

管道很簡單。跳進深水池會讓你很難受。(或者也許是你的教練沒有更好地指導你的錯。)

為了更熟悉管道,我建議您編寫兩個非常簡單的程序:

  1. 一種只是將一些文本寫入標準輸出並退出。它可以很簡單——“快速的棕色狐狸跳過懶狗。”, “Lorem ipsum dolor sit amet, consectetur adipiscing elit, …”,一個短字元串(甚至可能是一個字元)重複了很多次——任何你想要的. 使用printfwritefprintf(stdout, …)或您喜歡的任何其他功能。

要測試這個程序,只需在 shell 提示符下執行它。它應該顯示選擇的文本並退出(返回到您的 shell 提示符)。 2. 一種只是從標準輸入讀取文本並將其寫入標準輸出。使用getcgetsread或您喜歡的任何其他功能。當你得到文件結束時退出。檢查手冊頁以了解您使用的任何功能,以查看它如何指示文件結束。

要測試這個程序,請創建一個文本文件(稱為類似jon_file.txt)並將一些文本放入其中。您可以通過說出類似的話來快速完成此操作echo "Hello world" > jon_file.txt,或者您可以使用編輯器。然後鍵入prog2 < jon_file.txt。它應該顯示文件的內容並退出(返回到 shell 提示符)。

不要呼叫pipe,dup或任何花哨的東西——甚至不要呼叫openor close。(一定要包含任何你想要的調試和/或審計程式碼,以確保你了解什麼時候發生的事情。)然後執行prog1 | prog2. 如果你做對了,你會得到你期望的輸出。

現在嘗試通過添加sleep對程序的呼叫來“破壞”它。如果你打破它,讓我知道你是怎麼做到的。這幾乎是不可能的——除非你讓一個程序(或兩個程序)的睡眠時間超過你願意坐下來等待的時間,否則你總是可以prog2輸出所有prog1寫入的數據。

如果上面的例子沒有說清楚:讓父子程序(或者,一般來說,管道兩側的程序)“同時”執行是正確的做法。1讀取程序不會因為目前  管道中沒有數據而“先終止” 。正如你應該從上面的練習中學到的,如果一個程序試圖從一個目前沒有數據的管道中讀取,read系統呼叫將強製程序等待直到數據到達。讀取程序不會終止,直到管道中沒有數據 並且*不再有數據。*2   (此時,read將返回一個文件結尾。)“不再有數據到來”條件由編寫程序關閉管道(或退出,這是等效的,因為exit呼叫close所有打開的文件描述符)指示。

我不明白你為什麼read在這一點上為系統呼叫出汗——不過,如果你還不知道如何使用它,這證實了我的懷疑,即你的講師呈現的材料不合邏輯。(我假設您的意思是read系統呼叫而不是read命令。)您的程序有意義的唯一方法是 ifruncmd(pcmd->right)是通過某種方法從標準輸入中讀取的內容(例如prog2上面的程序)。看起來你的程序只是在做 shell 的功能——設置管道,然後讓程序執行。在那個級別上,您的程序(在您向我們展示的範圍內)沒有理由進行任何 I/O(讀取或寫入)。


1相關閱讀:管道命令以什麼順序執行?

2當然,這是過於簡單化了。正如您將很快了解到的,如果您還沒有,您可以設計讀取程序以在目前管道中沒有數據時終止——但這不是預設行為。或者,您可以將讀取程序設計為在任意數量的其他條件下終止——例如,如果它q從管道中讀取 a。或者它可能被信號殺死,等等……


六個月後我回顧這個答案,我發現我真的沒有解決整個問題。我報導了下半場,但沒有報導上半場。所以,從上面繼續,

  1. 修改第一個程序,將大量數據——至少 100,000 (10 5 ) 或 102400 (2 10 ×10 2 ) 個字元——寫入標準輸出。此外,如果您還沒有這樣做,請修改它以將一些正在進行的狀態資訊寫入 stderr。這可能非常簡單;例如,**.**每 1000(或 1024)個字元到 stdout,一個“ ”到 stderr,完成後到 stderr一個“ ” !\n

要對此進行測試,請執行prog1 > /dev/null. 如果您按照我的建議(上圖),您應該會看到 100 個點 ( .),然後是!一個換行符。如果您沒有對 中的任何呼叫sleep() 或其他耗時的函式prog1,則此輸出應該會很快出現。

然後執行prog1 | wc -c。如上所述,它應該顯示您的 stderr 狀態資訊,然後是 您寫入標準輸出的字節數100000102400(這將是 的輸出wc -c,報告它從其標準輸入(管道)讀取了多少字節。) 2. 將第二個程序修改為sleep開始讀取前 10 或 20 秒。

要對此進行測試,請prog2 < jon_file.txt再次執行。顯然,它應該暫停您在 中指定的時間sleep(),然後顯示文件的內容並退出(返回到您的 shell 提示符)。

現在執行prog1 | prog2 > /dev/null。但是,在你這樣做之前,你可能想嘗試猜測會發生什麼。

    ︙

    ︙

    ︙

我希望它會列印一些點——可能是 8、可能是 64 或 65,也可能是其他數字——然後是暫停,然後是其餘的點,然後是!. 這是因為prog1可以立即開始寫,即使prog2還沒有讀。管道可以保存數據,直到prog2準備好開始讀取——但只能達到一個點。管道有緩衝限制。這可能是 8000(或 8192)、64000(或 65536)或其他一些數字。當管道已滿時,系統將強制prog1等待。當prog2開始讀取時,它會排空管道;這為管道騰出空間來保存更多數據,因此prog1允許再次開始寫入。

如果您一開始沒有看到上述行為,請嘗試增加數字:200,000 字節、30 秒等。

所以當你的老師批評你的程序的初稿時,他是部分正確的。(或者,也許他是完全正確的,而你錯誤地引用了他的話。)正如你所理解的,該版本的程序等待runcmd(pcmd->left)程序(管道編寫器)完成,然後它會啟動runcmd(pcmd->right)(管道閱讀器)。但是如果左邊的程序輸出 100,000 個字節呢?它會填滿管道,然後等到它可以再寫一些。但是在“某人”從管道中讀取數據並耗盡儲存緩衝區之前,它將無法寫入更多內容。但是在管道寫入器完成之前,主程序不會啟動管道讀取器。每個人都在等待其他人做某事,直到第一個人做了某事,他們才會這樣做。(“只要你給我錢,我就給你珠寶。”/“不,我會**在你給我珠寶之後給你錢。”)所以,是的;底線:如果數據停止通過管道移動,因為它已滿並且沒有程序正在從中讀取,那麼兩個程序將無限掛起。

這種情況在文化上被隨意稱為Catch-22。在電腦科學中,它正式稱為死鎖,非正式地稱為致命擁抱

引用自:https://unix.stackexchange.com/questions/397553