如何从命令行测试oom-killer

Win*_*nix 9 command-line memory-usage ram

OOM杀手或移出存储器杀手是一个过程,在 Linux的当系统上存储器严重不足内核采用。... 这通过确保分配给进程的内存正在被积极使用来最大化系统内存的使用。

这个自我回答的问题问:

  • 如何从命令行测试oom-killer?

比自我回答所用的 1/2 小时更快的方法将被接受。

Eli*_*gan 10

快速触发 OOM 杀手的关键是避免陷入磁盘访问。所以:

  1. 避免交换,除非您的目标是专门测试使用交换时 OOM 的行为。您可以在测试前禁用交换,然后再重新启用它。swapon -s告诉您当前启用了哪些交换。sudo swapoff -a禁用所有交换;sudo swapon -a通常足以重新启用它们。

  2. 避免在非交换磁盘访问中穿插内存访问。 这种基于 globbing 的方法最终会耗尽您的可用内存(如果您的文件系统中有足够的条目),但它需要这么多内存的原因是存储通过访问您的文件系统获得的信息。即使使用 SSD,很可能大部分时间都花在从磁盘读取上,即使交换已关闭。如果您的目标是专门测试穿插磁盘访问的内存访问的 OOM 行为,那么该方法是合理的,甚至可能是理想的。否则,您可以更快地实现目标。

一旦禁用交换,任何很少从物理磁盘读取的方法都应该非常快。这包括tail /dev/zero(由falstaff发现上面Doug Smythies评论)。尽管它从字符 device 读取,但该“设备”只生成空字节(即全零字节),并且一旦打开设备节点,就不会涉及任何物理磁盘访问。该方法有效,因为在其输入中查找尾随行,但零流不包含换行符,因此它永远不会丢弃任何行。/dev/zerotail

如果您正在寻找一种解释性语言的单行程序,可以通过算法分配和填充内存,那么您很幸运。几乎在任何通用解释型语言中,分配大量内存并写入其中而不用其他方式使用它是很容易的。这是一个 Perl 单行程序,它似乎与tail /dev/zero(虽然我没有对其进行广泛的基准测试)一样快:

perl -wE 'my @xs; for (1..2**20) { push @xs, q{a} x 2**20 }; say scalar @xs;'
Run Code Online (Sandbox Code Playgroud)

在具有 4 GiB RAM 的旧机器上关闭交换,tail /dev/zero每次运行它们都需要大约 10 秒钟。两者在 RAM 多得多的较新机器上仍然可以正常工作。perl如果您的目标是简洁,您可以使该命令更短。

Perl 单行重复生成 ( q{a} x 2**20) 分开的中等长度的字符串——每个字符串大约一百万个字符——并通过将它们存储在一个数组 ( @xs) 中来保持它们的周围。您可以调整测试的数字。如果您没有使用所有可用内存,则单行输出创建的字符串总数。假设 OOM 杀手确实杀死了perl——使用上面显示的确切命令并且没有资源配额妨碍,我相信在实践中它总是会——那么你的 shell 应该向你显示Killed. 然后,在任何 OOM 情况下,dmesg都有详细信息。

虽然我喜欢这种方法,但它确实说明了一些关于编写、编译和使用 C 程序的有用信息——就像Doug Smythies 的回答中的那个。分配内存和访问内存在高级解释性语言中感觉不像是分开的事情,但在 C 中你可以注意到,如果你愿意,可以调查这些细节。


最后,您应该始终检查 OOM 杀手实际上是什么杀死了您的程序。检查的一种方法是检查dmesg。与流行的看法相反,即使在 Linux 上,分配内存的尝试也可能很快失败。使用大量分配显然会失败很容易实现这一点……但即使是那些也可能意外发生。看似合理的分配可能很快失败。比如在我的测试机上,perl -wE 'say length q{a} x 3_100_000_000;'成功,perl -wE 'say length q{a} x 3_200_000_000;'打印:

Out of memory!
panic: fold_constants JMPENV_PUSH returned 2 at -e line 1.
Run Code Online (Sandbox Code Playgroud)

