首页 > 解决方案 > 如果请求超过可用物理内存,如何让 malloc/calloc 失败(即不使用交换)

问题描述

malloc/calloc 显然使用交换空间来满足超出可用可用内存的请求。这几乎使系统挂起,因为磁盘使用指示灯一直亮着。发生在我身上之后,我并不确定为什么,我编写了以下 5 行测试程序来检查这确实是系统挂起的原因,

/* --- test how many bytes can be malloc'ed successfully --- */
#include <stdio.h>
#include <stdlib.h>
int main ( int argc, char *argv[] ) {
  unsigned int nmalloc = (argc>1? atoi(argv[1]) : 10000000 ),
               size    = (argc>2? atoi(argv[2]) : (0) );
  unsigned char *pmalloc = (size>0? calloc(nmalloc,size):malloc(nmalloc));
  fprintf( stdout," %s malloc'ed %d elements of %d bytes each.\n",
    (pmalloc==NULL? "UNsuccessfully" : "Successfully"),
    nmalloc, (size>0?size:1) );
  if ( pmalloc != NULL ) free(pmalloc);
  } /* --- end-of-function main() --- */

如果您的两个命令行参数的乘积超过物理内存,那确实会挂起系统。最简单的解决方案是 malloc/calloc 自动失败的某种方式。更难且不可移植的是编写一个 popen() 的 free 命令的小包装器,解析输出,并且只有在可用的“空闲”内存可以满足请求时才调用 malloc/calloc,也许有一点安全因素内置。

有没有更简单、更便携的方法来实现这一点?(显然与这个问题类似,calloc 或 malloc 可以用于在 OSX 中仅分配物理内存吗?但我希望得到某种“是”的答案。)

    E dit
--------------

决定遵循 Tom 的 /proc/meminfo 建议。也就是说,与其 popen()'ing "free",不如直接解析现有且易于解析的 /proc/meminfo 文件。然后,表格的单行宏

#define noswapmalloc(n) ( (n) < 1000l*memfree(NULL)/2? malloc(n) : NULL )

完成工作。如下所示的 memfree() 并不像我想要的那样可移植,但如果/当需要时,可以轻松透明地被更好的解决方案替换,而现在不是。

#include <stdio.h>
#include <stdlib.h>
#define _GNU_SOURCE                     /* for strcasestr() in string.h */
#include <string.h>
char    *strcasestr();                  /* non-standard extension */

/* ==========================================================================
 * Function:    memfree ( memtype )
 * Purpose:     return number of Kbytes of available memory
 *              (as reported in /proc/meminfo)
 * --------------------------------------------------------------------------
 * Arguments:   memtype (I)     (char *) to null-terminated, case-insensitive
 *                              (sub)string matching first field in
 *                              /proc/meminfo (NULL uses MemFree)
 * --------------------------------------------------------------------------
 * Returns:     ( int )         #Kbytes of memory, or -1 for any error
 * --------------------------------------------------------------------------
 * Notes:       o
 * ======================================================================= */
/* --- entry point --- */
int     memfree ( char *memtype ) {
  /* ---
   * allocations and declarations
   * ------------------------------- */
  static char memfile[99] = "/proc/meminfo"; /* linux standard */
  static char deftype[99] = "MemFree";  /* default if caller passes null */
  FILE  *fp = fopen(memfile,"r");       /* open memfile for read */
  char  memline[999];                   /* read memfile line-by-line */
  int   nkbytes = (-1);                 /* #Kbytes, init for error */
  /* ---
   * read memfile until line with desired memtype found
   * ----------------------------------------------------- */
  if ( memtype == NULL ) memtype = deftype; /* caller wants default */
  if ( fp == NULL ) goto end_of_job;    /* but we can't get it */
  while ( fgets(memline,512,fp)         /* read next line */
  !=      NULL ) {                      /* quit at eof (or error) */
    if ( strcasestr(memline,memtype)    /* look for memtype in line */
    !=   NULL ) {                       /* found line with memtype */
      char *delim = strchr(memline,':'); /* colon following MemType */
      if ( delim != NULL )              /* NULL if file format error? */
        nkbytes = atoi(delim+1);        /* num after colon is #Kbytes */
      break; }                          /* no need to read further */
    } /* --- end-of-while(fgets()!=NULL) --- */
  end_of_job:                           /* back to caller with nkbytes */
    if ( fp != NULL ) fclose(fp);       /* close /proc/meminfo file */
    return ( nkbytes );                 /* and return nkbytes to caller */
  } /* --- end-of-function memfree() --- */

