当前位置:首页 > > 嵌入式软件实战派
[导读]这次我成功将妹子约到了公司附近的咖啡馆,继续探讨RTOS的heap的技术特点。当我把准备好的数据和动图展示在她面前,她立马激动起来了。

这次我成功将妹子约到了公司附近的咖啡馆,继续探讨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_xfree_x分别调用了pvPortMallocvPortFree,并打印一些信息。
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的编译运行问题一个个搞定了。
Note:我把整个工程打包好了,关注公众号“嵌入式软件实战派”,在后台回复“heap”即可获得下载链接。
通过输出的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
“所以,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: 0x407080Alloc : addr=0x00407080, size:0x01, remain:0x27F0Alloc : addr=0x00407088, size:0x02, remain:0x27E8Alloc : addr=0x00407090, size:0x04, remain:0x27E0Alloc : addr=0x00407098, size:0x08, remain:0x27D8Alloc : addr=0x004070A0, size:0x10, remain:0x27C8Alloc : addr=0x004070B0, size:0x20, remain:0x27A8Free : addr=0x00407080, remain:0x27A8Alloc : addr=0x004070D0, size:0x01, remain:0x27A0Free : addr=0x00407090, remain:0x27A0Alloc : addr=0x004070D8, size:0x02, remain:0x2798Free : addr=0x00407098, remain:0x2798Alloc : addr=0x004070E0, size:0x04, remain:0x2790Alloc : addr=0x004070E8, size:0x04, remain:0x2788Free : addr=0x004070A0, remain:0x2788Alloc : addr=0x004070F0, size:0x01, remain:0x2780Alloc : addr=0x004070F8, size:0x02, remain:0x2778Alloc : addr=0x00407100, size:0x04, remain:0x2770Alloc : addr=0x00407108, size:0x08, remain:0x2768Alloc : addr=0x00407110, size:0x10, remain:0x2758Alloc : addr=0x00407120, size:0x01, remain:0x2750Free : addr=0x004070F8, remain:0x2750Free : addr=0x00407100, remain:0x2750Free : addr=0x00407108, remain:0x2750Free : addr=0x00407110, remain:0x2750Alloc : addr=0x00407128, size:0x06, remain:0x2748Alloc : 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.

FreeRTOS
“那么,它是怎么跨区域的呢?”她突然好奇起来。
“给你看段代码吧。其实它多了一个函数。”
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().


本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: 驱动电源

在工业自动化蓬勃发展的当下,工业电机作为核心动力设备,其驱动电源的性能直接关系到整个系统的稳定性和可靠性。其中,反电动势抑制与过流保护是驱动电源设计中至关重要的两个环节,集成化方案的设计成为提升电机驱动性能的关键。

关键字: 工业电机 驱动电源

LED 驱动电源作为 LED 照明系统的 “心脏”,其稳定性直接决定了整个照明设备的使用寿命。然而,在实际应用中,LED 驱动电源易损坏的问题却十分常见,不仅增加了维护成本,还影响了用户体验。要解决这一问题,需从设计、生...

关键字: 驱动电源 照明系统 散热

根据LED驱动电源的公式,电感内电流波动大小和电感值成反比,输出纹波和输出电容值成反比。所以加大电感值和输出电容值可以减小纹波。

关键字: LED 设计 驱动电源

电动汽车(EV)作为新能源汽车的重要代表,正逐渐成为全球汽车产业的重要发展方向。电动汽车的核心技术之一是电机驱动控制系统,而绝缘栅双极型晶体管(IGBT)作为电机驱动系统中的关键元件,其性能直接影响到电动汽车的动力性能和...

关键字: 电动汽车 新能源 驱动电源

在现代城市建设中,街道及停车场照明作为基础设施的重要组成部分,其质量和效率直接关系到城市的公共安全、居民生活质量和能源利用效率。随着科技的进步,高亮度白光发光二极管(LED)因其独特的优势逐渐取代传统光源,成为大功率区域...

关键字: 发光二极管 驱动电源 LED

LED通用照明设计工程师会遇到许多挑战,如功率密度、功率因数校正(PFC)、空间受限和可靠性等。

关键字: LED 驱动电源 功率因数校正

在LED照明技术日益普及的今天,LED驱动电源的电磁干扰(EMI)问题成为了一个不可忽视的挑战。电磁干扰不仅会影响LED灯具的正常工作,还可能对周围电子设备造成不利影响,甚至引发系统故障。因此,采取有效的硬件措施来解决L...

关键字: LED照明技术 电磁干扰 驱动电源

开关电源具有效率高的特性,而且开关电源的变压器体积比串联稳压型电源的要小得多,电源电路比较整洁,整机重量也有所下降,所以,现在的LED驱动电源

关键字: LED 驱动电源 开关电源

LED驱动电源是把电源供应转换为特定的电压电流以驱动LED发光的电压转换器,通常情况下:LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: LED 隧道灯 驱动电源
关闭