如果请求超出可用物理内存(即不使用swap),如何使malloc / calloc失败

时间:2019-04-11 05:36:15

标签: c linux malloc

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()的一个免费命令,解析输出,并且仅在可用的“空闲”内存可以满足该请求的情况下才调用malloc / calloc,也许有一点安全因素内置的。

有没有更简单,更便携的方法来实现这一目标? (显然与此问题can calloc or malloc be used to allocate ONLY physical memory in OSX?类似,但我希望得到某种“是”的答案。)

E d i t
--------------

决定遵循汤姆的/ proc / meminfo建议。也就是说,不是popen()设置为“免费”,而是直接解析现有且易于解析的/ 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

1 个答案:

答案 0 :(得分:5)

  

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

好吧,

Malloc / calloc使用虚拟内存。 “虚拟”意味着它不是真实的-这是一种由伪造和谎言构成的虚构幻觉。您的整个过程都建立在这些虚构的幻象之上:线程是虚拟CPU,套接字是虚拟网络连接,C语言实际上是“ C抽象机”的规范,过程是虚拟计算机(实现语言的抽象机)。

您不应该在魔术帘后面看。您不应该知道物理内存存在。该系统不会挂起-幻象只是速度较慢,但​​这很好,因为C抽象机没有说明应该花多长时间并且不提供任何性能保证。

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

  

如果请求超出可用物理内存(即不使用swap),如何使malloc / calloc失败

如果要躲在魔幻的窗帘后面,则需要仔细定义目标。

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

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

编辑

这里的问题是提供错觉的操作系统不是很好。当您使用malloc()calloc()分配大量虚拟内存时;操作系统应该能够使用一小部分真实内存来欺骗您,并避免消耗大量真实内存。特别是(对于在常规硬件上运行的大多数现代操作系统);操作系统应该能够用一个充满零的页面填充一个巨大的虚拟内存区域,该页面被多次映射(在许多虚拟地址上)为“只读”,因此分配大量的虚拟内存几乎不需要物理完全没有RAM(直到您写入虚拟内存,导致OS分配满足修改所需的最少物理内存)。当然,如果最终写入所有分配的虚拟内存,那么最终将耗尽物理内存并使用一些交换空间。但这可能会逐渐发生,而不会一次全部发生-与单个巨大的延迟相比,分散在很长一段时间内的许多微小延迟要被发现的可能性要小得多。

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