malloc устанавливает errno в EAGAIN

Рассмотрим следующую программу:

#include <sys/mman.h>                                                           
#include <stdlib.h>                                                             
#include <errno.h>                                                              

int                                                                             
main()                                                                          
{                                                                              
  errno = 0;
  mlockall(MCL_FUTURE);                                           
  char *a = malloc(1);                                                      
  if (!a)                                                                       
    exit(errno);                                                                
  munlockall();                                                                 
  exit(0);                                                                      
}

При работе от обычного пользователя получаю:

~ ./a.out                                                             
~ echo $?                                                             
11

От /usr/include/asm-generic/errno-base.h:

#define EAGAIN    11  /* Try again */                                     

При запуске от имени пользователя root или при передаче MCL_FUTURE | MCL_CURRENT он работает успешно. Я предположил, что либо прав недостаточно, либо флаги были неправильными, но ни EPERM, ни EINVAL не были возвращены.

Эта ошибка не указана ни в справочной странице ни функций, ни в спецификации POSIX для mlockall. Размещение printf после mlockall показывает, что именно malloc устанавливает errno.

И что еще более странно, malloc, кажется, не устанавливает EAGAIN (или я ищу не в том месте):

/usr/src/glibc/glibc-2.19/malloc grep -r . -e EAGAIN

Так в чем же дело?

~ uname -r                                                                                                                                                                                                 18:15:04 
3.16-2-486
~ gcc --version                                                                                                                                                                                            18:15:05 
gcc (Debian 4.9.2-10) 4.9.2
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

~ ldd --version                                                                                                                                                                                            18:15:11 
ldd (Debian GLIBC 2.19-18+deb8u1) 2.19
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Written by Roland McGrath and Ulrich Drepper.
~                                                                                                                                                                                                          18:15:15

person Alex    schedule 08.12.2015    source источник
comment
Вы устанавливаете errno в 0, вызываете две функции, а затем проверяете значение errno. Вы не можете сказать, был ли он установлен mlockall или malloc. Вы упомянули добавление вызова printf, но его нет в опубликованном вами коде. Вы должны установить errno в 0 перед каждым вызовом и проверять его сразу после каждого вызова.   -  person Keith Thompson    schedule 08.12.2015
comment
В каком каталоге находится a.out?   -  person Fiddling Bits    schedule 08.12.2015
comment
Из справочной страницы Если указан MCL_FUTURE, то более поздний системный вызов (например, mmap(2), sbrk(2), malloc(3)) может завершиться ошибкой, если он приведет к превышению количества заблокированных байтов. разрешенный максимум (см. ниже). В тех же обстоятельствах увеличение стека также может дать сбой: ядро ​​откажет в расширении стека и отправит процессу сигнал SIGSEGV.   -  person alvits    schedule 08.12.2015
comment
Проверьте возвращаемое значение из mlockall().   -  person Andrew Henle    schedule 08.12.2015
comment
malloc() в общем случае не является системным вызовом. Он просто вызывает вспомогательный распределитель времени выполнения C.   -  person Martin James    schedule 08.12.2015
comment
В наши дни malloc(3) обычно реализуется с использованием mmap(2) для получения памяти от ОС. А mmap(2) задокументировано как способное установить errno в EAGAIN, особенно если заблокировано слишком много памяти.   -  person Nate Eldredge    schedule 08.12.2015
comment
@NateEldredge В настоящее время malloc(3) обычно реализуется с использованием mmap(2) для получения памяти от ОС. Нет. См. fossies.org/dox/glibc-2.22/malloc_8c_source.html Цена на mmap сейчас тоже высока; каждый раз, когда glibc выполняет mmap из ядра, ядро ​​вынуждено обнулять память, которую оно отдает приложению. Обнуление памяти стоит дорого и потребляет много кэша и пропускной способности памяти. Это не имеет ничего общего с эффективностью системы виртуальной памяти, выполняя mmap у ядра просто нет другого выбора, кроме как обнулить.   -  person Andrew Henle    schedule 09.12.2015
comment
@AndrewHenle: Но если вы прочитаете дальше, вы увидите, что mmap(2) на самом деле вызывается во многих случаях. Фактически, я пытался запустить программу ОП под strace(1). Что происходит, так это то, что malloc сначала вызывает sbrk(2), что терпит неудачу. Затем он возвращается к нескольким вызовам mmap, все из которых терпят неудачу с EAGAIN. И это, по-видимому, объясняет, почему возвращается errno == EAGAIN, когда malloc(3) возвращается.   -  person Nate Eldredge    schedule 09.12.2015
comment
@NateEldredge Да, mmap вызывается во многих случаях. Но во многих случаях это не так. Ваше утверждение В наши дни malloc(3) обычно реализуется с использованием mmap(2), похоже, подразумевает, что используется исключительно mmap. Кроме того, предполагая, что OP работает в Linux и использует glibc для источника, который я связал, возвращение EAGAIN в случае сбоя, как мне кажется, нарушает стандарт POSIX, а также противоречит его собственной справочной странице.   -  person Andrew Henle    schedule 09.12.2015
comment
@AndrewHenle: прошу прощения за неточность. Сейчас я написал ответ, который объясняет ситуацию более подробно.   -  person Nate Eldredge    schedule 09.12.2015
comment
@AndrewHenle: Что касается POSIX, я вижу, что ENOMEM — это единственное значение errno, указанное в описании POSIX для malloc(3). Но здесь мы видим Реализации... могут генерировать дополнительные ошибки, если явно запрещены для конкретной функции. Поэтому я не думаю, что это нарушение POSIX.   -  person Nate Eldredge    schedule 09.12.2015


Ответы (3)


