2
votes

I am studying memory management and have a question about how malloc works. The malloc man page states that:

Normally, malloc() allocates memory from the heap, and adjusts the size of the heap as required, using sbrk(2). When allocating blocks of memory larger than MMAP_THRESHOLD bytes, the glibc malloc() implementation allocates the memory as a private anonymous mapping using mmap(2). MMAP_THRESHOLD is 128 kB by default, but is adjustable using mallopt(3).

To verify it, I did an experiment with a piece of code:

#include<stdlib.h>
#include<stdio.h>

int main()
{
    int size = 10;
    int *p = malloc(size);

    if(p)
    {
        printf("allocated %d bytes at addr: %p \n", size, p);
        free(p);
    }
    else
    {
        free(p);
    }

    return 0;
}

I traced this program with strace to see what syscall was used. Here is the result:

strace result

Why in this example did malloc call mmap instead of brk?

2
Your screenshot clearly shows a call to brk and not to mmap (though it's not certain that your malloc caused it, since malloc allocates in chunks and parcels out without syscalls). Are you confused by the mmap calls in the process initialization? Just do e.g. printf("Hello World\n") to see when initialization ends and your main starts.that other guy

2 Answers

3
votes

All those mmap() calls are part of your program's startup when it's loading shared libraries. It's standard stuff you'll see when you strace most programs.

The real action is in the last few lines:

  • Two calls to brk() coming from malloc().
  • An fstat() and a write() call coming from printf().

You can add a printout to the top of main() to see when your code actually starts running.

(It's important to call the write() syscall directly instead of printing with printf() or puts(). The stdio functions call malloc() internally which muddles what we're trying to test.)

#include <unistd.h>

int main()
{
    write(1, "start\n", 6);

    ...
}

When I do that I see the write() call right before the brk(NULL), which I've marked below with a blank line:

...
mmap(0x7f1b34802000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7f1b34802000
mmap(0x7f1b34808000, 15072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f1b34808000
close(3)                                = 0
arch_prctl(ARCH_SET_FS, 0x7f1b34a124c0) = 0
mprotect(0x7f1b34802000, 16384, PROT_READ) = 0
mprotect(0x558c3cd9a000, 4096, PROT_READ) = 0
mprotect(0x7f1b34a33000, 4096, PROT_READ) = 0
munmap(0x7f1b34a13000, 128122)          = 0

write(1, "start\n", 6)                  = 6
brk(NULL)                               = 0x558c3dc58000
brk(0x558c3dc79000)                     = 0x558c3dc79000
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 4), ...}) = 0
write(1, "allocated 10 bytes at addr: 0x55"..., 44) = 44
exit_group(0)                           = ?
+++ exited with 0 +++
2
votes

Most libc implementations are open source. Study the source code of glibc or of musl-libc. Both implement malloc and free. Use also strace(1)

Usually, they use mmap(2) or sometimes sbrk(2)

Of course they try to minimize the number of system calls, at least for small memory sizes.