Por que essas bombas bash fork funcionam de maneira diferente e qual é o significado de & nele?

16

Entendo como uma bomba de forquilha normal funciona, mas realmente não entendo por que o & no final da bomba de forquilha comum é necessária e por que esses scripts se comportam de maneira diferente:

:(){ (:) | (:) }; :

e

:(){ : | :& }; :

O primeiro causa um aumento no uso da CPU antes de me lançar de volta à tela de login. O último apenas faz com que meu sistema congele, forçando-me a reinicializar com força. Por que é que? Ambos criam continuamente novos processos, então por que o sistema se comporta de maneira diferente?

Ambos os scripts também se comportam de maneira diferente de

:(){ : | : }; :

o que não causa nenhum problema, mesmo que eu esperasse que eles fossem iguais. A página do manual do bash afirma que os comandos em um pipeline já foram executados em um subshell, então sou levado a acreditar que: | : já deve ser suficiente. Eu acredito e deveria apenas executar o pipeline em um novo subshell, mas por que isso muda tanto?

Edit: Usando htop e limitando a quantidade de processos, eu pude ver que a primeira variante cria uma árvore de processos real, a segunda variante cria todos os processos no mesmo nível e a última variante não parece criar nenhum processo em absoluto. Isso me confunde ainda mais, mas talvez ajude de alguma forma?

Dan K.
fonte
2
Penso que a sua última variante está faltando um ponto e vírgula::(){ : | :; }; :
adonis

Respostas:

22

AVISO NÃO TENTE EXECUTAR ISTO EM UMA MÁQUINA DE PRODUÇÃO. APENAS NÃO. Aviso: Para tentar qualquer "bomba", verifique se ulimit -uestá em uso. Leia abaixo [a] .

Vamos definir uma função para obter o PID e a data (hora):

bize:~$ d(){ printf '%7s %07d %s\n' "$1" "$BASHPID" "$(date +'%H:%M:%S')"; }

Uma função simples e sem problemas bombpara o novo usuário (proteja-se: leia [a] ):

bize:~$ bomb() { d START; echo "yes"; sleep 1; d END; } >&2

Quando essa função é chamada para ser executada, funciona assim:

bize:~$ bomb
  START 0002786 23:07:34
yes
    END 0002786 23:07:35
bize:~$

O comando dateé executado, um "sim" é impresso, um sono por 1 segundo, o comando de fechamento datee, finalmente, a função sai da impressão de um novo prompt de comando. Nada chique.

| tubo

Quando chamamos a função assim:

bize:~$ bomb | bomb
  START 0003365 23:11:34
yes
  START 0003366 23:11:34
yes
    END 0003365 23:11:35
    END 0003366 23:11:35
bize:~$

Dois comandos começar em algum momento, os dois terminam 1 segundo mais tarde e , em seguida, o prompt retornos.

Essa é a razão do canal |, para iniciar dois processos em paralelo.

& fundo

Se mudarmos a chamada, adicionamos um final &:

bize:~$ bomb | bomb &
[1] 3380
bize:~$
  START 0003379 23:14:14
yes
  START 0003380 23:14:14
yes
    END 0003379 23:14:15
    END 0003380 23:14:15

O prompt retorna imediatamente (toda a ação é enviada para o plano de fundo) e os dois comandos são executados como antes. Observe o valor do "número do trabalho" [1]impresso antes do PID do processo 3380. Posteriormente, o mesmo número será impresso para indicar que o tubo terminou:

[1]+  Done                    bomb | bomb

Esse é o efeito de & .

Essa é a razão da & : para iniciar os processos mais rapidamente.

Nome mais simples

Podemos criar uma função chamada simplesmente bpara executar os dois comandos. Digitado em três linhas:

bize:~$ b(){
> bomb | bomb
> }

E executado como:

bize:~$ b
  START 0003563 23:21:10
yes
  START 0003564 23:21:10
yes
    END 0003564 23:21:11
    END 0003563 23:21:11

Observe que não usamos no ;na definição de b(as novas linhas foram usadas para separar elementos). No entanto, para uma definição em uma linha, é comum usar ;, assim:

bize:~$ b(){ bomb | bomb ; }

A maioria dos espaços também não é obrigatória, podemos escrever o equivalente (mas menos claro):

bize:~$ b(){ bomb|bomb;}

Também podemos usar a &para separar o }(e enviar os dois processos para o plano de fundo).

A bomba

Se fizermos a função morder sua cauda (chamando a si mesma), obteremos a "bomba de forquilha":

bize:~$ b(){ b|b;}       ### May look better as b(){ b | b ; } but does the same.

E para torná-lo mais rápido, envie o canal para o segundo plano.

bize:~$ b(){ b|b&}       ### Usually written as b(){ b|b& }

Se anexarmos a primeira chamada à função após um requisito ;e alterar o nome para :obtermos:

bize:~$ :(){ :|:&};:

Geralmente escrito como :(){ :|:& }; :

Ou, escrito de uma maneira divertida, com outro nome (um homem da neve):

☃(){ ☃|☃&};☃

O ulimit (que você deveria ter definido antes de executar isso) fará com que o prompt retorne rapidamente depois de muitos erros (pressione enter quando a lista de erros parar para obter o prompt).

A razão para isso ser chamado de "fork pump" é que a maneira pela qual o shell inicia um sub shell é bifurcando o shell em execução e depois chamando exec () para o processo bifurcado com o comando para executar.

Um tubo "bifurcará" dois novos processos. Fazer isso até o infinito causa uma bomba.
Ou um coelho como era originalmente chamado porque se reproduz muito rapidamente.


Cronometragem:

  1. :(){ (:) | (:) }; time :
    Terminados
    0m45.627s reais

  2. :(){ : | :; }; time :
    Terminados
    0m15.283s reais

  3. :(){ : | :& }; time :
    0m00.002 s reais
    ainda em execução


Seus exemplos:

  1. :(){ (:) | (:) }; :

    Onde o segundo fechamento )separa }é uma versão mais complexa de :(){ :|:;};:. Cada comando em um pipe é chamado dentro de um sub-shell de qualquer maneira. Qual é o efeito do ().

  2. :(){ : | :& }; :

    É a versão mais rápida, escrita para não ter espaços: :(){(:)|:&};:(13 caracteres).

  3. :(){ : | : }; : ### funciona no zsh, mas não no bash.

    Possui um erro de sintaxe (no bash), um metacaractere é necessário antes do fechamento },
    assim:

    :(){ : | :; }; :

[a] Crie um novo usuário limpo (eu chamarei de meubize). Efetue login neste novo usuário em um consolesudo -i -u bizeou:

$ su - bize
Password: 
bize:~$

Verifique e altere o max user processeslimite:

bize:~$ ulimit -a           ### List all limits (I show only `-u`)
max user processes              (-u) 63931
bize:~$ ulimit -u 10        ### Low
bize:~$ ulimit -a
max user processes              (-u) 1000

Usando apenas 10 obras, como é apenas um usuário novo solitária: bize. Torna mais fácil chamar killall -u bizee livrar o sistema da maioria das bombas (não todas). Por favor, não pergunte quais ainda funcionam, não vou contar. Mas ainda: é bastante baixo, mas no lado seguro, adapte-se ao seu sistema .
Isso garantirá que uma "bomba de forquilha" não entrará em colapso no seu sistema .

Leitura adicional:

Comunidade
fonte