为什么这些bash fork炸弹的工作方式不同,&在其中的意义是什么?


16

我知道普通的叉子炸弹是如何工作的,但是我真的不明白为什么在普通bash叉子炸弹的末尾需要&,以及为什么这些脚本的行为有所不同:

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

:(){ : | :& }; :

前者会导致CPU使用率激增,然后再将我带回到登录屏幕。后者反而只会导致我的系统死机,迫使我硬重启。这是为什么?两者都不断创建新流程,那么为什么系统的行为有所不同?

这两个脚本的行为也与

:(){ : | : }; :

即使我希望它们都一样,这也根本不会引起任何问题。bash手册页指出管道中的命令已经在子shell中执行,因此我被认为::应该已经足够了。我相信,应该在新的子Shell中运行管道,但是为什么会有如此大的变化?

编辑:使用htop并限制进程数量,我能够看到第一个变量创建了一个实际的进程树,第二个变量创建了同一级别的所有进程,而最后一个变量似乎未创建任何进程完全没有 这使我更加困惑,但是也许可以有所帮助?


2
我认为您的最后一个变体缺少分号::(){ : | :; }; :
阿多尼斯(Adonis)

Answers:


22

警告请勿尝试在生产机器上运行此程序。只是不要。 警告:要尝试使用任何“炸弹”,请确保ulimit -u已在使用中。阅读以下[a]

让我们定义一个函数来获取PID和日期(时间):

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

bomb针对新用户的简单非发行功能(保护自己:阅读[a]):

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

当调用该函数执行时,其工作方式如下:

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

date执行该命令,然后打印“是”,睡眠1秒钟,然后关闭命令date,最后,该函数退出并打印新的命令提示符。没有什么花哨。

| 管

当我们这样调用函数时:

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

某个时间开始启动两个命令,两个命令在1秒后结束,然后提示符返​​回。

这就是pipe |并行启动两个进程的原因。

& 背景

如果我们更改通话,则添加结尾&

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

提示将立即返回(所有操作均发送到后台),并且两个命令将像以前一样执行。请注意在[1]该过程的PID之前打印的“作业编号”的值3380。稍后,将打印相同的数字以指示管道已结束:

[1]+  Done                    bomb | bomb

那是效果&

这就是&:加快进程启动速度的原因。

更简单的名字

我们可以创建一个函数来简单b地执行这两个命令。输入三行:

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

并执行为:

bize:~$ b
  START 0003563 23:21:10
yes
  START 0003564 23:21:10
yes
    END 0003564 23:21:11
    END 0003563 23:21:11

请注意,我们;在的定义中使用了no b(换行符用于分隔元素)。但是,对于一行定义,通常使用;,如下所示:

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

大多数空格也不是强制性的,我们可以编写等效的空格(但不太清楚):

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

我们还可以使用a &分隔}(并将两个进程发送到后台)。

炸弹。

如果使函数咬尾(通过调用自身),则会得到“叉子炸弹”:

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

为了使其更快地调用更多功能,请将管道发送到后台。

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

如果我们将第一个调用追加到必填项之后,;然后将名称更改为:

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

通常写为 :(){ :|:& }; :

或者,以有趣的方式写上另一个名字(一个雪人):

☃(){ ☃|☃&};☃

ulimit(在运行此命令之前应已设置)将使提示在出现很多错误后迅速返回(在错误列表停止时按Enter以获取提示)。

之所以称为“叉炸弹”,是因为Shell启动子Shell的方式是分叉运行中的Shell,然后使用要运行的命令调用exec()到fork的进程。

管道将“分叉”两个新过程。达到无穷大会炸弹。
或最初称为兔子的原因是它繁殖得如此之快。


定时:

  1. :(){ (:) | (:) }; time :
    终止
    实数0m45.627s

  2. :(){ : | :; }; time :
    终止
    实0m15.283s

  3. :(){ : | :& }; time :
    真正的0m00.002 s
    仍在运行


您的示例:

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

    第二次关闭)分隔的}是的更复杂版本:(){ :|:;};:。无论如何,管道中的每个命令都在子外壳内部被调用。是哪个效果的()

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

    是更快的版本,没有空格::(){(:)|:&};:(13个字符)。

  3. :(){ : | : }; : ###在zsh中有效,但在bash中无效。

    出现语法错误(以bash表示),在结束之前需要一个元字符}
    如下所示:

    :(){ : | :; }; :

[a] 创建一个新的干净用户(我称我为bize)。在控制台中登录到该新用户sudo -i -u bize,或者:

$ su - bize
Password: 
bize:~$

检查然后更改max user processes限制:

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

仅使用10个作品,就只有一个单独的新用户:bize。它使调用killall -u bize和摆脱系统大多数(不是全部)炸弹变得更加容易。请不要问哪个仍然有效,我不会告诉您。但是仍然:相当低但是出于安全考虑,请适应您的系统
将确保“叉子炸弹”不会破坏您的系统

进一步阅读:

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.