昨天我正在尝试编译根来自源的包。由于我是在 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 从外壳中清空管道,那么构建将按预期恢复并完成......
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
您的系统可能没问题,但这可能是并行运行构建时发生的竞争条件。
如果您的系统出现问题,它会在其他情况下挂起/崩溃,而不仅仅是在进行并行构建时。