《Mastering the FreeRTOS Real Time Kernel》读书笔记(1)堆内存管理

这是161204的版本,不完全覆盖目前最新版本的内核。

0.关于freeRTOS

首先提出了了在小型嵌入式系统中为何需要多任务管理的问题,介绍了freeRTOS的用途。然后开始做广告,吹了一波freeRTOS的好处。其中要注意一些关键的名词:任务优先级分配、任务通知、队列、信号量、互斥锁、软定时器、事件组、钩子函数等。这些内容会在后面的章节进行介绍。

1.FreeRTOS的文件结构

主要介绍在github上下载到的FreeRTOS包含哪些内容。

1.2 分布

可以将freeRTOS看成一个库,或者看成一个软件。其通过配置,可以运行在30多种不同的处理器架构上。而配置Freertos需要通过一个叫做FreeRTOSConfig.h的头文件。不同编译器或者处理器的该头文件不同,可以直接从众多例程中复制出来直接使用,或者微调后使用。
官方发行版中顶级目录有FreeRTOS(主要内核)和FreeRTOS-Plus(生态系统组件),其下分别都有Source和Demo两个文件夹。在FreeRTOS/Source中有task.clist.cqueue.ctimers.cevent—_groups.ccroutine.c这些源文件,有些总是需要,有些可选。
FreeRTOS/Source/portable中是一些针对特定编译器和架构的端口文件,相当于在不同国家旅游时的通行证。
头文件包含的路径一共要准备3个:FreeRTOS/Source/include、FreeRTOS/Source/portable/[compiler]/[architecture]、和指向FreeRTOSConfig.h的路径。

1.3 演示Demo

主要是教读者如何使用已有的演示Demo

1.4 创建FreeRTOS项目

包括修改Demo和从头开始创建两种方法。主要就是复制源文件进工程和修改端口文件。

1.5 数据类型和编码风格指南

FreeRTOS针对每个端口都有唯一的protmacro.h头文件,包含了针对不同单片机的两个特定数据类型TickType_t & BaseType_t。
变量名:c对应char、s对应int16_t(short)、l对应int32_t(long)、x对应BaseType_t。p对应指针变量前缀,u是无符号变量前缀。
函数名:函数前缀同变量名前缀。
格式化:一个制表符总是等于4个空格。
宏macro:宏的主体为全大写字母,小写字母作前缀,表示宏的定义位置。如taskENTER_CRITICAL(),该宏在task.h。pdTRUE = pdPASS = 1; pdFALSE = pdFAIL = 0;

注意:由于FreeRTOS需要在不同编译器上通过编译,所以其源代码包含的类型转换很多。

2.堆内存管理

注意:V9.0.0开始可以完全静态分配FreeRTOS应用程序,不需要包含堆内存管理器。这边主要介绍动态分配。
为什么需要动态内存分配。FreeRTOS每次创建内核对象时都需要分配RAM。但是内核对象其被删除时,需要释放对应的RAM。原本C库的malloc和free不适合单片机,所以重新设计相应的申请和释放函数pvPortMalloc()vPortFree()
为此,FreeRTOS准备了5种内存管理示例:heap_1.c, heap_2.c, heap_3.c, heap_4.c and heap_5.c。它们都在 FreeRTOS/Source/portable/MemMang 目录下。

Heap_1

无法使用动态内存的版本,没有使用vPortFree()。当调用pvPortMalloc()时,heap_1会在FreeRTOS堆空间内进行申请更小的空间,将申请的空间划分为任务控制块(TCB)和一个该任务专用栈。

Heap_2

Heap_4的原始版本,为了向后兼容而保留,建议使用增强的完整版Heap_4。
heap_2允许释放不用内存使其空闲,可以再次申请。再次申请的逻辑为:使用大小满足,且最接近请求字节数的空闲内存块。但是其不能将相邻的空闲内存合并,该功能在Heap_4中可以实现。
书例:5b、25b、100b的三块空闲内存,申请20b内存,该块内存将分配与25b空闲内存处,其中多余5b仍为空闲内存,就是此时还有5b、5b、100b三块空闲内存。
Heap_2适用于重复创建和删除任务的应用程序,前提是分配给创建的任务和堆栈大小不变。

