Bash

Bash:PID 什麼時候完全釋放?

  • September 4, 2019

**免責聲明:**這個問題出現的時間比預期的要長得多。我把它分成5個子問題。在打開它之前我真的很想澄清我的想法,但太多的方面讓我感到困惑。


為了弄清楚如何以可靠的方式正確處理 Bash 中的程序,我偶然發現了這篇 Greg 的 Wiki 文章。在那裡,而不是在開始時,有這個聲明

如果您仍處於啟動您想要做某事的子程序的父程序中,那就完美了。您可以保證 PID 是您的子程序(死或活),原因如下所述。您可以使用kill它發出信號、終止它,或者只是檢查它是否仍在執行。您可以使用wait它來等待它結束或在它結束時獲取它的退出程式碼。

在頁面的最後,找到了下面解釋的上述原因

每個 UNIX 程序也有一個父程序。此父程序是啟動它的程序,但init如果父程序在新程序之前結束,則可以更改為該程序。(也就是說,init將選擇孤立的程序。)了解這種父/子關係至關重要,因為它是 UNIX 中可靠程序管理的關鍵。程序死亡後,程序的 PID 將永遠​​不會被釋放以供使用,直到父程序wait的 PID 來查看它是否結束並檢索其退出程式碼。如果父程序結束,該過程將返回到init,它會為您執行此操作。

這一點很重要:如果父程序管理它的子程序,那麼可以絕對肯定的是,即使子程序死了,在父程序wait為此處理之前,沒有其他新程序可以意外回收子程序的 PID PID 並註意到孩子死了。這為父程序提供了保證,它為子程序提供的 PID 將始終指向該子程序,無論它是活著的還是“殭屍”。沒有其他人有這樣的保證。

不幸的是,此保證不適用於 shell 腳本。Shell 積極地獲取其子程序並將退出狀態儲存在記憶體中,在呼叫wait. 但是因為在你呼叫之前孩子已經被收割了wait,所以沒有殭屍持有PID。核心可以自由地重用該 PID,而您的保證已被違反。


到目前為止,我已經多次閱讀以上段落,但我仍然不確定我是否正確掌握了它背後的資訊。

**問題 1:**從第二個長引用,特別是最後一段,我會得出結論,在 shell(我只對 Bash 感興趣)腳本中,我不能 100% 確定我儲存在變數中的 PID 仍然是指我啟動的後台程序,因為它可能被核心重用於任何其他程序(甚至不是子程序)。它是否正確?上述保證適用於系統中的什麼地方?

**問題2:*第二句的最後一段似乎與第一句矛盾。一般來說,在 shell 腳本中是否真的“如果您仍在啟動子程序的父程序中$$ … $$您保證 PID 是您的子程序(死或活)”*?

**問題 3:**我試圖在網路上找到有關此主題的其他資源,並且一如既往,很難區分真相和不准確的陳述。我得到了一些確認,但也有一些疑問。參考這個這個問題,似乎在後台啟動腳本中的程序,將其 PID 儲存在變數中,做一些事情,然後結合使用 PIDwait來獲取其退出程式碼或kill發送信號可能會由於 PID 核心的重用而失敗。有通用食譜嗎?

**問題 4:**我還發現這條評論建議“讓後台(程序)將返回程式碼儲存在一個文件中,並讓父級從文件中獲取它”。這是可靠的方法嗎?

**問題 5:**關於 的使用是否有註意事項wait -n?我認為,如果我沒有明確地將(可能重用)PID 提供給wait,則不會發生任何錯誤。但是,在 Bash v4.4 中,啟用作業控制的選項似乎很有用,. 在 Bash v5.0 中仍然如此嗎?-n``wait``set -m

獎勵問題: 這個答案說的類似於 Greg 的 Wiki。

只有一種情況可以安全地使用 pid 發送信號:當目標程序是發送信號的程序的直接子程序,而父程序尚未等待它時。

什麼是子?跟小孩子有區別嗎?

…在 shell(我只對 Bash 感興趣)腳本中,我不能 100% 確定我儲存在變數中的 PID 仍然指的是我啟動的後台程序,因為它可能會被核心重用於任何其他程序。 ..

