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模型中,堆的模型如下图所示:

幸运并非没有恐惧和烦恼;厄运并非没有安慰与希望。

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

相关文章:

你感兴趣的文章:

标签云: