本文的任务创建函数 基于FreeRTOS Kernel V10.0.1
FreeRTOS提供了两种基本的任务创建函数,分别为动态创建与静态创建。
函数原型为:
/* 动态创建任务 */
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, /* 任务的函数入口 */
const char * const pcName, /* 任务名字 */
const configSTACK_DEPTH_TYPE usStackDepth,/* 栈的深度,后面会讲解 */
void * const pvParameters,/* 传入参数 */
UBaseType_t uxPriority, /* 任务优先级 */
TaskHandle_t * const pxCreatedTask ) /* 任务句柄 */
/* 静态创建任务 */
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,/* 任务的函数入口 */
const char * const pcName, /* 任务名字 */
const uint32_t ulStackDepth,/* 栈的深度 */
void * const pvParameters,/* 传入参数 */
UBaseType_t uxPriority,/* 任务优先级 */
StackType_t * const puxStackBuffer,/* 任务的数据栈 */
StaticTask_t * const pxTaskBuffer )/* 任务的信息栈、句柄 */
在FreeRTOS内部,每个任务都有自己专有的任务栈来存储相关信息,所以在创建一个任务时需要进行的操作包括:1)分配栈内存 2)
任务需要两个内存块,分别用来保存任务的数据、任务的控制块(TCB)。 使用xTaskCreate()创建任务时,这两个内存块在函数内部自动被申请了,不需要用户手动配置。
一般情况下,对于堆来说,生长方向是向上增长的,即堆的内存地址是增加的;对于栈来说,栈是向下生长的,即栈的内存地址是减小的。
在FreeRTOS源码中,对生长方向不同的栈都做了处理。
1)对于栈向下生长的情况,在创建任务时,先申请任务栈空间,再申请TCB空间。
2)对于栈向上生长的情况,在创建任务时,先申请TCB空间,再申请任务栈空间
本文中的栈是向下生长的。
创建任务时,通过执行以下代码可以保证任务已经有内存空间了,并且通过TCB我就可以访问该任务的栈空间。TCB是一个任务的所有,任务的全部家当都受TCB控制。真不愧叫TCB(task control block)任务控制块啊。
StackType_t *pxStack;/* 任务栈内存申请 */
pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );
if( pxStack != NULL )
{
pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );/* TCB内存申请 */
if( pxNewTCB != NULL )
pxNewTCB->pxStack = pxStack; /* 在TCB中存储任务栈的地址 */
}
在开始的任务创建函数中,传入的参数包括,任务入口,任务名字,任务栈深度,任务参数,任务优先级,任务句柄(TCB)
既然传入了这些参数,就需要把这些参数融合进任务中,如何实现任务的初始化呢?
/* 将任务的栈空间全部添入0xa5 : 1010 0101 填入0xa5的目的是增强抗干扰和方便调试 */
( void ) memset( pxNewTCB->pxStack, ( int ) 0xa5U, ( size_t ) ulStackDepth * sizeof( StackType_t ) );
StackType_t *pxTopOfStack;
/* 该指针指向任务栈的栈顶 注意:栈的地址向下生长 */
pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( 0x0007) ) );/* 进行对齐操作 */
/* 在TCB中存储任务的名字 */
for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
{
pxNewTCB->pcTaskName[ x ] = pcName[ x ];
}
/* 在任务中存储任务优先级 */
pxNewTCB->uxPriority = uxPriority;
执行完以上代码后,任务置办的家当包括:一块土地、名字、社会地位,有了土地,但是还没有建筑。在这块土地上,我们要构建的建筑包括:任务入口(函数入口),传入的参数。
以下代码是伪造现场。为什么要伪造现场呢?是因为在RTOS中,任务是交替执行的,任务在发生交换时,会保存现场,任务执行完,会返回现场继续执行。但是在任务第一次创建的时候,需要我们自己创建现场,在以后的日子里,现场将由硬件完成保存。
/* 传入的参数包括了,指向任务栈顶的指针,任务入口地址,任务相关参数 */
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
/* 具体的伪造过程如下 */
StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
{
pxTopOfStack--;/* 增加的偏移量用于说明MCU在中断进入/退出时使用堆栈的方式 */
*pxTopOfStack = 0x01000000; /* xPSR 程序状态寄存器的第24位置1,表示使用thumb指令集 */
pxTopOfStack--;
*pxTopOfStack = ( ( StackType_t ) pxCode ) & 0xfffffffeUL; /* PC CM3 中的指令至少是半字对齐的,所以 PC 的 LSB 总是读回 0*/
pxTopOfStack--;
*pxTopOfStack = ( StackType_t ) prvTaskExitError; /* LR 保存函数的返回地址,返回函数使用汇编编写,关闭了所有中断,并进入死循环,*/
pxTopOfStack -= 5; /* R12, R3, R2 and R1. */
*pxTopOfStack = ( StackType_t ) pvParameters; /* R0 */
pxTopOfStack -= 8; /* R11, R10, R9, R8, R7, R6, R5 and R4. */
return pxTopOfStack;
}
将任务的TCB结构体赋值给任务句柄,这样通过操作任务句柄就能操作整个任务了。
通过任务句柄,我们可以改变任务优先级、删除任务、挂起任务、通知任务等操作。
if( ( void * ) pxCreatedTask != NULL )
{
*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
}
前面我们为任务买了土地、盖了房子、取了名字、获取了社会地位,可谓是万事俱备,只欠东风了,接下来我们就应该将任务扔到残酷的社会的不同环境中进行工作。放入不同的环境在Free RTOS中即放入不同的链表。新创建的任务是处于就绪状态,时刻准备着工作。
static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )
{
uxCurrentNumberOfTasks++;/* 用于记录链表中存在的任务数 */
if( pxCurrentTCB == NULL )/* 当前没有任务或者其余任务都挂起了 */
{
pxCurrentTCB = pxNewTCB;
if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 )
{
prvInitialiseTaskLists();
}
}
else /* 当前任务不止有一个 对比优先级 */
{
if( xSchedulerRunning == pdFALSE )
{
if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
{
pxCurrentTCB = pxNewTCB;
}
}
}
}
以上就是本次学习的主要内容,主要概述了Free RTOS创建任务的大致流程,而Free RTOS还提供了静态创建任务,但是其大概的过程是一样的,具体可以阅读Free RTOS源码。以上内容参考FreeRTOS Kernel V10.0.1 源码和《Cortex-M3内核权威指南》。