昨天我正在嘗試編譯根來自來源的包。由於我是在 6 核怪物機器上編譯它,所以我決定繼續使用make -j 6
.一開始編譯非常順利且非常快,但在某些時候make
僅在一個核心上使用 100% CPU 時就會掛起。
我做了一些谷歌搜尋並發現這在 ROOT 留言板上發文。由於我自己組裝了這台電腦,所以我擔心我沒有正確安裝散熱器,CPU 過熱或其他問題。不幸的是,我工作的地方沒有可以把它放進去的冰箱。
我安裝了該lm-sensors
軟體包並make -j 6
再次運行,這次監視 CPU 溫度。儘管溫度很高(接近 60°C),但從未超過高溫或臨界溫度。
我嘗試運行make -j 4
,但在編譯過程中的某個時候再次make
掛起,這次是在不同的位置。
最後我編譯了一下,運行了一下make
,效果很好。我的問題是:為什麼它掛了?由於它停在兩個不同的位置,我猜這是由於某種競爭條件,但我認為make
應該足夠聰明,讓一切都按正確的順序排列,因為它提供了選項-j
。
答案1
我沒有這個確切問題的答案,但我可以嘗試向您提供可能發生的情況的提示:Makefile 中缺少依賴項。
例子:
target: a.bytecode b.bytecode
link a.bytecode b.bytecode -o target
a.bytecode: a.source
compile a.source -o a.bytecode
b.bytecode: b.source
compile b.source a.bytecode -o a.bytecode
如果你調用make target
一切都會正確編譯。a.source
首先(任意但確定性地)執行的編譯。然後b.source
進行編譯。
但如果make -j2 target
兩個compile
命令將並行運行。你實際上會注意到你的 Makefile 的依賴關係被破壞了。第二次編譯假設a.bytecode
已經編譯,但它沒有出現在依賴項中。所以很可能會發生錯誤。正確的依賴行應該b.bytecode
是:
b.bytecode: b.source a.bytecode
回到你的問題,如果你不幸運,指令可能會因為缺少依賴項而掛在 100% CPU 迴圈中。這可能是這裡發生的情況,順序建構無法揭示缺少的依賴關係,但並行建構已經揭示了它。
答案2
我意識到這是一個非常老的問題,但它仍然出現在搜尋結果的頂部,所以這是我的解決方案:
GNU make 有一個 jobserver 機制來確保 make 及其遞歸子程序不會消耗超過指定數量的核心: http://make.mad-scientist.net/papers/jobserver-implementation/
它依賴所有進程共享的管道。每個想要分叉額外子程序的進程必須先消耗管道中的令牌,然後在完成後放棄它們。如果子進程不傳回它消耗的令牌,則頂層 make while 將永遠掛起,等待它們返回。
https://bugzilla.redhat.com/show_bug.cgi?id=654822
我在 Solaris 機器上使用 GNU make 建置 binutils 時遇到此錯誤,其中「sed」不是 GNU sed。透過修改 PATH 使 sed==gsed 優先於系統 sed 解決了該問題。不過,我不知道為什麼 sed 會消耗管道中的令牌。
答案3
make
似乎造成了僵局。使用ps -ef
,這些進程似乎是罪魁禍首:
根 695 615 1 22:18 ? 00:00:00 進行預先建置-j32 根 2127 695 20 22:18 ? 00:00:04 make -f Makefile.prenobuild
如果檢查每個進程正在做什麼,子進程正在寫入檔案描述符 4,而父進程正在等待所有子進程退出:
root@ltzj2-6hl3t-b98zz:/# strace -p 2127 strace:附加進程 2127 寫(4,“+”,1
root@ltzj2-6hl3t-b98zz:/# strace -p 695 strace:附加進程 695 {{等待4(-1, }}
文件描述符 4 恰好是一個管道:
root@ltzj2-6hl3t-b98zz:/# ls -la /proc/2127/fd/4 l-wx------ 1 root root 64 Sep 3 22:22 /proc/2127/fd/4 -> 'pipe:[1393418985]'
此管道僅位於父進程和子進程之間:
root@ltzj2-6hl3t-b98zz:/# lsof | grep 1393418985 使 695 根 3r FIFO 0,12 0t0 1393418985 管 使 695 根 4w FIFO 0,12 0t0 1393418985 管 使 2127 根 3r FIFO 0,12 0t0 1393418985 管 使 2127 根 4w FIFO 0,12 0t0 1393418985 管
因此,看起來 2127 試圖將輸出新增至管道回到 695,但 695 處於掛起狀態wait4()
,因此它永遠不會清空該管道。
如果我使用 cat 從 shell 中清空管道,那麼建置將按預期恢復並完成...
root@ltzj2-6hl3t-b98zz:/# cat /proc/695/fd/3 +++++++++++++++++++++++++++++++++
建置解除阻塞並繼續運行...
我最初的理解是錯誤的,但經過更多調查後,我最終發現了這個 Linux 核心缺陷:
關於如何掛起的確切解釋如下:https://lore.kernel.org/lkml/1628086770.5rn8p04n6j.none@localhost/。
您可以透過將以下解決方法應用於 gnu make 原始碼來解決這個等待內核補丁的問題:
--- a/src/posixos.c 2020-01-02 23:11:27.000000000 -0800 +++ b/src/posixos.c 2021-09-18 09:12:02.786563319 -0700 @@ -179,8 +179,52 @@ jobserver_release(int is_fatal) { 整數 r; - EINTRLOOP (r, write (job_fds[1], &token, 1)); - 如果 (r != 1) + 整數n; + 字元b[32]; + + /* 使用非阻塞寫入以避免多個 make 子程序造成的死鎖 + * 同時釋放作業。 */ + set_blocking(job_fds[1], 0); + memset(b,令牌,sizeof(b)); + n = 1; + 同時 ( n > 0 ) + { + r = 寫(job_fds[1], b, n); + /* 系統呼叫中斷,請重試 */ + 如果 ( r == -1 ) + { + if ( 錯誤編號 == EINTR ) + 繼續; + + /* 我們到達這裡是因為這個進程和另一個進程都試圖寫入管道 + * 完全相同的時間,且管道僅包含 1 頁。我們輸了,另一個 + * 進程獲勝(寫入管道)。我們只能先重置這個條件 + * 從管道讀取。當然,這意味著我們需要額外回傳一個 + * 令牌。 */ + if ( errno == EWOULDBLOCK || errno == EAGAIN ) + { + if ( jobserver_acquire(0) ) + { + n++; + /* 可能接近不可能... */ + 如果 (n > 32) + 中斷; + 繼續; + } + } + } + if ( r == 0 ) /* 寫入了 0 個位元組,但沒有錯誤,請重試 */ + 繼續; + 如果 ( r > 0 ) + { + n -= r; + 繼續; + } + 中斷; /* 所有其他錯誤,中斷。 */ + } + set_blocking(job_fds[1], 1); + + 如果 (n != 0) { 如果(是致命的) pfatal_with_name (_("寫入作業伺服器"));
答案4
make
您的系統可能沒問題,但這可能是並行運行建置時發生的競爭條件。
如果您的系統出現問題,它會在其他情況下掛起/崩潰,而不僅僅是在進行並行建置時。