一、内存管理
(1).概览
每当任务,队列或是信号量被创建时,内核需要进行动态内存分配。虽然可以调用标准的malloc()与free()库函数,但必须承担以下若干问题:
1. 这两个函数在小型嵌入式系统中可能不可用。
2. 这两个函数的具体实现可能会相对较大,会占用较多宝贵的代码空间。
3. 这两个函数通常不具备线程安全特性。
4. 这两个函数具有不确定性。每次调用时的时间开销都可能不同。
5. 这两个函数会产生内存碎片。 6. 这两个函数会使得链接器配置得复杂。
不同的嵌入式系统具有不同的内存配置和时间要求。所以单一的内存分配算法只可能适合部分应用程序。因此,FreeRTOS 将内存分配作为可移植层面(相对于基本的内核代码部分而言)。这使得不同的应用程序可以提供适合自身的具体实现。
当内核请求内存时,其调用pvPortMalloc()而不是直接调用malloc();当释放内存时,调用vPortFree()而不是直接调用 free()。pvPortMalloc()具有与 malloc()相同的函数原型;
vPortFree()也具有与free()相同的函数原型。
FreeRTOS 自带有三种pvPortMalloc()与vPortFree()实现范例,这三种方式都会在 本章描述。FreeRTOS的用户可以选用其中一种,也可以采用自己的内存管理方式。这三个范例对应三个源文件:heap_1.c,heap_2.c,heap_3.c——这三个文件都 放在目录FreeRTOS\Source\Portable\MemMang 中。早期版本的FreeRTOS所采用的 原始内存池和内存块分配方案已 经被移除了,因为定义内存块和内存池的大小需要较深入的努力和理解。在小型嵌入式系统中,通常是在启动调度器之前创建任务,队列和信号量。这种情 况表明,动态分配内存只会出现在应用程序真正开始执行实时功能之前,而且内存一旦分配就不会再释放。这就意味着选择内存分配方案时不必考虑一些复杂的因素,比如确 定性与内存碎片等,而只需要从性能上考虑,比如代码大小和简易性。
(2).内存分配方案范例
Heap_2.c
Heap_2.c 也是使用了一个由configTOTAL_HEAP_SIZE定义大小的简单数组。不同于heap_1的是,heap_2采用了一个最佳匹配算法来分配内存,并且支持内存释放。由于声明了一个静态数组,所以会让整个应用程序看起来耗费了许多内存——即使是在 数组没有进行任何实际分配之前。 最佳匹配算法保证pvPortMalloc()会使用最接近请求大小的空闲内存块。
比如,考 虑以下情形:
1. 堆空间中包含了三个空闲内存块,分别为5字节,25字节和100字节大小。
2. pvPortMalloc()被调用以请求分配20字节大小的内存空间。匹配请求字节数的最小空闲内存块是具有25字节大小的内存块——所以pvPortMalloc() 会将这个25字节块再分为一个20字节块和一个5字节块
3.然后返回一个指向20字 节块的指针。剩下的5字节块则保留下来,留待以后调用pvPortMalloc()时使用。 Heap_2.c 并不会把相邻的空闲块合并成一个更大的内存块,所以会产生内存碎片 ——如果分配和释放的总是相同大小的内存块,则内存碎片就不会成为一个问题。 Heap_2.c 适合用于那些重复创建与删除具有相同栈空间任务的应用程序。
A表示数组在创建了三个任务后的情形。数组的顶部还剩余一个大空闲块。
B表示数组在删除了一个任务后的情形。顶部的大空闲块保持不变,并多出了两个小的空闲块,分别是被删除任务的TCB和任务栈。
C表示数组在又创建了一个任务后的情形。创建一个任务会产生两次调用pvPortMalloc(),一次是分配 TCB,一次是分配任务栈(调用pvPortMalloc()发 生在xTaskCreate() API 函数内部)。 每个TCB都具有相同大小,所以最佳匹配算法可以确保之前被删除的任务占用的TCB空间被重新分配用作新任务的TCB空间。 新建任务的栈空间与之前被删除任务的栈空间大小相同,所以最佳匹配算法会保证之前被删除任务占用的栈空间会被重新分配用作新任务的栈空间。 数组顶部的大空闲块依然保持不变。 Heap_2.c 虽然不具备确定性,但是比大多数标准库实现的malloc()与free()更有效率。