F r e e R T O S FreeRTOS FreeRTOS从版本 V 9.0.0 V9.0.0 V9.0.0开始,内核对象所用的存储空间可以在编译时静态分配或在运行时动态分配,早期的版本不同时支持静态分配和动态分配,这里讲到的堆存储管理是和动态分配相关的。从版本 V 9.0.0 V9.0.0 V9.0.0开始,如果某个应用中所有 F r e e R T O S FreeRTOS FreeRTOS的内核对象所用到的存储空间都是在编译时分配好了(静态分配),那么该应用可以完全不使用堆存储管理相关的接口。静态分配的 A P I API API接口的名字都带有 s t a t i c static static关键字,动态分配的 A P I API API接口的名字不带有 s t a t i c static static关键字,下面我们以创建任务的接口函数来简单的说明以下。
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,const char * const pcName,const configSTACK_DEPTH_TYPE usStackDepth,void * const pvParameters,UBaseType_t uxPriority,TaskHandle_t * const pxCreatedTask )
上面的接口是用来创建任务的接口的动态版本,创建任务所需要的 R A M RAM RAM空间(任务控制块和任务栈等)在调用这个接口的时候动态分配,这里分配的内存空间是从 F r e e R T O S FreeRTOS FreeRTOS的堆内存空间里面分配的:
- 对于 h e a p _ 1 heap\_1 heap_1, h e a p _ 2 heap\_2 heap_2和 h e a p _ 4 heap\_4 heap_4分配方案,这里的堆内存空间在 F r e e R T O S FreeRTOS FreeRTOS中其实就是一个字节数组,数组的大小由宏 c o n f i g T O T A L _ H E A P _ S I Z E configTOTAL\_HEAP\_SIZE configTOTAL_HEAP_SIZE定义,如图1所示。如果宏 c o n f i g A P P L I C A T I O N _ A L L O C A T E D _ H E A P configAPPLICATION\_ALLOCATED\_HEAP configAPPLICATION_ALLOCATED_HEAP定义为0的话,这个数组由 F r e e R T O S FreeRTOS FreeRTOS默认定义,如果宏 c o n f i g A P P L I C A T I O N _ A L L O C A T E D _ H E A P configAPPLICATION\_ALLOCATED\_HEAP configAPPLICATION_ALLOCATED_HEAP定义为1的话,这个数组由用户定义,这里的好处是用户可以指定这个数组放在指定的存储空间地址。
- 对于 h e a p _ 3 heap\_3 heap_3分配方案,这里的堆内存空间由启动文件里面的堆内存参数指定,如图2所示。
- 对于 h e a p _ 5 heap\_5 heap_5分配方案,这里的堆内存空间可以由几段地址不连续的存储空间组合起来。
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,const char * const pcName, const uint32_t ulStackDepth,void * const pvParameters,UBaseType_t uxPriority,StackType_t * const puxStackBuffer,StaticTask_t * const pxTaskBuffer )
上面的接口是用来创建任务的接口的静态版本,创建任务所需要的 R A M RAM RAM空间(任务控制块和任务栈等)在调用这个接口之前就已经分配好了,已经分配好了的用于任务的堆栈空间的 R A M RAM RAM空间是参数 p u x S t a c k B u f f e r puxStackBuffer puxStackBuffer指向的存储空间,已经分配好了的用于任务的任务控制块的 R A M RAM RAM空间是参数 p x T a s k B u f f e r pxTaskBuffer pxTaskBuffer指向的存储空间。
动态分配和静态分配那个好取决于具体应用以及程序开发者的喜好,各有优缺点,两种方法可以用于同一个 F r e e R T O S FreeRTOS FreeRTOS应用。
动态分配具有极大简便性的优势以及减少应用所需 R A M RAM RAM空间的潜力,这是因为:
- 动态分配接口函数的参数更少
- R A M RAM RAM空间的分配在调用相应接口函数的时候自动发生而不用程序开发者关心。
- 当之前创建的内核对象不再使用的时候,它所占用的 R A M RAM RAM空间会被自动释放且释放的空间可以被其它内核对象再次使用,这样也潜在的减少了应用所占用 R A M RAM RAM空间的大小。
- 动态分配接口函数会返回堆存储空间的使用情况,我们可以根据这些信息来优化堆存储空间的大小。
- 堆存储空间的分配方案有多种选择( h e a p _ 1 heap\_1 heap_1, h e a p _ 2 heap\_2 heap_2, h e a p _ 3 heap\_3 heap_3, h e a p _ 4 heap\_4 heap_4和 h e a p _ 5 heap\_5 heap_5),也可以自己实现堆存储空间的分配方案。
如果要使用以下动态分配的接口,必须将宏 c o n f i g S U P P O R T _ D Y N A M I C _ A L L O C A T I O N configSUPPORT\_DYNAMIC\_ALLOCATION configSUPPORT_DYNAMIC_ALLOCATION不定义或者定义为1:
- xTaskCreate()
- xQueueCreate()
- xTimerCreate()
- xEventGroupCreate()
- xSemaphoreCreateBinary()
- xSemaphoreCreateCounting()
- xSemaphoreCreateMutex()
- xSemaphoreCreateRecursiveMutex()
静态分配具有给程序开发者提供更多可控行这一有点:
- 可以指定创建的内核对象的位置
- 所需的最大 R A M RAM RAM空间在 l i n k link link时就可以确定,而不是要等到运行时。
- 因为所需的 R A M RAM RAM空间是事先分配好的,因此不存在存储空间分配失败的问题。
- 允许 F r e e R T O S FreeRTOS FreeRTOS用在不允许动态分配的应用中。
如果要使用以下静态分配的接口,必须将宏 c o n f i g S U P P O R T _ S T A T I C _ A L L O C A T I O N configSUPPORT\_STATIC\_ALLOCATION configSUPPORT_STATIC_ALLOCATION为1:
- xTaskCreateStatic()
- xQueueCreateStatic()
- xTimerCreateStatic()
- xEventGroupCreateStatic()
- xSemaphoreCreateBinaryStatic()
- xSemaphoreCreateCountingStatic()
- xSemaphoreCreateMutexStatic()
- xSemaphoreCreateRecursiveMutexStatic()
动态分配方案是 C C C语言编程的概念,并不是专门针对多任务和 F r e e R T O S FreeRTOS FreeRTOS的概念。对于我们这一小节讲到的堆存储管理,相应的内核对象用到的 R A M RAM RAM空间是在堆内存空间里面动态分配的,但是一般的编译器提供的动态分配方案并不是一直都适用于实时应用。 C C C语言的标准库里面是有存储空间分配( m a l l o c malloc malloc)和释放( f r e e free free)的接口的,但是 C C C语言的标准库里面的这些和存储空间分配相关的接口在下面的情况下可能不适用:
- 某些小型的嵌入式系统可能没有这些接口
- 这些接口的实现的代码规模可能有些大,需要占据较多的存储空间
- 很多接口不具有线程安全的特性
- 它们具有不确定性属性:每次调用所花费的时候可能不一样
- 这些接口的调用会产生存储空间的碎片化问题
目前最新版本的 F r e e R T O S FreeRTOS FreeRTOS将堆存储管理的代码放到 p o r t a b l e l a y e r portable\quad layer portablelayer 而不是内核代码层,这是因为不同的嵌入式系统具有不同的动态存储分配方案和时序要求,单一的动态存储分配算法不能覆盖所有。目前 F r e e R T O S FreeRTOS FreeRTOS本身提供了5种动态存储分配方案,分别对应 h e a p _ 1. c heap\_1.c heap_1.c, h e a p _ 2. c heap\_2.c heap_2.c, h e a p _ 3. c heap\_3.c heap_3.c, h e a p _ 4. c heap\_4.c heap_4.c和 h e a p _ 5 heap\_5 heap_5这五个文件,位于图3的源码目录(当然程序开发者也可以自己定义动态存储分配方案),分配存储空间的时候调用接口 p v P o r t M a l l o c pvPortMalloc pvPortMalloc,释放存储空间的时候调用接口 v P o r t F r e e ( ) vPortFree() vPortFree()。
h e a p _ 1. c heap\_1.c heap_1.c的分配方案是5种分配方案中最简单的一个,这种分配方案没有具体实现接口 v P o r t F r e e ( ) vPortFree() vPortFree(),也就是分配的存储空间不允许释放。这种方案:
- 可以用于从不删除内核对象的应用(很多小型和深度的嵌入式系统创建的内核对象从系统启动开始就一直存在,从不删除)
- 这种分配方案的行为是确定的,相关接口的调用时间是确定的,每次调用所花费的时间相同且不会产生存储空间碎片化问题。
- 这种分配方案非常简单,所分配的空间来至于一个静态分配的数组,如图1所示。这意味着它非常适合于不允许真正的动态分配的应用。
h e a p _ 1. c heap\_1.c heap_1.c的分配方案具体也是很简单的,当需要存储空间的时候就从图1的数组中取指定大小的存储空间(当然这里有对请求的地址空间的大小做对齐操作,假设是8个字节对齐且此时要求分配的字节数是11,那么实际分配的字节数是16)。不仅对请求的存储空间的大小有对齐操作,对每次分配的存储空间的起始地址也有对齐的操作(假设是8个字节对齐的话,如果准备从图1的数组定义的存储空间的地址 X X X开始分配,如果 X X X不是8个倍数,那么就应该从地址 X + a X+a X+a开始分配,这里 a > 0 a>0 a>0q且 a < 8 a<8 a<8)。从前面我们可以知道,如果宏 c o n f i g A P P L I C A T I O N _ A L L O C A T E D _ H E A P configAPPLICATION\_ALLOCATED\_HEAP configAPPLICATION_ALLOCATED_HEAP定义为0的话,图1中的数组由 F r e e R T O S FreeRTOS FreeRTOS默认定义且这个数组的起始地址由连接器默认指定,那么就有可能出现图1中数组的起始地址不是8的倍数的情况。假设现在图1中数组的起始地址为 0 x 20008003 0x20008003 0x20008003且准备分配11个字节的存储空间,但是最后实际是从地址 0 x 20008008 0x20008008 0x20008008开始分配16个字节的存储空间,如图4所示。接着如果还需要分配存储空间的话就从地址 0 x 20008018 0x20008018 0x20008018开始分配。如果第二次也是要分配11个字节的话,分配后的情况如图5所示。接着如果还需要分配存储空间的话就从地址 0 x 20008028 0x20008028 0x20008028开始分配。如果第3次要分配8个字节的话,分配后的情况如图6所示。
h e a p _ 2. c heap\_2.c heap_2.c的分配方案现在被看成是一种遗产,因为 h e a p _ 4. c heap\_4.c heap_4.c的分配方案更可取。 h e a p _ 2. c heap\_2.c heap_2.c的分配方案使用了一种最佳匹配算法,这种分配方案具体实现了接口 v P o r t F r e e ( ) vPortFree() vPortFree(),也就是分配的存储空间在不需要的时候可以释放以便其其被再次利用,但是这种方案没有对相邻的空闲存储块进行合并的机制,因此可能会产生存储空间的碎片化问题。和 h e a p _ 1. c heap\_1.c heap_1.c的分配方案一样,当需要存储空间的时候也是从图1的数组中取指定大小的存储空间。
h e a p _ 2. c heap\_2.c heap_2.c的分配方案:
- 这种分配方案的接口是不确定的(每次调用的时间可能不一样),但是还是比 C C C语言的标准库的效率要高。
- 如果内核对象的创建的顺序比较随机可能会导致存储空间的碎片化问题。
- 可以用于重复删除内核对象的应用中(有可能会产生存储空间的碎片化问题)。
- 如果分配的存储空间和释放的存储空间的大小是随机,那么这种场景不建议使用这种分配方案。
h e a p _ 2. c heap\_2.c heap_2.c的分配方案会将所有的空闲块用一个链表给记录下来,且链表中表示空闲块的节点按照空闲块的大小从小到大排序,当有存储空间分配请求的时候 h e a p _ 2. c heap\_2.c heap_2.c的分配方案使用的最佳匹配算法会从空闲链表的起点开始寻找空间链表,直到找到第一个比所请求分配的存储空间大的空闲块,这个空闲块就会分配给所请求的内核对象,如果这个空闲块的字节数大于请求分配的空间的字节数,那么这个空闲块的一部分会给所请求的空间,剩下的空间会被划分为另一个空闲块而插入到空闲块链表中。
对于 h e a p _ 2. c heap\_2.c heap_2.c分配方案,第一次在使用接口 p v P o r t M a l l o c pvPortMalloc pvPortMalloc分配存储空间的时候会对堆存储空间进行初始化(调用接口 p r v H e a p I n i t prvHeapInit prvHeapInit),其实就是初始化记录空闲块的链表,初始化完成时候如图7所示。这里和 h e a p _ 1. c heap\_1.c heap_1.c分配方案一样也会对存储空间的起始地址进行对齐操作,这里假设图1中的数组的起始地址为 0 x 20008003 0x20008003 0x20008003。这里要注意的是每一块空闲块开头的地址存储的是这个空闲块对应于在空闲链表中的节点,当第一次调用接口 p v P o r t M a l l o c pvPortMalloc pvPortMalloc分配存储空间的时候图1中的数组被当成一个空闲块。
这里和 h e a p _ 1. c heap\_1.c heap_1.c分配方案一样也会对请求的地址空间的大小做对齐操作,假设是8个字节对齐且此时要求分配的字节数是11,空闲块链表的节点占据的空间大小为4个字节,那么最后实际分配的字节数为24。这里的存储空间的分配算法的流程是首先从空闲链表的起点开始直到找到第一个比所请求分配的存储空间大于等于的空闲块,然后把这个空闲块分配给相应的内核对象,从前面我们知道每一个空闲块的最前面存储的是这个空闲块对应于在空闲链表中的节点,但是当这个空闲块分配给内核对象的时候,这个空闲块前面存储这个空闲块对应于在空闲链表中的节点的空间内核对象是没有使用的,这一点要注意。
如果当前准备分配出去的空闲块的空间除去将要分配给内核对象的空间之外还有一定的多余量(大于一定值),那么这个当前准备分配出去的空闲块会被分成两个空闲块,一部分分配给请求空间的内核对象,另一部分形成一个新的空闲块插入到空闲块链表中。假设此时要求分配的字节数是11,空闲块链表的节点占据的空间大小为4个字节,那么分配完成时候的情况如图8所示。注意这里分配给内核对象的存储空间的实际起始地址是 0 x 20080010 0x20080010 0x20080010,也就是说内核对象并没有使用这个24字节的空闲块的前8个字节。
空闲块链表的节点的结构体如下所示,这里需要注意的是元素 x B l o c k S i z e xBlockSize xBlockSize的最高位如果是1的话就表明这个空闲块已经被分配出去了,如果是0的话就表明这个空闲块还没有被分配出去。
/* Define the linked list structure. This is used to link free blocks in order of their size. */
typedef struct A_BLOCK_LINK
{struct A_BLOCK_LINK * pxNextFreeBlock; /*<< The next free block in the list. */size_t xBlockSize; /*<< The size of the free block. */
} BlockLink_t;
对于 h e a p _ 2. c heap\_2.c heap_2.c分配方案,它的存储空间的释放流程是:在图8中我们知道尽管第一次分配给内核对象的空闲块的实际大小为24字节,从地址 0 x 20080008 0x20080008 0x20080008开始,但是实际上内核对象使用的只有16字节,从地址 0 x 20080010 0x20080010 0x20080010开始。如果要释放这个分配的存储空间的话,存储空间的释放接口的参数是地址 0 x 20080010 0x20080010 0x20080010,这里要注意。存储空间的释放接口首先将地址 0 x 20080010 0x20080010 0x20080010后退空闲块链表的节点的结构体大小个字节(经过了对齐处理),这样会变成 0 x 20080008 0x20080008 0x20080008,然后将存储在从地址 0 x 20080008 0x20080008 0x20080008开始的空闲块链表的节点的结构体的元素 x B l o c k S i z e xBlockSize xBlockSize的最高位置0,表明这个空闲块没有被分配。最后就会将这个存储在从地址 0 x 20080008 0x20080008 0x20080008开始的空闲块链表的节点插入到空闲块链表中。图8中分配的那个空闲块释放之后如图9所示。此时整个数组变成了两个待分配的空闲块。
h e a p _ 3. c heap\_3.c heap_3.c的分配方案就没有上面好说的,它只是通过调用 C C C语言标准库中的 m a l l o c malloc malloc和 f r e e free free接口来实现存储空间的分配,只不过为了线程安全性在调用 C C C语言标准库中的 m a l l o c malloc malloc和 f r e e free free接口的时候会暂停 F r e e R T O S FreeRTOS FreeRTOS的调度器,在调用结束之后再恢复调度器。
h e a p _ 4. c heap\_4.c heap_4.c的分配方案和 h e a p _ 2. c heap\_2.c heap_2.c的分配方案基本差不多,但是 h e a p _ 4. c heap\_4.c heap_4.c的分配方案对于临近的空闲块会进行合并的操作,将两个小的空闲块合并成了一个大的空闲块,这样可以尽量减少存储空间的碎片化。 h e a p _ 4. c heap\_4.c heap_4.c的分配方案也有一个空闲块的链表来管理空闲块,但是这个链表中的空闲块并不是按照空闲块的大小从小到大排序的而是按照空闲块在图1中的数组的地址进行排序的,地址低的靠前,这样做是为了配合 h e a p _ 4. c heap\_4.c heap_4.c的分配方案的空闲块的合并操作。 h e a p _ 2. c heap\_2.c heap_2.c的分配方案使用了一种最佳匹配算法,它从空闲块链表里面找到的空闲块是所有空闲块中大于等于等于所请求的空间的空闲块中最小的空闲块,也就是所有空闲块中大于等于等于所请求的空间的空闲块中最接近所请求空间的空闲块。 h e a p _ 4. c heap\_4.c heap_4.c的分配方案使用了一种第一个匹配算法,它所找到的空闲块有可能并不是所有空闲块中大于等于等于所请求的空间的空闲块中最接近所请求空间的空闲块,因为这里管理空闲块的空闲链表里面的节点并不是按照空闲块的大小进行排序的。假设如图10所示的空间有3个空闲块,按地址排序大小分别是 5 b y t e s 5\quad bytes 5bytes, 40 b y t e s 40\quad bytes 40bytes和 15 b y t e s 15\quad bytes 15bytes,如果现在请求分配 15 b y t e s 15\quad bytes 15bytes的空间,那么按照 h e a p _ 4. c heap\_4.c heap_4.c的分配方案此时会从 40 b y t e s 40\quad bytes 40bytes的空闲块中去分配。
h e a p _ 4. c heap\_4.c heap_4.c的分配方案第一次在使用接口 p v P o r t M a l l o c pvPortMalloc pvPortMalloc分配存储空间的时候也会对堆存储空间进行初始化(调用接口 p r v H e a p I n i t prvHeapInit prvHeapInit),其实就是初始化记录空闲块的链表,初始化完成时候如图11所示。这里也会对存储空间的起始地址进行对齐操作,这里假设图1中的数组的起始地址为 0 x 20008003 0x20008003 0x20008003,对齐之后会舍弃几个字节数据,整个堆存储空间将会从地址 0 x 20008008 0x20008008 0x20008008开始。这里要注意的是每一块空闲块开头的地址存储的是这个空闲块对应于在空闲链表中的节点,当第一次调用接口 p v P o r t M a l l o c pvPortMalloc pvPortMalloc分配存储空间的时候图1中的数组被当成一个空闲块。这里和 h e a p _ 2. c heap\_2.c heap_2.c的分配方案还有一点不同的是不像 h e a p _ 2. c heap\_2.c heap_2.c的分配方案中有单独的空闲块链表开头节点 x S t a r t xStart xStart和空闲块链表结尾节点 x E n d xEnd xEnd, h e a p _ 4. c heap\_4.c heap_4.c的分配方案有单独的空闲块链表开头节点 x S t a r t xStart xStart,但是没有空闲块链表结尾节点 x E n d xEnd xEnd,空闲块链表结尾节点直接放在图1中的数组所占的存储空间的最后。
h e a p _ 4. c heap\_4.c heap_4.c分配方案一样也会对请求的地址空间的大小做对齐操作,假设是8个字节对齐且此时要求分配的字节数是11,空闲块链表的节点占据的空间大小为4个字节,那么最后实际分配的字节数为24。这里和 h e a p _ 2. c heap\_2.c heap_2.c分配方案一样,空闲块链表的节点的元素 x B l o c k S i z e xBlockSize xBlockSize的最高位如果是1的话就表明这个空闲块已经被分配出去了,如果是0的话就表明这个空闲块还没有被分配出去。这里的存储空间的分配算法的流程是首先从空闲链表的起点开始直到找到第一个比所请求分配的存储空间大于等于的空闲块,然后把这个空闲块分配给相应的内核对象,从前面我们知道每一个空闲块的最前面存储的是这个空闲块对应于在空闲链表中的节点,但是当这个空闲块分配给内核对象的时候,这个空闲块前面存储这个空闲块对应于在空闲链表中的节点的空间内核对象是没有使用的,这一点要注意。
如果当前准备分配出去的空闲块的空间除去将要分配给内核对象的空间之外还有一定的多余量(大于一定值),那么这个当前准备分配出去的空闲块会被分成两个空闲块,一部分分配给请求空间的内核对象,另一部分形成一个新的空闲块插入到空闲块链表中(如果即将插入到空闲块链表中的空闲块在地址上和前面或后面的空闲块在地址上是连续的,那么就会进行合并的操作来形成更大的空闲块)。假设此时要求分配的字节数是11,空闲块链表的节点占据的空间大小为4个字节,那么分配完成时候的情况和图8基本一样。注意这里分配给内核对象的存储空间的实际起始地址是 0 x 20080010 0x20080010 0x20080010,也就是说内核对象并没有使用这个24字节的空闲块的前8个字节。
对于 h e a p _ 4. c heap\_4.c heap_4.c分配方案,它的存储空间的释放流程是:从前面我们知道尽管第一次分配给内核对象的空闲块的实际大小为24字节,从地址 0 x 20080008 0x20080008 0x20080008开始,但是实际上内核对象使用的只有16字节,从地址 0 x 20080010 0x20080010 0x20080010开始。如果要释放这个分配的存储空间的话,存储空间的释放接口的参数是地址 0 x 20080010 0x20080010 0x20080010,这里要注意。存储空间的释放接口首先将地址 0 x 20080010 0x20080010 0x20080010后退空闲块链表的节点的结构体大小个字节(经过了对齐处理),这样会变成 0 x 20080008 0x20080008 0x20080008,然后将存储在从地址 0 x 20080008 0x20080008 0x20080008开始的空闲块链表的节点的结构体的元素 x B l o c k S i z e xBlockSize xBlockSize的最高位置0,表明这个空闲块没有被分配。最后就会将这个存储在从地址 0 x 20080008 0x20080008 0x20080008开始的空闲块链表的节点插入到空闲块链表中,这里和 h e a p _ 2. c heap\_2.c heap_2.c分配方案不同的是,如果遇到有地址连续的空闲块会进行合并的操作。
对于 h e a p _ 5. c heap\_5.c heap_5.c分配方案,它和 h e a p _ 4. c heap\_4.c heap_4.c分配方案的分配存储空间和释放存储空间的算法是一样的,最大区别是 h e a p _ 4. c heap\_4.c heap_4.c分配方案中用来分配的存储空间是一段地址连续的存储区域(图1中定义的数组),但是在 h e a p _ 5. c heap\_5.c heap_5.c分配方案中,用来分配的存储空间可以是多段地址不连续存储空间块。在 F r e e R T O S FreeRTOS FreeRTOS提供的5种存储空间分配方案中, h e a p _ 5. c heap\_5.c heap_5.c分配方案是唯一一个需要在任何接口函数调用之前必须先显式的调用接口 v P o r t D e f i n e H e a p R e g i o n s vPortDefineHeapRegions vPortDefineHeapRegions的分配方案。如果 h e a p _ 5. c heap\_5.c heap_5.c分配方案确定使用多块地址不连续的存储空间来作为用来分配的存储空间的话,接口 v P o r t D e f i n e H e a p R e g i o n s vPortDefineHeapRegions vPortDefineHeapRegions用来指定每一块存储空间的起始地址和大小,每一块存储区域的开始地址和大小用以下的结构体来指定,以下结构体的数组作为接口 v P o r t D e f i n e H e a p R e g i o n s vPortDefineHeapRegions vPortDefineHeapRegions的参数,在这个数组里面的结构体按照存储块的起始地址进行排序,起始地址最小的放在数组的索引为0的位置,起始地址最大的放在数组索引最大的位置且这个数组最后必须以另外一个结构体来结束,这个结构体的元素 p u c S t a r t A d d r e s s pucStartAddress pucStartAddress的值为 N U L L NULL NULL。
/* Used by heap_5.c to define the start address and size of each memory region* that together comprise the total FreeRTOS heap space. */
typedef struct HeapRegion
{uint8_t * pucStartAddress;size_t xSizeInBytes;
} HeapRegion_t;
假设现在某款 S T M 32 STM32 STM32的芯片有图12所示的三块不连续的 R A M RAM RAM空间, R A M 1 RAM1 RAM1的前一部分准备给工程里面的其它变量或堆栈使用,剩下的那一部分以及 R A M 2 RAM2 RAM2和 R A M 3 RAM3 RAM3空间准备给 F r e e R T O S FreeRTOS FreeRTOS作为内核对象的分配空间,那么我们可以这样定义接口 v P o r t D e f i n e H e a p R e g i o n s vPortDefineHeapRegions vPortDefineHeapRegions的参数,如下所示:
/* Define the start address and size of the three RAM regions. */
#define RAM1_START_ADDRESS ( ( uint8_t * ) 0x0001xxxx )
#define RAM1_SIZE ( x * 1024 )
#define RAM2_START_ADDRESS ( ( uint8_t * ) 0x00020000 )
#define RAM2_SIZE ( 32 * 1024 )
#define RAM3_START_ADDRESS ( ( uint8_t * ) 0x00030000 )
#define RAM3_SIZE ( 32 * 1024 )
/* Create an array of HeapRegion_t definitions, with an index for each of the three RAM regions, and terminating the array with a NULL address. The HeapRegion_t structures must appear in start address order, with the structure that contains the lowest start address appearing first. */
const HeapRegion_t xHeapRegions[] =
{{ RAM1_START_ADDRESS, RAM1_SIZE },{ RAM2_START_ADDRESS, RAM2_SIZE },{ RAM3_START_ADDRESS, RAM3_SIZE },{ NULL, 0 } /* Marks the end of the array. */
};
int main( void )
{/* Initialize heap_5. */vPortDefineHeapRegions( xHeapRegions );/* Add application code here. */
}
在上面的代码中 R A M 1 RAM1 RAM1的起始地址定义为 0 x 0001 x x x x 0x0001xxxx 0x0001xxxx,这是因为 R A M 1 RAM1 RAM1的前一部分是给工程里面的其它变量或堆栈使用的,剩下的那一部分才是给 F r e e R T O S FreeRTOS FreeRTOS作为内核对象的分配空间的,至于多少分配给工程里面的其它变量或堆栈使用,多少分配给 F r e e R T O S FreeRTOS FreeRTOS作为内核对象的分配空间,这个是不确定的,会随着应用需求的改变,因此当应用需求改变的时候我们可能也不得不去改变上面宏定义 R A M 1 _ S T A R T _ A D D R E S S RAM1\_START\_ADDRESS RAM1_START_ADDRESS的值,不然当未来某种应用中工程里面的其它变量或堆栈使用的空间较大而超过地址 0 x 0001 x x x x 0x0001xxxx 0x0001xxxx的时候就会造成工程里面的其它变量或堆栈使用的空间和 F r e e R T O S FreeRTOS FreeRTOS作为内核对象的分配空间重叠,这样可能造成程序崩溃。因此这不是一种好的方法。
在下面的代码中将 R A M 1 RAM1 RAM1中分配给 F r e e R T O S FreeRTOS FreeRTOS作为内核对象的分配空间的存储空间定义为一个数组,这样 R A M 1 RAM1 RAM1中分配给 F r e e R T O S FreeRTOS FreeRTOS作为内核对象的分配空间的存储空间的起始地址每次都是由 L I N K E R LINKER LINKER确定,而不是每次由程序开发者指定,这样就就解决了前面的方案中的不便利性,分配给 F r e e R T O S FreeRTOS FreeRTOS作为内核对象的分配空间的存储空间的大小由宏 R A M 1 _ H E A P _ S I Z E RAM1\_HEAP\_SIZE RAM1_HEAP_SIZE指定。因为就算此时分配给工程里面的其它变量或堆栈使用的空间加上分配给 F r e e R T O S FreeRTOS FreeRTOS作为内核对象的分配空间超过了 R A M 1 RAM1 RAM1中的大小,在编译的时候会报错的,可以有一个提醒,但是前面的方案中是不会有提醒的,因为工程里面的其它变量或堆栈使用的空间和 F r e e R T O S FreeRTOS FreeRTOS作为内核对象的分配空间会重叠。
/* Define the start address and size of the two RAM regions not used by the linker. */
#define RAM2_START_ADDRESS ( ( uint8_t * ) 0x00020000 )
#define RAM2_SIZE ( 32 * 1024 )
#define RAM3_START_ADDRESS ( ( uint8_t * ) 0x00030000 )
#define RAM3_SIZE ( 32 * 1024 )
/* Declare an array that will be part of the heap used by heap_5. The array will be placed in RAM1 by the linker. */
#define RAM1_HEAP_SIZE ( 30 * 1024 )
static uint8_t ucHeap[ RAM1_HEAP_SIZE ];
/* Create an array of HeapRegion_t definitions. The first entry only describes the ucHeap array, so heap_5 will only use the part of RAM1 that contains the ucHeap array. The HeapRegion_t structures must still appear in start address order, with the structure that contains the lowest start address appearing first. */
const HeapRegion_t xHeapRegions[] =
{{ ucHeap, RAM1_HEAP_SIZE },{ RAM2_START_ADDRESS, RAM2_SIZE },{ RAM3_START_ADDRESS, RAM3_SIZE },{ NULL, 0 } /* Marks the end of the array. */
};
int main( void )
{/* Initialize heap_5. */vPortDefineHeapRegions( xHeapRegions );/* Add application code here. */
}