Skip to content
Advertisement

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?

Advertisement

Answer

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
Advertisement