1.函数介绍

创建方法有三种,删除一种

函数

描述

xTaskCreate()

动态的创建一个任务

xTaskCreateStatic()

使用静态的方法创建一个任务

xTaskCreateRestricted()

创建一个使用MPU进行限制的任务,相关内存使用动态内存分配

xTaskDelete()

删除一个任务

  1. xTaskCreate()
    学这个,看源码是很有必要的,我们来瞅一瞅源码的函数定义
    函数返回值:pdPass 任务创建成功
    errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY: 任务创建失败,堆栈内存不足
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )      //这是一个宏定义,意思是支持动态内存,可以参考一下第一节的内容
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, //任务的函数
const char * const pcName, //任务的名称
const uint16_t usStackDepth,//堆栈的大小,把这个数值乘上四然后单位写成B,这就是真实的大小
void * const pvParameters, //任务参数
UBaseType_t uxPriority, //优先级
TaskHandle_t * const pxCreatedTask //任务句柄,任务创建成功以后会返回此任务的任务句柄,这个句柄其实就是任务的任务堆栈。此参数就用来保存这个任务句柄。其他API函数可能会使用这个句柄。
) PRIVILEGED_FUNCTION; /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
#endif
  1. xTaskCreateStatic()
    静态创建任务,需要我们来提供相应的内存空间。返回值如下:
    NULL:任务创建失败,puxStackBuffer 或者 pxTaskBuffer为NULL的时候会发生这个错误
    其他值:任务创建成功,这个返回值就是任务句柄,动态创建任务的时候要提供返回值,静态的方法创建任务会自动返回任务句柄
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode, //任务函数
const char * const pcName, //任务名称
const uint32_t ulStackDepth, //堆栈大小,如果给数组,这个就是数组的大小
void * const pvParameters, //任务参数
UBaseType_t uxPriority, //优先级
StackType_t * const puxStackBuffer,//任务堆栈,一般是数组,数组类型要为StackType_t
StaticTask_t * const pxTaskBuffer //任务控制块TCB task_controllor_block
) PRIVILEGED_FUNCTION; /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
#endif /* configSUPPORT_STATIC_ALLOCATION */
  1. xTaskDelete()
    任务删除函数
    可以删除上面两个方法创建出来的函数,删除的任务就真的没了,句柄也没了,如果任务是动态方法创建的,任务删除后必须要给空闲任务一定的运行时间,为什么呢?因为被删除的动态任务所申请的堆栈和控制内存在空闲任务中才会被释放掉。
    只有内核创建的任务内存才会被自动释放,用户调用pvPortMalloc()自行分配的内存不会被释放,必须用户调用pvPortFree()才行,否则就会内存泄漏。
    删除时候传入的是TaskHandle_t xTaskToDelete ,是不是很熟悉,就是我们的句柄
#if ( INCLUDE_vTaskDelete == 1 )

void vTaskDelete( TaskHandle_t xTaskToDelete )
{
TCB_t *pxTCB;

taskENTER_CRITICAL();
{
/* If null is passed in here then it is the calling task that is
being deleted. */
pxTCB = prvGetTCBFromHandle( xTaskToDelete );

/* Remove task from the ready list. */
if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
taskRESET_READY_PRIORITY( pxTCB->uxPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
}

/* Is the task waiting on an event also? */
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
{
( void ) uxListRemove( &( pxTCB->xEventListItem ) );
}
else
{
mtCOVERAGE_TEST_MARKER();
}

/* Increment the uxTaskNumber also so kernel aware debuggers can
detect that the task lists need re-generating. This is done before
portPRE_TASK_DELETE_HOOK() as in the Windows port that macro will
not return. */
uxTaskNumber++;

if( pxTCB == pxCurrentTCB )
{
/* A task is deleting itself. This cannot complete within the
task itself, as a context switch to another task is required.
Place the task in the termination list. The idle task will
check the termination list and free up any memory allocated by
the scheduler for the TCB and stack of the deleted task. */
vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xStateListItem ) );
/*
增加任务删除变量使得空闲任务知道一个任务是否被删除,并且它会检查这个任务列表。
Increment the ucTasksDeleted variable so the idle task knows
there is a task that has been deleted and that it should therefore
check the xTasksWaitingTermination list. */
++uxDeletedTasksWaitingCleanUp;

/* The pre-delete hook is primarily for the Windows simulator,
in which Windows specific clean up operations are performed,
after which it is not possible to yield away from this task -
hence xYieldPending is used to latch that a context switch is
required. */
portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending );
}
else
{
--uxCurrentNumberOfTasks;
prvDeleteTCB( pxTCB );

/* Reset the next expected unblock time in case it referred to
the task that has just been deleted. */
prvResetNextTaskUnblockTime();
}

traceTASK_DELETE( pxTCB );
}
taskEXIT_CRITICAL();