正確的。

shell 的程式方式是,一旦子程序死亡,shell 將立即呼叫wait()它(將終止狀態儲存為其內部狀態的一部分),這將釋放 PID 以供另一個程序重用。

在 shell 腳本中是否真的*“如果您仍在啟動子程序的父程序中$$ … $$您保證 PID 是您的子程序(死或活)”*?

不,這不是真的。

因為,如前所述(以及引用中),shell 本身將立即獲得子程序,這基本上破壞了該保證。

在腳本中的後台啟動程序,將其 PID 儲存在變數中,做一些事情,然後將 PID 與 wait 結合使用以獲取其退出程式碼或與 kill 發送信號的天真方式可能會由於重用而失敗PID的核心。

使用外殼,這是你能做的最好的事情。

請注意, usingwait並不是真正的問題,只是 using kill,因為您的子程序可能已經死亡,PID 已被重用並且您正在殺死另一個程序。

wait本身是在 shell 中實現的。當它獲取子程序時,它將終止狀態儲存在記憶體中,因此它可以wait使用該資訊實現其內置(以及等待仍在執行的子程序。)

另請注意,核心通常會盡量避免重用 PID,至少嘗試延遲重用 PID,這正是因為在某些情況下無法保證 PID 未被重用,因此核心會盡量減少這種情況一個信號將被傳遞到錯誤的程序。

有通用食譜嗎?

為了可靠性?

是的,在 C 或 Python、Perl、Ruby 等中實現啟動後台程序的程式碼。不在 shell 中。

那些不會有這個問題,因為預設情況下它們不會像 shell 那樣收穫孩子,你必須在那裡明確地做到這一點。

或者考慮使用系統管理器(例如 systemd)啟動後台程序。

“讓後台(程序)將返回程式碼儲存在一個文件中,並讓父級從文件中獲取它”。這是可靠的方法嗎?

也許。

您對那裡沒有乾擾的保證較少。很難有一個只有單個程序可以寫入而沒有其他程序可以寫入的位置。

呼叫並非如此wait,核心確保它不會被不同的程序偽造。

此外,該wait呼叫還可以告訴您有關程序被殺死甚至崩潰的資訊,在這種情況下,如果您依賴程序本身將其返回狀態記錄在文件中,您可能會得到不完整的資訊……

此外,PID 重用的主要問題是殺死該 PID,通過 獲取返回程式碼確實沒有問題wait,並且kill使用文件儲存返回程式碼並沒有真正解決問題。

有關於使用的警告 wait -n嗎?

並不真地。wait是可靠的,並且 AFAICT 不受 PID 重用的影響,因為當 shell 獲得一個孩子時,它會保留該資訊,包括正在使用的 PID 和返回程式碼,作為其內部狀態的一部分。

當您呼叫 時wait,您將從該表中獲取資訊。

我認為如果 PID 被同一個 shell 的新背景子級重用,那麼可能存在一個潛在問題,之前wait已經在第一個實例上呼叫過,從那時起,該表中會發生衝突,你最終會得到兩個使用相同的 PID 分離後台程序。這是一個極端案例,我想這是非常罕見的,但可能是真實的。不太確定外殼在這些情況下會做什麼……它也可能取決於外殼的實現,並且可能因版本而異。

如上所述,這個問題的真正解決方案是保持關於 PID 存在的保證,當這些保證對您很重要時,使用 shell 以外的東西。

什麼是子?跟小孩子有區別嗎?

這和孩子一樣。

這是一個你自己分叉的孩子。

例如,如果您的子程序派生出一個程序並將該程序的 PID 傳遞給您,您將無法保證它會繼續存在。

既然獲得該過程是您孩子的工作,那麼您的孩子可以保證在他們獲得之前不會重複使用 PID。不是你的。

當然,父程序可以與子程序協調以擴展該保證,例如通過阻止它在查詢子程序該 PID 是否仍然是它所期望的那個時阻止它獲取任何子程序,然後向它發送一個信號,或者可能通過詢問孩子(有保證)代表父母發送信號。

希望這將有助於清除它。

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