FreeRTOS代码剖析之1:内存管理Heap

内存管理是一个操作系统的重要组成部分之一,所有应用程序都离不开操作系统的内存管理。因此,在剖析freertos的内核代码之前,前对freertos的内存管理进行研究。
现在以freertos8.0.1进行剖析研究。参考资料为《using the freertos real time kernel-a practical guide opened》。
heap_1.c的注释说明,heap_1.c只是简单地实现了pvportmalloc()这一个函数,这个堆的实现方案并不允许已分配的内存再次被释放。(the simplest possible implementation of pvportmalloc(). note that this implementation does not allow allocated memory to be freed again.)
/* allocate the memory for the heap. */
static uint8_t ucheap[ configtotal_heap_size ];
static size_t xnextfreebyte = ( size_t ) 0;
首先看到的是两个全局变量。第一个是ucheap,第二个是xnextfreebyte。根据名字的意思可以看出,ucheap就是freertos可以用的整个堆的空间数组,其大小是在freertosconfig.h中定义的常量configtotal_heap_size,默认是17*1024,即17kb;而xnextfreebyte,则是指向下一个还没被用上的内存堆所在的数组下标,由于一开始整个堆都没被用上,所以它的默认值为0。
接下来要分析的是void *pvportmalloc( size_t xwantedsize )这一个函数。这个函数是heap_1.c的重点。它的工作流程如下:
第一步:对齐处理;第二步:分配内存;第三步:勾子函数调用。
第一步的代码如下:
/* ensure that blocks are always aligned to the required number of bytes. */
#if portbyte_alignment != 1
if( xwantedsize & portbyte_alignment_mask )
{
/* byte alignment required. */
xwantedsize += ( portbyte_alignment - ( xwantedsize & portbyte_alignment_mask ) );
}
#endif
在说这一部分的时候,要先看看portmacro.h中的一个常量portbyte_alignment,这个常量指示字节对齐数,其默认值为8,即默认以8个字节进行内存对齐。第二个要看的是portable.h中的一个常量portbyte_alignment_mask,这个常量是根据portbyte_alignment的值进行定义的,其对应关系如下:
portbyte_alignment
portbyte_alignment_mask
8(表示以8个字节对齐)
0x0007
4(表示以4个字节对齐)
0x0003
2(表示以2个字节对齐)
0x0001
1(表示以1个字节对齐)
0x0000
备注:在移植的时候,可以根据硬件平台的对齐方式修改portbyte_alignment,这样可以避免内存空间的浪费。
第一步的工作主要是将用户所需要的内存空间大小进行对齐。如果是以1个字节对齐,则这一步可以跳过(条件编译)。条件编译内部,if( xwantedsize & portbyte_alignment_mask )主要是用来判断用户所需要的内存大小是否已对齐,例如,在默认情况下(以8个字节对齐),如果用户申请的内存大小为13个字节,经过和字节对齐掩码进行与操作后的结果为0x0005,即没有对齐;如果用户申请的内存大小为16个字节,经过和字节对齐掩码进行与操作后的结果为0x0000,即已经对齐。
字节对齐的方法在if语块里。可以发现用户申请内存大小和字节对齐掩码进行与操作后,其结果和需要补齐的字节数相加,刚好等于字节对齐掩码的值,因此只要用掩码值减去与操作的结果,就可以得到需要补齐的字节数,这样只要把补齐的字节数加到用户申请的内存大小就可以使其字节对齐。
第二步就是真正在堆中分配内存了。在分配内存一开始的时候,系统首先调用vtasksuspendall()将所有的任务都挂起,以防止上下文切换。这个函数在这里只是为了确保内存分配过程不被其它中断打断,具体的实现流程以后再慢慢分析,这里就不详细展开了。紧接着,系统要对这个堆进行对齐工作。这里的对齐和上面说的对齐不是一回事。这里说的对齐是因为freertos管理的堆是一个全局数组,并不能保证数组首地址按portbyte_alignment对齐。因此freertos对堆首地址做了这个对齐处理。要留意的是,这个对齐处理只做了一次。原因是对齐后的堆首地址是一个静态变量,初始值赋为null。而当这个变量为null时才进行对齐处理,对齐处理后这个变量就指向堆首地址,这样在下一次调用pvportmalloc()时就不会再进行对齐处理了。对齐处理的代码如下。
if( pucalignedheap == null )
{
/* ensure the heap starts on a correctly aligned boundary. */
pucalignedheap = ( uint8_t * ) ( ( ( portpointer_size_type ) &ucheap[ portbyte_alignment ] ) & ( ( portpointer_size_type ) ~portbyte_alignment_mask ) );
}
一开始看这段代码的时候我还是有点迷惑的,为什么要用&ucheap[ portbyte_alignment ]进行与运算面不是用&ucheap[ 0 ]呢?可以考虑以下的这种情况,假如堆数组地址为0x00000006,在默认情况下(portbyte_alignment=8)pucalignedheap的结果为0x00000000,但这个地址已经超出了堆数组的地址范围了,这样就容易修改内存其它地址上的值了。因此,用&ucheap[ portbyte_alignment ]进行运算是为了最后的运算结果还是在堆数组地址的范围内。
但是另一方面,freertos对堆数组进行地址对齐操作,这样的后果就是要是原本堆数组首地址没有对齐,则进行对齐操作后就会使堆大小改变了。因此,freertos对堆数组的大小进行重新定义。
/* a few bytes might be lost to byte aligning the heap start address. */
#define configadjusted_heap_size ( configtotal_heap_size - portbyte_alignment )
在heap_1模型中,堆的模型如下图所示:
由于分配出去的内存空间不需要回收,因此每一次分配空间的时候只需要按需要的内存大小在空闲空间上分割出来就可以了。分割时,首先要检查需要的内存大小有没有超出空闲空间的大小,还要检查假如分配完空间后,其末地址是否溢出。假如没有超出空闲空间大小,出没有发生内存溢出现象,才进行分配,记录新分配空间的首地址到pvreturn,并重新记录新的空闲空间的首地址经nextfreebyte。代码如下:
/* check there is enough room left for the allocation. */
if( ( ( xnextfreebyte + xwantedsize ) xnextfreebyte ) )/* check for overflow. */
{
/* return the next free byte then increment the index past this block. */
pvreturn = pucalignedheap + xnextfreebyte;
xnextfreebyte += xwantedsize;
}
tracemalloc( pvreturn, xwantedsize );
代码最后的tracemalloc( pvreturn, xwantedsize )是一个宏,用于输出内存分配的调试信息,这个宏定义在freertos.h中,默认为空,如果需要将这些调试信息输出到串口或其它东西,就可以修改这个宏将信息输出到所需要的地方。
到这里,分配内存的过程就几乎结束了,所以在第二步的最后就要调用xtaskresumeall()将所有挂起的任务重新恢复。
当然,并不是所有的内存分配过程都会成功的,当内存分配失败的时候,如果在freertos.h中有定义宏configuse_malloc_failed_hook=1,则会调用一个勾子函数vapplicationmallocfailedhook()。在这个勾子函数中,用户可以进行其它一些必要的操作,这里就不展开描述了。
最后的最后,就是返回新分配内存的首地址pvreturn。如果分配失败则pvreturn就为null。
到这里,整个pvportmalloc()的工作流程就结束了。
由于heap_1的模型是只分配不回收,因此对于vportfree()里的实现则是什么都不干。vportinitialiseblocks()则只是初始化xnextfreebyte而已。还有xportgetfreeheapsize()也只是用于返回剩余内存空间的大小而已,非常简单,也不用细讲了。
总结:这是我第一次写的技术笔记,原本是想先剖析lwip,然后再剖析freertos的。不过发现自己对tcp/ip的认识还不够深,对lwip的好多代码还看不懂,因此还是先从freertos开始吧。希望接下来我能够坚持,把freertos的整个代码剖析完毕。我想对freertos进行剖析,不仅要剖析它的代码是怎么写的,还要剖析它的代码为什么要这样写的。感觉这个还是挻有价值的,毕竟freertos是一个免费的嵌入式操作系统,要是剖析之后能够对它进行优化,则对以后做产品有一个很大的帮助。呃,就这样吧。
文章转载自:帆星的天空的博客

索尼发布电影摄影机ILME-FX6新版固件升级Ver.4.00
做技术不需要念博士?工程师你想被叫一辈子“王工”吗?
美光科技发布2020财年第二财季财报 疫情或带动DRAM平均售价
还没搜就刷到了?
光导纤维导光的基本原理
FreeRTOS代码剖析之1:内存管理Heap
《环球时报》对话倪祖根:企业发展要内外兼修,创业者应以身作则
EtherCAT总线快速入门
关于AI框架中图层IR的分析
“全大核魔法”炼成!天玑9300安兔兔跑分超205万顶破天花板了!
稳压管及其稳压特性
如何减少AWG的高压、高频信号生成中的失真
一款新型声光控LED节能灯系统的设计方案
2020年智能电视机大屏运营将有望迎来新的高点
亚信推出最新AxRobot EtherCAT七轴助力控制机器手臂解决方案
全液压转向器原理
使用钽电容时有什么注意事项
无人驾驶汽车未尘埃落地 飞行汽车已跃跃欲试
谷歌用1.7亿美元摆平YouTube非法收集儿童的个人信息
BMC模具制品打孔和毛边设计方案的介绍