我试图通过手动调用来查看 oom_kill 的工作情况。
我动态分配内存,并尝试首先使用 while 循环无限使用它们,然后使用 for 循环来测试内存不足。
但是在第一种情况下,我使用 while 循环,它在没有交换的情况下抛出分段错误,并且在使用交换时变得无响应,而在调用 for 循环内存不足(oom_kill)时。
两者的示例代码:
第一种情况: while:
int main (void) {
char* p;
while (1) {
p=malloc(1<<20);
memset (p, 0, (1<<20));
}
}
第二种情况:对于:
int main (void) {
int i, n = 0;
char *pp[N];
for (n = 0; n < N; n++) {
pp[n] = malloc(1<<20);
if (pp[n] == NULL)
break;
}
printf("malloc failure after %d MiB\n", n);
for (i = 0; i < n; i++) {
memset (pp[i], 0, (1<<20));
printf("%d\n", i+1);
}
其中 N 是调用 oom 的某个非常大的数字。对于第二种情况,参考了这个https://www.win.tue.nl/~aeb/linux/lk/lk-9.html。
为什么会这样?我在 while 循环中犯了什么错误?
内核版本:4.15
为什么会这样?
要调用 OOM Killer,您需要遇到无法完成对内存的访问的情况,因为没有足够的 RAM 来完成访问。为此,您需要首先进行大量分配(虚拟内存映射),然后写入它们。
触发 OOM Killer 的过程非常简单:
分配大量内存
写入分配的内存
在内核调用 OOM 杀手以提供更多 RAM/交换空间来填充之前,您必须有足够的预分配内存,以导致从 RAM 中驱逐的所有内容(例如内存映射文件),并使用所有交换空间正在写入的虚拟内存的支持。
我在 while 循环中犯了什么错误?
一个错误,一个逻辑错误。
错误是,您不检查
malloc()
是否返回 NULL。当进程没有更多可用虚拟内存(或内核出于任何原因拒绝提供更多虚拟内存)时,malloc()
返回 NULL。 (在正常操作中,每个进程可用的虚拟内存对于非特权用户来说是有限的;运行例如 ulimit -a
查看当前限制。)
因为您在分配内存时立即访问内存,所以当 RAM 和 SWAP 耗尽时,内核只是拒绝允许您的进程进行更多操作,并且
malloc()
返回 NULL。然后,您取消引用 NULL 指针(通过使用 memset(NULL, 0, 1<<20)
),这会导致分段错误。
逻辑问题是该方案不会触发内核 OOM 杀手。
请记住,为了触发内核 OOM 杀手,您的进程必须分配尚未访问的内存。仅当内核已经提供了虚拟内存,但无法用实际 RAM 来支持它时,才会调用 OOM 杀手,因为 RAM 中没有任何可逐出的内容,并且交换区已满。
在你的情况下,OOM杀手不会被唤起,因为当内核耗尽RAM和交换空间时,它可以简单地拒绝提供更多(虚拟内存),导致
malloc()
返回NULL。
(Linux 内核内存子系统是一个正在积极开发的子系统,因此您看到的确切行为取决于内核版本、RAM 和交换量以及内存管理器可调参数(例如,位于 /proc/sys/ 下的可调参数) vm/)。以上描述了最常见或典型的情况和配置。)
您也不需要外部阵列。例如,您可以将分配链接到链表:
#include <stdlib.h>
#include <stdio.h>
#ifndef SIZE
#define SIZE (2*1024*1024) /* 2 MiB */
#endif
struct list {
struct list *next;
size_t size;
char data[];
}
struct list *allocate_node(const size_t size)
{
struct list *new_node;
new_node = malloc(sizeof (struct list) + size);
if (!new_node)
return NULL;
new_node->next = NULL;
new_node->size = size;
}
int main(void)
{
size_t used = 0;
struct list *root = NULL, *curr;
/* Allocate as much memory as possible. */
while (1) {
curr = allocate_node(SIZE - sizeof (struct list));
if (!curr)
break;
/* Account for allocated total size */
used += SIZE;
/* Prepend to root list */
curr->next = root;
root = curr;
}
printf("Allocated %zu bytes.\n", used);
fflush(stdout);
/* Use all of the allocated memory. */
for (curr = root; curr != NULL; curr = curr->next)
if (curr->size > 0)
memset(curr->data, ~(unsigned char)0, curr->size);
printf("Wrote to %zu bytes of allocated memory. Done.\n", used);
fflush(stdout);
return EXIT_SUCCESS;
}
注意,上面的代码未经测试,甚至未经编译,但逻辑是合理的。如果您发现其中存在错误,或者有其他问题,请在评论中告诉我,以便我进行验证和修复。
您正在阅读的文档来自 2003 年。它选择分配的大得难以置信的数字是 10,000 MiB。
在 2018 年的今天,当新计算机可能配备 16GiB RAM 时,这种分配绝对可以成功,不会出现任何问题。
我在 while 循环中犯了什么错误?
分段错误可能是向 memset() 传递空指针的结果,因为 malloc() 出错时将返回 NULL。
您的第二个示例通过始终检查 malloc() 的返回值来避免此错误。
我使用了 while 循环...它对交换没有响应...
来自您提到的您正在阅读的文件:
有时进程在访问内核无法提供的内存时会出现段错误,有时它们被杀死,有时其他进程被杀死,有时内核挂起。
除了提到内核版本之外,您对操作系统和系统的描述非常模糊。大概这是 32 位版本?
内存不足实际上有两种方法。您的程序可能超出分配的(虚拟)内存量,或者系统实际上可能用完内存页。
请注意,内存(页)的可用性是物理内存大小、交换空间大小、内存使用情况和进程负载的复杂组合。
参考:当 Linux 内存不足时,作者:Mulyadi Santosa 或此处。