【问题标题】:Why does process creation using `clone` result in an out-of-memory failure?为什么使用“克隆”创建进程会导致内存不足失败?
【发布时间】:2017-07-13 14:36:00
【问题描述】:

我有一个在 32GB 机器上分配大约 20GB RAM 的进程。在某些事件之后,我将数据从父进程流式传输到子进程的标准输入。在子进程生成时,必须在父进程中保留 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,并且在 clone() 之后立即调用 exec()。

【问题讨论】:

  • 请分享代码,为什么不使用fork?
  • 这可能是一个过度使用的问题。尝试以 root 用户身份执行echo "1" >/proc/sys/vm/overcommit_memory。
  • 您总是可以在进程生命周期的早期生成子节点,并保留它直到您需要它。
  • 您可能应该提供详细信息,例如您的 Rust 版本、您使用的操作系统和操作系统版本等。
  • @Shepmaster,我喜欢你在分配 20G 之前生成孩子的建议。孩子可以坐在等待状态,直到需要它。向前迈出的另一步可能是将所有处理都放在孩子身上。每次我尝试在此过程中进行处理时,我都必须稍后更改为仅控制器的父级。现在我就这样开始了。

标签: unix memory process rust fork


【解决方案1】:

问题

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

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

父子进程现在是并发进程。您当前使用的 Rust 调用似乎是一个克隆调用,其行为与纯分叉非常相似,因此您的大小为 20G x 2 - 32G = 8G,不考虑操作系统所需的空间以及其他任何可能的空间跑步。克隆调用以负返回值返回,并且 errno 由对 ENOMEM errno 的调用设置。

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

推荐

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

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

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

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

内存过度使用总是

将 overcommit_memory 设置为 1 会消除问题中引用的克隆错误条件,因为 Rust 调用调用读取该设置的 LINUX 克隆调用。但是这个解决方案有几个注意事项表明上述建议是优越的,主要是 1 的值是危险的,尤其是对于生产环境。

背景

关于 OpenBSD rfork 和克隆调用的内核讨论随后发生在 1990 年代末和 2000 年代初。源自这些讨论的特性允许比进程更少极端的分叉,这对称地就像在 pthread 之间提供更广泛的独立性一样。其中一些讨论已经对已进入 POSIX 标准化的传统流程衍生产生了扩展。

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

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

注意事项

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

便携性和使用寿命

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

危险

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

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

一种非默认的 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

【讨论】:

  • 第2步有COW机制吗? “内存翻倍”是什么意思?哪个内存,物理的还是虚拟的?有问题的PC上overcommit_memory的设置是什么?
  • “分配虚拟内存时,它必须与物理内存空间相对应”——并非如此。 Linux中有overcommit,默认开启:9.6 Overcommit和win.tue.nl/~aeb/linux/lk/lk-9.html&stackoverflow.com/questions/38688824的OOM。
  • 不是。 Linux 通常允许分配比物理内存大小(RAM+swap)更多的虚拟内存。该内存没有映射到真实的东西,在访问每个页面时都会出现“页面错误”中断,内核将分配真实的物理页面,安装正确的映射并重新启动失败的指令。当没有要分配的物理内存时,就会出现OOM。这可以在 overcommit_memory 中关闭(并且 THP 可能会执行更复杂的任务)。请添加对您答案的引用,阅读一些文档,并在您不完全理解问题时尽量不要发布。
  • 并且在 fork 父内存将被 COWed:在父子之间共享,但被标记为只读。在对每个接触的页面(来自任何进程)进行第一次写访问时,将再次出现页面错误,并分配物理内存以进行复制和取消共享页面(COW = Copy-On-Write)。因此,fork 将为 ptes 和 vma 表消耗 mem(为子表创建新的虚拟映射),并且会做一些过度使用启发式记账,但不会在 fork 分配 20 GB phys。
  • 内存只在触摸时被复制,所以是COW。但是虚拟机记帐必须考虑到用户可能会触及所有这些的事实。内核中的过度使用逻辑默认设置为 2,而不是 1。设置 2 是启发式过度使用,这意味着内核允许 一些 过度使用,但会拒绝过多的过度使用。
猜你喜欢
  • 2011-11-28
  • 2017-05-26
  • 1970-01-01
  • 1970-01-01
  • 2018-02-05
  • 2014-11-23
  • 2019-06-28
  • 1970-01-01
  • 2012-06-26
相关资源
最近更新 更多