不建议在分叉时执行外部程序
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
可以用 stat
、readdir
、glob
函数 ...)。也许您可能会考虑 添加一些 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 年了。
我有一个可以占用 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
可以用 stat
、readdir
、glob
函数 ...)。也许您可能会考虑 添加一些 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 年了。