Als Ergebnis der Pipe in x | y
, wird eine Subshell erstellt, die die Pipeline als Teil der Prozessgruppe im Vordergrund enthält. Dadurch werden weiterhin Subshells erstellt (über fork()
) auf unbestimmte Zeit, wodurch eine Gabelbombe entsteht.
$ 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
Der Fork tritt jedoch erst dann auf, wenn der Code ausgeführt wird, was der letzte Aufruf von :
ist in Ihrem Code.
So zerlegen Sie die Funktionsweise der Gabelbombe:
:()
- Definieren Sie eine neue Funktion namens:
{ :|: & }
- eine Funktionsdefinition, die die aufrufende Funktion rekursiv im Hintergrund in eine andere Instanz der aufrufenden Funktion leitet:
- Fork-Bomb-Funktion aufrufen
Dies ist in der Regel nicht zu speicherintensiv, saugt aber PIDs auf und verbraucht CPU-Zyklen.
Das letzte Bit des Codes, ;:
führt die Funktion :(){ ... }
aus . Hier tritt der Fork auf.
Das Semikolon beendet den ersten Befehl, und wir beginnen einen weiteren, d. h. den Aufruf der Funktion :
. Die Definition dieser Funktion beinhaltet einen Aufruf an sich selbst (:
) und die Ausgabe dieses Aufrufs wird an eine Hintergrundversion :
geleitet . Dies stützt den Prozess auf unbestimmte Zeit.
Jedes Mal, wenn Sie die Funktion :()
aufrufen Sie rufen die C-Funktion fork()
auf . Letztendlich werden dadurch alle Prozess-IDs (PIDs) auf dem System erschöpft.
Beispiel
Sie können den |:&
austauschen mit etwas anderem, damit Sie sich ein Bild davon machen können, was vor sich geht.
Richten Sie einen Beobachter ein
Tun Sie dies in einem Terminalfenster:
$ watch "ps -eaf|grep \"[s]leep 61\""
Stellen Sie die Gabelbombe mit "Sicherung verzögert" ein
In einem anderen Fenster führen wir eine leicht modifizierte Version der Gabelbombe aus. Diese Version wird versuchen, sich selbst zu drosseln, damit wir untersuchen können, was sie tut. Unsere Version schläft 61 Sekunden lang, bevor sie die Funktion :()
aufruft .
Außerdem werden wir auch den ersten Aufruf in den Hintergrund stellen, nachdem er aufgerufen wurde. Strg + z , und geben Sie dann bg
ein .
$ :(){ sleep 61; : | : & };:
# control + z
[1]+ Stopped sleep 61
[2] 5845
$ bg
[1]+ sleep 61 &
Wenn wir jetzt jobs
ausführen Befehl im Anfangsfenster sehen wir Folgendes:
$ jobs
[1]- Running sleep 61 &
[2]+ Running : | : &
Nach ein paar Minuten:
$ jobs
[1]- Done sleep 61
[2]+ Done : | :
Beim Beobachter einchecken
In der Zwischenzeit im anderen Fenster, in dem wir watch
ausführen :
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
Prozesshierarchie
Und ein ps -auxf
zeigt diese Prozesshierarchie:
$ 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
...
...
Aufräumzeit
Ein killall bash
wird die Dinge stoppen, bevor sie außer Kontrolle geraten. Das Aufräumen auf diese Weise kann ein wenig schwerfällig sein, eine freundlichere, sanftere Art, die möglicherweise nicht jeden bash
zerreißt Shell down, wäre folgendes zu tun:
-
Bestimmen Sie, in welchem Pseudo-Terminal die Gabelbombe laufen soll
$ tty /dev/pts/4
-
Beenden Sie das Pseudo-Terminal
$ pkill -t pts/4
Also, was ist los?
Nun, jeder Aufruf von bash
und sleep
ist ein Aufruf der C-Funktion fork()
aus dem bash
Shell, von der aus der Befehl ausgeführt wurde.