不建议在分叉时执行外部程序

Executing an external program when forking is not advisable

我有一个可以占用 4-8GB 内存的大型服务器软件。

这使得 fork-exec 变得很麻烦,因为 fork 本身会花费大量时间,而且默认行为似乎是 fork 将失败,除非有足够的内存来复制整个常驻内存。

因为这在分析时开始显示为最热点(60% 的时间花在 fork 上),所以我需要解决它。

避免 fork-exec 例程的最简单方法是什么?

在 Linux 和(可能)MacOSX 和大多数其他 Unix 或 POSIX 系统上,你基本上无法避免 fork(2) (or the equivalent clone(2) syscall..., or the obsolete vfork which I don't recommend using) + execve(2) to start an external command (à la system(3), or à la posix_spawn)。

是什么让您认为它正在成为一个问题? 8GB 进程虚拟地址 space 在今天并不是什么大问题(至少在 8GB 或 16GB RAM 的机器上,比如我的桌面)。由于所有最近的 Unix 和 Linux.

使用的惰性 copy-on-write 技术,您实际上不需要两倍的 RAM(但您确实需要交换 space)

也许您认为交换 space 可能是个问题。在 Linux 上,您可以添加交换 space,也许通过交换文件;只是 运行 作为 root:

 dd if=/dev/zero of=/var/tmp/myswap bs=1M count=32768
 mkswap /var/tmp/myswap
 swapon /var/tmp/myswap

(当然,请确保 /var/tmp/ 不是 tmpfs 挂载的文件系统,而是位于某个磁盘上,也许是 SSD 磁盘....)

当你不再需要大量交换时 space, 运行 swapoff /var/tmp/myswap.....

您还可以在程序开头附近启动一些外部 shell 进程(à la popen),稍后您可以向它发送 shell 命令。看看我的execicar.c程序寻找灵感,或者合适就用(我10年前为了类似的目的写的,具体忘记了)

或者在你的程序开始时分叉一些解释器(Lua, Guile...)并向它发送一些命令。

运行超过每秒几十个命令(启动任何外部程序)不合理,应该被视为设计错误,恕我直言。也许你正在 运行ning 的命令可以被进程内函数替换(例如 /bin/ls 可以用 statreaddirglob 函数 ...)。也许您可能会考虑 添加一些 plugin ability (with dlopen(3)dlsym) 到您的代码中(以及 运行 来自插件的功能,而不是经常启动相同的程序)。或者也许 在您的代码中嵌入解释器(Lua、Guile、...)。

例如,对于 Web 服务器,查找旧的 CGI vs FastCGI or HTTP forwarding (e.g. URL redirection) or embedded PHP or HOP or Ocsigen

This makes fork-exec cumbersome, as the fork itself can take significant time

这只对了一半。您没有指定 OS,但 fork(2) 通过使用 copy-on-write 在 Linux 中进行了相当优化(我相信其他 UNIX 变体)。 Copy-on-write表示操作系统不会复制整个parent内存地址space,直到child(或parent)写入内存。所以你可以放心,如果你有一个使用 8 GB 内存的 parent 进程然后你 fork,你将不会使用 16 GB 内存 - 尤其是 如果child execs() 立即执行。

fork will fail unless there is enough memory for a copy of the entire resident memory.

没有。 fork(2) 产生的唯一开销是 child 的任务结构的复制和分配、PID 的分配以及复制 parent 的页表。如果没有足够的内存来复制整个 parent 的地址 space,fork(2) 而不是 失败,如果没有'没有足够的内存来分配新的任务结构和页表。如果已达到用户的最大进程数,它也可能会失败。您可以在 man 2 fork 中确认这一点(注意:请参阅下面的评论)。

如果您仍然不想使用 fork(2),您可以使用 vfork(2),它根本不复制 - 它甚至不复制页表 - 一切都与共享parent。您可以使用它来创建一个开销可以忽略不计的新 child 进程,然后执行 exec() 一些东西。请注意 vfork(2) 会阻塞调用线程,直到 child 退出或调用七个 exec() 函数之一。在调用任何 exec() 函数之前,您也不应修改 child 进程内的内存。

您提到您可以每秒 fork+exec 10k 次。这听起来很过分。您是否考虑过将您正在 exec 使用的东西变成守护进程?或者在您的应用程序中实现这些外部程序?不得不分叉那么多听起来很狡猾。

尽管有内存支持它,

fork 很可能开始为您失败,因为您正在使用 linux 的风格,它已禁用(或限制)内存过度使用。检查文件 /proc/sys/vm/overcommit_memory。如果它是 1 那么我的猜测是错误的并且还有其他奇怪的事情发生了。如果它是 0 那么你根本不允许过度使用。如果它是 2 那么你需要阅读文档以了解它是如何配置的。

上面提到的一个解决方案就是添加交换(永远不会被使用)。

另一种解决方案是实现一个小型守护进程,它将接收命令并执行那些 fork 和 exec,为您管道返回您需要的任何输出。

N.B。 fork 大进程理论上可以和小进程一样快。 fork 的性能取决于你有多少内存映射,而不是它们覆盖了多少内存。设置写时复制是根据映射完成的。除了在某些操作系统上设置匿名映射的 COW 与这些映射中的内存量成线性关系,但我不知道 Linux 在这里做什么,上次我在 Linux 中研究了 VM 系统已经超过 15 年了。