我有一个进程在 32GB 的机器上分配了大约 20GB 的 RAM。在一些事件之后,我将数据从父进程流式传输到子进程的标准输入。在生成子进程时,必须在父进程中保留 20GB 的数据。
该应用程序是用 Rust 编写的,我正在调用 Command::new('path/to/command')
创建子进程。
当我生成子进程时,操作系统正在捕获内存不足错误。
跟踪输出:
[pid 747] 16:04:41.128377 clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7ff4c7f87b10) = -1 ENOMEM (Cannot allocate memory)
为什么会出现陷阱?子进程不应消耗超过 1GB 和
exec()
在 clone()
之后立即调用.
最佳答案
问题
当 Rust 调用创建子进程时,在 C/C++ 级别会发生一些事情。这是一种简化,但它有助于解释困境。
父进程和子进程现在是并发进程。您当前使用的 Rust 调用似乎是一个克隆调用,其行为很像一个纯叉,因此您的大小为 20G x 2 - 32G = 8G,不考虑操作系统所需的空间和其他任何可能运行。克隆调用以负返回值返回,并且 errno 由对 ENOMEM errno 的调用设置。
如果添加物理内存、压缩数据或通过不需要在任何时候将其全部存储在内存中的进程流式传输数据的架构解决方案都不是选项,那么经典的解决方案相当简单。
推荐
将父流程设计为精益。然后生成两个工作子进程,一个处理您的 20GB 需求,另一个处理您的 1GB 需求1。这些 child 可以通过管道、文件、共享内存、套接字、信号量、信号和/或其他通信机制相互连接,就像 parent 和 child 一样。
许多成熟的软件包从 Apache httpd 到嵌入式蜂窝塔路由守护程序都使用这种设计模式。它是可靠的、可维护的、可扩展的和可移植的。
32G 可能足以满足 20G 和 1G 处理需求,以及操作系统和精益父进程。
尽管此解决方案肯定会解决您的问题,但如果以后要重用或扩展代码,那么研究涉及数据帧或多维切片的潜在流程设计更改以支持数据流和内存需求减少可能是有值(value)的。
总是内存过量使用
将 overcommit_memory 设置为 1 可以消除问题中引用的克隆错误条件,因为 Rust 调用会调用读取该设置的 LINUX 克隆调用。但是这个解决方案有几个警告指出上述建议是优越的,主要是值 1 是危险的,尤其是对于生产环境。
背景
在 1990 年代末和 2000 年代初,有关 OpenBSD rfork 和克隆调用的内核讨论随之而来。源自这些讨论的特性允许比进程更少的极端 fork ,这类似于在 pthread 之间提供更广泛的独立性。其中一些讨论产生了对已进入 POSIX 标准化的传统流程产生的扩展。
在 2000 年代初期,Linux Torvalds 提出了一种标志结构来确定执行模型的哪些组件是共享的,执行 fork 时会复制哪些组件,从而模糊了进程和线程之间的区别。由此,克隆调用出现了。
在这些线程中,如果有的话,不会过多讨论过度使用内存。设计目标是更多地控制 fork 的结果,而不是将内存使用优化委托(delegate)给操作系统启发式,这是
overcommit_memory = 0
的默认设置。做。注意事项
内存过量使用超出了这些扩展,增加了其模式权衡的复杂性 2、设计趋势警告 3、实际运行时间限制 4 和性能影响 5。
便携性和生命周期
此外,如果没有标准化,使用内存过量使用的代码可能无法移植,生命周期问题也很重要,尤其是当设置控制函数的行为时。如果设置系统发生更改,则无法保证向后兼容,甚至会出现一些 Markdown 警告。
危险
linuxdevcenter 文档 2 说,“1 总是过度使用。也许你现在意识到这种模式的危险。”,还有其他迹象表明总是过度使用 6、7。
LINUX、Windows和VMWare上overcommit的实现者可能保证可靠性,但这是一个统计游戏,结合过程控制的许多其他复杂性,在某些条件下可能会导致某些不稳定的特性。甚至名称 overcommit 也告诉我们一些有关其作为实践的真实特征的信息。
非默认的 overcommit_memory 模式,对于该模式有几个警告是问题,但适用于立即案件的立即审判可能以后会导致间歇性可靠性。
可预测性及其对系统可靠性和响应时间一致性的影响
类 UNIX 操作系统中的进程概念,从贝尔实验室开始,就是进程向其容器(操作系统)发出具体请求。结果既可预测又二元。请求被拒绝或被批准。一旦被授予,进程就被赋予对资源的完全控制和直接访问权,直到进程放弃对资源的使用。
虚拟内存的交换空间方面违反了这一原则,当 RAM 被大量消耗时,它表现为工作站上的事件严重减速。例如,在开发过程中,有时按下一个键必须等待 10 秒钟才能看到显示屏上的字符。
结论
有很多方法可以最大限度地利用物理内存,但希望分配的内存使用很少,这样做可能会带来负面影响。过度使用时交换导致的性能下降是一个有据可查的例子。如果您在 RAM 中保留 20G 数据,情况可能尤其如此。
只分配需要的东西,以智能方式 fork ,使用线程,并释放肯定不再需要的内存会导致内存节俭,而不会影响可靠性,在交换磁盘使用中造成峰值,并且可以在不受到系统资源限制的情况下运行.
Command::new
设计师的职位调用可能是基于这个角度。在这种情况下,在 fork 之后多久调用 exec 并不是在 spawn 期间请求多少内存的决定因素。注释和引用文献
[1] 生成 worker 子进程可能需要进行一些代码重构,从表面上看似乎太麻烦,但重构可能出奇的简单并且非常有益。
[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
关于unix - 为什么使用 `clone` 创建进程会导致内存不足故障?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42410226/