O que poderia estar causando o travamento do make ao compilar em vários núcleos?

O que poderia estar causando o travamento do make ao compilar em vários núcleos?

Ontem eu estava tentando compilar oRAIZpacote da fonte. Como eu estava compilando em uma máquina monstro de 6 núcleos, decidi prosseguir e construir usando vários núcleos usando make -j 6. A compilação foi suave e muito rápida no início, mas em algum momento maketravou usando 100% da CPU em apenas um núcleo.

Eu pesquisei no Google e encontreiessepostar nos painéis de mensagens ROOT. Como eu mesmo construí este computador, fiquei preocupado por não ter aplicado o dissipador de calor corretamente e a CPU estar superaquecendo ou algo assim. Infelizmente, não tenho uma geladeira aqui no trabalho onde possa enfiá-la. ;-)

Instalei o lm-sensorspacote e executei make -j 6novamente, desta vez monitorando a temperatura da CPU. Embora tenha subido (perto de 60 C), nunca passou da temperatura alta ou crítica.

Tentei executar make -j 4, mas maketravei novamente em algum momento durante a compilação, desta vez em um local diferente.

No final, compilei apenas rodando makee funcionou bem. Minha pergunta é: por que estava pendurado? Devido ao fato de ter parado em dois pontos diferentes, acho que foi devido a algum tipo de condição de corrida, mas acho que makedeveria ser inteligente o suficiente para colocar tudo na ordem certa, já que oferece essa -jopção.

Responder1

Não tenho uma resposta para esse problema específico, mas posso tentar dar uma dica do que pode estar acontecendo: Dependências ausentes em Makefiles.

Exemplo:

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

Se você ligar, make targettudo será compilado corretamente. A compilação a.sourceé executada (arbitrariamente, mas deterministicamente) primeiro. Em seguida, a compilação b.sourceé executada.

Mas se vocês make -j2 targetdois compilecomandos forem executados em paralelo. E você realmente notará que as dependências do seu Makefile estão quebradas. A segunda compilação assume a.bytecodeque já está compilada, mas não aparece nas dependências. Portanto, é provável que aconteça um erro. A linha de dependência correta b.bytecodedeve ser:

b.bytecode: b.source a.bytecode

Voltando ao seu problema, se você não tiver sorte, é possível que um comando trave em um loop de 100% da CPU, devido a uma dependência ausente. Provavelmente é isso que está acontecendo aqui, a dependência ausente não pôde ser revelada por uma construção sequencial, mas foi revelada por sua construção paralela.

Responder2

Sei que esta é uma pergunta muito antiga, mas ainda aparece no topo dos resultados da pesquisa, então aqui está a minha solução:

GNU make possui um mecanismo de servidor de trabalho para garantir que make e seus filhos recursivos não consumam mais do que o número especificado de núcleos: http://make.mad-scientist.net/papers/jobserver-implementation/

Depende de um canal compartilhado por todos os processos. Cada processo que deseja bifurcar filhos adicionais deve primeiro consumir tokens do pipe e, em seguida, abandoná-los quando terminar. Se um processo filho não retornar os tokens que consumiu, o make while travará para sempre esperando que eles sejam retornados.

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

Encontrei este erro ao construir binutils com GNU make na minha caixa Solaris, onde "sed" não é GNU sed. Mexer no PATH para fazer sed==gsed ter prioridade sobre o sistema sed corrigiu o problema. Eu não sei por que o sed estava consumindo tokens do cano.

Responder3

makeparece criar um impasse. Usando ps -ef, esses processos parecem ser os culpados:

raiz 695 615 1 22:18 ? 00:00:00 fazer PRÉ-CONSTRUÇÃO -j32
raiz 2127 695 20 22:18 ? 00:00:04 make -f Makefile.prenobuild

Se você verificar o que cada um está fazendo, o processo filho está gravando no descritor de arquivo 4 e o processo pai está aguardando a saída de todos os processos filhos:

