2

for a study project I have to code a reimplementation of malloc() and free() using mmap() and munmap().

I'm running on the last Ubuntu. For my tests I use the command time -v (from /usr/bin/time) which shows me a lot of information about my program including the memory. Here are some examples:

So we can see Minor page faults which corresponds to the number of reclaimed pages change according to our usage, but especially that if we use free() after a malloc() the number of reclaimed pages return to their initial number which is not the case with my reimplementation:

Here are bits of my code to visualize what I do.

In my malloc():

static t_page *__alloc_page(size_t size)
{
    struct rlimit limit;
    t_page *page;

    getrlimit(RLIMIT_AS, &limit);
    if (size > limit.rlim_max)
        return (NULL);
    page = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (page == MAP_FAILED)
        return (NULL);
    ft_bzero(page, sizeof(t_page));
    page->size = size;
    page->used_size = sizeof(t_page);
    return (page);
}

In my free():

static void __free_page(t_page *page)
{
    t_binding *binder = __get_binder(page);

    binder->count--;
    if (binder->pages == page)
        binder->pages = page->next;
    
    if (page->prev != NULL)
        page->prev->next = page->next;
    if (page->next != NULL)
        page->next->prev = page->prev;

    if (munmap(page, page->size) == -1)
        ft_putstr("free(): munmap error\n");
}

For information my size is always a multiple of getpagesize() (N * getpagesize()).

Here's how I do my tests

First I compile my files malloc.c free.c etc. into a dynamic library (libmalloc.so).
Then I build two binary with the main that follows. One is compiled with my malloc and the other one with the libc.

clang main.c -o libc_malloc
clang main.c -D LIBMALLOC libmalloc.so -o my_malloc
#ifdef LIBMALLOC
# include "../includes/malloc.h"
#else
# include <stdlib.h>
#endif

int main(void)
{
    int i;
    char *addr;

    i = 0;
    while (i < 1024) 
    {
        addr = (char*)malloc(1024);
        addr[0] = 42;
        free(addr);
        i++; 
    }
    return (0);
}

I also have a script that allows me to run my binary with my dynamic library named run.sh:

#!/bin/sh
export LD_LIBRARY_PATH="."
export LD_PRELOAD="`pwd`/libmalloc.so"
$@

Finally I run my two binary with time -v like this:

/usr/bin/time -v ./libc_malloc
./run.sh /usr/bin/time -v ./my_malloc

How to reproduce in a minimalist way

#include <sys/mman.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

int main(void)
{
    int i;
    char *addr;

    i = 0;
    #ifdef _MMAP_
        printf("mmap\n");
    #else
        printf("malloc\n");
    #endif
    while (i < 1024) 
    {
        #ifdef _MMAP_
            addr = mmap(NULL, 4 * getpagesize(), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
        #else
            addr = malloc(4 * getpagesize());
        #endif
        addr[0] = 42;
        #ifdef _MMAP_
            munmap(addr, 4 * getpagesize());
        #else
            free(addr);
        #endif
        i++; 
    }
    return (0);
}

Copy this main above into a file (main.c).
Create two binary as follows:

clang main.c -o using_malloc
clang -D _MMAP_ main.c -o using_mmap

Then run them with time -v:

/usr/bin/time -v ./using_malloc
/usr/bin/time -v ./using_mmap

What I have tried

While searching the internet I came across this post which has exactly the same problem as mine:
higher page reclaims when using munmap
But the proposed solutions do not work (and i can't use it).
I am not allowed to use functions like posix_madvise() or msync() either...
I tried them anyway to see if they would solve my problem but without success.
I also ran someone else's project. His works well, while we seem to be doing the same thing.
Am I missing something?

lucocozz
  • 21
  • 3
  • See my answer: [Malloc is using 10x the amount of memory necessary](https://stackoverflow.com/a/39761864/5382650) It talks about `RSS` (resident set size). Note that mapping/unmapping isn't quite the same as RSS, so without `posix_madvise/madvise/msync` you won't have as much control over the RSS of a process. – Craig Estey Mar 26 '23 at 22:19
  • Also, for additional information, see my other answer: [How does mmap improve file reading speed?](https://stackoverflow.com/a/37173063/5382650) In that answer there are links to [yet more of] my answers: [Which segments are affected by a copy-on-write?](https://stackoverflow.com/a/37060802/5382650) and [read line by line in the most efficient way *platform specific*](https://stackoverflow.com/a/33620968/5382650) that provide additional detail on these matters. – Craig Estey Mar 26 '23 at 22:22
  • First of all, are you certain that `malloc` and `free` are calling *your* versions instead of those from the standard library? Getting this to happen can take some effort. – Nate Eldredge Mar 26 '23 at 23:23
  • Can you put together a [mcve] in a single code block, along with the exact commands to build and run it, the output that you get, and why you think it is wrong? It's hard to follow bits and pieces of code, and your example `main` functions don't help as they don't seem to actually measure anything. – Nate Eldredge Mar 26 '23 at 23:24
  • Yes sorry I updated @NateEldredge And yes I'm sure that my malloc and free are well called – lucocozz Mar 27 '23 at 16:10

1 Answers1

0

I have found my problem. In my main, I continuously execute malloc() and free() one after the other. When the system executes munmap(), it tries to optimize by not immediately removing the page allocated by mmap() in order to reuse it later. However, this still results in the creation of new pages during subsequent calls to mmap().

To fix this, free() need to leaves one remaining page so that munmap() is not called in a loop.

My explanations are not very clear, please feel free to provide more details.

Tyler2P
  • 2,324
  • 26
  • 22
  • 31
lucocozz
  • 21
  • 3