FreeRTOS源码学习_02-创建任务
一、写在前面
- FreeRTOS版本:V10.4.5
- 内存分配方式:动态分配
- Port.c版本:GCC/ARM_CM4F_V10.4.5
我自己最近在学习FreeRTOS操作系统,在使用中发现,虽然官方的英文注释十分的详尽,但是很多地方不是特别好理解,初学者看了注释后还是一头雾水。因此决定将自己的使用理解以及注释写在这里,方便大家参考。
注意:因为加了注释符后,注释会变成斜体,看着很别扭,因此文中注释统一采用 “ ** … ** ” 的方式
第一阶段为基础篇 分为四篇:
01-任务调度器:分析开启任务调度函数
02-创建任务:分析任务是如何被创建的
03-链表操作:系统中的任务、消息、信号量等都与链表息息相关,分析FreeRTOS中的链表操作(待更新)
04-汇编指令解析:分析FreeRTOS中的汇编代码(待更新)
注释写的十分详细,直接写在源码中,篇幅较长,希望耐心看完!
如果这篇文章帮助到了您,作者甚是欣慰。若文中有不正确或不恰当的地方,也请大家及时指正!
二、源码分析
任务的一个核心的东西是任务控制快,这个东西保存了任务的一切信息,通过任务控制块,可以知道该任务的优先级、任务状态、任务事件等等。因此首先来分析任务控制块这个结构信息,然后再看任务创建源码就会一目了然。
1、tskTCB结构分析
typedef struct tskTaskControlBlock
{
**
这是一个 32位的指针,用来保存该任务TCB(任务控制块)的栈首地址,就是我们在创建任务时,为这个任务分配的内存首地址
**
volatile StackType_t * pxTopOfStack;
**
下面的条件编译是针对有MPU功能的芯片的操作,文章不予考虑
**
#if ( portUSING_MPU_WRAPPERS == 1 )
xMPU_SETTINGS xMPUSettings; /*< The MPU settings are defined as part of the port layer. THIS MUST BE THE SECOND MEMBER OF THE TCB STRUCT. */
#endif
**
存放任务状态的链表
**
ListItem_t xStateListItem;
**
存放任务事件的链表(事件是FreeRTOS的一项功能,后面会有专门的章节来介绍)
**
ListItem_t xEventListItem;
**
存放当前任务优先级
**
UBaseType_t uxPriority;
**
指向当前任务的任务堆栈,就是为这个任务运行所分配的内存
**
StackType_t * pxStack;
**
任务的名字
**
char pcTaskName[ configMAX_TASK_NAME_LEN ];
**
堆栈的栈底
**
#if ( ( portSTACK_GROWTH > 0 ) || ( configRECORD_STACK_HIGH_ADDRESS == 1 ) )
StackType_t * pxEndOfStack;
#endif
**
嵌套临界区的深度(临界区是为了保护临界段代码所设置的,FreeRTOS中保护方式就是关闭中断)FreeRTOS支持临界区嵌套,即调用了n次保护,就必须n次释放保护,才会打开中断
**
#if ( portCRITICAL_NESTING_IN_TCB == 1 )
UBaseType_t uxCriticalNesting;
#endif
**
这两个变量主要是调试的时候使用的,用来记录创建的TCB数量和任务数量
**
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxTCBNumber; /*< Stores a number that increments each time a TCB is created. It allows debuggers to determine when a task has been deleted and then recreated. */
UBaseType_t uxTaskNumber; /*< Stores a number specifically for use by third party trace code. */
#endif
#if ( configUSE_MUTEXES == 1 )
**
任务的基础优先级,在互斥信号量会用到,这里暂不深究
**
UBaseType_t uxBasePriority;
**
互斥信号量个数
**
UBaseType_t uxMutexesHeld;
#endif
**
任务标签,在创建任务的时候可以给每个任务分配一个指定的数字来作为这个任务的标签
**
#if ( configUSE_APPLICATION_TASK_TAG == 1 )
TaskHookFunction_t pxTaskTag;
#endif
**
本地存储(无需关心)
**
#if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
void * pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
#endif
**
存储任务运行时间,可以统计任务占用CPU的百分比
**
#if ( configGENERATE_RUN_TIME_STATS == 1 )
configRUN_TIME_COUNTER_TYPE ulRunTimeCounter;
#endif
#if ( configUSE_NEWLIB_REENTRANT == 1 )
/* Allocate a Newlib reent structure that is specific to this task.
* Note Newlib support has been included by popular demand, but is not
* used by the FreeRTOS maintainers themselves. FreeRTOS is not
* responsible for resulting newlib operation. User must be familiar with
* newlib and must provide system-wide implementations of the necessary
* stubs. Be warned that (at the time of writing) the current newlib design
* implements a system-wide malloc() that must be provided with locks.
*
* See the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html
* for additional information. */
struct _reent xNewLib_reent;
#endif
#if ( configUSE_TASK_NOTIFICATIONS == 1 )
**
任务通知,32位的值,具体会在后面讲解(比如:A任务给B任务发送通知,0表示B任务打开LED,1表示B任务关闭LED)
**
volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
**
任务通知状态
**
volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
#endif
**
标记任务的创建方式,动态创建为0,静态创建为1
**
#if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
uint8_t ucStaticallyAllocated;
#endif
**
任务延时终止,比如原本让A任务延时200ms,现在有事情需要A任务去解决,可以临时取消A任务的延时,即终止延时。
**
#if ( INCLUDE_xTaskAbortDelay == 1 )
uint8_t ucDelayAborted;
#endif
#if ( configUSE_POSIX_ERRNO == 1 )
int iTaskErrno;
#endif
} tskTCB;
2、任务创建:BaseType_t xTaskCreate()
BaseType_t xTaskCreate(
**
指向任务运行的入口函数(无返回值)
**
TaskFunction_t pxTaskCode,
**
创建任务的名字
**
const char * const pcName,
**
任务占用的堆栈大小(单位:字 在32位处理器中 1代表4个字节)
**
const configSTACK_DEPTH_TYPE usStackDepth,
**
任务的参数(一般不需要用到 设置为NULL)
**
void * const pvParameters,
**
任务的优先级大小
**
UBaseType_t uxPriority,
**
保存任务的TCB结构(包含任务的所有信息,动态创建时,若不需要查看任务的一些参数,写为NUL就可以)
**
TaskHandle_t * const pxCreatedTask )
{
TCB_t * pxNewTCB;
BaseType_t xReturn;
**
下面的代码是为该任务的堆栈分配内存,首先需要判断堆栈的生长方向(向上生长或者向下生长)
如果堆栈向上生长,那么先为TCB分配内存,再为任务堆栈分配内存
如果堆栈向下生长,那么先为任务堆栈分配内存,再为TCB分配内存
这样做得目的是:尽最大可能保证TCB的完整性,因为堆栈可能溢出,举个例子:
如果堆栈是向上生长的,先为任务堆栈分配内存,再为TCB分配内存,那么一旦任务堆栈溢出,则会改写TCB的内容。
要知道TCB存放的是该任务的所有信息,相当于该任务的命脉,改写后不仅可能该任务会异常,甚至会引起系统崩溃
**
#if ( portSTACK_GROWTH > 0 )/* 这里堆栈向上生长的情况 */
{
**
调用 "pvPortMalloc" 函数分配 TCB_t(称为 任务控制块 用来保存任务的所有信息) 大小的内存
**
pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
**
判断是否申请成功 如果申请成功,pxNewTCB 指向分配的内存地址,否则内存不足,任务创建失败
**
if( pxNewTCB != NULL )
{
**
这里为任务的堆栈分配内存,并将内存首地址赋给该任务控制块的堆栈首地址(任务堆栈:指为该任务运行分配的内存)这里分配 usStackDepth * sizeof( StackType_t )字节
因为pvPortMallocStack函数的参数是字节,前面说过,堆栈大小参数单位是字,32位处理器中一个字是4个字节,sizeof( StackType_t )在32位处理器中是4。
比如 usStackDepth 是200,则实际分配的堆栈大小为200*4 = 800字节
**
pxNewTCB->pxStack = ( StackType_t * ) pvPortMallocStack( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );
**
判断是否任务堆栈内存分配成功
**
if( pxNewTCB->pxStack == NULL )
{
**
没有分配成功,则删除先前任务控制块分配的内存,因为任务控制块就是保存该任务信息的,现在该任务都没有创建成功,也不需要保存该任务的信息了。好比钱包,就是用来装钱的,没有钱放进去,那么钱包也没有存在的价值了
(不知为何,举这个例子时我的心在隐隐作痛-^-)
**
vPortFree( pxNewTCB );
**
内存释放后,一定要将指针清0,否则会影响下面的判断,认为内存分配成功了
**
pxNewTCB = NULL;
}
}
}
#else /* 堆栈向下生长 */
{
StackType_t * pxStack;
**
上面讲过,向下生长的堆栈先申请任务堆栈
**
pxStack = pvPortMallocStack( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack and this allocation is the stack. */
if( pxStack != NULL )
{
**
为TCB(任务控制块)结构申请内存
**
pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
if( pxNewTCB != NULL )
{
**
分配成功后,将内存首地址赋给该任务控制块的堆栈首地址
**
pxNewTCB->pxStack = pxStack;
}
else
{
**
同样,内存申请失败,同样的需要删除任务堆栈,因为没有足够的内存用来存放该任务的任务控制块
好比一杯奶茶,奶茶杯比作任务控制块,奶茶比作任务堆栈,因为任务控制块保存任务所有信息,就像奶茶杯存放奶茶。现在没有奶茶杯来存放奶茶,那么奶茶也没办法喝到,也需要“扔掉”
这里不需要将指针清0,因为 pxStack 在下面没有用到,并且为局部变量
**
vPortFreeStack( pxStack );
}
}
else
{
**
这里是任务堆栈的内存没有申请成功 需要将 pxNewTCB 赋值为NULL,因为 pxNewTCB 在初始化时没有赋值 很可能不为NULL
**
pxNewTCB = NULL;
}
}
#endif /* portSTACK_GROWTH */
**
进过上面的判断 如果 pxNewTCB 不为NULL 那么内存一定已经申请成功了
**
if( pxNewTCB != NULL )
{
#if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e9029 !e731 Macro has been consolidated for readability reasons. */
{
/* Tasks can be created statically or dynamically, so note this
* task was created dynamically in case it is later deleted. */
pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
}
#endif /* tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE */
**
初始化任务的参数信息 具体见第三小节-3 初始化任务参数
**
prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
**
将创建的任务添加到就绪列表中 具体见第三小节-3 初始化任务参数
**
prvAddNewTaskToReadyList( pxNewTCB );
xReturn = pdPASS;
}
else
{
xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
}
return xReturn;
}