FreeRTOS-FreeRTOS概述

本文详细介绍了FreeRTOS的移植过程,包括文件组织、内存管理模块的选择与使用,以及如何创建任务。特别关注了Heap_1至Heap_5的不同内存管理策略,还讨论了与C库malloc和free的差异,以及malloc失败钩子函数的配置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

FreeRTOS

FreeRTOS目录结构

移植过程

创建任务

FreeRTOS数据类型

FreeRTOS命名规范

内存管理

Heap_1

Heap_2

Heap_3

Heap_4

Heap_5

Heap相关函数

pvPortMalloc/vPortFree

xPortGetFreeHeapSize

xPortGetMinimunEverFreeHeapSize

malloc失败的钩子函数


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命名规范

变量名前缀含义
cchar
sint16_t,short
lint32_t,long
xBaseType_t,其它非标准的类型:结构体、task handle、queue handle等
uunsigned
p指针
ucuint8_t,unsigned char
pcchar指针
函数名前缀含义
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
通用宏
pdTRUE1
pdFALSE0
pdPASS1
pdFAIL0

内存管理

在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失败时才会调用此函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值