Onde está o garfo () na bomba do garfo: () {: |: &};:?

25

Aviso: A execução desse comando na maioria dos shells resultará em um sistema quebrado que precisará de um desligamento forçado para corrigir

Entendo a função recursiva :(){ :|: & };:e o que ela faz. Mas eu não sei onde é a chamada do sistema fork. Não tenho certeza, mas suspeito no cano |.

mavillan
fonte
Relacionado (e vale a pena ler): Como funciona uma bomba de garfo?
terdon

Respostas:

30

Como resultado da inserção do pipe x | y, um subshell é criado para conter o pipeline como parte do grupo de processos em primeiro plano. Isso continua a criar subshells (via fork()) indefinidamente, criando assim uma bomba de forquilha.

$ for (( i=0; i<3; i++ )); do
>     echo "$BASHPID"
> done
16907
16907
16907
$ for (( i=0; i<3; i++ )); do
>     echo "$BASHPID" | cat
> done
17195
17197
17199

No entanto, a bifurcação não ocorre até que o código seja executado, que é a chamada final de :seu código.

Para desmontar como a bomba dos garfos funciona:

  • :() - defina uma nova função chamada :
  • { :|: & } - uma definição de função que canaliza recursivamente a função de chamada em outra instância da função de chamada em segundo plano
  • : - chame a função de bomba de garfo

Isso tende a não consumir muita memória, mas absorve PIDs e consome ciclos da CPU.

Chris Down
fonte
Em x | y, por que existe um sub-shell criado? Pelo que entendi, quando o bash vê um pipe, ele executa a pipe()chamada do sistema, que retorna dois fds. Agora, command_left é execed e a saída é alimentada para command_right como entrada. Agora, command_right está execed. Então, por que é BASHPIDdiferente a cada vez?
Abhijeet Rastogi
2
@shadyabhi É simples - xe yexistem 2 comandos separados sendo executados em 2 processos separados, então você tem 2 subshells separados. Se for xexecutado no mesmo processo que o shell, isso significa que xdeve ser incorporado.
Jw013
24

O último pedaço do código, ;:está a executar a função :(){ ... }. É aqui que o garfo está ocorrendo.

O ponto-e-vírgula finaliza o primeiro comando e estamos iniciando outro, ou seja, invocando a função :. A definição dessa função inclui uma chamada para si mesma ( :) e a saída dessa chamada é canalizada para uma versão em segundo plano :. Isso sustenta o processo indefinidamente.

Toda vez que você está chamando a função :()que você está chamando a função C fork(). Eventualmente, isso esgotará todos os IDs de processo (PIDs) no sistema.

Exemplo

Você pode trocar por |:&outra coisa para ter uma idéia do que está acontecendo.

Configurar um inspetor

Em uma janela do terminal, faça o seguinte:

$ watch "ps -eaf|grep \"[s]leep 61\""

Configure a bomba do garfo com atraso de fusível

Em outra janela, rodaremos uma versão ligeiramente modificada da bomba de forquilha. Esta versão tentará se controlar para que possamos estudar o que está fazendo. Nossa versão irá dormir por 61 segundos antes de chamar a função :().

Também chamaremos a chamada inicial em segundo plano, depois que ela for chamada. Ctrl+ ze digite bg.

$ :(){ sleep 61; : | : & };:

# control + z
[1]+  Stopped                 sleep 61
[2] 5845
$ bg
[1]+ sleep 61 &

Agora, se executarmos o jobscomando na janela inicial, veremos o seguinte:

$ jobs
[1]-  Running                 sleep 61 &
[2]+  Running                 : | : &

Após alguns minutos:

$ jobs
[1]-  Done                    sleep 61
[2]+  Done                    : | :

Verifique com o observador

Enquanto isso, na outra janela em que estamos executando watch:

Every 2.0s: ps -eaf|grep "[s]leep 61"                                                                                                                                             Sat Aug 31 12:48:14 2013

saml      6112  6108  0 12:47 pts/2    00:00:00 sleep 61
saml      6115  6110  0 12:47 pts/2    00:00:00 sleep 61
saml      6116  6111  0 12:47 pts/2    00:00:00 sleep 61
saml      6117  6109  0 12:47 pts/2    00:00:00 sleep 61
saml      6119  6114  0 12:47 pts/2    00:00:00 sleep 61
saml      6120  6113  0 12:47 pts/2    00:00:00 sleep 61
saml      6122  6118  0 12:47 pts/2    00:00:00 sleep 61
saml      6123  6121  0 12:47 pts/2    00:00:00 sleep 61

Hierarquia do processo

E a ps -auxfmostra essa hierarquia de processos:

$ ps -auxf
saml      6245  0.0  0.0 115184  5316 pts/2    S    12:48   0:00 bash
saml      6247  0.0  0.0 100988   468 pts/2    S    12:48   0:00  \_ sleep 61
....
....
saml      6250  0.0  0.0 115184  5328 pts/2    S    12:48   0:00 bash
saml      6268  0.0  0.0 100988   468 pts/2    S    12:48   0:00  \_ sleep 61
saml      6251  0.0  0.0 115184  5320 pts/2    S    12:48   0:00 bash
saml      6272  0.0  0.0 100988   468 pts/2    S    12:48   0:00  \_ sleep 61
saml      6252  0.0  0.0 115184  5324 pts/2    S    12:48   0:00 bash
saml      6269  0.0  0.0 100988   464 pts/2    S    12:48   0:00  \_ sleep 61
...
...

Tempo de limpeza

A killall bashirá parar as coisas antes que elas fiquem fora de controle. Fazer a limpeza dessa maneira pode ser um pouco pesado, uma maneira mais gentil e gentil, que não bashdestrói todos os cascos, seria o seguinte:

  1. Determine em que pseudo terminal a bomba dos garfos será executada

    $ tty
    /dev/pts/4
  2. Mate o pseudo terminal

    $ pkill -t pts/4

Então o que está acontecendo?

Bem, cada invocação de bashe sleepé uma chamada para a função C fork()do bashshell de onde o comando foi executado.

slm
fonte
7
bashpode estar em execução em terminais separados. Melhor seria usar pkill -t pts/2.
Maciej Piechotka 31/08
@MaciejPiechotka - obrigado pela dica. Nunca vi isso antes, eu adicionei à resposta!
slm