a photo of Whexy

Wenxuan

CyberSecurity Researcher at Northwestern University

别闹了Fork:在 Linux 上快速创建大型进程

Whexy /
November 04, 2021

Forking 是在 Linux 中创建进程的方式。尽管多年来 fork 已经改进到使用 COW(写时复制)语义,它仍然必须从父进程向子进程复制一定数量的数据。实验室的一些学生在使用模糊测试机器时遇到了一些问题...

TL;DR

  • 如果你想启动一个新程序,使用 posix_spawn() 替换 fork()
  • 如果你想同时运行多个进程处理巨大任务,使用进程池来避免耗时的页表复制。

Fork 很慢

实验室的一些学生在使用模糊测试机器。模糊测试用变异的输入启动应用程序数千次,以查看是否触发了 bug。

这听起来像是用高计算能力暴力对待应用程序, 就像深度学习对用户数据做的事情一样。

困扰我们的是,随着模糊测试的进行,启动目标应用程序所需的时间越来越长。我们对程序进行了采样,发现最耗时的部分是 fork() 系统调用。事实证明,收集每次启动运行信息的模糊测试进程在内存中越来越大。当进程膨胀到一定程度时,克隆成为一个我们不能忽视的性能影响因素。

我们知道 fork() 已经被优化了几十年。它现在使用 COW(写时复制)语义,这意味着在修改之前不会复制用户空间中的数据。但是,它仍然必须从父进程向子进程复制一定数量的数据。

进程池和 Spawn

我们立即想到动态语言解释器面临着和我们一样的问题。随着程序的执行,解释器进程也会经历内存膨胀。如果他们选择创建一个新进程,这可能会非常昂贵。我们调查了 Python 面对多进程程序时提供的几个解决方案。考虑了两个最具代表性的解决方案:池和 spawn。

进程池

进程池技术是一个简单直接的解决方案。它在程序初始化时创建几个空进程,在进程变得过大之前,然后在需要时将任务分配给进程执行。执行完成后,进程被回收到池中以供使用。1

Thread Pool

限制

  1. 我们必须提前确定所需的并发进程数量。如果我想同时运行 100 个进程,发现池中只有 5 个可用,程序必须停止等待,直到运行中的进程被释放并回收。
  2. 对于已经被 exec() 替换为全新任务的进程,我们无法将其回收到池中。这意味着我们将永远失去这个进程。

总结:池化技术应该用于多任务处理,特别是多进程程序的初始化。但是,它不应该用于快速启动其他进程。

Spawn

当使用 exec() 启动其他进程时,我们知道 fork() 中的复制是不必要的,因为子进程立即被替换为新进程。那么它到底复制了什么呢?最突出的部分是页表。页表是 Linux 为实现 COW 必须复制的部分。它记录特定内存地址中的数据是否已被修改。

Thread Pool

vfork()

Berkeley 版本的 Unix (BSD) 在 1980 年代初引入了 vfork() 系统调用。vfork(2) 不会将父进程复制到子进程。两个进程共享父进程的虚拟地址空间;父进程被挂起直到子进程退出或调用 exec() 2

Thread Pool
🤔

死锁

人们发现当应用程序有多个线程运行时,vfork() 可能引入新问题:死锁。 死锁可能由于动态链接器 ld.so.1 参与解析必要符号而发生。 特别是,假设子进程调用外部函数(如 exec())。在这种情况下, 动态链接器可能被调用来解析过程链接表 (PLT) 条目, 动态链接器将获取互斥锁。这个锁可能已经被父进程中的不同线程持有。 如果这种情况发生,它将在父子进程之间创建死锁,因为父进程被挂起 直到子进程调用了 exec()exit()。结果,父子进程都会挂起。 我发现这很有趣,因为我在用 Rust 编写异步程序时遇到了同样的问题。 看我的文章 异步互斥锁

posix_spawn()

在 Linux 上,posix_spawn() 只是用 fork()exec() 实现的。如果安全的话,它将使用 vfork() 代替 fork()。你可以使用带有 POSIX_SPAWN_USEVFORK 标志的 posix_spawn(2) 来避免从大进程分叉时复制页表的开销,同时 Linux 可以保护你免受我们上面提到的死锁。

这是我们最终采用的解决方案。它能够快速创建新进程,避免 fork 的无效复制,并且不需要提前创建进程,无论池大小如何。

Footnotes

  1. Multi-processing in Python; Process vs Pool | by Nikhil Verma | Medium

  2. Minimizing Memory Usage for Creating Application Subprocesses (archive.org)

© LICENSED UNDER CC BY-NC-SA 4.0