FreeRTOS Cortex-M3 核心技术

本文是关于FreeRTOS在Cortex-M3上的核心技术,主要涉及任务的创建、任务内存管理和任务切换,展示了系统切换到用户级运行FreeRTOS任务的过程。
内存管理基于heap_2,基于Demo FreeRTOS\Demo\CORTEX_STM32F103_Keil

1. 概念

本文基本包含所有FreeRTOS port Cortex-M3的芯片级技术,基本包含FreeRTOS\Source\portable\RVDS\ARM_CM3\port.c中的内容。

FreeRTOS Port:FreeRTOS终端,每一种编译器、设备类型都可称为port,比如GCC可以称为port,STM32也可以称为port,cortex-m3也可以称为port。
堆栈(Stack):即是栈,官方的翻译称堆栈。
堆(Heap):堆是与堆栈相对于的堆,动态分配内存时使用的部分。

TCB、Stack:(heap_2)本文的Stack与堆栈有一定的区别,它也是堆栈的意思,但是特指task所使用的stack。task占用的内存包括TCB和Stack。
ucHeap:整块可自由分配的内存的大小,是需要开发者手动配置的。

阅读本文建议的基本要求:

  • 已经阅读过并理解FreeRTOS官方指导手册《FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide》或者中文翻译版
  • 熟悉Cortex-M3基本概念和技术
  • 熟悉汇编、C语言
  • 拥有足够的操作系统相关知识

通篇点到为止,适合结合源码阅读。

2. 创建任务

以官方Demo为例,创建了一个Led闪烁的任务。

xTaskCreate( vLEDFlashTask, "LEDx", ledSTACK_SIZE, NULL, uxPriority, 
( TaskHandle_t * ) NULL );

vLEDFlashTask : 任务函数体
第二参数:任务名称,仅作为可读字符串,无实际意义
ledSTACK_SIZE: 任务的堆栈深度
第四参数:传给任务的参数(void*)指针
uxPriority:任务优先级,任务的优先级都是事先安排的,同一类型的任务优先级一般相同
第六参数:任务变化的回调(包括优先级变化、任务要被删除等)

堆栈大小(字节数) = 堆栈的深度 x 堆栈的宽度(比如4 bytes)
堆栈的宽度是通过StackType_t决定的,在M3中,它是32-bits(4字节)

xTaskCreate被宏定义为xTaskGenericCreate,如果是使用MPU,再次被定义为MPU_xTaskGenericCreate,这里不涉及MPU。

BaseType_t xTaskGenericCreate( TaskFunction_t pxTaskCode, const char * const pcName, const uint16_t usStackDepth, void * const pvParameters,
UBaseType_t uxPriority, TaskHandle_t * const pxCreatedTask, StackType_t * const puxStackBuffer, const MemoryRegion_t * const xRegions ) 
/*lint !e971 Unqualified char types are allowed for strings and single characters only. */

前面的几个参数,与XTaskCreate一一对应,最后两个参数,这里是为固定的NULL(包括puxStackBuffer, xRegions)。

2-1. 申请内存

TCB_t * pxNewTCB;
pxNewTCB = prvAllocateTCBAndStack( usStackDepth, NULL );

先申请一个TCB Block,再申请一个Stack的Block,一个任务有两个Block。
总大小等于 usStackDepth * sizeof(StackType_t)。

2-1-1. TCB Block

直接进入该函数的内部

pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );

使用pvPortMalloc申请内存,从函数的定义可以猜到,它在Source/portable相关文件中实现。

FreeRTOS的动态内存管理方式有5个,分别是heap_1/2/3/4/5.
以heap_2为例。

看看void *pvPortMalloc( size_t xWantedSize )的heap_2实现。

先通过vTaskSuspendAll();禁止任务调度,任务调度暂停与启动的做法是维护一个volatile类型的全局变量。
PRIVILEGED_DATA static volatile UBaseType_t uxSchedulerSuspended = ( UBaseType_t ) pdFALSE;

第一次申请时,还要对heap进行初始化:prvHeapInit();
初始化的结果是,将heap均分一个完整的大Block,并将一个BlockLink_t结构体存放到heap的头部。

xWantedSize 是任务获得的可用内存大小,实际申请时,还需要加上heap头部结构体的大小,并进行8字节对齐。
调整后的xWantedSize如果小于configTOTAL_HEAP_SIZE,就表示可以进行分配了。

怎么分配,当然分配Block,一个一个Block地配,直到分配的内存总量等于mWantedSize,或者内存不足。

如果是第一次分配,那么,一个Block的大小就是configADJUSTED_HEAP_SIZE,除去heap头部,可用大小是非常可观。

但是太大了,于是将多余的部分分出来,独立为一个新的Block.

新的Block的头部,同样是一个heap头部,下次分配内存时,同样类似上述过程。

最后,将新的Block添加到Block list中。

最后的最后,恢复任务调度。

注意这里的返回值,是一个TCB_t结构体。

2-1-2. Stack Block

类似申请TCB Block,将一个新的Block分配给TCB_t结构体中的pxStack成员。
申请好之后,使用memset对整块Block清零。

Stack大小 = usStackDepth - TCB Block

Stack可以配置为向上增长或者向下生长。

将Stack的信息保存到TCB中。

2-2. Heap初始化

prvHeapInit();

它以BlockLink_t类型为基本管理单位,每一个该类型代表一个RAM Block。当内存还没别分配时,是一个完整的大Block。
Block的总大小与configADJUSTED_HEAP_SIZE一致。

#define configADJUSTED_HEAP_SIZE ( configTOTAL_HEAP_SIZE - portBYTE_ALIGNMENT )
与configTOTAL_HEAP_SIZE相当。

有必要看看它的函数体:

static void prvHeapInit( void )
{
   
BlockLink_t *pxFirstFreeBlock;
uint8_t *pucAlignedHeap

你可能感兴趣的:(RTOS,FreeRTOS,RTOS,Cortex-M3,任务切换,SVC,PendSV)