为什么使用`clone`创建进程会导致内存不足?

Nex*_*tar 5 unix memory fork process rust

我有一个在32GB机器上分配大约20GB RAM的进程.在一些事件之后,我将数据从父进程流式传输到子进程的stdin.在子进程生成时,必须在父进程中保留20GB的数据.

该应用程序是用Rust编写的,我打电话Command::new('path/to/command')来创建子进程.

当我生成子进程时,操作系统正在捕获内存不足错误.

strace输出:

[pid 747] 16:04:41.128377 clone(child_stack = 0,flags = CLONE_CHILD_CLEARTID | CLONE_CHILD_SETTID | SIGCHLD,child_tidptr = 0x7ff4c7f87b10)= -1 ENOMEM(无法分配内存)

陷阱为什么会发生?子进程的消耗不应超过1GB,并exec()在之后立即调用clone().

Dou*_*eco 5

问题

当 Rust 调用创建子进程时,C/C++ 级别会发生一些事情。这是一种简化,但有助于解释这个困境。

  1. 流是重复的(使用 dup2 或类似的调用)
  2. 父进程被分叉(使用 fork 或 clone 系统调用)
  3. 分叉进程执行子进程(通过 execvp 系列的调用)

父进程和子进程现在是并发进程。您当前使用的 Rust 调用似乎是一个克隆调用,其行为非常类似于纯 fork,因此您的内存不足 20G x 2 - 32G = 8G,而不考虑操作系统所需的空间以及其他任何可能的空间。跑步。克隆调用返回负返回值,并且 errno 通过调用 ENOMEM errno 设置。

如果添加物理内存、压缩数据或通过不需要在任何时候将全部数据都存储在内存中的进程进行流式传输的架构解决方案都不是选项,那么经典解决方案相当简单。

推荐

设计精益的父流程。然后生成两个工作子进程,一个处理您的 20GB 需求,另一个处理您的 1GB 需求1。这些子级可以通过管道、文件、共享内存、套接字、信号量、信令和/或其他通信机制相互连接,就像父级和子级一样。

从 Apache httpd 到嵌入式蜂窝塔路由守护程序的许多成熟软件包都使用这种设计模式。它可靠、可维护、可扩展且可移植。

32G 可能足以满足 20G 和 1G 处理需求,以及操作系统和精益父流程。

尽管此解决方案肯定会解决您的问题,但如果以后要重用或扩展代码,那么研究涉及数据帧或多维切片的潜在流程设计更改以支持数据流和内存需求减少可能是有价值的。

内存总是过量使用

将 overcommit_memory 设置为 1 可以消除问题中引用的克隆错误情况,因为 Rust 调用会调用读取该设置的 LINUX 克隆调用。但此解决方案有几个警告,表明上述建议更优越,主要是 1 的值是危险的,特别是对于生产环境。

背景

关于 OpenBSD rfork 和克隆调用的内核讨论在 20 世纪 90 年代末和 2000 年代初展开。这些讨论产生的特性允许比进程更少的极端分叉,这与在 pthread 之间提供更广泛的独立性对称。其中一些讨论已经产生了传统进程衍生的扩展,并已进入 POSIX 标准化。

在 2000 年代初,Linux Torvalds 提出了一种标志结构来确定执行模型的哪些组件是共享的,以及在执行分叉时复制哪些组件,从而模糊了进程和线程之间的区别。由此,克隆召唤出现了。

在这些线程中,没有过多讨论内存的过度使用(如果有的话)。设计目标是更多地控制分叉的结果,而不是将内存使用优化委托给操作系统启发式,这是默认设置的作用overcommit_memory = 0。

注意事项

内存过度使用超出了这些扩展范围,增加了其模式2、设计趋势警告3、实际运行时间限制4和性能影响5 之间权衡的复杂性。

便携性和寿命

此外,如果没有标准化,使用内存过度使用的代码可能无法移植,并且寿命问题也很重要,尤其是当设置控制函数的行为时。如果设置系统发生更改,则无法保证向后兼容性,甚至会发出一些贬值警告。

危险

linuxdevcenter 文档2说,“1 总是过度使用。也许您现在意识到这种模式的危险。”,并且还有其他迹象表明始终过度使用6, 7存在危险。

LINUX、Windows和VMWare上的过量使用的实现者可能会保证可靠性,但这是一个统计游戏,与过程控制的许多其他复杂性相结合,在某些条件下可能会导致某些不稳定的特性。甚至过度使用这个名字也告诉了我们一些关于它作为一种实践的真实特征。

非默认的 overcommit_memory 模式会出现多个警告,但适用于立即案例的立即试验,稍后可能会导致间歇性可靠性。

可预测性及其对系统可靠性和响应时间一致性的影响

从贝尔实验室开始,类 UNIX 操作系统中的进程的想法是,进程向其容器(操作系统)发出具体请求。结果既可预测又是二进制的。该请求要么被拒绝,要么被批准。一旦获得授权,进程就会获得对资源的完全控制和直接访问,直到进程放弃对资源的使用。

虚拟内存的交换空间方面违反了这一原则,当 RAM 被大量消耗时,工作站上的活动会明显减速。例如,在开发过程中,有时按下一个键后必须等待十秒钟才能看到显示屏上的字符。

结论

有很多方法可以充分利用物理内存,但希望分配的内存使用稀疏可能会带来负面影响。过度使用时交换会导致性能下降,这是有据可查的例子。如果您在 RAM 中保存 20G 的数据,情况可能尤其如此。

仅分配所需内容、以智能方式分叉、使用线程以及释放肯定不再需要的内存,从而节省内存,而不会影响可靠性、造成交换磁盘使用量峰值,并且可以在系统资源限制范围内运行,无需任何警告。

通话设计者的立场Command::new可能就是基于这个角度。在这种情况下,fork 之后多久调用 exec 并不是在生成期间请求多少内存的决定因素。

注释和参考文献

[1] 生成工人子代可能需要一些代码重构,从表面上看似乎很麻烦,但重构可能出人意料地简单并且非常有益。

[2] http://www.linuxdevcenter.com/pub/a/linux/2006/11/30/linux-out-of-memory.html?page=2

[3] https://www.etalabs.net/overcommit.html

[4] http://www.gabesvirtualworld.com/memory-overcommit-in-Production-yes-yes-yes/

[5] https://labs.vmware.com/vmtj/memory-overcommitment-in-the-esx-server

[6] https://github.com/kubernetes/kubernetes/issues/14452

[7] http://linuxtoolkit.blogspot.com/2011_08_01_archive.html

  • 内存只有在触摸时才会被复制,所以它是COW。但虚拟机核算必须考虑到用户可能会接触到所有这些的事实。内核中的过量使用逻辑默认设置为 2,而不是 1。设置 2 是启发式过量使用,这意味着内核允许“某些”过量使用,但会拒绝真正过量的过量使用。 (2认同)