Ваш вызов mlockall() запрашивает блокировку всех будущих выделений памяти. Однако ОС устанавливает максимальный объем памяти, который может быть заблокирован любым непривилегированным процессом. Вы можете запросить эту сумму с помощью getrlimit(RLIMIT_MEMLOCK,...). В моей системе это 65536 байт.

Теперь, когда я запускаю вашу программу в своей системе, используя strace(1), чтобы увидеть, какие системные вызовы сделаны, я получаю следующее:

mlockall(MCL_FUTURE)                    = 0
brk(0)                                  = 0x2318000
brk(0x2339000)                          = 0x2318000
mmap(NULL, 1048576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = -1 EAGAIN (Resource temporarily unavailable)
mmap(NULL, 134217728, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = -1 EAGAIN (Resource temporarily unavailable)
mmap(NULL, 67108864, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = -1 EAGAIN (Resource temporarily unavailable)
mmap(NULL, 134217728, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = -1 EAGAIN (Resource temporarily unavailable)
mmap(NULL, 67108864, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = -1 EAGAIN (Resource temporarily unavailable)
exit_group(11)                          = ?

Итак, malloc сначала использует brk, чтобы попытаться выделить 135168 байт (0x2339000-0x2318000). Это не удается, потому что превышен предел блокировки, и поэтому brk оставляет "точку останова" (верхняя часть сегмента данных процесса) неизменной. (См. примечание на справочной странице brk(2) о различных соглашениях между библиотекой C и версиями ядра brk().)

Затем malloc пытается вместо этого выделить 1048576 байт, используя mmap. Это также не удается (поскольку он превышает 65536 байт), и здесь мы видим, что возвращается код ошибки EAGAIN. Страница руководства для mmap(2) документирует, что errno установлено в EAGAIN, если «Файл был заблокирован или слишком много памяти было заблокировано», последнее из которых здесь как раз имеет место. malloc, как и многие библиотечные функции, будет проходить через значение errno, оставленное системными вызовами, которые он делает, поэтому EAGAIN — это то, что вы видите, когда возвращается malloc.

(Дополнительные вызовы mmap с PROT_NONE, по-видимому, предназначены для резервирования некоторого адресного пространства для будущего использования и помогают гарантировать, что будущие выделения будут выровнены надлежащим образом. См. malloc/arena.c в исходном коде glibc для кровавых подробностей. В этом случае они тоже терпят неудачу, но это не так актуально)

Короче говоря, проблема в том, что malloc пытается запросить у ОС значительно больший объем памяти, чем вы, пользователь, запросили. Это делается для повышения эффективности, поскольку в большинстве случаев вы будете выделять больше небольших фрагментов памяти, и вам не нужно выполнять системный вызов для каждого из них. Но этот объем превышает лимит для заблокированной памяти, поэтому он не работает. EAGAIN — это код ошибки, установленный системным вызовом mmap в данном случае.

Возможно, на справочной странице malloc следует упомянуть эту возможную настройку errno, но довольно часто библиотечные функции более высокого уровня не описывают все возможные способы установки errno базовыми системными вызовами. (Например, fprintf(3) вызывает write(2), что может установить errno в ENOSPC, если диск заполнен, но вы не найдете никаких упоминаний об этом на справочной странице fprintf(3).) Вы просто должны знать.

Если вы хотите использовать mlockall(MCL_FUTURE), то вы, вероятно, не можете планировать выделение памяти с помощью malloc(3). Вам придется получить его вручную от sbrk(2) или mmap(2), и, конечно же, спланировать, чтобы он не превышал соответствующего лимита или изящно потерпел неудачу. Это довольно неудобно и накладывает ограничения, поэтому, если вам нужна какая-то заблокированная память, и вы не root, вы, вероятно, захотите вместо этого просто использовать mlock(2) для достаточно маленьких объектов.

person Nate Eldredge    schedule 08.12.2015
comment
На самом деле C99 прямо говорит (7.5): Значение errno может быть установлено ненулевым вызовом библиотечной функции независимо от наличия ошибки, при условии, что использование errno не задокументировано в описании функции в этом документе. Международный стандарт — другими словами, errno может быть установлен произвольно функцией стандартной библиотеки, если стандарт не налагает особых требований, чего нет в случае fprintf или malloc среди многих других. Я уверен, что более современные варианты стандарта имеют ту же или аналогичную формулировку. - person davmac; 09.12.2015
comment
@davmac: Действительно, и, как я уже говорил выше, аналогично POSIX (2.3) говорит: Реализации не должны генерировать номера ошибок, отличные от описанных здесь, для условий ошибок, описанных в этом томе IEEE Std 1003.1-2001, но могут генерировать дополнительные ошибки, если это явно не запрещено для конкретной функции. - person Nate Eldredge; 09.12.2015

Какое значение возвращает функция mlockall()?

Согласно стандарту POSIX:

[СНОВА]

Часть или вся память, идентифицированная операцией, не может быть заблокирована при выполнении вызова.

Согласно справочной странице Linux:

   EAGAIN Some or all of the specified address range could not be
          locked.
person Andrew Henle    schedule 08.12.2015
comment
Как я уже отмечал выше, по крайней мере, в моих тестах mlockall() на самом деле завершается успешно, а errno вообще не устанавливается. ОП не очень ясно объяснил это, но на самом деле это malloc (или, скорее, mmap, который он вызывает), который устанавливает errno = EAGAIN. - person Nate Eldredge; 09.12.2015

  1. mlockall номер ошибки установки, причина Не удалось заблокировать часть или весь указанный диапазон адресов.
  2. Стандарт malloc() не устанавливает для errno значение EAGAIN в случае сбоя.

справочная страница

mlockall

malloc

person Dilip Kumar    schedule 08.12.2015