Linux 上内存过量使用的目的是什么?

Linux 上内存过量使用的目的是什么?

我知道关于内存过度使用我非常不喜欢它并且通常禁用它。我是不是想着设定值基于系统进程(比如那些运行sudo或者后缀)但是一个普通的 Linux 进程是由一些没有管理员权限的用户在某些命令行上启动的。

一个编写良好的程序可能malloc(或者mmap经常被使用malloc)比可用内存更多,并且在使用它时崩溃。如果没有内存过度使用,该mallocmmap将会失败,而编写良好的程序会捕获该失败。malloc当使用失败的结果时,编写得不好的程序(在没有检查失败的情况下使用)会崩溃malloc

当然虚拟地址空间mmap(由so by扩展malloc)与 RAM 不同(RAM 是由内核管理的资源,请参阅;进程的虚拟地址空间初始化为执行(2)并由mmap&扩展sbrk,因此不消耗直接地仅内存虚拟内存)。

请注意,优化 RAM 使用可以通过疯狂的维斯(2)(这可以给出一个提示,使用MADV_DONTNEED内核将一些页面交换到磁盘上),当真正需要时。想要过度承诺的程序可以使用映射(2)MAP_NORESERVE。我对内存过度使用的理解就好像每个内存映射(通过execvemmap)都隐式使用MAP_NORESERVE

我的看法是它对于有很多错误的程序很有用。但恕我直言,真正的开发人员应该总是检查 和相关虚拟地址空间更改函数的失败mallocmmap例如这里)。我研究过的大多数自由软件程序的源代码都有这样的检查,也许像某些xmalloc功能....

是否有现实生活中的程序(例如,打包在典型的 Linux 发行版中)实际上需要并且正在以合理且有用的方式使用内存复用?我不认识他们!

禁用内存复用有哪些缺点?许多较旧的 Unix(例如上个世纪的 SunOS4、SunOS5)没有它,恕我直言,它们malloc(甚至可能是一般的全系统性能malloc)也没有差多少(从那时起的改进与内存过度使用无关) 。

我相信内存过度使用对于懒惰的程序员来说是一个错误。

该程序的用户可以设置一些资源限制设置限制(2)RLIMIT_AS由父进程调用(例如ulimit内置的/bin/bash; 或limit内置的桀骜,或任何现代的等价物,例如atcrontabbatch,...),或祖父母进程(直到最终/sbin/init pid 1 或其现代系统变体)。

答案1

过度使用的原因是为了避免物理 RAM 的利用不足。进程分配的虚拟内存量和实际映射到物理页帧的虚拟内存量之间存在差异。事实上,进程启动后,它保留的 RAM 非常少。这是由于需求分页:进程具有虚拟内存布局,但直到读取或写入内存时才建立从虚拟内存地址到物理页帧的映射。

程序通常不会使用其整个虚拟内存空间,并且在程序运行期间所触及的内存区域会发生变化。例如,可以丢弃到包含仅在运行开始时执行的初始化代码的页框的映射,并且该页框可以用于其他映射。

这同样适用于数据:当程序调用 时malloc,它会保留足够大的连续虚拟地址空间来存储数据。然而,直到实际使用页面时才建立到物理页框的映射,如果曾经。或者考虑一下程序堆栈:每个进程都会获得一个相当大的连续虚拟内存区域,专门用于堆栈(通常为 8 MB)。进程通常只使用这个堆栈空间的一小部分;小型且运行良好的程序使用的堆栈空间甚至更少。

Linux 计算机通常有许多异构进程在其生命周期的不同阶段运行。从统计上看,在任何时间点,它们都不需要为它们已分配的每个虚拟页面(或稍后在程序运行中分配)共同需要映射。

严格的非过度使用方案将在分配虚拟页时创建从虚拟地址页到物理 RAM 页帧的静态映射。这将导致系统可以同时运行的程序少得多,因为大量的 RAM 页框将被毫无用途地保留。

我不否认过度使用内存有其危险,并且可能导致内存不足的情况,而处理起来很混乱。这一切都是为了找到正确的妥协方案。

答案2

你这样说,好像懒惰不被认为是一种美德在编程中:)。

大量的软件都针对简单性和可维护性进行了优化,并将幸存的低内存条件作为非常低的优先级。通常将分配失败视为致命的。退出耗尽内存的进程可以避免出现没有可用内存的情况,并且如果不分配更多内存或不进行全面预分配的复杂性,系统就无法取得进展。

请注意检查分配和死亡与不检查和崩溃之间的区别有多么细微。将过度使用归咎于程序员不去检查 malloc() 是成功还是失败是不公平的。

