Search code examples
linuxmultithreadingmallocdynamic-memory-allocationglibc

A question about malloc implementation in glibc


I was reading source code of glibc.
In function void *__libc_malloc(size_t bytes):

void *__libc_malloc(size_t bytes) {
  mstate ar_ptr;
  void *victim;
  _Static_assert(PTRDIFF_MAX <= SIZE_MAX / 2, "PTRDIFF_MAX is not more than half of SIZE_MAX");
  if (!__malloc_initialized) ptmalloc_init();

  ...
}

It shows that if the first thread was created, it calls ptmalloc_init(), and links thread_arena with main_arena, and sets __malloc_initialized to true.
On the other hand, the second thread was blocked by the following code in ptmalloc_init():

static void ptmalloc_init(void) {
  if (__malloc_initialized) return;
  __malloc_initialized = true;
  thread_arena = &main_arena;
  malloc_init_state(&main_arena);
  ...

Thus the thread_arena of the second thread is NULL, and it has to mmap() additional arena.
My question is:
It seems possible to cause race condition because there's no any lock with __malloc_initialized, and thread_arenas of the first thread and second thread may both link with main_arena, why not use lock to protect __malloc_initialized?


Solution

  • It seems possible to cause race condition because there's no any lock with __malloc_initialized

    It is impossible1 for a program to create a second running thread without having called an allocation routine (and therefore ptmalloc_init) while it was still single-threaded.

    Because of that, ptmalloc_init can assume that it runs while there is only a single thread.


    1Why is it impossible? Because creating a thread itself calls calloc.

    For example, in this program:

    #include <pthread.h>
    
    void *fn(void *p) { return p; }
    int main()
    {
      pthread_t tid;
      pthread_create(&tid, NULL, fn, NULL);
      pthread_join(tid, NULL);
      return 0;
    }
    

    ptmalloc_init is called here (only a single thread exists at that point):

    Breakpoint 2, ptmalloc_init () at /usr/src/debug/glibc-2.34-42.fc35.x86_64/malloc/arena.c:283
    283       if (__malloc_initialized)
    (gdb) bt
    #0  ptmalloc_init () at /usr/src/debug/glibc-2.34-42.fc35.x86_64/malloc/arena.c:283
    #1  __libc_calloc (n=17, elem_size=16) at malloc.c:3526
    #2  0x00007ffff7fdd6c3 in calloc (b=16, a=17) at ../include/rtld-malloc.h:44
    #3  allocate_dtv (result=result@entry=0x7ffff7dae640) at ../elf/dl-tls.c:375
    #4  0x00007ffff7fde0e2 in __GI__dl_allocate_tls (mem=mem@entry=0x7ffff7dae640) at ../elf/dl-tls.c:634
    #5  0x00007ffff7e514e5 in allocate_stack (stacksize=<synthetic pointer>, stack=<synthetic pointer>,
        pdp=<synthetic pointer>, attr=0x7fffffffde30)
        at /usr/src/debug/glibc-2.34-42.fc35.x86_64/nptl/allocatestack.c:429
    #6  __pthread_create_2_1 (newthread=0x7fffffffdf58, attr=0x0, start_routine=0x401136 <fn>, arg=0x0)
        at pthread_create.c:648
    #7  0x0000000000401167 in main () at p.c:7