经历了著名的叉子炸弹问题在 Askubuntu 和许多其他 Stack Exchange 网站上,我不太明白每个人都在说什么,因为这是显而易见的。
很多答案(最好的例子) 说这个:
“
{:|: &}
表示运行该函数:
并将其输出:
再次发送到该函数”
出色地,到底是什么的输出是:
?正在向对方传递什么:
?
并且:
本质上,您正在创建一个调用自身的函数两次每次调用都没有任何方法自行终止。
具体是如何执行的两次?在我看来,:
在第一个:
完成执行之前,没有任何内容传递给第二个,这实际上永远不会结束。
例如C
,
foo()
{
foo();
foo(); // never executed
}
第二个根本foo()
没有执行,只是因为第一个foo()
永远不会结束。
我认为同样的逻辑适用于:(){ :|: & };:
和
:(){ : & };:
做同样的工作
:(){ :|: & };:
请帮助我理解逻辑。
答案1
管道并不要求第一个实例在另一个实例启动之前完成。实际上,它所做的只是重定向标准输出第一个实例的标准输入第二个,所以它们可以同时运行(因为叉子炸弹必须工作)。
那么, 的输出到底是什么
:
?什么正在传递给对方:
?
':' 不会向另一个 ':' 实例写入任何内容,它只是重定向标准输出到标准输入的第二个实例。如果它在执行期间写了一些东西(它永远不会,因为它除了分叉自己之外什么也不做),它会转到标准输入另一个实例的。
它有助于想象标准输入和标准输出作为一堆:
无论写入什么内容标准输入将被堆积起来,以供程序决定从中读取时使用,而标准输出工作方式相同:您可以写入一堆,以便其他程序可以在需要时从中读取。
这样就很容易想象像管道没有发生通信(两个空堆)或非同步写入和读取这样的情况。
究竟是如何执行两次的?在我看来,
:
在第一个:
完成执行之前,没有任何内容传递给第二个,这实际上永远不会结束。
由于我们只是重定向实例的输入和输出,因此不需要第一个实例在第二个实例开始之前完成。实际上,通常希望两者同时运行,以便第二个可以即时处理第一个正在解析的数据。这就是这里发生的情况,两者都将被调用,而无需等待第一个完成。这适用于所有管链命令行。
我认为同样的逻辑适用于 :(){ :|: & };: 和
:(){ : & };:
做同样的工作
:(){ :|: & };:
第一个不起作用,因为即使它本身递归运行,该函数也会在后台调用(: &
)。第一个:
不会等到“子”:
返回才结束自身,因此最终您可能只有一个:
运行实例。如果你有的:(){ : };:
话,它会起作用,因为第一个:
会等待“孩子”:
返回,而第一个会等待它自己的“孩子”:
返回,依此类推。
以下是不同命令在运行实例数量方面的情况:
:(){ : & };:
1 个实例(调用:
并退出) -> 1 个实例(调用:
并退出) -> 1 个实例(调用:
并退出) -> 1 个实例 -> ...
:(){ :|: &};:
1 个实例(调用 2:
并退出) -> 2 个实例(每个实例调用 2:
并退出) -> 4 个实例(每个实例调用 2:
并退出) -> 8 个实例 -> ...
:(){ : };:
1 个实例(调用:
并等待其返回) -> 2 个实例(子级调用另一个实例:
并等待其返回) -> 3 个实例(子级调用另一个实例:
并等待其返回) -> 4 个实例 -> ...
:(){ :|: };:
1 个实例(调用 2:
并等待它们返回) -> 3 个实例(子级:
分别调用 2 并等待它们返回) -> 7 个实例(子级:
分别调用 2 并等待它们返回) -> 15 个实例 -> ...
正如您所看到的,在后台调用该函数(使用&
)实际上会减慢 fork 炸弹的速度,因为被调用者将在被调用函数返回之前退出。