只有少量软件可以让您相信在分配失败时能够“正确”继续运行。通常应该期望内核能够存活。 sqlite 有一个众所周知的稳健测试其中包括内存不足测试,特别是因为它旨在支持各种小型嵌入式系统。

作为正常操作中不使用的故障路径,正确处理低内存条件会给维护和测试带来显着的额外负担。如果这种努力没有产生相应的效益,那么用在其他地方可能会更有利可图

当这种情况发生时,通常会出现大量分配的特殊情况,以处理最常见的失败原因。

允许一个一定数量过度使用的在这种背景下可能是最好的看待。它是 Linux 当前默认妥协的一部分。

请注意这样的想法:应该禁用内核级过度使用,而提供更多交换比你曾经想要使用的,也有它的讨厌者。随着时间的推移,RAM 和旋转硬盘驱动器之间的速度差距越来越大,因此当系统实际使用您允许的交换空间时,它通常可以被描述为“逐渐停止”。

答案3

我同意并赞成 Johan Myréen 的回答,但这里有更多解释可以帮助您理解这个问题。

您似乎混淆了交换区域,即用于存储较少使用的 RAM 和虚拟内存的磁盘空间。后者由 RAM 区域和磁盘区域组合而成。

进程正在保留和使用虚拟内存。他们不知道它存储在哪里。当它们需要访问 RAM 中不存在的某些数据时,进程(或线程)将被挂起,直到内核完成使数据页可用的工作。

当存在 RAM 需求时,内核通过在磁盘交换区域上存储较少使用的进程页面来释放一些 RAM。

当进程保留内存(即malloc等)时,非过度使用操作系统将虚拟内存的未使用部分标记为不可用。这意味着当进行分配的进程实际上需要访问保留页面时,将保证它们存在。

缺点是内存不能被任何其他进程使用,这会阻止这些进程对其页面进行分页,因此如果这些进程处于非活动状态,则会浪费 RAM。最糟糕的是,如果保留的总和大于交换区域的大小,则 RAM 页面也将被保留以匹配保留,尽管不包含任何数据。这是一个非常糟糕的情况,因为你的 RAM 是两个都无法使用的没用过。最后,最坏的情况是无法接受大量预留,因为没有更多可用的虚拟内存(交换+内存)。进行预订的过程通常会崩溃。

另一方面,像 Linux 这样的过度使用操作系统押注在任何给定时间都不会出现虚拟内存短缺。它们接受大多数内存预留(但不是不切实际的内存预留,这可以或多或少地进行调整),一般来说,这可以更好地利用 RAM 和交换资源。

这类似于航空公司超额预订座位。这提高了入住率,但一些乘客可能会不满意。希望航空公司可以为他们预订另一趟航班,并可能对他们进行补偿,而 Linux 只是将体重较重的乘客从飞行的飞机上扔出去……

总而言之,Linux 以一种惰性的、“尽力而为”的方式保留内存,而其他几个操作系统确实保证了保留。

实际情况是,当一个使用大量虚拟内存的程序先执行 fork,然后再执行 exec 时,过度使用才有意义。

假设您有 4 GB 的 RAM,其中 3 GB 可用于虚拟内存,4 GB 可用于交换。有一个进程保留了 4 GB,但只使用了其中的 1 GB。由于没有分页,因此系统性能良好。在非超额使用的操作系统上,该进程无法分叉,因为分叉后,需要保留 4 GB 以上的虚拟内存,而只剩下 3 GB。

在 Linux 上,此 fork(或克隆)系统调用将成功完成(但在幕后作弊),并且在以下 exec(如果有)之后,这些保留但未使用的 4 GB 将被释放,不会造成任何损害。

答案4

杀灭过程可能是理想的

其他答案讨论了过度承诺如何更有效。此外,有时终止进程是正确的做法。

首先考虑代码while(1){malloc(1)}。如果过度使用,它最终会被 OOM 杀手杀死。如果没有过度使用,它将吞噬所有可用内存,导致系统崩溃。

一种没有过度使用的解决方案是使用 cgroup 限制内存量。然而,这就留下了选择合理默认值的挑战。有时没有任何合理的默认值。有些科学任务就像解决暂停问题一样困难,因此您不知道它们需要多少内存和时间才能完成。通过让科学用户安全地分配可用内存的每个字节,您可以最大限度地提高他们的成功机会,因为他们知道,如果更重要的进程需要他们的内存,OOM 杀手就会将他们打倒。

相关内容