mirror of
https://gitlab.rtems.org/rtems/rtos/rtems.git
synced 2026-08-23 20:59:30 +08:00
634 lines
15 KiB
C
634 lines
15 KiB
C
/*
|
|
* RTEMS Malloc Family Implementation
|
|
*
|
|
*
|
|
* COPYRIGHT (c) 1989-1999.
|
|
* On-Line Applications Research Corporation (OAR).
|
|
*
|
|
* The license and distribution terms for this file may be
|
|
* found in the file LICENSE in this distribution or at
|
|
* http://www.rtems.com/license/LICENSE.
|
|
*
|
|
* $Id$
|
|
*/
|
|
|
|
#if HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#define __RTEMS_VIOLATE_KERNEL_VISIBILITY__
|
|
#include <rtems.h>
|
|
#include <rtems/libcsupport.h>
|
|
#include <rtems/score/apimutex.h>
|
|
#ifdef RTEMS_NEWLIB
|
|
#include <sys/reent.h>
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <sys/types.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
|
|
#include <unistd.h> /* sbrk(2) */
|
|
#include <inttypes.h>
|
|
|
|
#include <rtems/chain.h>
|
|
|
|
#ifndef HAVE_UINTMAX_T
|
|
/* Fall back to unsigned long if uintmax_t is not available */
|
|
#define unsigned long uintmax_t
|
|
|
|
#ifndef PRIuMAX
|
|
#define PRIuMAX "lu"
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef MALLOC_ARENA_CHECK
|
|
#define SENTINELSIZE 12
|
|
#define SENTINEL "\xD1\xAC\xB2\xF1" "BITE ME"
|
|
#define CALLCHAINSIZE 5
|
|
struct mallocNode {
|
|
struct mallocNode *back;
|
|
struct mallocNode *forw;
|
|
int callChain[CALLCHAINSIZE];
|
|
size_t size;
|
|
void *memory;
|
|
};
|
|
static struct mallocNode mallocNodeHead = { &mallocNodeHead, &mallocNodeHead };
|
|
void reportMallocError(const char *msg, struct mallocNode *mp)
|
|
{
|
|
unsigned char *sp = (unsigned char *)mp->memory + mp->size;
|
|
int i, ind = 0;
|
|
static char cbuf[500];
|
|
ind += sprintf(cbuf+ind, "Malloc Error: %s\n", msg);
|
|
if ((mp->forw->back != mp) || (mp->back->forw != mp))
|
|
ind += sprintf(cbuf+ind, "mp:0x%x mp->forw:0x%x mp->forw->back:0x%x mp->back:0x%x mp->back->forw:0x%x\n",
|
|
mp, mp->forw, mp->forw->back, mp->back, mp->back->forw);
|
|
if (mp->memory != (mp + 1))
|
|
ind += sprintf(cbuf+ind, "mp+1:0x%x ", mp + 1);
|
|
ind += sprintf(cbuf+ind, "mp->memory:0x%x mp->size:%d\n", mp->memory, mp->size);
|
|
if (memcmp((char *)mp->memory + mp->size, SENTINEL, SENTINELSIZE) != 0) {
|
|
ind += sprintf(cbuf+ind, "mp->sentinel: ");
|
|
for (i = 0 ; i < SENTINELSIZE ; i++)
|
|
ind += sprintf(cbuf+ind, " 0x%x", sp[i]);
|
|
ind += sprintf(cbuf+ind, "\n");
|
|
}
|
|
ind += sprintf(cbuf+ind, "Call chain:");
|
|
for (i = 0 ; i < CALLCHAINSIZE ; i++) {
|
|
if (mp->callChain[i] == 0)
|
|
break;
|
|
ind += sprintf(cbuf+ind, " 0x%x", mp->callChain[i]);
|
|
}
|
|
printk("\n\n%s\n\n", cbuf);
|
|
}
|
|
#endif
|
|
|
|
Chain_Control RTEMS_Malloc_GC_list;
|
|
|
|
rtems_id RTEMS_Malloc_Heap;
|
|
size_t RTEMS_Malloc_Sbrk_amount;
|
|
|
|
#ifdef RTEMS_DEBUG
|
|
#define MALLOC_STATS
|
|
#define MALLOC_DIRTY
|
|
#endif
|
|
|
|
#ifdef MALLOC_STATS
|
|
#define MSBUMP(f,n) rtems_malloc_stats.f += (n)
|
|
|
|
struct {
|
|
uint32_t space_available; /* current size of malloc area */
|
|
uint32_t malloc_calls; /* # calls to malloc */
|
|
uint32_t free_calls;
|
|
uint32_t realloc_calls;
|
|
uint32_t calloc_calls;
|
|
uint32_t max_depth; /* most ever malloc'd at 1 time */
|
|
uintmax_t lifetime_allocated;
|
|
uintmax_t lifetime_freed;
|
|
} rtems_malloc_stats;
|
|
|
|
#else /* No rtems_malloc_stats */
|
|
#define MSBUMP(f,n)
|
|
#endif
|
|
|
|
void RTEMS_Malloc_Initialize(
|
|
void *start,
|
|
size_t length,
|
|
size_t sbrk_amount
|
|
)
|
|
{
|
|
rtems_status_code status;
|
|
void *starting_address;
|
|
uintptr_t old_address;
|
|
uintptr_t uaddress;
|
|
|
|
/*
|
|
* Initialize the garbage collection list to start with nothing on it.
|
|
*/
|
|
Chain_Initialize_empty(&RTEMS_Malloc_GC_list);
|
|
|
|
/*
|
|
* If the starting address is 0 then we are to attempt to
|
|
* get length worth of memory using sbrk. Make sure we
|
|
* align the address that we get back.
|
|
*/
|
|
|
|
starting_address = start;
|
|
RTEMS_Malloc_Sbrk_amount = sbrk_amount;
|
|
|
|
if (!starting_address) {
|
|
uaddress = (uintptr_t)sbrk(length);
|
|
|
|
if (uaddress == (uintptr_t) -1) {
|
|
rtems_fatal_error_occurred( RTEMS_NO_MEMORY );
|
|
/* DOES NOT RETURN!!! */
|
|
}
|
|
|
|
if (uaddress & (CPU_HEAP_ALIGNMENT-1)) {
|
|
old_address = uaddress;
|
|
uaddress = (uaddress + CPU_HEAP_ALIGNMENT) & ~(CPU_HEAP_ALIGNMENT-1);
|
|
|
|
/*
|
|
* adjust the length by whatever we aligned by
|
|
*/
|
|
|
|
length -= uaddress - old_address;
|
|
}
|
|
|
|
starting_address = (void *)uaddress;
|
|
}
|
|
|
|
/*
|
|
* If the BSP is not clearing out the workspace, then it is most likely
|
|
* not clearing out the initial memory for the heap. There is no
|
|
* standard supporting zeroing out the heap memory. But much code
|
|
* with UNIX history seems to assume that memory malloc'ed during
|
|
* initialization (before any free's) is zero'ed. This is true most
|
|
* of the time under UNIX because zero'ing memory when it is first
|
|
* given to a process eliminates the chance of a process seeing data
|
|
* left over from another process. This would be a security violation.
|
|
*/
|
|
|
|
if ( rtems_cpu_configuration_get_do_zero_of_workspace() )
|
|
memset( starting_address, 0, length );
|
|
|
|
/*
|
|
* Unfortunately we cannot use assert if this fails because if this
|
|
* has failed we do not have a heap and if we do not have a heap
|
|
* STDIO cannot work because there will be no buffers.
|
|
*/
|
|
|
|
status = rtems_region_create(
|
|
rtems_build_name( 'H', 'E', 'A', 'P' ),
|
|
starting_address,
|
|
length,
|
|
CPU_HEAP_ALIGNMENT,
|
|
RTEMS_DEFAULT_ATTRIBUTES,
|
|
&RTEMS_Malloc_Heap
|
|
);
|
|
if ( status != RTEMS_SUCCESSFUL )
|
|
rtems_fatal_error_occurred( status );
|
|
|
|
#ifdef MALLOC_STATS
|
|
/* zero all the stats */
|
|
(void) memset( &rtems_malloc_stats, 0, sizeof(rtems_malloc_stats) );
|
|
#endif
|
|
|
|
MSBUMP(space_available, length);
|
|
}
|
|
|
|
#ifdef RTEMS_NEWLIB
|
|
void *malloc(
|
|
size_t size
|
|
)
|
|
{
|
|
void *return_this;
|
|
void *starting_address;
|
|
uint32_t the_size;
|
|
uint32_t sbrk_amount;
|
|
rtems_status_code status;
|
|
Chain_Node *to_be_freed;
|
|
|
|
MSBUMP(malloc_calls, 1);
|
|
|
|
if ( !size )
|
|
return (void *) 0;
|
|
|
|
/*
|
|
* Do not attempt to allocate memory if in a critical section or ISR.
|
|
*/
|
|
|
|
if (_System_state_Is_up(_System_state_Get())) {
|
|
if (_Thread_Dispatch_disable_level > 0)
|
|
return (void *) 0;
|
|
|
|
if (_ISR_Nest_level > 0)
|
|
return (void *) 0;
|
|
}
|
|
|
|
/*
|
|
* If some free's have been deferred, then do them now.
|
|
*/
|
|
while ((to_be_freed = Chain_Get(&RTEMS_Malloc_GC_list)) != NULL)
|
|
free(to_be_freed);
|
|
|
|
/*
|
|
* Try to give a segment in the current region if there is not
|
|
* enough space then try to grow the region using rtems_region_extend().
|
|
* If this fails then return a NULL pointer.
|
|
*/
|
|
|
|
#ifdef MALLOC_ARENA_CHECK
|
|
size += sizeof(struct mallocNode) + SENTINELSIZE;
|
|
#endif
|
|
status = rtems_region_get_segment(
|
|
RTEMS_Malloc_Heap,
|
|
size,
|
|
RTEMS_NO_WAIT,
|
|
RTEMS_NO_TIMEOUT,
|
|
&return_this
|
|
);
|
|
|
|
if ( status != RTEMS_SUCCESSFUL ) {
|
|
/*
|
|
* Round to the "requested sbrk amount" so hopefully we won't have
|
|
* to grow again for a while. This effectively does sbrk() calls
|
|
* in "page" amounts.
|
|
*/
|
|
|
|
sbrk_amount = RTEMS_Malloc_Sbrk_amount;
|
|
|
|
if ( sbrk_amount == 0 )
|
|
return (void *) 0;
|
|
|
|
the_size = ((size + sbrk_amount) / sbrk_amount * sbrk_amount);
|
|
|
|
if ((starting_address = (void *)sbrk(the_size))
|
|
== (void*) -1)
|
|
return (void *) 0;
|
|
|
|
status = rtems_region_extend(
|
|
RTEMS_Malloc_Heap,
|
|
starting_address,
|
|
the_size
|
|
);
|
|
if ( status != RTEMS_SUCCESSFUL ) {
|
|
sbrk(-the_size);
|
|
errno = ENOMEM;
|
|
return (void *) 0;
|
|
}
|
|
|
|
MSBUMP(space_available, the_size);
|
|
|
|
status = rtems_region_get_segment(
|
|
RTEMS_Malloc_Heap,
|
|
size,
|
|
RTEMS_NO_WAIT,
|
|
RTEMS_NO_TIMEOUT,
|
|
&return_this
|
|
);
|
|
if ( status != RTEMS_SUCCESSFUL ) {
|
|
errno = ENOMEM;
|
|
return (void *) 0;
|
|
}
|
|
}
|
|
|
|
#ifdef MALLOC_STATS
|
|
if (return_this)
|
|
{
|
|
size_t actual_size;
|
|
uint32_t current_depth;
|
|
status = rtems_region_get_segment_size(
|
|
RTEMS_Malloc_Heap, return_this, &actual_size);
|
|
MSBUMP(lifetime_allocated, actual_size);
|
|
current_depth = rtems_malloc_stats.lifetime_allocated -
|
|
rtems_malloc_stats.lifetime_freed;
|
|
if (current_depth > rtems_malloc_stats.max_depth)
|
|
rtems_malloc_stats.max_depth = current_depth;
|
|
}
|
|
#endif
|
|
|
|
#ifdef MALLOC_DIRTY
|
|
(void) memset(return_this, 0xCF, size);
|
|
#endif
|
|
|
|
#ifdef MALLOC_ARENA_CHECK
|
|
{
|
|
struct mallocNode *mp = (struct mallocNode *)return_this;
|
|
int key, *fp, *nfp, i;
|
|
rtems_interrupt_disable(key);
|
|
mp->memory = mp + 1;
|
|
return_this = mp->memory;
|
|
mp->size = size - (sizeof(struct mallocNode) + SENTINELSIZE);
|
|
fp = (int *)&size - 2;
|
|
for (i = 0 ; i < CALLCHAINSIZE ; i++) {
|
|
mp->callChain[i] = fp[1];
|
|
nfp = (int *)(fp[0]);
|
|
if((nfp <= fp) || (nfp > (int *)(1 << 24)))
|
|
break;
|
|
fp = nfp;
|
|
}
|
|
while (i < CALLCHAINSIZE)
|
|
mp->callChain[i++] = 0;
|
|
memcpy((char *)mp->memory + mp->size, SENTINEL, SENTINELSIZE);
|
|
mp->forw = mallocNodeHead.forw;
|
|
mp->back = &mallocNodeHead;
|
|
mallocNodeHead.forw->back = mp;
|
|
mallocNodeHead.forw = mp;
|
|
rtems_interrupt_enable(key);
|
|
}
|
|
#endif
|
|
return return_this;
|
|
}
|
|
|
|
void *calloc(
|
|
size_t nelem,
|
|
size_t elsize
|
|
)
|
|
{
|
|
register char *cptr;
|
|
int length;
|
|
|
|
MSBUMP(calloc_calls, 1);
|
|
|
|
length = nelem * elsize;
|
|
cptr = malloc( length );
|
|
if ( cptr )
|
|
memset( cptr, '\0', length );
|
|
|
|
MSBUMP(malloc_calls, -1); /* subtract off the malloc */
|
|
|
|
return cptr;
|
|
}
|
|
|
|
void *realloc(
|
|
void *ptr,
|
|
size_t size
|
|
)
|
|
{
|
|
size_t old_size;
|
|
rtems_status_code status;
|
|
char *new_area;
|
|
|
|
MSBUMP(realloc_calls, 1);
|
|
|
|
/*
|
|
* Do not attempt to allocate memory if in a critical section or ISR.
|
|
*/
|
|
|
|
if (_System_state_Is_up(_System_state_Get())) {
|
|
if (_Thread_Dispatch_disable_level > 0)
|
|
return (void *) 0;
|
|
|
|
if (_ISR_Nest_level > 0)
|
|
return (void *) 0;
|
|
}
|
|
|
|
/*
|
|
* Continue with realloc().
|
|
*/
|
|
if ( !ptr )
|
|
return malloc( size );
|
|
|
|
if ( !size ) {
|
|
free( ptr );
|
|
return (void *) 0;
|
|
}
|
|
|
|
#ifdef MALLOC_ARENA_CHECK
|
|
{
|
|
void *np;
|
|
np = malloc(size);
|
|
if (!np) return np;
|
|
memcpy(np,ptr,size);
|
|
free(ptr);
|
|
return np;
|
|
}
|
|
#endif
|
|
status =
|
|
rtems_region_resize_segment( RTEMS_Malloc_Heap, ptr, size, &old_size );
|
|
|
|
if( status == RTEMS_SUCCESSFUL ) {
|
|
return ptr;
|
|
}
|
|
else if ( status != RTEMS_UNSATISFIED ) {
|
|
errno = EINVAL;
|
|
return (void *) 0;
|
|
}
|
|
|
|
new_area = malloc( size );
|
|
|
|
MSBUMP(malloc_calls, -1); /* subtract off the malloc */
|
|
|
|
/*
|
|
* There used to be a free on this error case but it is wrong to
|
|
* free the memory per OpenGroup Single UNIX Specification V2
|
|
* and the C Standard.
|
|
*/
|
|
|
|
if ( !new_area ) {
|
|
return (void *) 0;
|
|
}
|
|
|
|
status = rtems_region_get_segment_size( RTEMS_Malloc_Heap, ptr, &old_size );
|
|
if ( status != RTEMS_SUCCESSFUL ) {
|
|
errno = EINVAL;
|
|
return (void *) 0;
|
|
}
|
|
|
|
memcpy( new_area, ptr, (size < old_size) ? size : old_size );
|
|
free( ptr );
|
|
|
|
return new_area;
|
|
|
|
}
|
|
|
|
void free(
|
|
void *ptr
|
|
)
|
|
{
|
|
rtems_status_code status;
|
|
|
|
MSBUMP(free_calls, 1);
|
|
|
|
if ( !ptr )
|
|
return;
|
|
|
|
/*
|
|
* Do not attempt to free memory if in a critical section or ISR.
|
|
*/
|
|
|
|
if (_System_state_Is_up(_System_state_Get())) {
|
|
if ((_Thread_Dispatch_disable_level > 0) || (_ISR_Nest_level > 0)) {
|
|
Chain_Append(&RTEMS_Malloc_GC_list, (Chain_Node *)ptr);
|
|
return;
|
|
}
|
|
}
|
|
|
|
#ifdef MALLOC_ARENA_CHECK
|
|
{
|
|
struct mallocNode *mp = (struct mallocNode *)ptr - 1;
|
|
struct mallocNode *mp1;
|
|
int key;
|
|
rtems_interrupt_disable(key);
|
|
if ((mp->memory != (mp + 1))
|
|
|| (memcmp((char *)mp->memory + mp->size, SENTINEL, SENTINELSIZE) != 0))
|
|
reportMallocError("Freeing with inconsistent pointer/sentinel", mp);
|
|
mp1 = mallocNodeHead.forw;
|
|
while (mp1 != &mallocNodeHead) {
|
|
if (mp1 == mp)
|
|
break;
|
|
mp1 = mp1->forw;
|
|
}
|
|
if (mp1 != mp)
|
|
reportMallocError("Freeing, but not on allocated list", mp);
|
|
mp->forw->back = mp->back;
|
|
mp->back->forw = mp->forw;
|
|
mp->back = mp->forw = NULL;
|
|
ptr = mp;
|
|
rtems_interrupt_enable(key);
|
|
}
|
|
#endif
|
|
#ifdef MALLOC_STATS
|
|
{
|
|
size_t size;
|
|
status = rtems_region_get_segment_size( RTEMS_Malloc_Heap, ptr, &size );
|
|
if ( status == RTEMS_SUCCESSFUL ) {
|
|
MSBUMP(lifetime_freed, size);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
status = rtems_region_return_segment( RTEMS_Malloc_Heap, ptr );
|
|
if ( status != RTEMS_SUCCESSFUL ) {
|
|
errno = EINVAL;
|
|
assert( 0 );
|
|
}
|
|
}
|
|
|
|
#ifdef MALLOC_ARENA_CHECK
|
|
void checkMallocArena(void)
|
|
{
|
|
struct mallocNode *mp = mallocNodeHead.forw;
|
|
int key;
|
|
rtems_interrupt_disable(key);
|
|
while (mp != &mallocNodeHead) {
|
|
if ((mp->forw->back != mp)
|
|
|| (mp->back->forw != mp))
|
|
reportMallocError("Pointers mangled", mp);
|
|
if((mp->memory != (mp + 1))
|
|
|| (memcmp((char *)mp->memory + mp->size, SENTINEL, SENTINELSIZE) != 0))
|
|
reportMallocError("Inconsistent pointer/sentinel", mp);
|
|
mp = mp->forw;
|
|
}
|
|
rtems_interrupt_enable(key);
|
|
}
|
|
#endif
|
|
|
|
/* end if RTEMS_NEWLIB */
|
|
#endif
|
|
|
|
#ifdef MALLOC_STATS
|
|
/*
|
|
* Dump the malloc statistics
|
|
* May be called via atexit() (installable by our bsp) or
|
|
* at any time by user
|
|
*/
|
|
|
|
void malloc_dump(void)
|
|
{
|
|
uint32_t allocated = rtems_malloc_stats.lifetime_allocated -
|
|
rtems_malloc_stats.lifetime_freed;
|
|
|
|
printf("Malloc stats\n");
|
|
printf(" avail:%"PRIu32"k allocated:%"PRIu32"k (%"PRId32"%%) "
|
|
"max:%"PRIu32"k (%"PRIu32"%%)"
|
|
" lifetime:%"PRIuMAX"k freed:%"PRIuMAX"k\n",
|
|
rtems_malloc_stats.space_available / 1024,
|
|
allocated / 1024,
|
|
/* avoid float! */
|
|
(allocated * 100) / rtems_malloc_stats.space_available,
|
|
rtems_malloc_stats.max_depth / 1024,
|
|
(rtems_malloc_stats.max_depth * 100) / rtems_malloc_stats.space_available,
|
|
rtems_malloc_stats.lifetime_allocated / 1024,
|
|
rtems_malloc_stats.lifetime_freed / 1024
|
|
);
|
|
printf(" Call counts: malloc:%"PRIu32" free:%"PRIu32" realloc:%"PRIu32" calloc:%"PRIu32"\n",
|
|
rtems_malloc_stats.malloc_calls,
|
|
rtems_malloc_stats.free_calls,
|
|
rtems_malloc_stats.realloc_calls,
|
|
rtems_malloc_stats.calloc_calls);
|
|
}
|
|
|
|
|
|
void malloc_walk(size_t source, size_t printf_enabled)
|
|
{
|
|
register Region_Control *the_region;
|
|
Objects_Locations location;
|
|
|
|
_RTEMS_Lock_allocator(); /* to prevent deletion */
|
|
the_region = _Region_Get( RTEMS_Malloc_Heap, &location );
|
|
if ( location == OBJECTS_LOCAL )
|
|
{
|
|
_Heap_Walk( &the_region->Memory, source, printf_enabled );
|
|
}
|
|
_RTEMS_Unlock_allocator();
|
|
}
|
|
|
|
#else
|
|
|
|
void malloc_dump(void)
|
|
{
|
|
return;
|
|
}
|
|
|
|
void malloc_walk(size_t source, size_t printf_enabled)
|
|
{
|
|
return;
|
|
}
|
|
|
|
#endif
|
|
|
|
/*
|
|
* "Reentrant" versions of the above routines implemented above.
|
|
*/
|
|
|
|
#ifdef RTEMS_NEWLIB
|
|
void *_malloc_r(
|
|
struct _reent *ignored,
|
|
size_t size
|
|
)
|
|
{
|
|
return malloc( size );
|
|
}
|
|
|
|
void *_calloc_r(
|
|
struct _reent *ignored,
|
|
size_t nelem,
|
|
size_t elsize
|
|
)
|
|
{
|
|
return calloc( nelem, elsize );
|
|
}
|
|
|
|
void *_realloc_r(
|
|
struct _reent *ignored,
|
|
void *ptr,
|
|
size_t size
|
|
)
|
|
{
|
|
return realloc( ptr, size );
|
|
}
|
|
|
|
void _free_r(
|
|
struct _reent *ignored,
|
|
void *ptr
|
|
)
|
|
{
|
|
free( ptr );
|
|
}
|
|
|
|
#endif
|