基于OSAL的嵌入式裸机事件驱动框架——内存管理osal_memory
参考
OSAL动态内存分配与管理-CSDN博客
TI OSAL内存管理实现方式 - uCOS-II/III等OS综合区
这部分代码和原OSAL内存管理的代码有细微差异,但是逻辑上是差不多的,这些应该是由大佬修改过的。
此内存管理源码来源:
https://gitee.com/Pencil888/osal-stm32/blob/master/OSAL/osal/osal_memory.c
OSAL的内存管理是堆管理,OSAL_Memory.c里面定义了一个很大的数组theHeap,
内存管理把堆空间分成两个部分,第一个部分是针对小块内存的管理,第二部分是针对大块内存的管理。
这样做的好处是容易申请到连续的大空间,因为小块内存处理会使整个内存空间碎片化,那么会导致内存空间不连续,不连续的空间是对申请大空间是非常不利的。在申请堆空间时,会自动合并之前释放的free块,等到找到合适连续块时,会自动裁剪多余的块空间,以免造成空间的浪费。
缺点:代码可读性差,与FreeRTOS的heap4相比,个人觉得FreeRTOS的heap4更好些
osal_memory.h
#ifndef OSALMEM_METRICS_H
#define OSALMEM_METRICS_H
#include "osal.h"
#define MAXMEMHEAP 1024*6 //内存池大小,单位字节
#define OSALMEM_METRICS 1 //定义有效则开启内存统计
void osal_mem_init( void );
void osal_mem_kick( void );
void *osal_mem_alloc( osal_uint16_t size );
void osal_mem_free( void *ptr );
#if OSALMEM_METRICS
osal_uint16_t osal_heap_block_max( void );
osal_uint16_t osal_heap_block_cnt( void );
osal_uint16_t osal_heap_block_free( void );
osal_uint16_t osal_heap_mem_used( void );
osal_uint16_t osal_heap_high_water( void );
osal_uint16_t osal_heap_mem_usage_rate( void );
#endif
#endifc
osal_memory.c
变量定义
如果使用外部RAM,theHeap被定义为一个指向外部RAM起始地址EXT_RAM_BEG的指针. 反之则定义一个静态数组_theHeap,数组的大小为MAXMEMHEAP / sizeof( halDataAlign_t ),halDataAlign_t类型。
静态数组意味着其生命周期从程序开始一直持续到程序结束,且其存储空间在静态存储区,而不是在栈上。这使得 _theHeap 可以在整个程序运行期间保持其值。
__attribute__((aligned(32))): 这是一个GCC编译器的扩展属性,用于指定变量的对齐方式。在这种情况下,_theHeap 数组将被对齐到32字节的边界。这意味着 _theHeap 的起始地址将是32的倍数。这种对齐方式有助于提高内存访问效率,尤其是在处理需要特定对齐的数据(如某些类型的硬件寄存器访问)时。
#if defined( EXTERNAL_RAM )
static byte *theHeap = (byte *)EXT_RAM_BEG;
#else
// static halDataAlign_t _theHeap[ MAXMEMHEAP / sizeof( halDataAlign_t ) ];
// static __align(4) halDataAlign_t _theHeap[ MAXMEMHEAP / sizeof( halDataAlign_t ) ]; static __attribute__ ((aligned (32))) halDataAlign_t _theHeap[ MAXMEMHEAP / sizeof( halDataAlign_t ) ];
//根据实际使用的芯片设定内存对齐,__align(32),非常重要!!!
static osal_byte_t *theHeap = (osal_byte_t *)_theHeap;
#endif
memMax记录系统运行过程中曾经分配的最大内存总量。这个变量可以帮助开发者了解系统在最坏情况下需要多少内存,以便进行有效的内存规划
blkCnt在osal_mem_init函数中被设置为2,这是因为初始化时会有一个固定长度分配区域和一个可变长度分配区域,这两个区域被视为初始的内存块。
blkFree在osal_mem_init函数中被设置为2,与blkCnt的初始化原因相同,固定长度分配区域和可变长度分配区域被视为初始的内存块,且它们被视为已分配但不可释放的状态。
其他初始化为0
#if OSALMEM_METRICS
static osal_uint16_t blkMax; // Max cnt of all blocks ever seen at once. 最大内存块数量
static osal_uint16_t blkCnt; // Current cnt of all blocks. 当前系统中已经分配的内存块总数
static osal_uint16_t blkFree; // Current cnt of free blocks.当前系统中空闲的内存块数量
static osal_uint16_t memAlo; // Current total memory allocated.当前系统中已经分配的总内存大小
static osal_uint16_t memMax; // Max total memory ever allocated at once.曾经分配的最大内存总量
#endif
osal_mem_init
- 设置内存区最后一个字为0
- 设置小内存区(固定内存区)长度
- 设置可变内存区长度
- 将两个内存区分割,使其不能合并
/*********************************************************************
* @fn osal_mem_init * * @brief Initialize the heap memory management system. * * @param void * * @return void */void osal_mem_init( void )
{
osalMemHdr_t *tmp;
// Setup a NULL block at the end of the heap for fast comparisons with zero.
//将内存池最后一个位置为空,注意这里的空块是指长度为osalMemHdr_t的内存块,占4个字节
//这个空块用于防止在搜索可用内存时产生溢出。
// 通过检查这个空块是否为零可以快速判断是否已经到达内存池的末尾
tmp = (osalMemHdr_t *)theHeap + (MAXMEMHEAP / HDRSZ) - 1;
*tmp = 0;
// Setup a small-block bucket.
//设置固定长度内存区长度,232字节,内存区第一个字保存该区长度
tmp = (osalMemHdr_t *)theHeap;//tmp被重新指向内存池的起始位置
*tmp = SMALLBLKHEAP;
// Setup the wilderness.
//设置可变内存区长度,内存区第一个字保存该区长度
tmp = (osalMemHdr_t *)theHeap + (SMALLBLKHEAP / HDRSZ);
*tmp = ((MAXMEMHEAP / HDRSZ) * HDRSZ) - SMALLBLKHEAP - HDRSZ;
#if ( OSALMEM_GUARD )
/*ready变量用于确保osal_mem_alloc和osal_mem_free在内存管理系统初始化完成之后才被调用*/
ready = OSALMEM_READY;
#endif
// Setup a NULL block that is never freed so that the small-block bucket
// is never coalesced with the wilderness. /*设置一个永远不会释放的NULL块,这样小块区就永远不会与可变内存区合并*/
/*ff1和ff2是两个指向内存块的指针,分别用于管理固定长度内存区和可变长度内存区*/
ff1 = tmp;//ff1首先被设置为可变内存区的起始位置
/*通过调用osal_mem_alloc(0)在固定长度内存区和可变长度内存区之间分配一个0大小的内存块。
* 这个内存块虽然大小为0,但处于已使用的状态,确保了两个内存区不会被合并*/
ff2 = osal_mem_alloc( 0 );
ff1 = (osalMemHdr_t *)theHeap;//ff1被重新指向内存池的起始位置,即固定长度内存区的起始位置
/*
上述语句将在固定长度分配区域和可变分配区域之间申请一个0大小的内存块,
相当于在固定分配区域和可变分配区域之间保留了一个一直处于使用状态, 但是指向长度为0的一个内存分配控制块。
这个内存块的是将两个分配区域隔离开,以免和可变长度分配区域合并。
*/
#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,是因为已经有一个小块内存区和一个可变内存区的空块被分配*/
blkCnt = blkFree = 2;
memAlo = 0;
#endif
}
ff2 = osal_mem_alloc( 0 ); 实际上分配的大小是 0 + 内存头 + 对齐需要大小
这里应该是4个字节,也就是内存头的大小HDRSZ。
这一部分空间实际上是从动态内存区里分出去的,所以实际上动态内存区的剩余空间为((MAXMEMHEAP / HDRSZ) * HDRSZ) - SMALLBLKHEAP - HDRSZ ×2
这样两部分内存区就分隔开了。
![![[Pasted image 20250207222329.png]]](https://i-blog.csdnimg.cn/direct/5402a80566e0406399c2339d54b560fa.png)
osal_mem_alloc
osal_mem_alloc()函数的主要功能是从内存池中分配指定大小的内存块,并返回指向该内存块的指针。在分配内存的过程中,函数会根据请求的内存大小选择合适的内存分配区域,并遍历内存池寻找可用的内存块。找到合适的内存块后,根据剩余空间的大小决定是否需要分割内存块,以避免内存浪费。此外,函数还负责更新内存使用统计信息,以便监控内存使用情况。
/*********************************************************************
* @fn osal_mem_alloc * * @brief Implementation of the allocator functionality. * * @param size - number of bytes to allocate from the heap. * * @return void * - pointer to the heap allocation; NULL if error or failure. */void *osal_mem_alloc(osal_uint16_t size )
{
osalMemHdr_t *prev;//指向前一片内存块的指针,上一轮的空闲内存块
osalMemHdr_t *hdr;//指向内存块头的指针
osal_uint16_t tmp;//临时变量,用于记录内存块长度
osal_byte_t coal = 0;//是否合并标志
#if ( OSALMEM_GUARD )
// Try to protect against premature use by HAL / OSAL.
if ( ready != OSALMEM_READY )
{
osal_mem_init();
}
#endif
/*需要分配的实际大小是用户请求的大小 + 内存控制头的大小 + 对齐所需要的大小*/
size += HDRSZ;
// Calculate required bytes to add to 'size' to align to halDataAlign_t.
//根据实际的芯片的字长halDataAlign_t进行字节对齐
if ( sizeof( halDataAlign_t ) == 2 )
{
size += (size & 0x01);
}
else if ( sizeof( halDataAlign_t ) != 1 )
{
const osal_byte_t mod = size % sizeof( halDataAlign_t );
if ( mod != 0 )
{
size += (sizeof( halDataAlign_t ) - mod);
}
}
OSAL_ENTER_CRITICAL(); // Hold off interrupts.
// Smaller allocations are first attempted in the small-block bucket. /*较小的分配首先在小块内存区中尝试*/
/*OSALMEM_SMALL_BLKSZ 定义了固定大小内存池中每个内存块的大小*/
if ( size <= OSALMEM_SMALL_BLKSZ )
{
hdr = ff1;
}
else
{
hdr = ff2;
}
tmp = *hdr;//读取内存块长度,tmp为内存块头,保存了该内存块的长度
/*寻找可用内存块*/
/*遍历内存池 → 检查当前内存块是否被使用 → 已被使用 → 下一个内存块
* → 未被使用 → 是否足够分配 → 足够分配 → 跳出循环*/
do
{
if ( tmp & OSALMEM_IN_USE ) //该内存块已被使用
{
tmp ^= OSALMEM_IN_USE; //该片已被使用,得出长度
coal = 0; //合并标志清零,因为遇到了一片已使用的内存
}
else //该片未被使用
{
if ( coal != 0 ) //上轮查找有找到空内存,但空间不够,要合并
{
#if ( OSALMEM_METRICS )
blkCnt--; //内存合并,总内存块计数减一
blkFree--; //内存合并,空闲内存块计数减一
#endif
*prev += *hdr; //加上本次找到的空内存大小
if ( *prev >= size ) //加上后内存大小符合申请需要
{
hdr = prev; //返回该内存块
tmp = *hdr; //得出长度
break;
}
}
else //上轮查找未找到空内存
{
if ( tmp >= size ) //该片内存大小符合需求,跳出查找循环
{
break;
}
coal = 1; //该片内存大小不符合需求,标记找到一块空内存
prev = hdr; //记录该内存
}
}
hdr = (osalMemHdr_t *)((osal_byte_t *)hdr + tmp); //偏移至下一片内存区域
tmp = *hdr; //读取该区域长度
if ( tmp == 0 ) //到达内存池末尾,说明没有找到可用内存,分配失败,返回NULL
{
hdr = ((void *)NULL);
break; }
}while ( 1 );
/*内存分割与合并*/
if ( hdr != ((void *)NULL))
{
tmp -= 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 *)((osal_byte_t *)hdr + size); //偏移
*next = tmp; //记录未使用区域剩余长度
*hdr = (size | OSALMEM_IN_USE); //标志本次申请区域已被使用,并记录本次使用长度
#if ( OSALMEM_METRICS )
blkCnt++; //内存分割,总内存块计数加一
if ( blkMax < blkCnt )
{
blkMax = blkCnt; //调整内存块数量最大值
}
memAlo += size; //调整已用内存大小
#endif
}
else
{
#if ( OSALMEM_METRICS )
memAlo += *hdr;
blkFree--; //内存不分割,空闲内存块计数减一
#endif
*hdr |= OSALMEM_IN_USE;
}
#if ( OSALMEM_METRICS )
if ( memMax < memAlo )
{
memMax = memAlo;
}
#endif
hdr++; //偏移,返回实际申请的内存地址
}
OSAL_EXIT_CRITICAL(); // Re-enable interrupts.
return (void *)hdr;
}
实际分配的内存空间大小是 申请空间 + 内存头 + 对齐需要的大小
返回地址是不包括内存头的
这个函数实现的挺巧妙的,使用每块内存块的第一个位置当内存头,内存头存储的是这个内存块的实际大小(申请大小+内存头+对齐需要大小),最高位为1时标记已分配。且可以实现内存块合并,但是可读性是比较差的,看了好久才看懂,整理一个流程图
![![[Pasted image 20250207205337.png]]](https://i-blog.csdnimg.cn/direct/71aaf2c24f674304abac71d54f95eadf.png)
osal_mem_free
释放内存块,将内存块头的分配标志清除,实际内存块的数据并没有清0。
合并操作是在osal_mem_alloc中进行的
/*********************************************************************
* @fn osal_mem_free * * @brief Implementation of the de-allocator functionality. * * @param ptr - pointer to the memory to free. * * @return void */void osal_mem_free( void *ptr )
{
osalMemHdr_t *currHdr;//指向当前内存块头的指针
//halIntState_t intState;
#if ( OSALMEM_GUARD )
// Try to protect against premature use by HAL / OSAL.
if ( ready != OSALMEM_READY )
{
osal_mem_init();
}
#endif
OSAL_ENTER_CRITICAL(); // Hold off interrupts.
/*将 ptr 减去一个 osalMemHdr_t 的大小(即内存块头的大小),以得到指向该内存块头的指针 currHdr*/ currHdr = (osalMemHdr_t *)ptr - 1;
*currHdr &= ~OSALMEM_IN_USE;//将该内存块标志位清零,表示该内存块已释放
/*如果当前释放的内存块的地址小于 ff1,说明该内存块属于固定长度内存区,则更新 ff1*/ if ( ff1 > currHdr )
{
ff1 = currHdr;//更新 ff1 为当前释放的内存块的地址
}
#if OSALMEM_METRICS
memAlo -= *currHdr;
blkFree++;
#endif
OSAL_EXIT_CRITICAL(); // Re-enable interrupts.
}
osal_mem_kick
调用此函数后,将ff1指向ff2的位置,也就是ff1和ff2都指向了动态内存区。之后再有内存分配就都分动态内存区的空间了。
只有调用osal_mem_free函数释放固定/小内存区的内存块,才会移动ff1回去,回到第一个空闲块。
/*********************************************************************
* @fn osal_mem_kick * * @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(). * 使FF1跳过固定长度区域,指向FF2(可变长度区域),也就是这个意思:
* 如果在固定分配区域中没有申请内存成功,
* 调用此函数后它将修改指向固定分配区域的指针使指针指向可变分配区域,
* 然后再调用osal_mem_alloc 它将在可变长度分配区域中进行内存分配。
* * 在分配完所有长期块后调用此函数一次-目前在osal_init_system()的末尾
* @param void * * @return void */void osal_mem_kick( void )
{
//halIntState_t intState;
OSAL_ENTER_CRITICAL(); // Hold off interrupts.
/* Logic in osal_mem_free() will ratchet ff1 back down to the first free * block in the small-block bucket. * osal_mem_free()中的逻辑将把ff1拉回到小块内存区中的第一个空闲块。
*/ ff1 = ff2;
OSAL_EXIT_CRITICAL(); // Re-enable interrupts.
}
![![[Pasted image 20250208112520.png]]](https://i-blog.csdnimg.cn/direct/913ca3016b774b9b9d4ad84aa2c90760.png)
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐


所有评论(0)