【问题标题】:malloc setting errno to EAGAINmalloc 将 errno 设置为 EAGAIN
【发布时间】:2015-12-08 20:21:37
【问题描述】:

考虑以下程序:

#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 均未返回。

该错误未在函数的手册页中指定,在 mlockall 的 POSIX 规范中也未指定。在 mlockall 之后放置一个 printf 表明是 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

【问题讨论】:

  • 您将errno 设置为0,调用两个 函数,然后检查errno 的值。您无法判断它是由mlockall 还是由malloc 设置的。您提到添加 printf 调用,但它不在您发布的代码中。您应该在每次调用前将errno 设置为0,并在每次调用后立即检查。
  • 来自手册页 如果指定了 MCL_FUTURE,那么以后的系统调用(例如 mmap(2)、sbrk(2)、malloc(3))可能会失败,如果它会导致锁定字节数超过允许的最大值(见下文)。在同样的情况下,堆栈增长同样可能失败:内核将拒绝堆栈扩展并向进程传递 SIGSEGV 信号。
  • malloc() 通常不是系统调用。它只是调用 C 运行时子分配器。
  • @AndrewHenle:我很抱歉说得不准确。我现在写了一个答案,更详细地解释了这种情况。
  • @AndrewHenle:关于 POSIX,我看到 ENOMEMmalloc(3) 的 POSIX 描述中列出的唯一 errno 值。但是here 我们看到“实现......可能会产生额外的错误,除非明确禁止特定功能。”所以我不认为这是违反 POSIX 的。

标签: c linux malloc


【解决方案1】:

您的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 然后尝试使用mmap 分配 1048576 字节。这也失败了(因为它超过了 65536 字节),在这里我们看到返回了 EAGAIN 错误代码。 mmap(2) 的手册页记录了 errno 设置为 EAGAIN 如果“文件已被锁定,或内存已被锁定”,后者正是这里的情况。 malloc 与许多库函数一样,将传递它所进行的系统调用留下的 errno 值,因此当 malloc 返回时,您会看到 EAGAIN

(额外的mmap 调用PROT_NONE 似乎是为了保留一些地址空间以供将来使用,并帮助确保将来的分配以适当的方式对齐。请参阅glibc 源代码中的malloc/arena.c 以获得血腥细节。在这种情况下,它们也失败了,但这并不重要。)

简而言之,问题在于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)

【讨论】:

  • 实际上,C99 明确表示 (7.5):如果没有记录 errno 的使用,则无论是否有错误,都可以通过库函数调用将 errno 的值设置为非零在本国际标准中对函数的描述中 - 换句话说,只要标准没有具体要求,errno 可以由标准库函数任意设置,而在 @ 的情况下则没有987654362@ 或 malloc 等等。我确信该标准的更现代变体具有相同或相似的措辞。
  • @davmac:确实,正如我在上面评论的那样,POSIX (2.3) 也说:“对于本卷 IEEE Std 1003.1 中描述的错误情况,实现不应生成与此处描述的错误编号不同的错误编号-2001,但除非明确禁止特定功能,否则可能会产生其他错误。”
【解决方案2】:

mlockall() 的返回值是多少?

the POSIX standard:

[EAGAIN]

在调用时无法锁定操作标识的部分或全部内存。

根据Linux man page

   EAGAIN Some or all of the specified address range could not be
          locked.

【讨论】:

  • 如上所述,至少在我的测试中,mlockall() 实际上成功了,并且根本没有设置errno。 OP没有解释得很清楚,但实际上是malloc(或者更确切地说是mmap,它调用)设置了errno = EAGAIN
【解决方案3】:
  1. mlockall 正在设置错误号,原因无法锁定部分或全部指定地址范围。
  2. 标准malloc() 不会在失败时将 errno 设置为 EAGAIN。

手册页

mlockall

malloc

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-05-29
    • 2010-11-08
    • 1970-01-01
    • 2021-12-27
    • 1970-01-01
    • 1970-01-01
    • 2020-12-11
    相关资源
    最近更新 更多