两者都没有触发 OOM 杀手。更一般地说:

  • 如果您的程序预先计算需要多少内存并在单个分配中请求它,则分配可能会成功(如果成功,当使用足够的内存时,OOM 杀手可能会或可能不会杀死程序),或者分配可能只是失败。
  • 通过向数组添加许多元素来将数组扩展到巨大长度通常会在实际实践中触发 OOM 杀手,但在测试中让它可靠地做到这一点却非常棘手。几乎总是这样做的方法——因为这是最有效的方法——是使每个新缓冲区的容量是旧缓冲区容量的x倍。x 的常见值包括 1.5 和 2(该技术通常称为“表加倍”)。这有时会弥合实际可以分配和使用的内存量与内核知道的内存量之间的差距,甚至无法假装分发。
  • 内存分配可能会因为与内核或实际可用内存量无关的原因而失败,这也不会触发 OOM 杀手。特别是,在成功执行大量微小分配后,程序可能会在任何大小的分配上快速失败。这种失败发生在由程序本身执行的簿记中——通常是通过像malloc(). 我怀疑这就是今天发生在我身上的事情,在使用bash数组(实际上是作为双向链表实现的)测试期间,bash退出并显示一条错误消息,指出9 个字节的分配失败。

OOM 杀手意外触发比有意触发要容易得多。

在试图故意触发 OOM 杀手时,解决这些问题的一种方法是从请求过多内存开始,然后逐渐减小,就像Doug Smythies 的 C 程序所做的那样。另一种方法是分配一大堆中等大小的内存块,这就是上面显示的 Perl one-liner 所做的:没有一个百万字符的字符串(加上幕后的一些额外内存使用)是特别繁重的,但综合起来,所有 1 兆字节的购买量加起来。


Dou*_*ies 7

这个答案使用了一个C程序来分配尽可能多的内存,然后逐渐实际使用它,导致OOM保护被“杀死”。

/*****************************************************************************
*
* bla.c 2019.11.11 Smythies
*       attempt to invoke OOM by asking for a rediculous amount of memory
*       see: https://askubuntu.com/questions/1188024/how-to-test-oom-killer-from-command-line
*       still do it slowly, in chunks, so it can be monitored.
*       However simplify the original testm.c, for this example.
*
* testm.cpp 2013.01.06 Smythies
*           added a couple more sleeps, in attempts to observe stuff on linux.
*
* testm.cpp 2010.12.14 Smythies
*           attempt to compile on Ubuntu Linux.
*
* testm.cpp 2009:03:18 Smythies
*           This is not the first edit, but I am just adding the history
*           header.
*           How much memory can this one program ask for and sucessfully get?
*           Done in two calls, to more accurately simulate the program I
*           and wondering about.
*           This edit is a simple change to print the total.
*           the sleep calls have changed (again) for MS C version 2008.
*           Now they are more like they used to be (getting annoying).
*                                                                     Smythies
*****************************************************************************/

#include <stdio.h>
#include <stdlib.h>

#define CR 13

int main(){
   char *fptr;
   long i, k;

   i = 50000000000L;

   do{
      if(( fptr = (char *)malloc(i)) == NULL){
         i = i - 1000;
      }
   }
   while (( fptr == NULL) && (i > 0));

   sleep(15);  /* for time to observe */
   for(k = 0; k < i; k++){   /* so that the memory really gets allocated and not just reserved */
      fptr[k] = (char) (k & 255);
   } /* endfor */
   sleep(60);  /* O.K. now you have 1 minute */
   free(fptr); /* clean up, if we get here */
   return(0);
}
Run Code Online (Sandbox Code Playgroud)

结果:

doug@s15:~/c$ ./bla
Killed
doug@s15:~/c$ journalctl -xe | grep oom
Nov 11 16:08:24 s15 kernel: mysqld invoked oom-killer: gfp_mask=0x100cca(GFP_HIGHUSER_MOVABLE), order=0, oom_score_adj=0
Nov 11 16:08:25 s15 kernel:  oom_kill_process+0xeb/0x140
Nov 11 16:08:27 s15 kernel: [  pid  ]   uid  tgid total_vm      rss pgtables_bytes swapents oom_score_adj name
Nov 11 16:08:27 s15 kernel: oom-kill:constraint=CONSTRAINT_NONE,nodemask=(null),cpuset=/,mems_allowed=0,global_oom,task_memcg=/user/doug/0,task=bla,pid=24349,uid=1000
Nov 11 16:08:27 s15 kernel: Out of memory: Killed process 24349 (bla) total-vm:32638768kB, anon-rss:15430324kB, file-rss:952kB, shmem-rss:0kB, UID:1000 pgtables:61218816kB oom_score_adj:0
Nov 11 16:08:27 s15 kernel: oom_reaper: reaped process 24349 (bla), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
Run Code Online (Sandbox Code Playgroud)

