Что может быть причиной зависания make при компиляции на нескольких ядрах?

Что может быть причиной зависания make при компиляции на нескольких ядрах?

Вчера я пытался скомпилироватьКОРЕНЬпакет из исходников. Поскольку я компилировал его на 6-ядерной машине-монстре, я решил пойти дальше и собрать с использованием нескольких ядер с помощью make -j 6. Компиляция прошла гладко и очень быстро поначалу, но в какой-то момент makeзависла, используя 100% CPU только на одном ядре.

Я немного погуглил и нашелэтотпост на форумах ROOT. Так как я сам собрал этот компьютер, я беспокоился, что не правильно установил радиатор и процессор перегревается или что-то в этом роде. К сожалению, у меня на работе нет холодильника, куда я мог бы его воткнуть. ;-)

Я установил lm-sensorsпакет и запустил make -j 6снова, на этот раз отслеживая температуру процессора. Хотя она и поднялась (почти до 60 C), она так и не поднялась выше высокой или критической температуры.

Я попробовал запустить make -j 4, но снова makeзавис в какой-то момент компиляции, на этот раз в другом месте.

В конце концов, я скомпилировал просто запуск, makeи все заработало нормально. Мой вопрос: почему он завис? Из-за того, что он остановился в двух разных местах, я бы предположил, что это было из-за какого-то состояния гонки, но я думаю, что он makeдолжен быть достаточно умен, чтобы привести все в правильный порядок, поскольку он предлагает такую -j​​возможность.

решение1

У меня нет точного ответа на этот вопрос, но я могу попытаться подсказать, что может происходить: отсутствуют зависимости в Makefiles.

Пример:

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 имеет механизм сервера заданий, который гарантирует, что make и его рекурсивные дочерние процессы не будут потреблять больше указанного количества ядер: http://make.mad-scientist.net/papers/jobserver-implementation/

Он опирается на канал, который разделяют все процессы. Каждый процесс, который хочет разветвить дополнительных потомков, должен сначала потребить токены из канала, а затем отказаться от них по завершении. Если дочерний процесс не возвращает потребленные токены, то верхний уровень make while зависает навсегда в ожидании их возврата.

https://bugzilla.redhat.com/show_bug.cgi?id=654822

Я столкнулся с этой ошибкой при сборке binutils с помощью GNU make на моем компьютере Solaris, где "sed" не является GNU sed. Игра с PATH, чтобы сделать sed==gsed приоритетнее системного sed, решила проблему. Я не знаю, почему sed потреблял токены из конвейера.

решение3

makeкажется, создает тупик. Используя ps -ef, эти процессы кажутся виновниками:

root 695 615 1 22:18 ? 00:00:00 сделать PREBUILD -j32
root 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 присоединен
{{wait4(-1, }}

Файловый дескриптор 4 представляет собой канал:

root@ltzj2-6hl3t-b98zz:/# ls -la /proc/2127/fd/4
l-wx------ 1 корень корень 64 3 сен 22:22 /proc/2127/fd/4 -> 'pipe:[1393418985]'

и этот канал существует только между родительским и дочерним процессами:

root@ltzj2-6hl3t-b98zz:/# lsof | grep 1393418985
сделать 695 корень 3r FIFO 0,12 0t0 1393418985 труба
сделать 695 root 4w FIFO 0,12 0t0 1393418985 труба
сделать 2127 корень 3r FIFO 0,12 0t0 1393418985 труба
сделать 2127 root 4w FIFO 0,12 0t0 1393418985 труба

Итак, похоже, что 2127 застрял, пытаясь добавить вывод в канал обратно к 695, но 695 находится в состоянии ожидания wait4(), поэтому он никогда не опустошит этот канал.

Если я опорожню трубу из оболочки с помощью cat, то сборка возобновится и завершится, как и ожидалось...

root@ltzj2-6hl3t-b98zz:/# cat /proc/695/fd/3
++++++++++++++++++++++++++++++++++++

Сборка разблокируется и продолжит работу...


Первоначально я не понимал, в чем дело, но после более тщательного изучения я в конечном итоге пришел к следующему дефекту ядра Linux:

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=46c4c9d1beb7f5b4cec4dd90e7728720583ee348

Точное объяснение того, как это сделать, можно найти здесь: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 (целое is_fatal)
 {
   целочисленный г;
- EINTRLOOP (r, запись (job_fds[1], &token, 1));
- если (р != 1)
+ целое число n;
+ символ b[32];
+
+ /* Используйте неблокируемую запись, чтобы избежать взаимоблокировки из-за множественных дочерних элементов
+ * освобождение заданий в одно и то же время. */
+ установить_блокировку (job_fds[1], 0);
+ memset(b,токен,размер(b));
+ н = 1;
+ пока ( n > 0 )
+ {
+ r = запись (job_fds[1], b, n);
+ /* Прерванный системный вызов, попробуйте еще раз */
+ если ( г == -1 )
+ {
+ если ( errno == EINTR )
+ продолжить;
+
+ /* Мы попали сюда, потому что этот процесс и другой оба пытались записать в канал в
+ * точно в то же время, и труба содержит только 1 страницу. Мы потеряли, другой
+ * процесс выиграл (записал в конвейер). Мы можем сбросить это состояние только сначала
+ * чтение из трубы. Конечно, это означает, что нам затем нужно вернуть дополнительный
+ * токен. */
+ если ( errno == EWOULDBLOCK || errno == EAGAIN )
+ {
+ если ( jobserver_acquire(0) )
+ {
+ н++;
+ /* Вероятно, это почти невозможно... */
+ если (n > 32)
+ перерыв;
+ продолжить;
+ }
+ }
+ }
+ if ( r == 0 ) /* Записано 0 байт, но это не ошибка, попробуйте еще раз */
+ продолжить;
+ если ( г > 0 )
+ {
+ н -= г;
+ продолжить;
+ }
+ break; /* Все остальные ошибки, break. */
+ }
+ установить_блокировку (job_fds[1], 1);
+
+ если (н != 0)
     {
       если (фатально)
         pfatal_with_name (_("записать сервер заданий"));

решение4

makeВаша система может быть в порядке, но при параллельном запуске сборок может возникнуть состояние гонки .

Если с вашей системой что-то не так, она может зависнуть/выйти из строя в других сценариях, а не только при выполнении параллельных сборок.

Связанный контент