FreeRTOS的Heap1~Heap5有什么区别

Posted 嵌入式软件实战派

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了FreeRTOS的Heap1~Heap5有什么区别相关的知识,希望对你有一定的参考价值。

这次我成功将妹子约到了公司附近的咖啡馆,继续探讨RTOS的heap的技术特点。

当我把准备好的数据和动图展示在她面前,她立马激动起来了。

仿佛我递出来的是一束花、钻戒,她惊讶不已,除了脸上少了几分娇羞……

事情上这样的,我得好好重头讲一讲。

1. 妹子的问题

妹子好久没有问我问题了,想着应该是可以自己独立干项目了吧。这也是好事,毕竟是我亲自带出来的徒弟。但我还是怀念她经常问我问题的日子……

有一天傍晚快下班的时候,我问她,项目上的事自己能hold得住吗?有问题随时可以找我哦。

她想了想,欲言又止。

还没等她说话,我接着说,是遇到什么问题了吗?

“其实是这样的,我是遇到了OS上的一些疑惑问题,但也不是很要紧。看你那么忙,不好意思打扰你……”

“哦……没事啊,什么问题呢?”

她说,自从上次我跟她分析了OS占用内存的问题(见《妹子告诉我她被欺负了》)后,她就对OS的内存分配有不少疑惑,很想将它搞个一清二楚。

这不,她现在的问题是,FreeRTOS里的Heap1~Heap5有什么区别?

心想:额……这次完犊子了,我还真没怎么研究过这几个Heap的区别。但是我不能在她面前说不行啊。

突然,心生一计,若有其事淡定地跟她说,这是个好问题,得好好探讨下。然后我顺手拿出手机看了看时间,问她,明天晚上有空么?

“有。”她回答很干脆。

这次,我得换个方式跟她讲解这个Heap,不能像以前那样了……

2. 熬夜研究heap

于是,我背了笔记本回家,开始钻研这个heap的用法,得用丰富的知识和强有力的技能征服妹子。

首先,我将这几个heap的c文件移植到PC运行环境,用自动化手段分析其使用情况。

我写了个main.c文件,分别与heap_1.c~heap_5.c进行编译。

主要思路是,向heap申请并释放内存,看看其地址占用空间,就一目了然了。

    addr1 = alloc_x(1);
    addr2 = alloc_x(2);
    addr4 = alloc_x(4);
    addr8 = alloc_x(8);
    addr16 = alloc_x(16);
    addr32 = alloc_x(32);
​
    free_x(addr1);
    addr1 = alloc_x(1);
    free_x(addr4);
    addr2 = alloc_x(2);
    free_x(addr8);
    addr4 = alloc_x(4);
    addr4 = alloc_x(4);
    free_x(addr16);
    addr1 = alloc_x(1);
    addr2 = alloc_x(2);
    addr4 = alloc_x(4);
    addr8 = alloc_x(8);
    addr16 = alloc_x(16);
    addr1 = alloc_x(1);
    free_x(addr2);
    free_x(addr4);
    free_x(addr8);
    free_x(addr16);
    addr6 = alloc_x(6);
    addr10 = alloc_x(10);

这里面的alloc_x和free_x分别调用了pvPortMalloc和vPortFree,并打印一些信息。

void* alloc_x(size_t size)

    void* addr = pvPortMalloc(size);
    size_t res = xPortGetFreeHeapSize();
    printf("Alloc : addr=0x%p, size:0x%02X, remain:0x%04X\\r\\n", addr, size, res);
    return addr;

​
void free_x(void* addr)

    vPortFree( addr);
    size_t res = xPortGetFreeHeapSize();
    printf("Free  : addr=0x%p, remain:0x%04X\\r\\n", addr, res);

总以为很顺利,谁知道,这heap依赖的头文件一层套一层……

一狠心,大刀阔斧屏蔽了头文件里面的内容,手动添加heap需要的依赖项。

好不容易,编译通过了,在搞heap_1.c这个的时候,在运行到vPortFree,就挂了!

查了下代码,发现heap_1原来是不允许free内存的。

void vPortFree( void * pv )

    /* Memory cannot be freed using this scheme.  See heap_2.c, heap_3.c and
     * heap_4.c for alternative implementations, and the memory management pages of
     * https://www.FreeRTOS.org for more information. */
    ( void ) pv;
​
    /* Force an assert as it is invalid to call this function. */
    configASSERT( pv == NULL );

因为它只实现了pvPortMalloc而没实现vPortFree,所以这个vPortFree是没用的,仅仅是个兼容性接口而已。

接着,把后面的heap_2~heap_5的编译运行问题一个个搞定了。

通过输出的log信息,我还细心地做了个内存分配动图。

搞完这一波,看了下时间,竟然是晚上12点了。

然后,我居然兴奋得睡不着,想着怎么跟她讲解这个技术问题。

第二天,突发奇想,约她在公司附近的咖啡馆!她居然爽快地答应了!!

3. 促膝长谈

晚上下班,我们一起到了咖啡厅,就像一对小……伙伴程序员,背着笔记本电脑。

跟异性来这种地方,我还有些不好意思。