root@ltzj2-6hl3t-b98zz:/# strace -p 2127
strace: Processo 2127 anexado
escreva(4, "+", 1
root@ltzj2-6hl3t-b98zz:/# strace -p 695
strace: Processo 695 anexado
{{espera4(-1, }}

o descritor de arquivo 4 é um canal:

root@ltzj2-6hl3t-b98zz:/# ls -la /proc/2127/fd/4
l-wx ------ 1 raiz raiz 64 3 de setembro 22:22 /proc/2127/fd/4 -> 'pipe:[1393418985]'

e esse canal é apenas entre os processos pai e filho:

root@ltzj2-6hl3t-b98zz:/#lsof | grep1393418985
faça 695 raiz 3r FIFO 0,12 0t0 1393418985 tubo
fazer 695 raiz 4w FIFO 0,12 0t0 1393418985 tubo
fazer 2127 raiz 3r FIFO 0,12 0t0 1393418985 tubo
fazer 2127 raiz 4w FIFO 0,12 0t0 1393418985 tubo

então, parece que 2127 está preso tentando adicionar saída ao pipe de volta para 695, mas 695 está pendente wait4(), então ele nunca vai esvaziar esse pipe.

Se eu esvaziar o pipe do shell usando cat, a compilação será retomada e concluída conforme o esperado...

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

A compilação é desbloqueada e continua em execução...


Meu entendimento original estava errado, mas depois de mais investigações acabei descobrindo este defeito do kernel do Linux:

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

Uma explicação exata de como isso trava está aqui:https://lore.kernel.org/lkml/1628086770.5rn8p04n6j.none@localhost/.

Você pode contornar isso enquanto se aguarda um patch do kernel com a seguinte solução alternativa aplicada ao código-fonte do 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 é_fatal)
 {
   int r;
- EINTRLOOP (r, escrever (job_fds[1], &token, 1));
- se (r! = 1)
+intn;
+ caractere b[32];
+
+ /* Use gravação sem bloqueio para evitar deadlock de múltiplos filhos make
+ *liberando jobs ao mesmo tempo. */
+ set_blocking (job_fds[1], 0);
+ memset(b,token,tamanho(b));
+ n = 1;
+ enquanto (n > 0)
+ {
+ r = escrever (job_fds[1], b, n);
+ /* Chamada de sistema interrompida, tente novamente */
+ se (r == -1)
+ {
+ if ( errno == EINTR )
+ continuar;
+
+ /* Chegamos aqui porque este processo e outro tentaram escrever no pipe em
+ * exatamente ao mesmo tempo, e o pipe contém apenas 1 página. Nós perdemos, o outro
+ * processo vencido (escreveu no pipe). Só podemos redefinir esta condição primeiro
+ * leitura do cano. Claro, isso significa que precisaremos devolver um extra
+ * ficha. */
+ if ( errno == EWOULDBLOCK || errno == EAGAIN )
+ {
+ if (jobserver_acquire(0))
+ {
+n++;
+ /* Provavelmente quase impossível... */
+ se (n > 32)
+ pausa;
+ continuar;
+ }
+ }
+ }
+ if ( r == 0 ) /* Escreveu 0 bytes, mas não houve erro, tente novamente */
+ continuar;
+ se ( r > 0 )
+ {
+ n -= r;
+ continuar;
+ }
+ pausa; /* Todos os outros erros, break. */
+ }
+ set_blocking (job_fds[1], 1);
+
+ se (n! = 0)
     {
       se (é_fatal)
         pfatal_with_name (_("escrever servidor de trabalho"));

Responder4

seu sistema pode estar ok, mas pode ser uma condição de corrida acontecendo makeao executar compilações em paralelo.

Se algo estiver errado com seu sistema, ele travará/travará em outros cenários, não apenas ao fazer compilações paralelas.

informação relacionada