我不是内核开发人员,但是我花了多年时间思考这个问题,因为我多次遇到这个问题。实际上,我为整个情况想出了一个隐喻,所以让我告诉你。我会在我的故事中假设“交换”之类的东西不存在。无论如何,现在换32 GB RAM并没有多大意义。
想象您附近的某个地方,那里的水通过管道与每座建筑物相连,而城镇需要管理用水量。假设您每秒仅生产100单位水(由于没有储水箱,所有未使用的容量都会浪费掉)。每个家庭(家庭=一个小应用程序,一个终端,时钟小部件等)每秒需要一个1单位的水。一切都很好,因为您的人口大约为90,所以每个人都得到足够的水。
现在,市长(=您)决定要开一家大餐厅(=浏览器)。这家餐厅将容纳多位厨师(=浏览器标签)。每个厨师每秒需要1单位水。您从10位厨师开始,因此整个社区的总耗水量为100单位水,这仍然很好。
现在,有趣的事情开始了:您在餐厅雇用了另一位厨师,这显然使总需水量达到了101。你需要做点什么。
水管理(=内核)有3个选项。
1.第一个选项是刚刚拔下谁最近没有使用的水家的服务。很好,但是如果断开连接的房屋要再次使用水,则他们将需要再次进行冗长的注册过程。管理层可以断开多个房屋的连接,以释放更多的水资源。实际上,他们将断开最近未用水的所有房屋的连接,从而始终保持一定量的免费用水。
尽管您的城镇保持运转,但不利之处在于进度会停滞不前。您的大部分时间都花在等待水管理上以恢复服务。
这就是内核对文件支持页面的处理。如果运行大型可执行文件(例如chrome),则会将其文件复制到内存中。当内存不足或最近没有访问过某些部分时,内核会丢弃这些部分,因为它仍然可以从磁盘重新加载它们。如果做得过多,这会使您的桌面停顿下来,因为一切都将在等待磁盘IO。请注意,当您开始执行大量IO时,内核还将丢弃许多最近最少使用的页面。这就是为什么在您复制了多个大文件(如DVD图像)之后切换到后台应用程序需要花费一些时间的原因。
这对我来说是最烦人的行为,因为我讨厌筹码,而您对此没有任何控制权。能够将其关闭会很好。我在想一些类似的东西
sed -i 's/may_unmap = 1/may_unmap = (vm_swappiness >= 0)/' mm/vmscan.c
然后可以将vm_swappiness设置为-1以禁用此功能。在我的小测试中,这很好用,但是我不是内核开发人员,所以我没有将它发送给任何人(显然,上面的小修改还不完整)。
2。管理层可以拒绝新厨师的饮水要求。最初听起来是个好主意。但是,有两个缺点。首先,有些公司即使不使用也要求大量的用水。这样做的一个可能原因是避免他们需要额外水时与水管理部门交谈的所有开销。他们的用水量根据一天中的时间上下波动。以餐厅为例,与午夜相比,公司在中午需要更多的水。因此,他们要求使用所有可能使用的水,但这浪费了午夜的水分配。问题在于,并非所有公司都能正确预见其高峰使用量,因此他们提出了更多的要求,希望他们永远不必担心提出更多要求。
这就是Java的虚拟机的作用:它在启动时分配一堆内存,然后从那里开始工作。默认情况下,内核只会在Java应用程序实际开始使用内存时才分配内存。但是,如果禁用过量使用,内核将认真对待保留。仅当分配实际具有分配资源时,它才会允许分配成功。
但是,这种方法还有另一个更严重的问题。假设有一家公司每天开始请求一个单位的水量(而不是10个步骤)。最终,您将达到一个免费单位为0的状态。现在,该公司将无法分配更多资源。没关系,不管怎么说,谁在乎大公司。但是问题是小家庭也将无法请求更多的水!您将无法建造小型公共浴室来应对游客的突然涌入。您将无法为附近森林中的火灾提供紧急用水。
用计算机术语来说:在内存不足的情况下,如果没有过量使用,您将无法打开新的xterm,您将无法ssh进入计算机,也将无法打开新的选项卡以搜索可能的内容修复。换句话说,禁用过量使用也会使您的桌面在内存不足时变得无用。
3.现在,这是当公司开始使用过多水时解决问题的有趣方法。水管理炸毁了它!从字面上看:它去了餐厅的现场,将炸药扔进去,然后等它爆炸。这将立即大量减少城镇的用水需求,因此新人们可以搬进来,可以创建公共浴室等。作为市长,您可以重建餐厅,希望这次所需的用水量减少。例如,如果内部人员过多,您将告诉人们不要进入餐厅(例如,您将打开较少的浏览器标签)。
实际上,这是内核在所有选项都用完并且需要内存时执行的操作:它称为OOM杀手。它选择一个大型应用程序(基于许多启发式方法)并将其杀死,从而释放了大量内存,但维护了响应式桌面。实际上,Android内核会更加积极地执行此操作:当内存不足时,它会杀死最近最少使用的应用程序(相比之下,股票内核仅在万不得已时才这样做)。这在Android中称为Viking Killer。
我认为这是解决该问题的最简单的方法之一:好像您没有比这更多的选择,所以为什么不早于迟早解决它,对吗?问题在于内核有时会做很多工作来避免调用OOM杀手。这就是为什么您看到您的桌面非常慢并且内核对此无能为力的原因。但是幸运的是,您可以自己调用OOM杀手!首先,确保启用了魔幻的sysrq键(例如echo 1 | sudo tee
/proc/sys/kernel/sysrq
),然后每当您感到内核的内存不足时,只需按Alt + SysRQ,Alt + f。
好的,这一切都很好,但您想尝试一下吗?低内存情况很容易重现。我有一个非常简单的应用程序。您将需要运行两次。第一次运行将确定您有多少可用RAM,第二次运行将导致内存不足。请注意,此方法假定您已禁用交换功能(例如,执行sudo swapoff -a
)。代码和用法如下:
// gcc -std=c99 -Wall -Wextra -Werror -g -o eatmem eatmem.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char** argv)
{
int limit = 123456789;
if (argc >= 2) {
limit = atoi(argv[1]);
}
setbuf(stdout, NULL);
for (int i = 1; i <= limit; i++) {
memset(malloc(1 << 20), 1, 1 << 20);
printf("\rAllocated %5d MiB.", i);
}
sleep(10000);
return 0;
}
这是您的用法:
$ gcc -std=c99 -Wall -Wextra -Werror -g -o eatmem eatmem.c
$ ./eatmem
Allocated 31118 MiB.Killed
$ ./eatmem 31110
Allocated 31110 MiB.Killed
第一次调用检测到我们有31,118 MiB的可用RAM。因此,我告诉应用程序分配31,110个MiB RAM,以便内核不会杀死它,但会耗尽我几乎所有的内存。我的系统冻结了:即使鼠标指针也没有移动。我按了Alt + SysRQ,Alt + f,它杀死了我的eatmem进程,系统得到了恢复。
即使我们涵盖了在内存不足的情况下的处理方法,但最好的方法(就像其他任何危险的情况一样)是首先避免使用它。有很多方法可以做到这一点。我见过的一种常见方法是将行为异常的应用程序(如浏览器)放入与系统其余部分不同的容器中。在这种情况下,浏览器将无法影响您的桌面。但是预防本身不在问题的范围之内,所以我不会写它。
TL; DR:尽管当前尚无法完全避免分页,但可以通过禁用过量提交来缓解整个系统暂停。但是在内存不足的情况下,您的系统仍将无法使用,但方式会有所不同。无论上面如何,在内存不足的情况下,请按Alt + SysRQ,Alt + f可以终止内核选择的大部分过程。几秒钟后,系统应恢复其响应能力。这假定您已启用了不可思议的sysrq密钥(默认情况下未启用)。