我了解普通的 fork 炸弹是如何工作的,但我不太明白为什么公共 bash fork 炸弹末尾需要 & 以及为什么这些脚本的行为不同:
:(){ (:) | (:) }; :
和
:(){ : | :& }; :
前者会导致 CPU 使用率飙升,然后将我带回登录屏幕。后者只会导致我的系统冻结,迫使我硬重启。这是为什么?两者都不断创建新进程,那么为什么系统的行为不同呢?
这两个脚本的行为也不同
:(){ : | : }; :
这根本不会造成任何问题,尽管我本以为它们是相似的。 bash 手册页指出管道中的命令已经在子 shell 中执行,所以我相信: | : 应该已经足够了。我相信并且应该在新的子 shell 中运行管道,但为什么会发生如此大的变化?
编辑:使用 htop 并限制进程数量,我可以看到第一个变体创建了一个实际的进程树,第二个变体在同一级别上创建了所有进程,最后一个变体似乎没有创建任何进程根本不。这让我更加困惑,但也许它有帮助?
答案1
警告请勿尝试在生产机器上运行此程序。只是不要。
警告:要尝试任何“炸弹”,请确保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
被执行,然后打印“yes”,休眠 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 秒后结束,并且然后提示符返回。
|
这就是 pipeline并行启动两个进程的原因。
& 背景
如果我们改变呼叫添加结尾&
:
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&
来分隔}
(并将两个进程发送到后台)。
炸弹。
如果我们让函数咬住尾巴(通过调用自身),我们就会得到“fork 炸弹”:
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 键以获取提示)。
之所以将其称为“fork 炸弹”,是因为 shell 启动子 shell 的方式是分叉正在运行的 shell,然后使用要运行的命令对分叉进程调用 exec()。
管道将“分叉”两个新进程。这样做到无穷大会导致炸弹。
或者最初被称为“兔子”,因为它繁殖得很快。
定时:
:(){ (:) | (:) }; time :
终止
真实 0m45.627s:(){ : | :; }; time :
终止
真实 0m15.283s:(){ : | :& }; time :
真实 0m00.002 s
仍在运行
你的例子:
:(){ (:) | (:) }; :
第二个结尾将
)
分开的地方}
是 的更复杂版本:(){ :|:;};:
。无论如何,管道中的每个命令都会在子 shell 内调用。这就是 的效果()
。:(){ : | :& }; :
是更快的版本,写入时没有空格:
:(){(:)|:&};:
(13 个字符)。:(){ : | : }; :
### 在 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
并清除系统中的大多数(不是全部)炸弹。请不要问哪些仍然有效,我不会告诉。但仍然:相当低,但为了安全起见,请适应您的系统。
这将确保“叉子炸弹”不会崩溃您的系统。
进一步阅读: