打印

浅析FreeRTOS_v4.5.0内存分配与回收及其改进方案

[复制链接]
2158|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
gliethttp|  楼主 | 2007-9-30 10:34 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
浅析FreeRTOS_v4.5.0内存分配与回收函数及其改进方案---pvPortMalloc()和vPortFree()

**来源:http://gliethttp.cublog.cn

[注:以下为FreeRTOS_v4.5.0在at91sam7s64处理器上的动态内存分配、释放函数(gliethtp)]
  FreeRTOS_v4.5.0一共提供3中动态内存分配和回收机制,可以通过具体需要任意选择其中一种,
第1种:只是提供堆数组上地址的线性分配,不提供内存回收;
第2种:提供堆数组上地址的线性分配和回收,但是回收过程不能将小内存合并成大内存
第3种:使用编译器自带的malloc和free函数
以上不论哪一种方式都是对堆数组xHeap进行操作,其定义如下:
static struct xRTOS_HEAP
{
//ulDummy仅仅用来告诉编译器分配的configTOTAL_HEAP_SIZE个字节连续空间的起始地址是4字节对齐的
    unsigned portLONG ulDummy;
    unsigned portCHAR ucHeap[ configTOTAL_HEAP_SIZE ];
} xHeap;
//------------------------------------------------------------------------------
<1.1>第1种内存动态分配
void *pvPortMalloc( size_t xWantedSize )
{
void *pvReturn = NULL; 
    #if portBYTE_ALIGNMENT != 1
        if( xWantedSize & heapBYTE_ALIGNMENT_MASK )
        {
        //字节对齐,at91sam7s64默认使用4字节对齐
        //所以下面折行程序,将xWantedSize扩展成4字节的倍数,
        //比如:xWantedSize=13,那么xWantedSize=13+(4-13%4)=13+3=16(gliethttp)
            xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & heapBYTE_ALIGNMENT_MASK ) );
        }
    #endif

    vTaskSuspendAll();//锁住调度器
    {
        if( ( ( xNextFreeByte + xWantedSize ) < configTOTAL_HEAP_SIZE ) &&
            ( ( xNextFreeByte + xWantedSize ) > xNextFreeByte )    )//防止申请数据空间超出4字节范围,过大溢出
        {
            //还有足够的空间,那么把申请到的空间首地址作为返回地址
            pvReturn = &( xHeap.ucHeap[ xNextFreeByte ] );//取得堆数组中xNextFreeByte偏移处的地址值
            xNextFreeByte += xWantedSize;//调整xNextFreeByte,为下一次分配空间做准备
        }    
    }
