Как заставить malloc / calloc потерпеть неудачу, если запрос превышает свободную физическую память (т. Е. Не использовать swap) - PullRequest
2 голосов
/ 11 апреля 2019

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
--------------

Решил последовать совету Тома / proc / meminfo.То есть вместо popen () 'free' просто анализируйте существующий и легко разбираемый файл / proc / meminfo.И затем, однострочный макрос вида

# определяет noswapmalloc (n) ((n) <1000l * memfree (NULL) / 2? Malloc (n): NULL) </p>

заканчивает работу.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

Ответы [ 2 ]

5 голосов
/ 11 апреля 2019

malloc / calloc, очевидно, использует пространство подкачки для удовлетворения запроса, который превышает доступную свободную память.

Ну, нет.

Malloc / calloc использует виртуальную память.«Виртуальный» означает, что он не настоящий - это искусственно созданная иллюзия, созданная из подделки и лжи.Весь ваш процесс построен на этих искусственно созданных иллюзиях: поток - это виртуальный ЦП, сокет - это виртуальное сетевое соединение, язык C - это действительно спецификация для «абстрактной машины C», процесс - это виртуальный компьютер (реализующийабстрактная машина языков).

Ты не должен заглядывать за волшебный занавес.Вы не должны знать, что физическая память существует.Система не зависает - иллюзия только медленнее, но это прекрасно, потому что абстрактная машина C ничего не говорит о том, сколько времени должно быть, и не дает никаких гарантий производительности.

Что еще важнее;из-за иллюзии софт работает.Он не падает, потому что не хватает физической памяти.Отказ означает, что для успешного завершения программного обеспечения требуется бесконечное количество времени, и «бесконечное количество времени» на много порядков хуже, чем «медленнее из-за пространства подкачки».

Какзаставить malloc / calloc завершиться с ошибкой, если запрос превышает свободную физическую память (т. е. не использовать подкачку)

Если вы собираетесь заглянуть за волшебный занавес, вам нужно тщательно определить свои цели.

Например, представьте, что ваш процесс имеет 123 МБ кода и в настоящее время имеется 1000 МБ свободной физической ОЗУ;но (поскольку код находится в виртуальной памяти), только небольшая часть кода использует реальную память (а остальная часть кода находится на диске, потому что загрузчик ОС / исполняемый файл использовал файлы с отображенной памятью, чтобы не тратить реальную память до тех пор, пока она фактически не понадобится)).Вы решаете выделить 1000 МБ памяти (а поскольку операционная система, создающая иллюзию, не очень хороша, к сожалению, это приводит к выделению 1000 МБ реальной оперативной памяти).Затем вы выполняете еще немного кода, но код, который вы выполняете, еще не находится в реальной памяти, поэтому ОС должна извлечь код из файла на диске в физическое ОЗУ, но вы использовали все физическое ОЗУ, поэтому ОСнеобходимо отправить некоторые данные в пространство подкачки.

В другом примере представьте, что ваш процесс имеет 1 МБ кода и 1234 МБ данных, которые были аккуратно выделены, чтобы убедиться, что все вписывается в физическую память.Затем запускается совершенно другой процесс, и он выделяет 6789 МБ памяти для своего кода и данных;поэтому ОС отправляет все данные вашего процесса в пространство подкачки, чтобы удовлетворить другой процесс, который вы не можете контролировать.

РЕДАКТИРОВАТЬ

Проблема в том, чтоОС, обеспечивающая иллюзию, не очень хороша.Когда вы выделяете большой объем виртуальной памяти с помощью malloc() или calloc();ОС должна иметь возможность использовать крошечный кусочек реальной памяти, чтобы лгать вам и избегать потребления большого объема реальной памяти.В частности (для большинства современных операционных систем, работающих на обычном оборудовании);ОС должна быть способна заполнить огромную область виртуальной памяти одной страницей, полной нулей, которая много раз отображается (по многим виртуальным адресам) как «только для чтения», так что выделение огромного объема виртуальной памяти почти не требует физическойОЗУ вообще (пока вы не запишете в виртуальную память, заставляя ОС выделять наименьшее количество физической памяти, необходимой для удовлетворения изменений).Конечно, если вы в конечном итоге сделаете запись во всю выделенную виртуальную память, то вы в конечном итоге исчерпаете физическую память и используете некоторое пространство подкачки;но это, вероятно, будет происходить постепенно, а не все сразу - многие крошечные задержки, разбросанные за большой промежуток времени, гораздо менее вероятны, чем одна огромная задержка.