Heap_3

使用标准库的malloc()和free()函数,因此堆的大小不是像前两者,由configTOTAL_HEAP_SIZE设置,而是由链接器配置决定。
通过临时挂起FreeRTOS调度器,Heap_3使malloc()和free()线程安全。这些内容会在第7章资源管理中涉及。

Heap_4

与heap_1和heap_2一样,heap_4的工作原理是将数组细分为更小的块,数组是静态分配的,并由configTOTAL_HEAP_SIZE确定尺寸。
与heap_2在分配时就有不同,书例:堆包含三个空闲内存块,按照它们在数组中出现的顺序,分别为5字节、200字节和100字节,调用pvPortMalloc()来请求20字节的RAM,第一个适合请求的字节数的空闲块是200字节,因此pvPortMalloc在返回指针之前将200字节拆分为一个20字节的块和一个180字节的块,返回的指针指向20字节的块,新的180字节块仍可以被pvPortMalloc调用。并不是申请最接近请求字节数的空闲内存块。
但是heap_4在释放内存时,会自动将相邻的空闲内存合并。注意只是相邻的。合并后会成为一个更大的空闲内存块,可用于申请。
heap_2和4申请内存、heap_4合并内存的依据都是因为每块空闲内存都有一小段空间是记录空闲内存信息的,通过这些信息可以精准地进行操作。
heap_4代表的数组起始地址是可以设置的,具体方法详见本书。

Heap_5

heap_5用于分配和释放内存的算法和heap_4相同,不同的是heap_5不限于从单个静态声明的数组分配内存,heap_5可以从多个独立的内存空间分配内存,当运行 FreeRTOS 的系统提供的 RAM 没有在系统内存映射中显示为单个连续(没有空间)块时,Heap_5 很有用。
在撰写本文时,heap_5 是唯一提供的内存分配方案,必须在调用 pvPortMalloc() 之前显式初始化。 Heap_5 使用 vPortDefineHeapRegions() API 函数初始化。 使用 heap_5 时,必须在创建任何内核对象(任务、队列、信号量等)之前调用 vPortDefineHeapRegions()
vPortDefineHeapRegions用于指定每个单独的内存区域的起始地址和大小,这些区域构成heap_5使用的总内存。

void vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions );

每个单独的内存区域由HeapRegion_t类型的结构体描述,所有可用内存区域的描述作为 HeapRegion_t 结构数组传递到 vPortDefineHeapRegions()。

typedef struct HeapRegion
{
/* The start address of a block of memory that will be part of the heap.*/
uint8_t *pucStartAddress;
/* The size of the block of memory in bytes. */
size_t xSizeInBytes;
} HeapRegion_t;

比如定义三个不连续内存块可以使用如下代码。

/* Define the start address and size of the three RAM regions. */
#define RAM1_START_ADDRESS ( ( uint8_t * ) 0x00010000 )
#define RAM1_SIZE ( 65 * 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. */
}

堆相关函数

xPortGetFreeHeapSize

返回调用该函数时未分配的堆空间总量

size_t xPortGetFreeHeapSize( void );

xPortGetMinimumEverFreeHeapSize

返回 FreeRTOS 应用程序自启动以来系统的历史最小可用堆空间量。
这两个函数都没有提供有关如何将未分配的内存分成更小的块的信息。

vPortGetHeapStats()

提供关于堆状态的附加信息。

Malloc调用失败的Hook函数

如果 pvPortMalloc() 因为请求大小的块不存在而无法返回 RAM 块,那么它将返回 NULL,则不会创建内核对象。此时执行,以处理这种情况。

void vApplicationMallocFailedHook( void );

热门相关:盛世妖颜   重生成偏执霍少的小仙女   我是仙凡   我成了暴戾帝君的小娇包   上将大叔,狼来了!