//调度器解锁,此间会有若干处理,详细细节可以参见《浅析FreeRTOS_v4.5.0延时机制---vTaskDelay()的实现》
//**地址:http://blog.chinaunix.net/u1/38994/showart_392389.html
    xTaskResumeAll();

    return pvReturn;//将申请到的内存块起始地址返回
}
<1.2>第1种动态内存回收
void vPortFree( void *pv )
{
    ( void ) pv;//第1种方式不提供内存回收机制
}
//------------------------------------------------------------------------------
<2.1>第2种内存动态分配
void *pvPortMalloc( size_t xWantedSize )
{
xBlockLink *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
static portBASE_TYPE xHeapHasBeenInitialised = pdFALSE;//标志是否首次使用pvPortMalloc()
void *pvReturn = NULL;

    vTaskSuspendAll();//锁住调度器
    {
        if( xHeapHasBeenInitialised == pdFALSE )
        {
            //第1次调用pvPortMalloc(),那么需要初始化一些关键量,
            //主要是在xStart和xEnd之间将xHeap.ucHeap插进去,下面是一个简单的单向链接图示
            //xStart->xHeap.ucHeap->xEnd->NULL
            prvHeapInit();
            xHeapHasBeenInitialised = pdTRUE;
        }
        
        if( xWantedSize > 0 )
        {
//对heapSTRUCT_SIZE的定义是这样的:
//static const unsigned portSHORT heapSTRUCT_SIZE    = 
//( sizeof( xBlockLink ) + ( sizeof( xBlockLink ) % portBYTE_ALIGNMENT ) );
//我觉得这句话讲不出什么道理来,可能和linux中page之间的hole空间类似,是为了防止越界之类
//在空间上做的额外申请,因为xBlockLink数据区作为管理本段内存所使用的关键域,
//如果因为数据操作越界而被修改,那是相当可怕的尤其在动态内存申请、释放比较频繁的时候
//最后计算效果是这样的:如果sizeof( xBlockLink )=7[注:当然编译器不会让它是这个值,
//怎么着也是2的倍数,这里只是做个极端的例子(gliethttp)],
//那么heapSTRUCT_SIZE = 7+3=10,不过实际在at91sam7s64上sizeof( xBlockLink )=8,所以heapSTRUCT_SIZE=8
            xWantedSize += heapSTRUCT_SIZE;

            if( xWantedSize & heapBYTE_ALIGNMENT_MASK )
            {
//和方法1中一样,将xWantedSize调整为4的整倍数
                xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & heapBYTE_ALIGNMENT_MASK ) );
            }
        }

        if( ( xWantedSize > 0 ) && ( xWantedSize < configTOTAL_HEAP_SIZE ) )
        {
//xWantedSize是一个合法的数值,那么下面将检测自己是否还有这么大的空闲块
//因为在内存释放时不提供小内存块合并整理机制,所以小内存块会随着时间和不同大小内存块
//动态申请、释放的频繁发生最终变的越来越多,直到没有大内存可以申请到,全部都是小内存为止.

//其实可以采用以前一篇**《一种轻巧的“内存动态分配管理机制”》中提到的动态内存分配和申请方式,
//**地址:http://blog.chinaunix.net/u1/38994/showart_351550.html
//可以将相邻的小块内存合并成大块内存,很好的解决了内存合并生成大内存块的问题.当然这种方法
//可能远远不如linux中的Buddy伙伴算法灵活,但是在嵌入式系统中应该能够满足用户对内存的基本需求了.
            pxPreviousBlock = &xStart;//从空闲内存链表头开始找
            pxBlock = xStart.pxNextFreeBlock;
            while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock ) )
            {
                //当前pxBlock空闲块的太小
                pxPreviousBlock = pxBlock;
                pxBlock = pxBlock->pxNextFreeBlock;//看看下一个空闲块是否比xWantedSize大
            }

            if( pxBlock != &xEnd )
            {
//没有到xEnd内存结尾处,所以确实是找到了一个大小比较适合空闲内存块
                pvReturn = ( void * ) ( ( ( unsigned portCHAR * ) pxPreviousBlock->pxNextFreeBlock ) + heapSTRUCT_SIZE );
//pxPreviousBlock->pxNextFreeBlock~pxPreviousBlock->pxNextFreeBlock+heapSTRUCT_SIZE之间的空间存放管理数据
//pxNextFreeBlock和xBlockSize
                pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;//把本pxBlock从空闲链表上摘下
                if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE )
                {
//#define heapMINIMUM_BLOCK_SIZE    ( ( size_t ) ( heapSTRUCT_SIZE * 2 ) )
//如果本pxBlock剩余的字节数大于heapMINIMUM_BLOCK_SIZE,即:还可以用来申请heapSTRUCT_SIZE个字节数据
                    //那么切割本pxBlock内存块(注意:xWantedSize是已经包含heapSTRUCT_SIZE的了[gliethttp])
                    //pxNewBlockLink为切割出来的新的空闲内存块首地址,已经4字节对齐
                    pxNewBlockLink = ( void * ) ( ( ( unsigned portCHAR * ) pxBlock ) + xWantedSize );
                    //计算新空闲内存块包括heapSTRUCT_SIZE控制域和数据域的总大小
                    pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;    
                    pxBlock->xBlockSize = xWantedSize;//将申请到的内存块的大小,填入内存块控制域
                    //把切割出来的内存块添加到空闲内存链表xStart~xEnd之间
                    //(注:xStart~xEnd是按空闲内存大小,以从小到达的顺序链起来的单向链表[gliethttp])
                    //也就是这样虽然看上去,FreeRTOS可以实现各种内存块的申请,但是因为切割出去的小内存块
                    //在内存释放回收的时候,并不能自动进行内存合并整理,也就是空闲内存块不能变大,
                    //最后只能导致小内存空闲块越来越多,大内存空闲块越来越少,直到不能申请到大内存块
                    //可以使用《一种轻巧的“内存动态分配管理机制”》中的方法很好的解决(gliethttp)
                    //**地址:http://blog.chinaunix.net/u1/38994/showart_351550.html
                    prvInsertBlockIntoFreeList( ( pxNewBlockLink ) );
                }
            }
        }
    }
