¿Por qué estas bombas bash fork funcionan de manera diferente y cuál es el significado de y en ellas?

16

Entiendo cómo funciona una bomba tenedor normal, pero realmente no entiendo por qué se requiere el & al final de la bomba tenedor bash común y por qué estos scripts se comportan de manera diferente:

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

y

:(){ : | :& }; :

El primero causa un aumento en el uso de la CPU antes de devolverme a la pantalla de inicio de sesión. En cambio, este último solo hace que mi sistema se congele, lo que me obliga a reiniciar por completo. ¿Porqué es eso? Ambos crean continuamente nuevos procesos, entonces, ¿por qué el sistema se comporta de manera diferente?

Ambos scripts también se comportan de manera diferente a

:(){ : | : }; :

lo que no causa ningún problema, aunque hubiera esperado que fueran iguales. La página del manual de bash indica que los comandos en una tubería ya se ejecutan en una subshell, por lo que me hacen creer que: | : ya debería ser suficiente. Creo y debería ejecutar la tubería en una nueva subshell, pero ¿por qué eso cambia tanto?

Editar: Utilizando htop y limitando la cantidad de procesos, pude ver que la primera variante crea un árbol real de procesos, la segunda variante crea todos los procesos en el mismo nivel y la última variante no parece crear ningún proceso en absoluto. Esto me confunde aún más, pero ¿tal vez me ayuda de alguna manera?

Dan K.
fuente
2
Creo que su última variante es que falta un punto y coma::(){ : | :; }; :
adonis

Respuestas:

22

ADVERTENCIA NO INTENTE EJECUTAR ESTO EN UNA MÁQUINA DE PRODUCCIÓN. SOLO NO. Advertencia: Para probar cualquier "bomba" asegúrese de que ulimit -uesté en uso. Lea a continuación [a] .

Definamos una función para obtener el PID y la fecha (hora):

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

Una función simple y sin problemas bombpara el nuevo usuario (protéjase: lea [a] ):

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

Cuando se llama a esa función para que se ejecute funciona así:

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

Se dateejecuta el comando , luego se imprime un "sí", un sueño durante 1 segundo, luego el comando de cierre datey, finalmente, la función sale imprimiendo un nuevo símbolo del sistema. Nada sofisticado.

El | tubo

Cuando llamamos a la función así:

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:~$

Dos comandos se inician en algún momento, los dos finalizan 1 segundo más tarde y luego vuelve el indicador.

Esa es la razón de la tubería |, para iniciar dos procesos en paralelo.

& antecedentes

Si cambiamos la llamada agregando un 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

La solicitud vuelve inmediatamente (toda la acción se envía al fondo) y los dos comandos se ejecutan como antes. Tenga en cuenta el valor del "número de trabajo" [1]impreso antes del PID del proceso 3380. Más tarde, se imprimirá el mismo número para indicar que la tubería ha terminado:

[1]+  Done                    bomb | bomb

Ese es el efecto de &.

Esa es la razón de &: comenzar los procesos más rápido.

Nombre más simple

Podemos crear una función llamada simplemente bpara ejecutar los dos comandos. Escrito en tres líneas:

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

Y ejecutado 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

Tenga en cuenta que no utilizamos ;en la definición de b(las nuevas líneas se utilizaron para separar elementos). Sin embargo, para una definición en una línea, es habitual usar ;, como este:

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

La mayoría de los espacios tampoco son obligatorios, podemos escribir el equivalente (pero menos claro):

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

También podemos usar a &para separar el }(y enviar los dos procesos a un segundo plano).

La bomba.

Si hacemos que la función muerda su cola (llamándose a sí misma), obtenemos la "bomba tenedor":

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

Y para que llame a más funciones más rápido, envíe la canalización a un segundo plano.

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

Si agregamos la primera llamada a la función después de un requerimiento ;y cambiamos el nombre :, obtenemos:

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

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

O, escrito de forma divertida, con algún otro nombre (un hombre de nieve):

☃(){ ☃|☃&};☃

El ulimit (que debería haber configurado antes de ejecutar esto) hará que la solicitud vuelva rápidamente después de muchos errores (presione enter cuando la lista de errores se detenga para obtener la solicitud).

La razón de que esto se llame una "bomba tenedor" es que la forma en que el shell inicia un sub-shell es bifurcando el shell en ejecución y luego llamando a exec () al proceso bifurcado con el comando para ejecutar.

Una tubería "bifurcará" dos nuevos procesos. Hacerlo hasta el infinito causa una bomba.
O un conejo como se llamaba originalmente porque se reproduce muy rápido.


Sincronización:

  1. :(){ (:) | (:) }; time :
    Terminado
    real 0m45.627s

  2. :(){ : | :; }; time :
    Terminado
    0m15.283s reales

  3. :(){ : | :& }; time :
    0m00.002 verdadera s
    Still Running


Sus ejemplos:

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

    Donde el segundo cierre )separa }es una versión más compleja de :(){ :|:;};:. Cada comando en una tubería se llama dentro de un sub-shell de todos modos. ¿Cuál es el efecto de la ().

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

    Es la versión más rápida, escrita para no tener espacios: :(){(:)|:&};:(13 caracteres).

  3. :(){ : | : }; : ### funciona en zsh pero no en bash.

    Tiene un error de sintaxis (en bash), se necesita un metacarácter antes del cierre },
    ya que esto:

    :(){ : | :; }; :

[a] Cree un nuevo usuario limpio (lo llamaré míobize). Inicie sesión con este nuevo usuario en una consolasudo -i -u bize, o bien:

$ su - bize
Password: 
bize:~$

Verifique y luego cambie el max user processeslímite:

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 sólo 10 obras, es sólo un nuevo usuario solitaria: bize. Hace más fácil llamar killall -u bizey eliminar el sistema de la mayoría (no todas) de las bombas. Por favor, no pregunte cuáles siguen funcionando, no lo diré. Pero aún así: es bastante bajo, pero en el lado seguro, adáptese a su sistema .
Esto asegurará que una "bomba tenedor" no colapsará su sistema .

Otras lecturas:

Comunidad
fuente