[REENTRANT] malloc function in libc sprintf type functions

HI all,

About newlib reent question, I have check LINK, which should enable configUSE_NEWLIB_REENTRANToption in freertos config file for thread safe function.
I have the following 2questions:

  1. Is is ok to use pvPortMalloc() to wrap the malloc defined in libc for thead safe without enable configUSE_NEWLIB_REENTRANT? Because the schedule already be suspend in pvPortMalloc() function.
  2. If enable configUSE_NEWLIB_REENTRANT, what is __real__malloc_r() descitped in FreeRTOS_helpers/heap_useNewlib_ST.c at master · DRNadler/FreeRTOS_helpers (github.com)?

    Is the __real__malloc_r() equal to pvPortMalloc()?

BRs
Yao

Trying to write your own version of malloc to make it “thread-safe” is not guaranteed to work, as library functions that want to use the malloc facilities do not need to actually call malloc, but can use an internal function that provides that capability, thus the link ability to wrap malloc is not guaranteed to work. With newlib, if it was properly configured, you can just define functions __malloc_lock and __malloc_unlock to make it thread safe like:


void __malloc_lock(struct _reent* reent) {
	(void) reent;
	vTaskSuspendAll();
}

void __malloc_unlock(struct _reent* reent) {
	(void) reent;
	xTaskResumeAll();
}

This needs to be either in a .c file or wrapped with an extern "C" { if in a .cpp file

And to answer your more detailed question, __malloc_r is the actual function that newlib uses to do the function of malloc (and the function malloc() just calles malloc_r.

__real__malloc_r is the name the linker provides to your __wrap__malloc_r to let it access the original code of __malloc_r in your wrapper.

I would be wary of that code, as __real__malloc_r should be defined with the same parameters as __wrap__malloc_r, since that IS the prototype for that function.

Same applies to free as you surely know but also to realloc and calloc which should be wrapped as well. At least that was the case when I verified the newlib sources time ago.

The simpler thing is to define the __malloc_lock/unlock instead of wrapping, since FreeRTOS doesn’t provide a realloc equivalent.

There is a library that replaces all [vsn]printf() functions. These alternative functions are interrupt safe, they do not use any heap, and they have a very low stack usage. See printf-stdarg.c.

The only downsize is that the module doesn’t properly support floating point formats.

1 Like

The wrap_* functions are for debugging your malloc calls, used with the linker wrap function. Look up and study “gcc linker --wrap” and study the comments a few lines prior to the code you pictured above!
Hope that helps,
Best Regards, Dave

Richard is correct, I forgot to upload a fix to github, code above should read

  void *__wrap__malloc_r(void *reent, size_t nbytes) {
    (void)(reent);
    extern void * __real__malloc_r(void *reent, size_t nbytes);
    if(!inside_malloc) {
      MallocCallCnt++;
      TotalMallocdBytes += nbytes;
    }
    void *p = __real__malloc_r(reent, nbytes);
    return p;
  }

I’ll try to find time to update the repository…

Repository updated, sorry about that, Thanks Richard for pointing this out…

It was my impression that configUSE_NEWLIB_REENTRANT changes whether the TCB has the reent pointer to provide a place for the ..._r() functions to put thread/task specific information (states, etc.) instead of using a single structure shared across all tasks/threads. Usually, to get a reentrant malloc() (or any other allocator), either each task needs its own pool or there needs to be a global lock or critical section around the free pool walking/defragmenting/freeing/reserving/splitting/etc. This can be a very long process. On some systems, the memory pool is allocated to each task/process from the system heap in larger chunks using functions with names like brk() andsbrk(); those have a small critical section and once the task “owns” the chunk, the allocators such as malloc(), calloc(), realloc() and free() work using an allocator heap made up of chunks that no other task will be managing. When the allocators need more memory to satify a request, they get another chunk from the OS. When the task exits, the OS puts the chunks it had reserved for the task back into its heap, making them available for other tasks/processes. I’m pretty sure that FreeRTOS does not have this sophisticated behavior because it’s hard and overkill for most embedded applications.

heap_usenewlib supports newlib’s use of a single global lock. While it can be slow, presumably most embedded systems of this class are not pounding on free-storage. With configUSE_NEWLIB_REENTRANT FreeRTOS does create and clean up requisite task-specific reentrancy blocks on task creation/completion. Hope that’s clear!