//调度器解锁,此间会有若干处理,详细细节可以参见《浅析FreeRTOS_v4.5.0延时机制---vTaskDelay()的实现》
//**地址:http://blog.chinaunix.net/u1/38994/showart_392389.html
    xTaskResumeAll();

    return pvReturn;//返回申请到的内存数据区地址,pvReturn-heapSTRUCT_SIZE处存放了管理该pvReturn内存的控制数据
}
<2.2>第2种动态内存回收
void vPortFree( void *pv )
{
unsigned portCHAR *puc = ( unsigned portCHAR * ) pv;
xBlockLink *pxLink;
    if( pv )
    {
        puc -= heapSTRUCT_SIZE;//内存数据区的前heapSTRUCT_SIZE空间存放了管理本段内存数据区的控制数据
        pxLink = ( void * ) puc;
        vTaskSuspendAll();//锁住调度器
        {
//把释放出来的内存块添加到空闲内存链表xStart~xEnd之间
//(注:xStart~xEnd是按空闲内存大小,以从小到达的顺序链起来的单向链表[gliethttp])
//也就是这样虽然看上去,FreeRTOS可以实现各种内存块的申请,但是因为切割出去的小内存块
//在内存释放回收的时候,并不能自动进行内存合并整理,也就是空闲内存块不能变大,
//最后只能导致小内存空闲块越来越多,大内存空闲块越来越少,直到不能申请到大内存块
//可以使用《一种轻巧的“内存动态分配管理机制”》中的方法很好的解决(gliethttp)
//**地址:http://blog.chinaunix.net/u1/38994/showart_351550.html
            prvInsertBlockIntoFreeList( ( ( xBlockLink * ) pxLink ) );
        }
//调度器解锁,此间会有若干处理,详细细节可以参见《浅析FreeRTOS_v4.5.0延时机制---vTaskDelay()的实现》
//**地址:http://blog.chinaunix.net/u1/38994/showart_392389.html
        xTaskResumeAll();
    }
}
//------------------------------------------------------------------------------
<3.1>第3种内存动态分配
void *pvPortMalloc( size_t xWantedSize )
{
void *pvReturn;

    vTaskSuspendAll();
    {
        pvReturn = malloc( xWantedSize );//使用编译器自带的malloc函数
    }
    xTaskResumeAll();

    return pvReturn;
}
<3.2>第3种动态内存回收
void vPortFree( void *pv )
{
    if( pv )
    {
        vTaskSuspendAll();
        {
            free( pv );//使用编译器自带的free函数
        }
        xTaskResumeAll();
    }
}
//------------------------------------------------------------------------------
  综上可知,FreeRTOS_v4.5.0对内存的动态分配支持的并不好,内存动态分配很简单,分配出去就可以了,
但是在分配出去的内存的动态回收上,还是欠佳的,可以引入linux中的Buddy伙伴算法,就是可能麻烦一些,
对linux中的Buddy伙伴算法,前段时间我也研读过,
可以参看《浅析armlinux-Buddy(伙伴)算法-释放合并回收函数__free_pages_ok()》,
**地址:http://blog.chinaunix.net/u1/38994/showart_357790.html
我觉得在内存动态分配上最好的改进方式是采用《一种轻巧的“内存动态分配管理机制”》

**地址:http://blog.chinaunix.net/u1/38994/showart_351550.html中提到的那种内存动态分配与回收方式(gliethttp).

相关帖子

发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

4

主题

18

帖子

1

粉丝