运行仍然需要一段时间,但只有几分钟的时间。
采用mlock在C程序可能会帮助,但我没有尝试。

我的测试电脑是一台服务器,所以我watch -d free -m用来监控进度。

读者:搞乱OOM有点危险。如果您阅读所有这些答案和评论,您会注意到一些附带损害和不一致之处。我们无法控制其他任务何时可能需要更多内存,这很可能是在错误的时间。请谨慎操作,并建议在这些类型的测试后重新启动计算机。


Win*_*nix 3

修改后的答案

我最初的答案花了 1/2 小时来执行,并在这次修订中被删除:

ls -d /*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*
Run Code Online (Sandbox Code Playgroud)

oom-killer我将接受其他人的答案作为从命令行调用的更快方式。作为修订后的答案,我将解释如何从中获取相关的 oom-killer 详细信息journalctl以及它们的含义。


mjoao的这个更有效的答案可以耗尽 RAM:

logger --tag="kernel" "Start for oom-killer"; a=""; for b in {0..99999999}; do a=$b$a$a$a$a$a$a; done
Run Code Online (Sandbox Code Playgroud)

logger命令预先添加了journalctlRAM 消耗过程开始时的时间戳。

oom-killer 完成后,打开一个新终端并输入oomlog(稍后编写脚本内容):

$ oomlog
Nov 12 12:29:23 alien kernel[19202]: Start for oom-killer
Nov 12 12:30:02 alien kernel: 31981 total pagecache pages
Nov 12 12:30:02 alien kernel: 11627 pages in swap cache
Nov 12 12:30:02 alien kernel: Swap cache stats: add 10739122, delete 10727632, find 8444277/9983565
Nov 12 12:30:02 alien kernel: Free swap  = 0kB
Nov 12 12:30:02 alien kernel: Total swap = 8252412kB
Nov 12 12:30:02 alien kernel: 2062044 pages RAM
Nov 12 12:30:02 alien kernel: 0 pages HighMem/MovableOnly
Nov 12 12:30:02 alien kernel: 56052 pages reserved
Nov 12 12:30:02 alien kernel: 0 pages cma reserved
Nov 12 12:30:02 alien kernel: 0 pages hwpoisoned
Nov 12 12:30:02 alien kernel: [ pid ]   uid  tgid total_vm      rss nr_ptes nr_pmds swapents oom_score_adj name
Nov 12 12:30:02 alien kernel: [ 4358]  1000  4358  2853387  1773446    5578      13  1074744             0 bash
Nov 12 12:30:02 alien kernel: Out of memory: Kill process 4358 (bash) score 701 or sacrifice child
Nov 12 12:30:02 alien kernel: Killed process 4358 (bash) total-vm:11413548kB, anon-rss:7093784kB, file-rss:0kB, shmem-rss:0kB
Nov 12 12:30:03 alien kernel: oom_reaper: reaped process 4358 (bash), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
Run Code Online (Sandbox Code Playgroud)

更好的答案需要 30 秒来耗尽 RAM,这不太快(如tail /dev/zero)也不太慢(如我原来的答案)。

oomlog脚本将多页journalctl输出压缩为 16 行。

oom-killer [pid]字段的解释如下

  • pid进程 ID。
  • uid用户 ID。
  • tgid线程组 ID。
  • Total_vm虚拟内存使用量(以 4 kB 页为单位)
  • rss常驻内存使用(以 4 kB 页为单位)
  • nr_ptes页表条目
  • 互换条目互换条目
  • oom_score_adj通常为 0;较低的数字表示调用 OOM Killer 时进程终止的可能性较小。

oomlog bash 脚本

ls -d /*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*
Run Code Online (Sandbox Code Playgroud)