为了避免尴尬,单刀直入,打开笔记本,把准备好的材料跟她一一讲解。

“师兄,我们不点个咖啡吗?”

“呵呵呵……其实……你喜欢和什么?”

……

她说晚上不喝咖啡,于是我给她点了一杯奶茶,然后迫不及待地跟她将这个heap的情况。

一开始,我用官方的文档解释给她说明了下总体的情况:

我问她,“你知道FreeRTOS为什么不用C库中的malloc()和free()函数来分配和释放内存吗?”

“因为,不安全。”

我听她这么肯定的说,想着她肯定是对RTOS有很深入的认识的。

是的,C库中的malloc()和free()函数:

1. they are not always available on embedded systems,

2. they take up valuable code space, 

3. they are not thread safe, and

4.  they are not deterministic (the amount of time taken to execute the function will differ from call to call)

“所以,FreeRTOS的几个heap是为了解决这几个问题的。”

她点了点头,然后低头喝了一口奶茶。

我看她那手捧奶茶的温柔,恰好点缀了窗外的夕阳……多想多停留在这一刻,但我怕她看到我在看她,接着说:

“那么,这几个heap有什么区别呢?总的来说是这样的。”

heap_1 - 最简单的实现形式,不支持Free内存; 

heap_2 - 允许内存Free,但不会合并free的内存块; 

heap_3 - 是malloc() 和free() 的抽象层,多加了线程安全措施; 

heap_4 - 合并free的块,避免碎片

heap_5 - 类似heap_4,增加了块内存段操作。

“嗯!”她轻声说。其实她已经看过了这些解释了,但是不是很理解,她想深入一点的,深入一点的学习。

“‘Talk is cheap’,那么,我们直接上代码吧。”

她呵呵大笑。笑的样子也特别可爱。

“我们就按照官方的解释,来验证下。我写了段测试代码,你看看这段log就知道了。”

Base Addr: 0x407080
Alloc : addr=0x00407080, size:0x01, remain:0x27F0
Alloc : addr=0x00407088, size:0x02, remain:0x27E8
Alloc : addr=0x00407090, size:0x04, remain:0x27E0
Alloc : addr=0x00407098, size:0x08, remain:0x27D8
Alloc : addr=0x004070A0, size:0x10, remain:0x27C8
Alloc : addr=0x004070B0, size:0x20, remain:0x27A8
Free  : addr=0x00407080, remain:0x27A8
Alloc : addr=0x004070D0, size:0x01, remain:0x27A0
Free  : addr=0x00407090, remain:0x27A0
Alloc : addr=0x004070D8, size:0x02, remain:0x2798
Free  : addr=0x00407098, remain:0x2798
Alloc : addr=0x004070E0, size:0x04, remain:0x2790
Alloc : addr=0x004070E8, size:0x04, remain:0x2788
Free  : addr=0x004070A0, remain:0x2788
Alloc : addr=0x004070F0, size:0x01, remain:0x2780
Alloc : addr=0x004070F8, size:0x02, remain:0x2778
Alloc : addr=0x00407100, size:0x04, remain:0x2770
Alloc : addr=0x00407108, size:0x08, remain:0x2768
Alloc : addr=0x00407110, size:0x10, remain:0x2758
Alloc : addr=0x00407120, size:0x01, remain:0x2750
Free  : addr=0x004070F8, remain:0x2750
Free  : addr=0x00407100, remain:0x2750
Free  : addr=0x00407108, remain:0x2750
Free  : addr=0x00407110, remain:0x2750
Alloc : addr=0x00407128, size:0x06, remain:0x2748
Alloc : addr=0x00407130, size:0x0A, remain:0x2738

她准备站起来,把头凑过来看,我把笔记本转过去给她。然后我继续解释说,“这个Free是没有意义的”。

“哦?”她似乎有些不相信,然后试图口算这些地址值。

“我再给个图你看看。”

“哇哦……这颜色是什么意思,代表申请的内存块吗?怎么还有这么多孔隙的?”她低声问道,似乎有点好奇,又有点不是很相信的样子。

“是的,要颜色的地方是表示内存申请占有的地方,至于孔隙嘛,我一会再跟你解释下。”我接着说,“歪着头怎么看呢!要不坐过来我这边,我还有一个动图。”

“厉害!这都可以做出来,走心了……”

“你仔细看,其实这个vPortFree执行的地方,内存是没有变化的。”

“真的哦,heap_1是不能释放内存的。”

突然,我觉得我很有成就感。

“那么,为什么会有这么多孔隙的呢?我们看看这段代码就清楚了。”

void * pvPortMalloc( size_t xWantedSize )

    void * pvReturn = NULL;
    static uint8_t * pucAlignedHeap = NULL;
​
    /* Ensure that blocks are always aligned. */
    #if ( portBYTE_ALIGNMENT != 1 )
        
            if( xWantedSize & portBYTE_ALIGNMENT_MASK )
            
                /* Byte alignment required. Check for overflow. */
                if ( (xWantedSize + ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) )) > xWantedSize )
                
                    xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
                
                else
                
                    xWantedSize = 0;
                
            
        
    #endif

