Munmap() Failure with Enomem with Private Anonymous Mapping

Is merging pages allowed in mmap?

As described here

The munmap() function shall remove any mappings for those entire pages
containing any part of the address space of the process starting at
addr and continuing for len bytes.

so it is allowed to remove multiple mappings with a single munmap call (as if it was a single mapping).

There is a problem with your code though: how do you know if a page (p2) before your page (p1) is not used? It could be already allocated by other pars of the program (including malloc), by using MAP_FIXED like this you will rewrite (remap) its content:

When MAP_FIXED is set in the flags argument, the implementation is
informed that the value of pa shall be addr, exactly. If MAP_FIXED is
set, mmap() may return MAP_FAILED and set errno to [EINVAL]. If a
MAP_FIXED request is successful, the mapping established by mmap()
replaces any previous mappings for the process' pages in the range
[pa,pa+len).

So I don't think this trick can be useful in the general case, you should use mremap instead.

As for how this is implemented: Linux does merge sequential private anonymous mappings, so both will be merged into a single vma_struct in the kernel. This "feature" has undesirable side effects such as munmap failing to free memory with ENOMEM. But this is more of an implementation detail, not something you have control over.

Why does munmap needs a length as parameter?

One can map, say, 5 pages and later unmap one of them. And information about what pages to unmap is passed as address and length where the length is a multiple of page size.

What is the purpose of MAP_ANONYMOUS flag in mmap system call?

Anonymous mappings can be pictured as a zeroized virtual file.
Anonymous mappings are simply large, zero-filled blocks of memory ready for use.
These mappings reside outside of the heap, thus do not contribute to data segment fragmentation.

MAP_ANONYMOUS + MAP_PRIVATE:

  • every call creates a distinct mapping
  • children inherit parent's mappings
  • childrens' writes on the inherited mapping are catered in copy-on-write manner
  • the main purpose of using this kind of mapping is to allocate a new zeroized memory
  • malloc employs anonymous private mappings to serve memory allocation requests larger than MMAP_THRESHOLD bytes.

    typically, MMAP_THRESHOLD is 128kB.

MAP_ANONYMOUS + MAP_SHARED:

  • each call creates a distinct mapping that doesn't share pages with any other mapping
  • children inherit parent's mappings
  • no copy-on-write when someone else sharing the mapping writes on the shared mapping
  • shared anonymous mappings allow IPC in a manner similar to System V memory segments, but only between related processes

On Linux, there are two ways to create anonymous mappings:

  • specify MAP_ANONYMOUS flag and pass -1 for fd

        addr = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); 
    if (addr == MAP_FAILED)
    exit(EXIT_FAILURE);
  • open /dev/zero and pass this opened fd

        fd = open("/dev/zero", O_RDWR);   
    addr = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);

    (this method is typically used on systems like BSD, that do not have MAP_ANONYMOUS flag)

Advantages of anonymous mappings:
- no virtual address space fragmentation; after unmapping, the memory is immediately returned to the system

- they are modifiable in terms of allocation size, permissions and they can also receive advice just like normal mappings

- each allocation is a distinct mapping, separate from global heap

Disadvantages of anonymous mappings:
- size of each mapping is an integer multiple of system's page size, thus it can lead to wastage of address space

- creating and returning mappings incur more overhead than that of from the pre-allocated heap

if a program containing such mapping, forks a process, the child inherits the mapping.
The following program demonstrates this kinda inheritance:

#ifdef USE_MAP_ANON
#define _BSD_SOURCE
#endif
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
/*Pointer to shared memory region*/
int *addr;

#ifdef USE_MAP_ANON /*Use MAP_ANONYMOUS*/
addr = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (addr == MAP_FAILED) {
fprintf(stderr, "mmap() failed\n");
exit(EXIT_FAILURE);
}

#else /*Map /dev/zero*/
int fd;
fd = open("/dev/zero", O_RDWR);
if (fd == -1) {
fprintf(stderr, "open() failed\n");
exit(EXIT_FAILURE);
}

addr = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (addr == MAP_FAILED) {
fprintf(stderr, "mmap() failed\n");
exit(EXIT_FAILURE);
}

if (close(fd) == -1) { /*No longer needed*/
fprintf(stderr, "close() failed\n");
exit(EXIT_FAILURE);
}
#endif
*addr = 1; /*Initialize integer in mapped region*/

switch(fork()) { /*Parent and child share mapping*/
case -1:
fprintf(stderr, "fork() failed\n");
exit(EXIT_FAILURE);

case 0: /*Child: increment shared integer and exit*/
printf("Child started, value = %d\n", *addr);
(*addr)++;

if (munmap(addr, sizeof(int)) == -1) {
fprintf(stderr, "munmap()() failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);

default: /*Parent: wait for child to terminate*/
if (wait(NULL) == -1) {
fprintf(stderr, "wait() failed\n");
exit(EXIT_FAILURE);
}

printf("In parent, value = %d\n", *addr);
if (munmap(addr, sizeof(int)) == -1) {
fprintf(stderr, "munmap()() failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}

Sources:
The Linux Programming Interface

Chapter 49: Memory Mappings,

Author: Michael Kerrisk

Linux System Programming (3rd edition)

Chapter 8: Memory Management,

Author: Robert Love

How to unmap an mmap'd file by replacing with a mapping to empty pages

On Linux you can use mmap with MAP_FIXED to replace the mapping with any mapping you want. If you replace the entire mapping the reference to the file will be removed.



Related Topics



Leave a reply



Submit