Вчера я пытался скомпилироватьКОРЕНЬпакет из исходников. Поскольку я компилировал его на 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://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
Ваша система может быть в порядке, но при параллельном запуске сборок может возникнуть состояние гонки .
Если с вашей системой что-то не так, она может зависнуть/выйти из строя в других сценариях, а не только при выполнении параллельных сборок.