OSAL内存管理机制比较节省内存,仅占用2字节,如下图所示。它的局限性在于:因为记录内存块长度的len仅为15比特,所以最大长度为2^15-1=32767字节。
设有如下配置:MAXMEMHEAP=1024 SMALLBLK_BUCKET=128
BIGBLK_SZ=1024-128-2*2=892。调用osal_mem_init()之后堆栈的数据结构如下图所示:
随着用户调用osalInitTasks()初始化各任务后,堆栈会分配一些long lived allocation内存。之后,再调用osal_mem_kick(),此时系统会将ff1指针调整为“小块内存分配区”中第一个可用的内存,这样做可以提高内存分配时的查找效率。效果如下图所示:
动态分配内存函数osal_mem_alloc(size)先检查size大小,如果是小块内存将从ff1开始查找;否则,从BIGBLK_IDX处开始查找。
查找原理如下图所示:
(1) hdr所指内存块已经被占用,继续查找下一块;
(2) hdr所指内存块空闲但该块内存小于size,使用prev记录该内存指针;
(3) 需要将prev和hdr所指2块内存进行合并操作;
(4) 合并之后内存块满足size大小,执行分配;否则继续查找下一块,直到分配成功,或查找到堆栈的底部而失败返回。
如果hdr指向的空闲内存比所需size大过阀值则进行内存分割,如下图所示。首先将hdr所指内存分割成size和temp两块,然后设置size块内存已占用标志,最后返回ptr给用户。
内存的回收原理见下图,根据用户提交的ptr指针计算hdr指针(后退一个内存管理结构尺寸的偏移),然后将占用标志清除即可。
附录:源代码
/* ------------------------------------------------------------------------------------------------
* Constants
* ------------------------------------------------------------------------------------------------
*/
#define OSALMEM_IN_USE 0x8000
#if (MAXMEMHEAP & OSALMEM_IN_USE)
#error MAXMEMHEAP is too big to manage!
#endif
#define OSALMEM_HDRSZ sizeof(osalMemHdr_t)
// Round a value up to the ceiling of OSALMEM_HDRSZ for critical dependencies on even multiples.
#define OSALMEM_ROUND(X) ((((X) + OSALMEM_HDRSZ - 1) / OSALMEM_HDRSZ) * OSALMEM_HDRSZ)
/* Minimum wasted bytes to justify splitting a block before allocation.
* Adjust accordingly to attempt to balance the tradeoff of wasted space and runtime throughput
* spent splitting blocks into sizes that may not be practically usable when sandwiched between
* two blocks in use (and thereby not able to be coalesced.)
* Ensure that this size is an even multiple of OSALMEM_HDRSZ.
*/
#if !defined OSALMEM_MIN_BLKSZ
#define OSALMEM_MIN_BLKSZ (OSALMEM_ROUND((OSALMEM_HDRSZ * 2)))
#endif
/* Adjust accordingly to attempt to accommodate the block sizes of the vast majority of
* very high frequency allocations/frees by profiling the system runtime.
* This default of 16 accomodates the OSAL timers block, osalTimerRec_t, and many others.
* Ensure that this size is an even multiple of OSALMEM_MIN_BLKSZ for run-time efficiency.
*/
#if !defined OSALMEM_SMALL_BLKSZ
#define OSALMEM_SMALL_BLKSZ (OSALMEM_ROUND(16))
#endif
#if !defined OSALMEM_SMALL_BLKCNT
#define OSALMEM_SMALL_BLKCNT 8
#endif
/*
* These numbers setup the size of the small-block bucket which is reserved at the front of the
* heap for allocations of OSALMEM_SMALL_BLKSZ or smaller.
*/
// Size of the heap bucket reserved for small block-sized allocations.
// Adjust accordingly to attempt to accomodate the vast majority of very high frequency operations.
#define OSALMEM_SMALLBLK_BUCKET (OSALMEM_SMALL_BLKSZ * OSALMEM_SMALL_BLKCNT)
// Index of the first available osalMemHdr_t after the small-block heap which will be set in-use in
// order to prevent the small-block bucket from being coalesced with the wilderness.
#define OSALMEM_SMALLBLK_HDRCNT (OSALMEM_SMALLBLK_BUCKET / OSALMEM_HDRSZ)
// Index of the first available osalMemHdr_t after the small-block heap which will be set in-use in
#define OSALMEM_BIGBLK_IDX (OSALMEM_SMALLBLK_HDRCNT + 1)
// The size of the wilderness after losing the small-block heap, the wasted header to block the
// small-block heap from being coalesced, and the wasted header to mark the end of the heap.
#define OSALMEM_BIGBLK_SZ (MAXMEMHEAP - OSALMEM_SMALLBLK_BUCKET - OSALMEM_HDRSZ*2)
// Index of the last available osalMemHdr_t at the end of the heap which will be set to zero for
// fast comparisons with zero to determine the end of the heap.
#define OSALMEM_LASTBLK_IDX ((MAXMEMHEAP / OSALMEM_HDRSZ) - 1)
// For information about memory profiling, refer to SWRA204 "Heap Memory Management", section 1.5.
#if !defined OSALMEM_PROFILER
#define OSALMEM_PROFILER FALSE // Enable/disable the memory usage profiling buckets.
#endif
#if !defined OSALMEM_PROFILER_LL
#define OSALMEM_PROFILER_LL FALSE // Special profiling of the Long-Lived bucket.
#endif
#if OSALMEM_PROFILER
#define OSALMEM_INIT 'X'
#define OSALMEM_ALOC 'A'
#define OSALMEM_REIN 'F'
#endif
/* ------------------------------------------------------------------------------------------------
* Typedefs
* ------------------------------------------------------------------------------------------------
*/
typedef struct {
// The 15 LSB's of 'val' indicate the total item size, including the header, in 8-bit bytes.
unsigned len : 15;
// The 1 MSB of 'val' is used as a boolean to indicate in-use or freed.
unsigned inUse : 1;
} osalMemHdrHdr_t;
typedef union {
/* Dummy variable so compiler forces structure to alignment of largest element while not wasting
* space on targets when the halDataAlign_t is smaller than a UINT16.
*/
halDataAlign_t alignDummy;
uint16 val;
osalMemHdrHdr_t hdr;
} osalMemHdr_t;
/* ------------------------------------------------------------------------------------------------
* Local Variables
* ------------------------------------------------------------------------------------------------
*/
static __no_init osalMemHdr_t theHeap[MAXMEMHEAP / OSALMEM_HDRSZ];
static __no_init osalMemHdr_t *ff1; // First free block in the small-block bucket.
static uint8 osalMemStat; // Discrete status flags: 0x01 = kicked.
#if OSALMEM_METRICS
static uint16 blkMax; // Max cnt of all blocks ever seen at once.
static uint16 blkCnt; // Current cnt of all blocks.
static uint16 blkFree; // Current cnt of free blocks.
static uint16 memAlo; // Current total memory allocated.
static uint16 memMax; // Max total memory ever allocated at once.
#endif
#if OSALMEM_PROFILER
#define OSALMEM_PROMAX 8
/* The profiling buckets must differ by at least OSALMEM_MIN_BLKSZ; the
* last bucket must equal the max alloc size. Set the bucket sizes to
* whatever sizes necessary to show how your application is using memory.
*/
static uint16 proCnt[OSALMEM_PROMAX] = {
OSALMEM_SMALL_BLKSZ, 48, 112, 176, 192, 224, 256, 65535 };
static uint16 proCur[OSALMEM_PROMAX] = { 0 };
static uint16 proMax[OSALMEM_PROMAX] = { 0 };
static uint16 proTot[OSALMEM_PROMAX] = { 0 };
static uint16 proSmallBlkMiss;
#endif
/* ------------------------------------------------------------------------------------------------
* Global Variables
* ------------------------------------------------------------------------------------------------
*/
#ifdef DPRINTF_HEAPTRACE
extern int dprintf(const char *fmt, ...);
#endif /* DPRINTF_HEAPTRACE */
/**************************************************************************************************
* @fn osal_mem_init
*
* @brief This function is the OSAL heap memory management initialization callback.
*
* input parameters
*
* None.
*
* output parameters
*
* None.
*
* @return None.
*/
void osal_mem_init(void)
{
HAL_ASSERT(((OSALMEM_MIN_BLKSZ % OSALMEM_HDRSZ) == 0));
HAL_ASSERT(((OSALMEM_SMALL_BLKSZ % OSALMEM_HDRSZ) == 0));
#if OSALMEM_PROFILER
(void)osal_memset(theHeap, OSALMEM_INIT, MAXMEMHEAP);
#endif
// Setup a NULL block at the end of the heap for fast comparisons with zero.
theHeap[OSALMEM_LASTBLK_IDX].val = 0;
// Setup the small-block bucket.
ff1 = theHeap;
ff1->val = OSALMEM_SMALLBLK_BUCKET; // Set 'len' & clear 'inUse' field.
// Set 'len' & 'inUse' fields - this is a 'zero data bytes' lifetime allocation to block the
// small-block bucket from ever being coalesced with the wilderness.
theHeap[OSALMEM_SMALLBLK_HDRCNT].val = (OSALMEM_HDRSZ | OSALMEM_IN_USE);
// Setup the wilderness.
theHeap[OSALMEM_BIGBLK_IDX].val = OSALMEM_BIGBLK_SZ; // Set 'len' & clear 'inUse' field.
#if ( OSALMEM_METRICS )
/* Start with the small-block bucket and the wilderness - don't count the
* end-of-heap NULL block nor the end-of-small-block NULL block.
*/
blkCnt = blkFree = 2;
#endif
}
/**************************************************************************************************
* @fn osal_mem_kick
*
* @brief This function is the OSAL task initialization callback.
* @brief Kick the ff1 pointer out past the long-lived OSAL Task blocks.
* Invoke this once after all long-lived blocks have been allocated -
* presently at the end of osal_init_system().
*
* input parameters
*
* None.
*
* output parameters
*
* None.
*
* @return None.
*/
void osal_mem_kick(void)
{
halIntState_t intState;
osalMemHdr_t *tmp = osal_mem_alloc(1);
HAL_ASSERT((tmp != NULL));
HAL_ENTER_CRITICAL_SECTION(intState); // Hold off interrupts.
/* All long-lived allocations have filled the LL block reserved in the small-block bucket.
* Set 'osalMemStat' so searching for memory in this bucket from here onward will only be done
* for sizes meeting the OSALMEM_SMALL_BLKSZ criteria.
*/
ff1 = tmp - 1; // Set 'ff1' to point to the first available memory after the LL block.
osal_mem_free(tmp);
osalMemStat = 0x01; // Set 'osalMemStat' after the free because it enables memory profiling.
HAL_EXIT_CRITICAL_SECTION(intState); // Re-enable interrupts.
}
/**************************************************************************************************
* @fn osal_mem_alloc
*
* @brief This function implements the OSAL dynamic memory allocation functionality.
*
* input parameters
*
* @param size - the number of bytes to allocate from the HEAP.
*
* output parameters
*
* None.
*
* @return None.
*/
#ifdef DPRINTF_OSALHEAPTRACE
void *osal_mem_alloc_dbg( uint16 size, const char *fname, unsigned lnum )
#else /* DPRINTF_OSALHEAPTRACE */
void *osal_mem_alloc( uint16 size )
#endif /* DPRINTF_OSALHEAPTRACE */
{
osalMemHdr_t *prev = NULL;
osalMemHdr_t *hdr;
halIntState_t intState;
uint8 coal = 0;
size += OSALMEM_HDRSZ;
// Calculate required bytes to add to 'size' to align to halDataAlign_t.
if ( sizeof( halDataAlign_t ) == 2 )
{
size += (size & 0x01);
}
else if ( sizeof( halDataAlign_t ) != 1 )
{
const uint8 mod = size % sizeof( halDataAlign_t );
if ( mod != 0 )
{
size += (sizeof( halDataAlign_t ) - mod);
}
}
HAL_ENTER_CRITICAL_SECTION( intState ); // Hold off interrupts.
// Smaller allocations are first attempted in the small-block bucket, and all long-lived
// allocations are channeled into the LL block reserved within this bucket.
if ((osalMemStat == 0) || (size <= OSALMEM_SMALL_BLKSZ))
{
hdr = ff1;
}
else
{
hdr = (theHeap + OSALMEM_BIGBLK_IDX);
}
do
{
if ( hdr->hdr.inUse )
{
coal = 0;
}
else
{
if ( coal != 0 )
{
#if ( OSALMEM_METRICS )
blkCnt--;
blkFree--;
#endif
prev->hdr.len += hdr->hdr.len;
if ( prev->hdr.len >= size )
{
hdr = prev;
break;
}
}
else
{
if ( hdr->hdr.len >= size )
{
break;
}
coal = 1;
prev = hdr;
}
}
hdr = (osalMemHdr_t *)((uint8 *)hdr + hdr->hdr.len);
if ( hdr->val == 0 )
{
hdr = NULL;
break;
}
} while (1);
if ( hdr != NULL )
{
uint16 tmp = hdr->hdr.len - size;
// Determine whether the threshold for splitting is met.
if ( tmp >= OSALMEM_MIN_BLKSZ )
{
// Split the block before allocating it.
osalMemHdr_t *next = (osalMemHdr_t *)((uint8 *)hdr + size);
next->val = tmp; // Set 'len' & clear 'inUse' field.
hdr->val = (size | OSALMEM_IN_USE); // Set 'len' & 'inUse' field.
#if ( OSALMEM_METRICS )
blkCnt++;
if ( blkMax < blkCnt )
{
blkMax = blkCnt;
}
memAlo += size;
#endif
}
else
{
#if ( OSALMEM_METRICS )
memAlo += hdr->hdr.len;
blkFree--;
#endif
hdr->hdr.inUse = TRUE;
}
#if ( OSALMEM_METRICS )
if ( memMax < memAlo )
{
memMax = memAlo;
}
#endif
#if ( OSALMEM_PROFILER )
#if !OSALMEM_PROFILER_LL
if (osalMemStat != 0) // Don't profile until after the LL block is filled.
#endif
{
uint8 idx;
for ( idx = 0; idx < OSALMEM_PROMAX; idx++ )
{
if ( hdr->hdr.len <= proCnt[idx] )
{
break;
}
}
proCur[idx]++;
if ( proMax[idx] < proCur[idx] )
{
proMax[idx] = proCur[idx];
}
proTot[idx]++;
/* A small-block could not be allocated in the small-block bucket.
* When this occurs significantly frequently, increase the size of the
* bucket in order to restore better worst case run times. Set the first
* profiling bucket size in proCnt[] to the small-block bucket size and
* divide proSmallBlkMiss by the corresponding proTot[] size to get % miss.
* Best worst case time on TrasmitApp was achieved at a 0-15% miss rate
* during steady state Tx load, 0% during idle and steady state Rx load.
*/
if ((hdr->hdr.len <= OSALMEM_SMALL_BLKSZ) && (hdr >= (theHeap + OSALMEM_BIGBLK_IDX)))
{
proSmallBlkMiss++;
}
}
(void)osal_memset((uint8 *)(hdr+1), OSALMEM_ALOC, (hdr->hdr.len - OSALMEM_HDRSZ));
#endif
if ((osalMemStat != 0) && (ff1 == hdr))
{
ff1 = (osalMemHdr_t *)((uint8 *)hdr + hdr->hdr.len);
}
hdr++;
}
HAL_EXIT_CRITICAL_SECTION( intState ); // Re-enable interrupts.
#pragma diag_suppress=Pe767
HAL_ASSERT(((halDataAlign_t)hdr % sizeof(halDataAlign_t)) == 0);
#pragma diag_default=Pe767
#ifdef DPRINTF_OSALHEAPTRACE
dprintf("osal_mem_alloc(%u)->%lx:%s:%u\n", size, (unsigned) hdr, fname, lnum);
#endif /* DPRINTF_OSALHEAPTRACE */
return (void *)hdr;
}
/**************************************************************************************************
* @fn osal_mem_free
*
* @brief This function implements the OSAL dynamic memory de-allocation functionality.
*
* input parameters
*
* @param ptr - A valid pointer (i.e. a pointer returned by osal_mem_alloc()) to the memory to free.
*
* output parameters
*
* None.
*
* @return None.
*/
#ifdef DPRINTF_OSALHEAPTRACE
void osal_mem_free_dbg(void *ptr, const char *fname, unsigned lnum)
#else /* DPRINTF_OSALHEAPTRACE */
void osal_mem_free(void *ptr)
#endif /* DPRINTF_OSALHEAPTRACE */
{
osalMemHdr_t *hdr = (osalMemHdr_t *)ptr - 1;
halIntState_t intState;
#ifdef DPRINTF_OSALHEAPTRACE
dprintf("osal_mem_free(%lx):%s:%u\n", (unsigned) ptr, fname, lnum);
#endif /* DPRINTF_OSALHEAPTRACE */
HAL_ASSERT(((uint8 *)ptr >= (uint8 *)theHeap) && ((uint8 *)ptr < (uint8 *)theHeap+MAXMEMHEAP));
HAL_ASSERT(hdr->hdr.inUse);
HAL_ENTER_CRITICAL_SECTION( intState ); // Hold off interrupts.
hdr->hdr.inUse = FALSE;
if (ff1 > hdr)
{
ff1 = hdr;
}
#if OSALMEM_PROFILER
#if !OSALMEM_PROFILER_LL
if (osalMemStat != 0) // Don't profile until after the LL block is filled.
#endif
{
uint8 idx;
for (idx = 0; idx < OSALMEM_PROMAX; idx++)
{
if (hdr->hdr.len <= proCnt[idx])
{
break;
}
}
proCur[idx]--;
}
(void)osal_memset((uint8 *)(hdr+1), OSALMEM_REIN, (hdr->hdr.len - OSALMEM_HDRSZ) );
#endif
#if OSALMEM_METRICS
memAlo -= hdr->hdr.len;
blkFree++;
#endif
HAL_EXIT_CRITICAL_SECTION( intState ); // Re-enable interrupts.
}
#if OSALMEM_METRICS
/*********************************************************************
* @fn osal_heap_block_max
*
* @brief Return the maximum number of blocks ever allocated at once.
*
* @param none
*
* @return Maximum number of blocks ever allocated at once.
*/
uint16 osal_heap_block_max( void )
{
return blkMax;
}
/*********************************************************************
* @fn osal_heap_block_cnt
*
* @brief Return the current number of blocks now allocated.
*
* @param none
*
* @return Current number of blocks now allocated.
*/
uint16 osal_heap_block_cnt( void )
{
return blkCnt;
}
/*********************************************************************
* @fn osal_heap_block_free
*
* @brief Return the current number of free blocks.
*
* @param none
*
* @return Current number of free blocks.
*/
uint16 osal_heap_block_free( void )
{
return blkFree;
}
/*********************************************************************
* @fn osal_heap_mem_used
*
* @brief Return the current number of bytes allocated.
*
* @param none
*
* @return Current number of bytes allocated.
*/
uint16 osal_heap_mem_used( void )
{
return memAlo;
}
#endif
#if defined (ZTOOL_P1) || defined (ZTOOL_P2)
/*********************************************************************
* @fn osal_heap_high_water
*
* @brief Return the highest byte ever allocated in the heap.
*
* @param none
*
* @return Highest number of bytes ever used by the stack.
*/
uint16 osal_heap_high_water( void )
{
#if ( OSALMEM_METRICS )
return memMax;
#else
return MAXMEMHEAP;
#endif
}
#endif
/**************************************************************************************************
*/