/* Force a reschedule if it is the currently running task that has just
been deleted. */
if( xSchedulerRunning != pdFALSE )
{
if( pxTCB == pxCurrentTCB )
{
configASSERT( uxSchedulerSuspended == 0 );
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}

#endif /* INCLUDE_vTaskDelete */

2.动态创建删除实例

第一步定义一些需要的宏、变量和函数:

#define TASK1_PRIORITY 3        //任务优先级
#define TASK1_STACK_SIZE 120 //任务堆栈大小
TaskHandle_t task1TaskHandle; //任务处理
void task1( void * pvParameters ); //任务函数声明

第二步声明并定义一个任务:

void task1(void * pvParamter){
int i = 0;
while(1){
LED1=~LED1; //灯闪一闪
vTaskDelay(1000); //再延时一下下
}
}

第三步使用任务创建函数创建任务:

/创建任务
xTaskCreate((TaskFunction_t) task1,
(char *) "task1",
(uint16_t ) TASK1_STACK_SIZE, //堆栈大小
(void * ) NULL,
(UBaseType_t) TASK1_PRIORITY, //优先级,这里的数值不能是最小的0,因为零是空闲函数的优先级,咱是好孩子,不强人家的优先级,空闲任务呢也做了一些伟大的事情,它可以回收内存空间
(TaskHandle_t * )&task1TaskHandle); //任务处理句柄,这个是需要的,创建完成后,可以通过句柄对这个任务进行一些操作,比如进行删除任务

第四步开启任务的调度:

第一次使用的时候就忘记这个了,任务完全不会动,一定记得开启哦~

vTaskStartScheduler();

第五步进行任务删除:

如果我们希望LED灯闪烁一下后以后再也不闪该怎么办,看代码:

void task1(void * pvParamter){
int i = 0;
while(i<2){
LED1=~LED1; //灯闪一闪
vTaskDelay(1000); //再延时一下下
}
vTaskDelete(task1TaskHandle);
//或者 vTaskDelete(NULL);
}

3.静态创建删除实例

静态的创建任务相比静态的创建任务要复杂一些,我们来看一下具体的步骤:

第一步打开配置:

在FreeRTOSConfig.h 中配置如下内容:

#define configSUPPORT_STATIC_ALLOCATION         1   //支持静态的内存分配

第二步实现vApplicationGetTimerTaskMemory()和vApplicationGetIdleTaskMemory()这两个函数:

为什么要实现这两个函数呢,系统编译的时候会优先检测是否开启了上面的那个配置,如果开启了的话,系统创建空闲任务(vApplicationGetIdleTaskMemory)和定时任务(vApplicationGetTimerTaskMemory)的时候就会使用静态的方法,静态方法创建任务最大的特点就是要求用户自己分配存储空间,这样一来我们就要通过以上的两个函数,将我们分配好的空间位置,以及空间的大小告知空闲任务和定时器任务的创建者,具体的方法如下:

//空闲任务空间和函数实现
static StackType_t IdleTaskStack[configMINIMAL_STACK_SIZE]; //这个是我们分配的堆栈空间
static StaticTask_t IdleTaskTCB ;//这个是我们分配的TCB程序控制块的空间
void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer,
StackType_t **ppxIdleTaskStackBuffer,
uint32_t *pulIdleTaskStackSize )
{
//逐一赋值
*ppxIdleTaskTCBBuffer = &IdleTaskTCB;
*ppxIdleTaskStackBuffer = IdleTaskStack;
*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;

}

//定时器任务的空间分配和函数实现
static StackType_t TimerTaskStack[configTIMER_TASK_STACK_DEPTH];//定时器任务的堆栈空间
static StaticTask_t TimerTaskTCB ;//定时器任务的TCB空间
void vApplicationGetTimerTaskMemory( StaticTask_t **ppxTimerTaskTCBBuffer,
StackType_t **ppxTimerTaskStackBuffer,
uint32_t *pulTimerTaskStackSize ){
//逐一赋值
*ppxTimerTaskTCBBuffer = &TimerTaskTCB;
*ppxTimerTaskStackBuffer = TimerTaskStack;
*pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;

}

第三步声明并定义一个任务:

void led0_task(void *pvParameters)
{
printf("led0 is running !");
while(1){
printf("led0 is running !");
LED0=~LED0;
vTaskDelay(500);
}
}

第四步定义宏和创建任务:

//定义LED0的优先级
#define LED0_TASK_PRIO 2
//定义LED0的堆栈大小
#define LED0_STK_SIZE 50
//定义LED0的函数句柄
TaskHandle_t LED0Task_Handler;
//声明函数
void led0_task(void *pvParameters);

static StackType_t Task1TaskStack[LED0_STK_SIZE];//堆栈空间
static StaticTask_t Task1TaskTCB ;//任务控制块空间
LED0Task_Handler = xTaskCreateStatic(
(TaskFunction_t) led0_task,
(char *) "led0_task",
(uint32_t) LED0_STK_SIZE,
(void *) NULL,
(UBaseType_t) LED0_TASK_PRIO,
(StackType_t *) Task1TaskStack,/堆栈空间**/
(StaticTask_t *) &Task1TaskTCB ); /*程序控制块 */

第五步是开启任务调度:

vTaskStartScheduler();

第六步删除任务:

vTaskDelete(LED0Task_Handler);      /里面的参数是任务句柄

总结

以上就是关于FreeRTOS任务创建和删除的相关函数和操作,分为静态和动态两种,每种都有自己的好处,也有坏处,两者应该相辅相成,互相配合。