当仍有大量交换时,malloc失败

时间:2013-07-29 23:21:59

标签: c linux memory system-calls

为了测试内存不足行为,我使用GCC 4.7.1在32位Linux 3.2上编译了以下C程序,没有任何编译器标志:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main() {
    while (malloc(4096)) ;
    printf("%s", strerror(errno));
    return 0;
}

当我运行程序时,我发现在分配了大约2.5 GB的驻留内存后,malloc失败了(“无法分配内存”)。

该机器具有2GB的物理内存和4GB的交换空间。在程序运行期间没有观察到内核消息。

那么为什么Linux会停止发布内存呢?

相关问题:maximum memory which malloc can allocate,但它没有涉及Linux细节。

4 个答案:

答案 0 :(得分:9)

机器中的物理内存量与malloc的语义无关。进程具有固定的虚拟地址空间(对于32位进程通常为2GB)。 malloc的实现将返回addreses,直到它用完虚拟地址空间而不是物理RAM。

这是一个更详细的讨论

  

http://en.wikipedia.org/wiki/Virtual_address_space

答案 1 :(得分:4)

您正在观察的是VM溢出。

32位进程只能访问4GB的虚拟地址空间。 该虚拟地址空间必须适应

  • 您的流程文字
  • 您加载的任何共享库的文本(libc,libdl,...)
  • 至少一个堆栈
  • vsyscalls的文字
  • 任何其他VM映射

操作系统通常会对CPU上的内存控制器进行编程,为内核页面(其中包括vsyscalls)保留(大)部分VM空间,并将进程可用的内存限制为小于4GB。这曾经是VM的一半,现在它已经少了。

glibc malloc的实现为您的堆保留了一个(或多个)VM空间块,但它被编程为为共享映射留下足够的VM空间。 因此,根据虚拟地址空间中的其他内容,您可能会在整个VM饱和之前“内存不足”。

请咨询/proc/[pid]/maps以获取VM映射的位置。

使用完整虚拟机的唯一方法是使用mmap和一系列调用在您的免费虚拟机中的任何位置分配内存。尝试:

#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <unistd.h>

int main() {
    char * page;
    size_t memory_allocated = 0;
    while ((page = mmap(NULL, sysconf(_SC_PAGE_SIZE)
                        , PROT_READ | PROT_WRITE
                        , MAP_ANON | MAP_PRIVATE, -1, 0))
                 != MAP_FAILED) {
        memory_allocated += sysconf(_SC_PAGE_SIZE);
        // optionally touch the page
        // otherwise the mapping won't use physical RAM/swap
        // *page = 1; 
    }

    fprintf(stderr, "Allocated %zu MiB\n", memory_allocated >> 20);
    perror("mmap");
    return 0;
}

请不要尝试将其作为64位二进制文​​件,因为它会尝试在那里使用高达256TB的RAM。

在我的Mac OS X上,我得到:

$ gcc -m32 17935873.c
$ ./a.out
Allocated 3516 MiB
mmap: Cannot allocate memory

答案 2 :(得分:0)

您是否使用root权限运行此代码?一旦我尝试了具有user权限的内存分配代码,我就会收到错误。

这不是你的答案,但我想在这里分享我的示例代码,也许我们可以从中学到一些东西。使用这种方法,我们可以分配所需的内存量,直到内存耗尽。这是有效的代码(如果你尝试了更多的内存,那么它会给出错误)。

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#define A_MEGABYTE (1024 * 1024)
#define PHY_MEM_MEGS 1024 /* Adjust this number as required */
int main()
{
    char *some_memory;
    size_t size_to_allocate = A_MEGABYTE;
    int megs_obtained = 0;
    while (megs_obtained < (PHY_MEM_MEGS * 2))
    {
        some_memory = (char *)malloc(size_to_allocate);
        if (some_memory != NULL)
        {
            megs_obtained++;
            sprintf(some_memory, “Hello World”); //Message to check
            printf(“%s - now allocated %d Megabytes\n”, some_memory, megs_obtained);
        }
        else
        {
             exit(EXIT_FAILURE);
        }
    }
    exit(EXIT_SUCCESS);
}

可能会有所帮助。

EDIT

这取决于我的理解,如果我错了,请告诉我。

正如你所说,你得到2.5 GB内存,这是因为堆可以分配2/3 times的内存。因此,您将从Max 3 GB内存获得4 GBRest memory occupied by some processes and definitely process scheduler。没有智能操作系统允许自杀。

我在这里阅读了Operating System Galvin本书。根据你的案例中的书,如果你想分配更多的内存,只需要调用malloc 1000次(每个malloc调用一行),或者直到操作系统的Page Size。直到你在内存中交换页面,你才会得到Kernel Message(理论上)。你的页面没有从内存中传出,这是唯一的问题。

如果您要检查所有Swap area,请使用Thread sleep编写此代码,以便检查交换内存中的所有进程。

我没有写任何代码,所以如果你写一些代码请更新我们。

答案 3 :(得分:0)

内存可能会碎片化,因此有几个小于4k的空间块。 malloc只有在找到所请求的空间时才会返回指向已分配内存的指针。

此外,在未分配的存储器中存在嵌入指针,其指向或链接在一起的空闲区域(每个K&amp; R)。这是一个适用于Linux和Windows的一般声明。

在Windows中,我已经转储了malloc返回的指针周围的区域,发现Windows有大约16个字节的信息(我认为主要是句柄)为每个malloc'd区域添加前缀。这些前缀不会返回给malloc的调用者,但它们会占用空间并减少特定计算机上可用的区域。

您可以尝试使用第二个循环来降低分配:

   int main()
   {
       int avail = 4096;

       while (malloc(avail)) ;
       printf("%s", strerror(errno));

       avail = avail >> 1;
       while (malloc(avail)) ;
       printf("%s", strerror(errno));

       // etc. down to whatever granularity you want
       // or even better code an inner lup to do this

       return 0;
   }