Имея это в виду;Я хотел бы попробовать использовать mmap(..., MAP_ANONYMOUS, ...) вместо (плохо реализовано) malloc() или calloc().Это может означать, что вам приходится иметь дело с возможностью того, что выделенная виртуальная память не будет гарантированно инициализироваться нулями, но (в зависимости от того, для чего вы используете память) это, вероятно, будет легко обойти.

1 голос
/ 12 апреля 2019

Расширяя комментарий, который я сделал к исходному вопросу:

Если вы хотите отключить подкачку, используйте команду swapoff (sudo swapoff -a). Я обычно запускаю свою машину таким образом, чтобы она не зависала, когда Firefox делает то, чего не должен делать. Вы можете использовать setrlimit() (или команду ulimit), чтобы установить максимальный размер виртуальной машины, но это не будет должным образом компенсировать какой-то другой процесс, внезапно решивший стать бременем памяти (см. Выше).

Даже если вы выберете один из указанных выше вариантов, вам следует прочитать оставшуюся часть этого ответа, чтобы узнать, как избежать ненужной инициализации при первом вызове calloc().


Что касается вашего точного тестового жгута, то получается, что вы запускаете неудачное исключение из оптимизации GNU calloc().

Вот комментарий (теперь удаленный), который я сделал к другому ответу, который оказывается не совсем точным:

Я проверил источник glibc для библиотеки по умолчанию gloc / linux malloc и убедился, что calloc() обычно не очищает вручную память, которая была только что mmap'd. И malloc() не затрагивает память вообще.

Оказывается, я пропустил одно исключение для оптимизации calloc. Из-за того, как реализация GNU malloc инициализирует систему malloc, вызов first на calloc всегда использует memset() для установки вновь выделенного хранилища на 0. Каждый второй вызов на calloc() проходит через вся логика calloc, которая позволяет избежать вызова memset() для хранилища, которое было недавно mmap'd.

Таким образом, следующая модификация тестовой программы демонстрирует радикально другое поведение:

#include <stdio.h>
#include <stdlib.h>
int main ( int argc, char *argv[] ) {
  /* These three lines were added */
  void* tmp = calloc(1000, 1); /* force initialization */
  printf("Allocated 1000 bytes at %p\n", tmp);
  free(tmp);
  /* The rest is unchanged */
  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);
}

Обратите внимание, что если вы установите MALLOC_PERTURB_ в ненулевое значение, то оно используется для инициализации блоков malloc () 'и принудительно инициализирует блоки calloc () в 0. Это используется в тесте ниже.

Далее я использовал /usr/bin/time, чтобы показать количество сбоев страниц во время выполнения. Обратите внимание на количество незначительных сбоев, которые являются результатом того, что операционная система инициализирует нулями ранее не ссылающуюся страницу в анонимной области mmap (и некоторые другие случаи, например отображение страницы, уже присутствующей в кеше страниц Linux). Также посмотрите на размер резидентного набора и, конечно же, время выполнения.

$ gcc -Og -ggdb -Wall -o mall mall.c

$ # A simple malloc completes instantly without page faults
$ /usr/bin/time ./mall 4000000000
Allocated 1000 bytes at 0x55b94ff56260
 Successfully malloc'ed -294967296 elements of 1 bytes each.
0.00user 0.00system 0:00.00elapsed 100%CPU (0avgtext+0avgdata 1600maxresident)k
0inputs+0outputs (0major+61minor)pagefaults 0swaps

$ # Unless we tell malloc to initialise memory
$ MALLOC_PERTURB_=35 /usr/bin/time ./mall 4000000000
Allocated 1000 bytes at 0x5648c2436260
 Successfully malloc'ed -294967296 elements of 1 bytes each.
0.19user 1.23system 0:01.43elapsed 99%CPU (0avgtext+0avgdata 3907584maxresident)k
0inputs+0outputs (0major+976623minor)pagefaults 0swaps

# Same, with calloc. No page faults, instant completion.
$ /usr/bin/time ./mall 1000000000 4
Allocated 1000 bytes at 0x55e8257bb260
 Successfully malloc'ed 1000000000 elements of 4 bytes each.
0.00user 0.00system 0:00.00elapsed 100%CPU (0avgtext+0avgdata 1656maxresident)k
0inputs+0outputs (0major+62minor)pagefaults 0swaps

$ # Again, setting the magic malloc config variable changes everything
$ MALLOC_PERMUTE_=35 /usr/bin/time ./mall 1000000000 4
Allocated 1000 bytes at 0x5646f391e260
 Successfully malloc'ed 1000000000 elements of 4 bytes each.
0.00user 0.00system 0:00.00elapsed 100%CPU (0avgtext+0avgdata 1656maxresident)k
0inputs+0outputs (0major+62minor)pagefaults 0swaps
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...