Answers:
作为in管道的结果,将x | y
创建一个子外壳,以将管道包含为前台进程组的一部分。这样会继续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
在运行代码之前,实际上不会发生派生,但这是:
代码中的最终调用。
拆卸前叉炸弹的工作方式:
:()
-定义一个名为 :
{ :|: & }
-一个函数定义,该定义将调用函数递归地传递到后台调用函数的另一个实例中:
-调用前叉炸弹功能这往往不会占用太多内存,但是会吸收PID并占用CPU周期。
x | y
,为什么要创建一个子外壳?据我了解,当bash看到a时pipe
,它将执行pipe()
系统调用,并返回2 fds
。现在,将exec
编辑command_left 并将输出作为输入馈送到command_right。现在,已exec
编辑command_right 。那么,为什么BASHPID
每次都不同?
x
和y
2个在2个独立的进程中运行单独的命令,让你有2个独立的子shell。如果x
运行过程与外壳运行过程相同,则意味着它x
必须是内置的。
代码的最后一部分;:
正在运行函数:(){ ... }
。这是发生分叉的地方。
分号终止第一个命令,而我们正在启动另一个命令,即调用function :
。该函数的定义包括对自身(:
)的调用,并且此调用的输出通过管道传递到后台版本:
。这无限期地支持了该过程。
每次调用该函数时,:()
都在调用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
并ps -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
...
...
A killall bash
会在事情失控之前阻止它们。以这种方式进行清理可能会比较费力,一种较温和的方式(可能不会将每个bash
外壳都撕裂)将执行以下操作:
确定叉炸弹将在哪个伪终端中运行
$ tty
/dev/pts/4
杀死伪终端
$ pkill -t pts/4
好吧,每次调用bash
和sleep
都是从运行命令fork()
的bash
shell中调用C函数。
bash
可能在单独的终端上运行。最好使用pkill -t pts/2
。