为什么我不能用叉形炸弹使我的系统崩溃?

Pla*_*ton 58 linux process fork ulimit

最近我一直在挖掘有关 GNU/Linux 进程的信息,我遇到了臭名昭著的 fork 炸弹:

:(){ : | :& }; :
Run Code Online (Sandbox Code Playgroud)

理论上,它应该无限复制自己,直到系统耗尽资源......

但是,我已经尝试在CLI DebianGUI Mint发行版上进行测试,它似乎对系统影响不大。是的,创建了大量进程,一段时间后我在控制台消息中阅读了如下内容:

bash: fork: 资源暂时不可用

bash: fork: 重试: 没有子进程

但是一段时间后,所有进程都会被杀死,一切都恢复正常。我读过ulimit为每个用户设置了最大进程数,但我似乎无法将其提高到很远。

什么是针对叉形炸弹的系统保护?为什么在一切都冻结或至少滞后很多之前它不会自我复制?有没有办法用叉子炸弹真正使系统崩溃?

Hko*_*oof 87

您可能有一个使用 systemd 的 Linux 发行版。

Systemd为每个用户创建一个cgroup,一个用户的所有进程都属于同一个cgroup。

Cgroups 是一种 Linux 机制,用于设置系统资源的限制,例如最大进程数、CPU 周期、RAM 使用率等。这是一个不同的、更现代的资源限制层ulimit(使用getrlimit()系统调用)。

如果您运行systemctl status user-<uid>.slice(代表用户的 cgroup),您可以看到该cgroup 中允许的当前和最大任务数(进程和线程)。

$ systemctl status user-$UID.slice
? user-22001.slice - UID 22001 的用户切片
   加载:加载
  插入:/usr/lib/systemd/system/user-.slice.d
           ??10-defaults.conf
   活跃:自 EEST 2018-09-10 17:36:35 Mon 开始活跃;1 周 3 天前
    任务:17(限制:10267)
   内存:616.7M

默认情况下,systemd 允许每个用户的最大任务数是“系统范围最大值”( sysctl kernel.threads-max) 的33% ;这通常相当于约 10,000 个任务。如果要更改此限制:

  • 在 systemd v239 及更高版本中,用户默认值是通过TasksMax=设置的:

    /usr/lib/systemd/system/user-.slice.d/10-defaults.conf
    
    Run Code Online (Sandbox Code Playgroud)

    要调整特定用户的限制(将立即应用并存储在 /etc/systemd/system.control 中),请运行:

    systemctl [--runtime] set-property user-<uid>.slice TasksMax=<value>
    
    Run Code Online (Sandbox Code Playgroud)

    systemctl edit这里也可以使用覆盖单元设置(例如)的常用机制,但它们需要重新启动。例如,如果您想更改每个用户的限制,您可以创建/etc/systemd/system/user-.slice.d/15-limits.conf.

  • 在 systemd v238 及更早版本中,用户默认值是通过UserTasksMax= in设置的/etc/systemd/logind.conf。更改该值通常需要重新启动。

有关此的更多信息:

  • 12288 个进程(减去炸弹之前已经产生的进程)除了 * 尝试 * 创建一个新进程之外什么都不做,并没有真正影响现代系统。 (5认同)

Jos*_*hua 13

无论如何,这不会再使现代 Linux 系统崩溃。

它创建了大量进程,但在进程空闲时并没有真正消耗那么多 CPU。在现在用完 RAM 之前,您用完了进程表中的插槽。

如果您不像 Hkoof 指出的那样受 cgroup 限制,以下更改仍会导致系统宕机:

:(){ : | :& : | :& }; :
Run Code Online (Sandbox Code Playgroud)

  • 这实际上取决于您认为“崩溃”系统的原因。在大多数情况下,进程表中的槽用完会使系统瘫痪,即使它不会完全导致内核恐慌。 (5认同)
  • @AustinHemmelgarn:这就是为什么明智的系统会为 root 保留最后 4 个左右的进程 ID。 (4认同)
  • @mtraceur:只有在分叉开始失败时才会发生。 (4认同)
  • 为什么进程会“空闲”?每个分叉的进程都处于创建更多进程的无限递归中。因此,它在系统调用开销上花费了大量时间(一遍又一遍地进行`fork`),其余时间进行函数调用(大概是在 shell 的调用堆栈中为每次调用逐渐使用更多内存)。 (2认同)

T.E*_*.D. 9

早在 90 年代,我不小心对自己释放了其中一个。我无意中在包含 fork() 命令的 C 源文件上设置了执行位。当我双击它时,csh 尝试运行它而不是像我想要的那样在编辑器中打开它。

即便如此,它也没有使系统崩溃。Unix 足够强大,您的帐户和/或操作系统将具有进程限制。相反,它会变得非常缓慢,任何需要启动进程的东西都可能会失败。

幕后发生的事情是进程表填满了试图创建新进程的进程。如果其中一个进程终止(由于进程表已满导致 fork 出错,或者由于绝望的操作员试图恢复其系统的健全性),其他进程之一将愉快地 fork 一个新的进程来填充虚空。

“分叉炸弹”基本上是一种无意中自我修复的进程系统,其任务是保持进程表完整。阻止它的唯一方法是以某种方式一次杀死它们。

  • 一次性杀死他们比你想象的要容易 - 首先发出信号停止他们。 (2认同)
  • @Score_Under - 如果我不立即赶往离我最近的 Harris Nighthawk 看看是否能解决那里的问题,我希望你能原谅我。我想在它从失败的叉子死掉之前得到一个 PID 并发送信号给它可能是一个挑战,但我必须尝试一下。 (2认同)