叉子炸弹上的 fork() 在哪里:(){ :|: & };:?

叉子炸弹上的 fork() 在哪里:(){ :|: & };:?

警告:在大多数 shell 中运行此命令将导致系统损坏,需要强制关闭才能修复

我了解递归函数:(){ :|: & };:及其作用。但我不知道fork系统调用在哪里。我不确定,但我怀疑是在管道里|

答案1

由于 中的管道x | y,将创建一个子外壳来包含管道作为前台进程组的一部分。这将继续无限期地创建子 shell(通过fork()),从而创建叉子炸弹。

$ 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

然而,直到代码运行时,分叉才真正发生,这是:代码中的最终调用。

拆解叉子炸弹的工作原理:

  • :()- 定义一个新函数,称为:
  • { :|: & }- 一个函数定义,它将调用函数递归地传输到后台调用函数的另一个实例中
  • :- 调用fork炸弹函数

这往往不会占用太多内存,但会占用 PID 并消耗 CPU 周期。

答案2

代码的最后一位;:是运行该函数:(){ ... }。这就是分叉发生的地方。

分号终止第一个命令,我们将开始另一个命令,即调用函数:。该函数的定义包括对自身 ( :) 的调用,并且该调用的输出通过管道传输到后台版本:。这无限期地支持了这一过程。

每次调用该函数时,:()您都在调用 C 函数fork()。最终这将耗尽系统上的所有进程 ID (PID)。

例子

您可以将其替换|:&为其他内容,这样您就可以了解发生了什么。

设置观察者

在一个终端窗口中执行以下操作:

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

设置“保险丝延迟”叉子炸弹

在另一个窗口中,我们将运行叉子炸弹的稍微修改版本。这个版本将尝试自我限制,以便我们可以研究它在做什么。我们的版本在调用该函数之前将休眠 61 秒:()

此外,我们还将在调用初始调用后将其置于后台。Ctrl+ z,然后输入bg

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

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

现在,如果我们在初始窗口中运行jobs命令,我们将看到:

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

几分钟后:

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

与观察者一起检查

同时在我们运行的另一个窗口中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

流程层次结构

aps -auxf显示了这个进程层次结构:

$ 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
...
...

清理时间

Akillall bash会在事情失控之前阻止事情发生。以这种方式进行清理可能有点严厉,一种更温和、更温和的方式可能不会把每一个bash外壳都拆掉,那就是执行以下操作:

  1. 确定 fork 炸弹将在哪个伪终端中运行

    $ tty
    /dev/pts/4
    
  2. 杀死伪终端

    $ pkill -t pts/4
    

发生什么了?

每次调用bash和都是从运行命令的 shell调用sleepC 函数。fork()bash

相关内容