“哦……原来最大多占了一个portBYTE_ALIGNMENT的空间。”她端详了一阵这个代码说。

“所以,官方有说,自从有了static的内存分配方法,就很少用这个heap_1了。”

“嗯!”她似乎很满足的样子,伸手把奶茶拿过来,接着喝了一口。

我也喝了一口咖啡,突然觉得这个咖啡不苦了。

她起身,准备要坐回去。

“等下,我这还有其他数据,给你看看heap_2的情况。”我说后,她慢慢坐了下来。

夕阳已慢慢落在了城市的高楼之中,道路上依然车水马龙,而安静的咖啡馆有种说不出来的温馨。

“那么heap_2有什么不一样呢?我们直接看图吧。”

“咦?怎么有更多的孔隙了?”

“是啊,我也纳闷,但看了下源码,确实会这样,其中有一段是这样的。”

        /* The wanted size must be increased so it can contain a BlockLink_t
         * structure in addition to the requested amount of bytes. */
        if( ( xWantedSize > 0 ) &&
            ( ( xWantedSize + heapSTRUCT_SIZE ) >  xWantedSize ) ) /* Overflow check */
        
            xWantedSize += heapSTRUCT_SIZE;
​
            /* Byte alignment required. Check for overflow. */
            if( ( xWantedSize + ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) ) )
                    > xWantedSize )
            
                xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
                configASSERT( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) == 0 );
            
            else
            
                xWantedSize = 0;
            
        

我接着解释说,“你看,用heap_2每次申请一段内存,都要多占一段heapSTRUCT_SIZE和一段portBYTE_ALIGNMENT 空间。”

“哦,怪不得,我们项目定义了32K的RAM都差不多用完了,我算了下实际的分配,还差很远,原来问题在这呢!”她似乎有种茅舍顿开的感觉。

“那么heap_3又是怎样的呢?”她主动问我要图片来看了。

“我用PC上位机软件模拟的,heap_3用的是标准库的malloc()和free(),似乎没啥规律,其实没什么参考价值的。”

“好吧……那heap_4呢?”她似乎有点失落的样子,而又“肆无忌惮”起来,直问我要图看。

我不慌不忙打开了这个图。

“好像跟heap_2一样的哦……”

“是的,很像。但是,有个很重要的区别,你看这最后一帧内容,特别是最后紫色的这块。”我特意将heap_2的和heap_4的拿出来对比了下。

“看起来似乎heap_4更省空间……哈哈!”看着这两个图的对比,她乐了起来,像个天真的孩子。

“虽然还是很多孔隙,但是free掉的空间是可以合并的,这个heap_4的好处就在这了。”

“是的,师兄你真厉害!”

“哈哈!”

“对了,还有heap_5的呢?”

“哦?跟heap_4的没啥区别哦……”这说话拉长的语调,越来越可爱了。

“效果看起来,确实是一样的,但是heap_5是可以跨不连续区域的。官方文档也是这么说的。”

This scheme uses the same first fit and memory coalescence algorithms as heap_4, and allows the heap to span multiple non adjacent (non-contiguous) memory regions.

“那么,它是怎么跨区域的呢?”她突然好奇起来。

“给你看段代码吧。其实它多了一个函数。”

void vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions )

“这个const HeapRegion_t * const pxHeapRegions就是定义不同区域段的。”

“怎么分段的呢?”

“那,我们看这个官方的例子。”

// HeapRegion_t xHeapRegions[] =
// 
//      ( uint8_t * ) 0x80000000UL, 0x10000 , << Defines a block of 0x10000 bytes starting at address 0x80000000
//      ( uint8_t * ) 0x90000000UL, 0xa0000 , << Defines a block of 0xa0000 bytes starting at address of 0x90000000
//      NULL, 0                 << Terminates the array.
// ;
// vPortDefineHeapRegions( xHeapRegions ); << Pass the array into vPortDefineHeapRegions().

“哦,原来是这样,我得试试才行。”

“嗯,你很好学的嘛,哈哈!”

她也呵呵笑了起来。

看了下时间,已经过去一个多小时了。我们两人突然想起了一件事——还没吃完饭……

“我们一起去吃饭吧。”我试着邀请她。

“好啊,我请你吧。”看起来,她很开心的样子。

我心想,原来这么好约的么,哈哈哈!

“我还有个问题想请教下师兄。”

“嗯?什么问题?”

“这些动图,你是怎么做出来的,应该花了不少时间吧。”

“还好吧,有空我教你啊!”

“好啊。”

……

Note:关注公众号,在后台回复“heap”即可获得“heap测试程序工程以及动图文件下载链接。

以上是关于FreeRTOS的Heap1~Heap5有什么区别的主要内容,如果未能解决你的问题,请参考以下文章

FreeRTOS的Heap1~Heap5有什么区别

轻量级操作系统FreeRTOS的内存管理机制

轻量级操作系统FreeRTOS的内存管理机制

轻量级操作系统FreeRTOS的内存管理机制

freeRTOS与裸机程序相比有什么区别??

FreeRTOS学习目录