目录
xPortGetMinimunEverFreeHeapSize
FreeRTOS
FreeRTOS目录结构
移植过程
在工程中创建freertos文件夹,在freertos文件夹中创建src文件夹、inc文件夹、port文件夹。
freertos/src | 存放源码 |
freertos/inc | 存放头文件 |
freertos/port | 存放移植平台的相关文件 |
复制内存管理文件:复制FreeRTOS/Source/portable/MemMang/heap_4.c文件到freertos/port文件夹。
文件 优点 缺点 heap_1.c 分配简单,时间确定 只分配、不回收 heap_2.c 动态分配,最佳匹配 产生碎片、时间不定 heap_3.c 调用标准库函数 速度慢、时间不定 heap_4.c 相邻空闲内存可合并 可解决碎片问题、时间不定 heap_5.c 在heap_4.c文件基础上支持分隔的内存块 可解决碎片问题、时间不定
复制移植相关文件:复制FreeRTOS/Source/portable/RVDS/ARM_CM3文件下的port.c文件、portmacro.h文件到freertos/port文件夹。
复制通用核心文件:复制FreeRTOS/Source文件夹下的croutine.c文件、event_groups.c文件、list.c文件、queue.c文件、task.c文件、timers.c文件到freertos/src文件夹。
复制源码头文件:复制FreeRTOS/Source/include下的所有文件到freertos/inc文件夹。
复制配置文件:复制FreeRTOS/Demo/CORTEX_STM32F103_Keil文件夹下的FreeRTOSConfig.h文件到freertos文件夹。
到此配置完成,在IDE里把添加的文件导到工程中。
需要额外修改:
在FreeRTOSConfig.h文件中末尾#endif前添加3个宏。
#define xPortPendSVHandler PendSV_Handler
#define xPortSVCHandler SVC_Handler
#define xPortSysTickHandler SysTick_Handler
这三个宏是中断服务函数的宏,在FreeRTOS里用到,并且FreeRTOS操作系统已经实现了这三个中断服务函数。
因此在stm32f10x_it.c文件中注释掉3个相关的中断服务函数。
创建任务
#include "freertos.h"
#include "task.h"
TaskHandler_t myTaskHandler;
void myTask(void *arg)
{
whiel(1)
{
vTaskDelay(500);
}
}
int main(void)
{
初始化工作
// 任务函数名称,任务名称,分配的任务堆栈大小,任务传递参数,任务优先级,任务句柄
xTaskCreate(myTask, "myTask", 512, NULL, 2, &myTaskHandler);
// 开启调度
vTaskStartScheduler();
while(1)
{}
}
FreeRTOS数据类型
每个移植的版本都含有自己的portmacro.h头文件,里面定义了2个数据类型:TickType_t、BaseType_t。
TickType_t:
FreeRTOS配置了一个周期性的时钟中断:Tick Interrupt,每发生一次中断,中断次数tick count累加,tick count变量的类型就是TickType_t。
TickType_t可以是16位或32位,具体是在FreeRTOSConfig.h文件中由configUSE_16_BIT_TICKS宏定义,宏条件成立就为uint16_t,否则为uint32_t。
对于32位架构,建议把TickType_t配置为uint32_t。
BaseType_t:
这是架构最高效的数据类型。
在32位架构,他就是uint32_t。在16位架构,他就是uint16_t。在8位架构,他就是uint8_t。
BaseType_t通常用作简单的返回值类型和逻辑值(比如pdTRUE/pdFALSE)。
FreeRTOS命名规范
变量名前缀 | 含义 |
c | char |
s | int16_t,short |
l | int32_t,long |
x | BaseType_t,其它非标准的类型:结构体、task handle、queue handle等 |
u | unsigned |
p | 指针 |
uc | uint8_t,unsigned char |
pc | char指针 |
函数名前缀 | 含义 |
vTaskPrioritySet | 返回值类型:void 在task.c中定义 |
xQueueReceive | 返回值类型:BaseType_t 在queue.c中定义 |
pvTimerGetTimerID | 返回值类型:pointer to void 在timer.c中定义 |
宏的前缀 | 含义(在哪个文件里定义) |
port(比如portMAX_DELAY) | portable.h或portmacro.h |
task(比如taskENTER_CRITICAL()) | task.h |
pd(比如pdTRUE) | projdefs.h |
config(比如configUSE_PREEMPTION) | FreeRTOSConfig.h |
err(比如errQUEUE_FULL) | projdefs.h |
通用宏 | 值 |
pdTRUE | 1 |
pdFALSE | 0 |
pdPASS | 1 |
pdFAIL | 0 |
内存管理
在c语言的库函数中,有malloc、free等函数,但是在FreeRTOS中,它们不适用,因为:
malloc、free等函数实现过于复杂、占据的代码空间太大,不适合用在资源紧缺的嵌入式系统中;
并非线程安全的(thread-safe),运行具有不确定性(每次调用malloc、free等函数时花费的时间可能都不相同);
内存碎片化;
使用不同的编译器时,需要进行复杂的配置,有时候难以调试。
堆(heap):一块空闲的内存,需要提供管理函数。
malloc:从堆中划出一块空间给程序使用。
free:用完后,再把它标记为空闲,供下次使用。
栈(stack):函数调用局部变量时保存在栈中,当前程序的环境也是保存在栈中。可以从堆中分配一块空间用作栈。
FreeRTOS中内存管理的接口函数为:pvPortMalloc、vPortFree,对应于C库的malloc、free。文件存在于FreeRTOS/Source/portable/MemMang。
Heap_1
只实现了pvPortMalloc,没有实现vPortFree。如果程序不需要删除内核对象,就可以使用。
实现最简单,没有碎片问题。在一些要求非常严格的系统里不允许使用动态内存,就可以使用heap_1。
实现原理很简单,首先定义一个大数组。
/* Allocate the memory for the heap. */ #if ( configAPPLICATION_ALLOCATED_HEAP == 1 ) /* The application writer has already defined the array used for the RTOS * heap - probably so it can be placed in a special segment or address. */ extern uint8_t ucHeap[ configTOTAL_HEAP_SIZE ]; #else static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ]; #endif /* configAPPLICATION_ALLOCATED_HEAP */
然后pvPortMalloc()函数调用时,从这个数组中分配空间。
FreeRTOS在创建任务时,需要2个内核对象:task control block(TCB)、stack。
使用heap_1时,内存分配过程如下:
Heap_2
Heap_2之所以还保留,只是为了兼容以前的代码。新设计中不再推荐使用Heap_2(但效率仍然远高于malloc、free)。建议使用Heap_4来代替Heap_2,更高效。
Heap_2和Heap_1一样也是在数组上分配内存,区别在于Heap_2使用最佳匹配算法(best fit)来分配内存并且支持vPortFree。
最佳匹配算法(best fit):假设heap有3块空闲内存:5字节、25字节、100字节。
pvPortMalloc想申请20字节,则需找出最小的、能满足pvPortMalloc的内存:25字节。把25字节划分为20字节、5字节。返回这20字节的地址给分配,剩下的5字节仍然是空闲的状态,留给后续的pvPortMalloc使用。
与Heap_4相比,Heap_2不会合并相邻的空闲内存,所以Heap_2会导致严重的碎片化问题。但是如果申请内存时大小总是相同的,Heap_2是没有碎片化的问题的,适合频繁地创建、删除任务,但是任务的栈大小都是相同的(创建任务时需要分配TCB和栈,TCB总是一样的)场景。
使用heap_2时,内存分配过程如下:
Heap_3
使用标准C库的malloc、free函数,所以堆大小由链接器的配置决定,配置项 configAPPLICATION_ALLOCATED_HEAP 不再起作用。
标准C库的malloc、free函数并非线程安全的。Heap_3中先暂停FreeRTOS的调度器,再去调用标准C库的malloc、free函数,使用这种方法实现了线程安全。
Heap_4
Heap_4和Heap_1、Heap_2一样也是使用大数组来分配内存。Heap_4使用首次适应算法(first fit)来分配内存,并且还会把相邻的空闲内存合并为一个更大的空闲内存,有助于较少内存的碎片问题。适用于频繁分配、释放不同大小的内存场景。Heap_4执行时间不确定,但效率高于标准库的malloc、free。
首次适应算法(first fit):假设heap有3块空闲内存:5字节、200字节、100字节。
pvPortMalloc想申请20字节,则需找出第一个能满足pvPortMalloc的内存:200字节。把它划分为20字节、180字节。返回这20字节的地址给分配,剩下的180字节仍然是空闲的状态,留给后续的pvPortMalloc使用。
使用heap_4时,内存分配过程如下:
Heap_5
Heap_5分配、释放内存的算法和Heap_4是一样的。区别是Heap_5并不局限于管理一个大数组,可以管理多个块、分隔开的内存。适用于内存地址不连续的场景。
既然内存是分隔开的,所以需要进行初始化来确定内存位置和大小。使用pvPortMalloc前必须使用vPortDefineHeapRegions指定内存块的信息;
// 把pxHeapRegions数组传给vPortDefineHeapRegions函数即可初始化Heap_5
void vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions );
// 怎么指定一块内存?使用如下结构体
typedef struct HeapRegion
{
uint8_t * pucStartAddress; // 起始地址
size_t xSizeInBytes; // 大小
} HeapRegion_t;
// 怎么指定多块内存?使用一个HeapRegion_t数组,在这个数组中,低地址在前、高地址在后。
HeapRegion_t xHeapRegions[] =
{
{ ( uint8_t * ) 0x80000000UL, 0x10000 }, // 起始地址0x80000000,大小0x10000
{ ( uint8_t * ) 0x90000000UL, 0xa0000 }, // 起始地址0x90000000,大小0xa0000
{ NULL, 0 } // 表示数组结束
};
Heap相关函数
pvPortMalloc/vPortFree
void * pvPortMalloc( size_t xWantedSize );
void vPortFree( void * pv );
分配、释放内存。如果分配内存不成功,返回NULL。
xPortGetFreeHeapSize
size_t xPortGetFreeHeapSize( void );
查看当前还有多少空闲内存,可以用来优化内存的使用情况,如当所有内核对象都分配好后,执行此函数返回2000,那么configTOTAL_HEAP_SIZE就可减小2000。
在heap_3中无法使用该函数。
xPortGetMinimunEverFreeHeapSize
size_t xPortGetMinimumEverFreeHeapSize( void );
返回程序运行过程中的空闲内存容量的最小值。
只有heap_4、heap_5可使用该函数。
malloc失败的钩子函数
void * pvPortMalloc( size_t xWantedSize )vPortDefineHeapRegions
{
......
#if ( configUSE_MALLOC_FAILED_HOOK == 1 )
{
if( pvReturn == NULL )
{
extern void vApplicationMallocFailedHook( void );
vApplicationMallocFailedHook();
}
}
#endif
return pvReturn;
}
所以如果想要使用这个钩子函数:
在FreeRTOSConfig.h文件中,把configUSE_MALLOC_FAILED_HOOK宏定义为1。
提供vApplicationMallocFailedHook函数。
pvPortMalloc失败时才会调用此函数。