简体   繁体   中英

malloc setting errno to EAGAIN

Consider the following program:

#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);                                                                      
}

When running as a normal user I get:

~ ./a.out                                                             
~ echo $?                                                             
11

From /usr/include/asm-generic/errno-base.h :

#define EAGAIN    11  /* Try again */                                     

When running it as root or when passing MCL_FUTURE | MCL_CURRENT MCL_FUTURE | MCL_CURRENT it runs successfully. I assumed either the permissions were insufficient or the flags were wrong, but neither EPERM nor EINVAL was returned.

That error is not specified in the man page of neither functions, nor in the POSIX specification for mlockall. Placing a printf after mlockall reveals that it is malloc who is setting errno.

And even more odd, malloc doesn't seem to set EAGAIN (or I'm looking in the wrong place):

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

So what's the deal?

~ 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

Your mlockall() call asks for all future memory allocations to be locked. However, the OS sets a maximum amount of memory that can be locked by any one unprivileged process. You can query this amount with getrlimit(RLIMIT_MEMLOCK,...) . On my system it is 65536 bytes.

Now when I run your program on my system, using strace(1) to see what system calls are made, I get the following:

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)                          = ?

So malloc first uses brk to try to allocate 135168 bytes ( 0x2339000-0x2318000 ). This fails because it exceeds the lock limit, and so brk leaves the "break point" (the top of the process's data segment) unchanged. (See the note in the brk(2) man page about the differing conventions between the C library and kernel versions of brk() .)

malloc then tries instead to allocate 1048576 bytes using mmap . This also fails (as it exceeds 65536 bytes), and here we see the EAGAIN error code being returned. The man page for mmap(2) documents that errno is set to EAGAIN if "The file has been locked, or too much memory has been locked", the latter of which is exactly the case here. malloc , like many library functions, will pass through the errno value left by system calls that it makes, so EAGAIN is what you see when malloc returns.

(The extra mmap calls with PROT_NONE seem to be intended to reserve some address space for future use, and help ensure that future allocations are aligned in an appropriate way. See malloc/arena.c in the glibc source for gory details. They fail too in this case, but that's not so relevant.)

So in short, the issue is that malloc tries to ask the OS for a significantly larger amount of memory than you, the user, requested. This is for efficiency, since in most cases you are going to go on to allocate more small chunks of memory, and you don't want to make a system call for each one. But this amount exceeds the limit for locked memory, so it fails. EAGAIN is the error code set by the mmap system call in this case.

Perhaps the malloc man page should mention this possible errno setting, but it's pretty common that higher-level library functions don't describe all the possible ways errno could be set by the underlying system calls. (For instance, fprintf(3) calls write(2) , which could set errno to ENOSPC if the disk is full, but you won't find any mention of that in the fprintf(3) man page.) You're just supposed to know.

If you want to use mlockall(MCL_FUTURE) , then you probably can't plan to allocate memory using malloc(3) . You'll have to get it manually from sbrk(2) or mmap(2) , and of course, plan to keep it under the appropriate limit or fail gracefully. This is pretty inconvenient and restrictive, so if you have a need for some locked memory, and you're not root, you probably want to just use mlock(2) on sufficiently small objects instead.

  1. mlockall is setting error number, reason Some or all of the specified address range could not be locked.
  2. Standard malloc() does not set errno to EAGAIN on failure.

Man Page

mlockall

malloc

What's the return value from mlockall() ?

Per the POSIX standard :

[EAGAIN]

Some or all of the memory identified by the operation could not be locked when the call was made.

Per the Linux man page :

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM