警告:在大多数 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
外壳都拆掉,那就是执行以下操作:
确定 fork 炸弹将在哪个伪终端中运行
$ tty /dev/pts/4
杀死伪终端
$ pkill -t pts/4
发生什么了?
每次调用bash
和都是从运行命令的 shell调用sleep
C 函数。fork()
bash