#if defined(MEMFREETEST)
int     main ( int argc, char *argv[] ) {
  char  *memtype = ( argc>1? argv[1] : NULL );
  int   memfree();
  printf ( " memfree(\"%s\") = %d Kbytes\n Have a nice day.\n",
        (memtype==NULL?" ":memtype), memfree(memtype) );
  } /* --- end-of-function main() --- */
#endif

标签: clinuxmalloc

解决方案


malloc/calloc 显然使用交换空间来满足超出可用可用内存的请求。

嗯,不。

malloc/calloc 使用虚拟内存。“虚拟”意味着它不是真实的——它是由虚假和谎言构成的人为制造的幻觉。你的整个进程都建立在这些人为构建的幻象之上——线程是虚拟 CPU,套接字是虚拟网络连接,C 语言实际上是“C 抽象机”的规范,进程是虚拟计算机(实现语言的抽象机器)。

你不应该看魔法幕后。你不应该知道物理内存的存在。系统没有挂起——错觉只是变慢了,但这很好,因为 C 抽象机器没有说明任何事情应该花费多长时间,也没有提供任何性能保证。

更重要的是; 由于错觉,软件起作用了。它不会因为没有足够的物理内存而崩溃。失败意味着软件需要无限的时间才能成功完成,而“无限的时间”比“因为交换空间而变慢”要差很多数量级。

如果请求超过可用物理内存,如何让 malloc/calloc 失败(即不使用交换)

如果你要看看魔法幕后,你需要仔细定义你的目标。

例如,假设您的进程有 123 MiB 的代码并且当前有 1000 MiB 的可用物理 RAM;但是(因为代码在虚拟内存中)只有一小部分代码正在使用真实 RAM(其余代码在磁盘上,因为操作系统/可执行加载程序使用内存映射文件来避免浪费真实 RAM,直到它实际上是必要的)。您决定分配 1000 MiB 的内存(并且因为创建错觉的操作系统不是很好,不幸的是这会导致分配 1000 MiB 的实际 RAM)。接下来,您执行更多代码,但您执行的代码尚未在实际内存中,因此操作系统必须将代码从磁盘上的文件中提取到物理 RAM 中,但是您消耗了所有物理 RAM,因此操作系统必须将一些数据发送到交换空间。

再举一个例子,假设您的进程有 1 MiB 的代码和 1234 MiB 的数据,这些数据经过仔细分配以确保所有内容都适合物理内存。然后启动一个完全不同的进程,它为其代码和数据分配 6789 MiB 的内存;因此操作系统将所有进程的数据发送到交换空间,以满足您无法控制的其他进程。

编辑

这里的问题是提供错觉的操作系统不是很好。malloc()当您使用或分配大量虚拟内存时calloc(); 操作系统应该能够使用一小块真实内存来欺骗你,避免消耗大量的真实内存。特别是(对于在普通硬件上运行的大多数现代操作系统);操作系统应该能够用一个充满零的页面填充巨大的虚拟内存区域,该页面被多次映射(在许多虚拟地址上)为“只读”,因此分配大量虚拟内存几乎不需要物理RAM(直到您写入虚拟内存,导致操作系统分配满足修改所需的最少物理内存)。当然,如果您最终确实写入了所有分配的虚拟内存,那么您最终会耗尽物理内存并使用一些交换空间;

考虑到这一点;我很想尝试使用mmap(..., MAP_ANONYMOUS, ...)而不是(实施不佳)malloc()calloc(). 这可能意味着您必须处理分配的虚拟内存不能保证初始化为零的可能性,但是(取决于您使用内存的目的)这可能很容易解决。


推荐阅读