FreeRTOS从入门到精通 第三章(任务创建和任务删除)

参考教程:【正点原子】手把手教你学FreeRTOS实时系统_哔哩哔哩_bilibili

一、任务创建和删除的API函数

1、概述

(1)任务的创建和删除本质就是调用FreeRTOS的API函数。

API函数

描述

xTaskCreate()

动态方式创建任务

xTaskCreateStatic()

静态方式创建任务

vTaskDelete()

删除任务

(2)动态创建任务与静态创建任务:

①动态创建任务——任务的任务控制块以及任务的栈空间所需的内存,均由FreeRTOS从FreeRTOS管理的堆中分配。

②静态创建任务——任务的任务控制块以及任务的栈空间所需的内存,需用户分配提供。

(3)任务控制块:

①任务控制块结构体成员:

typedef struct tskTaskControlBlock       
{
    volatile StackType_t  * pxTopOfStack;    /* 任务栈栈顶,必须为TCB的首个成员 */
   	ListItem_t 			xStateListItem;   /* 任务状态列表项 */      
	ListItem_t 			xEventListItem;  /* 任务事件列表项 */     
    UBaseType_t 			uxPriority;      /* 任务优先级,数值越大,优先级越大 */
    StackType_t 		  * pxStack;		   /* 任务栈起始地址 */
    char 			   pcTaskName[ configMAX_TASK_NAME_LEN ];/* 任务名字 */		
	…(省略很多条件编译的成员)
} tskTCB;

①每个任务都有属于自己的任务控制块,用于保存任务的相关信息,类似每个人都有属于自己的身份证。

②任务栈栈顶,在任务切换时与任务上下文保存、任务恢复息息相关。

2、动态创建任务

(1)动态创建任务函数的接口定义:

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 	/* 任务句柄,就是任务的任务控制块 */
)

返回值

描述

pdPASS

任务创建成功

errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY

任务创建失败

(2)实现动态创建任务的流程:

①在FreeRTOSConfig.h文件中将宏configSUPPORT_DYNAMIC_ALLOCATION配置为1。

②定义函数入口参数。

③编写任务函数。

(3)动态创建任务函数的内部实现(大致流程):

①申请堆栈内存或任务控制块内存。

②TCB(任务控制块)结构体成员赋值。

③添加新任务到就绪列表中。

3、静态创建任务

(1)静态创建任务函数的接口定义:

TaskHandle_t xTaskCreateStatic
( 
	TaskFunction_t 				pxTaskCode,		/* 指向任务函数的指针 */
	const char * const 				pcName, 	    /* 任务名字 */
	const  uint32_t 		        usStackDepth, 	/* 任务堆栈大小,单位为字 */
	void * const 					pvParameters,	   /* 传递给任务函数的参数 */
	UBaseType_t 					uxPriority,	   /* 任务优先级 */
    StaticTask_t * const		    pxStackBuffer,	  /* 任务堆栈,一般为数组,由用户分配 */
    StaticTask_t * const		    pxTaskBuffer	  /* 任务控制块指针,由用户分配 */
)

返回值

描述

NULL

用户没有提供相应的内存,任务创建失败

其它值

任务句柄,任务创建成功

(2)实现静态创建任务的流程:

①在FreeRTOSConfig.h文件中将宏configSUPPORT_STATIC_ALLOCATION配置为1。

②定义空闲任务和定时器任务(可选,如果软件定时器失能则忽视)的任务堆栈及TCB。

③实现接口函数vApplicationGetIdleTaskMemory( )和vApplicationGetTimerTaskMemory ( )。

④定义函数入口参数。

⑤编写任务函数。

(3)静态创建任务函数的内部实现(大致流程):

①TCB(任务控制块)结构体成员赋值。

②添加新任务到就绪列表中。

4、任务删除

(1)任务删除函数接口定义:

void vTaskDelete
(
TaskHandle_t 		xTaskToDelete 		/* 待删除任务的任务句柄 */
)

①该函数用于删除已被创建的任务,被删除的任务将从就绪态任务列表、阻塞态任务列表、挂起态任务列表和事件列表中移除。

②当传入的参数为NULL,则代表删除任务自身,即当前正在运行的任务。

③空闲任务会负责释放被删除任务中由系统分配的内存,但是由用户在任务删除前申请的内存,则需要由用户在任务被删除前提前释放,否则将导致内存泄露。

(2)实现删除任务的流程:

①在FreeRTOSConfig.h文件中将宏INCLUDE_vTaskDelete配置为1。

②入口参数输入需要删除的任务句柄。

(3)任务删除函数的内部实现:

①通过传入的任务句柄,判断所需要删除哪个任务。

②将该任务从其所在列表中移除,包括就绪、阻塞、挂起、事件等列表。

③判断所需要删除的任务:

[1]如果是删除任务自身,需先添加到等待删除列表,内存释放将在空闲任务执行。

[2]如果是删除其它任务,则直接释放内存,任务数量减一。

④更新下一个任务的阻塞超时时间,以防被删除的任务就是下一个阻塞超时的任务。

二、任务创建和删除的动态方法实验

1、原理图与实验目标

(1)原理图:

(2)实验目标:

①设计4个任务——start_task、task1、task2、task3:

[1]start_task:用于创建其它三个任务,然后删除自身。

[2]task1:实现LED1状态反转。

[3]task2:实现LED2状态反转。

[4]task3:按下按键1,删除task1、task2;按下按键2,创建task1、task2。

②预期实验现象:

[1]程序下载到板子上后,两个LED灯闪烁。

[2]按下按键1后,LED灯停止闪烁,接着按下按键2,LED灯恢复闪烁。

2、实验步骤

(1)将上一章中移植了FreeRTOS的工程文件夹复制一份,在拷贝版中进行实验。

(2)在User分组下添加源文件FreeRTOS_experiment.c和头文件FreeRTOS_experiment.h,并在main.c文件中包含FreeRTOS_experiment.h。

(3)在FreeRTOSConfig.h文件中将宏configSUPPORT_DYNAMIC_ALLOCATION配置为1,注意代码的添加位置。

#define configSUPPORT_DYNAMIC_ALLOCATION 1  //支持动态申请内存

(4)核对Key.c文件和LED.c文件,其中出现的延时函数都需要更换为vTaskDelay函数。

①Key.c文件:

#include "stm32f10x.h"                  // Device header
#include "FreeRTOS.h"
#include "Task.h"

/**
  * 函    数:按键初始化
  * 参    数:无
  * 返 回 值:无
  */
void Key_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);		//将PB1和PB11引脚初始化为上拉输入
}

/**
  * 函    数:按键获取键码
  * 参    数:无
  * 返 回 值:按下按键的键码值,范围:0~2,返回0代表没有按键按下
  * 注意事项:此函数是阻塞式操作,当按键按住不放时,函数会卡住,直到按键松手
  */
uint8_t Key_GetNum(void)
{
	uint8_t KeyNum = 0;		//定义变量,默认键码值为0
	
	if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)			//读PB1输入寄存器的状态,如果为0,则代表按键1按下
	{
		vTaskDelay(20);											//延时消抖
		while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0);	    //等待按键松手
		vTaskDelay(20);											//延时消抖
		KeyNum = 1;												//置键码为1
	}
	
	if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0)			//读PB11输入寄存器的状态,如果为0,则代表按键2按下
	{
		vTaskDelay(20);											//延时消抖
		while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0);	    //等待按键松手
		vTaskDelay(20);											//延时消抖
		KeyNum = 2;												//置键码为2
	}
	
	return KeyNum;//返回键码值,如果没有按键按下,所有if都不成立,则键码为默认值0
}

②LED.c文件:

#include "stm32f10x.h"                  // Device header

/**
  * 函    数:LED初始化
  * 参    数:无
  * 返 回 值:无
  */
void LED_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);		  //将PA1和PA2引脚初始化为推挽输出
	
	/*设置GPIO初始化后的默认电平*/
	GPIO_SetBits(GPIOA, GPIO_Pin_1 | GPIO_Pin_2);	 //设置PA1和PA2引脚为高电平
}

/**
  * 函    数:LED1开启
  * 参    数:无
  * 返 回 值:无
  */
void LED1_ON(void)
{
	GPIO_ResetBits(GPIOA, GPIO_Pin_1);		//设置PA1引脚为低电平
}

/**
  * 函    数:LED1关闭
  * 参    数:无
  * 返 回 值:无
  */
void LED1_OFF(void)
{
	GPIO_SetBits(GPIOA, GPIO_Pin_1);		//设置PA1引脚为高电平
}

/**
  * 函    数:LED1状态翻转
  * 参    数:无
  * 返 回 值:无
  */
void LED1_Turn(void)
{
	if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_1) == 0)	//获取输出寄存器的状态,如果当前引脚输出低电平
	{
		GPIO_SetBits(GPIOA, GPIO_Pin_1);				//则设置PA1引脚为高电平
	}
	else												//否则,即当前引脚输出高电平
	{
		GPIO_ResetBits(GPIOA, GPIO_Pin_1);			//则设置PA1引脚为低电平
	}
}

/**
  * 函    数:LED2开启
  * 参    数:无
  * 返 回 值:无
  */
void LED2_ON(void)
{
	GPIO_ResetBits(GPIOA, GPIO_Pin_2);		//设置PA2引脚为低电平
}

/**
  * 函    数:LED2关闭
  * 参    数:无
  * 返 回 值:无
  */
void LED2_OFF(void)
{
	GPIO_SetBits(GPIOA, GPIO_Pin_2);		//设置PA2引脚为高电平
}

/**
  * 函    数:LED2状态翻转
  * 参    数:无
  * 返 回 值:无
  */
void LED2_Turn(void)
{
	if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_2) == 0)	//获取输出寄存器的状态,如果当前引脚输出低电平
	{                                                  
		GPIO_SetBits(GPIOA, GPIO_Pin_2);             //则设置PA2引脚为高电平
	}                                                  
	else                                             //否则,即当前引脚输出高电平
	{                                                  
		GPIO_ResetBits(GPIOA, GPIO_Pin_2);           //则设置PA2引脚为低电平
	}
}

(5)在FreeRTOS_experiment.c文件中添加如下内容。

①包含头文件、宏定义、任务函数声明及任务句柄:

#include "FreeRTOS.h"
#include "task.h"
#include "LED.h"
#include "Key.h"

//宏定义
#define START_TASK_STACK_SIZE 128   //start_task任务的堆栈大小
#define START_TASK_PRIO       1      //start_task任务的优先级
#define TASK1_STACK_SIZE      128    //task1任务的堆栈大小
#define TASK1_PRIO            2       //task1任务的优先级
#define TASK2_STACK_SIZE      128    //task2任务的堆栈大小
#define TASK2_PRIO            3       //task2任务的优先级
#define TASK3_STACK_SIZE      128    //task3任务的堆栈大小
#define TASK3_PRIO            4       //task3任务的优先级

//任务函数声明
void start_task(void);
void task1(void);
void task2(void);
void task3(void);

//任务句柄
TaskHandle_t start_task_handler;     //start_task任务的句柄
TaskHandle_t task1_handler;         //task1任务的句柄
TaskHandle_t task2_handler;         //task2任务的句柄
TaskHandle_t task3_handler;         //task3任务的句柄

②供主函数调用的实验函数:

void FreeRTOS_Test(void)
{
	//创建任务start_task
	xTaskCreate((TaskFunction_t)start_task,          //指向任务函数的指针
				"start_task",                    //任务名字
				START_TASK_STACK_SIZE,     //任务堆栈大小,单位为字
				NULL,                        //传递给任务函数的参数
				START_TASK_PRIO,            //任务优先级
				(TaskHandle_t *) &start_task_handler//任务句柄,就是任务的任务控制块
				);
	
	//开启任务调度器(开启任务调度器后,任务开始按抢占式算法进行调度)
	vTaskStartScheduler();
}

③start_task任务函数:

void start_task(void)
{
	//进入临界区(临界区保护,就是保护那些不想被打断的程序段)
	taskENTER_CRITICAL();
	
	//创建任务task1
	xTaskCreate((TaskFunction_t)task1,             //指向任务函数的指针
				"task1",                        //任务名字
				TASK1_STACK_SIZE,           //任务堆栈大小,单位为字
				NULL,                        //传递给任务函数的参数
				TASK1_PRIO,                  //任务优先级
				(TaskHandle_t *) &task1_handler   //任务句柄,就是任务的任务控制块
				);
	
	//创建任务task2
	xTaskCreate((TaskFunction_t)task2,             //指向任务函数的指针
				"task2",                        //任务名字
				TASK2_STACK_SIZE,           //任务堆栈大小,单位为字
				NULL,                        //传递给任务函数的参数
				TASK2_PRIO,                  //任务优先级
				(TaskHandle_t *) &task2_handler   //任务句柄,就是任务的任务控制块
				);
	
	//创建任务task3
	xTaskCreate((TaskFunction_t)task3,             //指向任务函数的指针
				"task3",                        //任务名字
				TASK3_STACK_SIZE,           //任务堆栈大小,单位为字
				NULL,                        //传递给任务函数的参数
				TASK3_PRIO,                  //任务优先级
				(TaskHandle_t *) &task3_handler   //任务句柄,就是任务的任务控制块
				);
	
	//删除任务自身
	vTaskDelete(NULL);
	
	//退出临界区
	taskEXIT_CRITICAL();
}

④task1任务函数:

void task1(void)
{
	while(1)     //task1永远不停歇(除非被其它任务删除)
	{
		LED1_Turn();      //LED1状态翻转
		vTaskDelay(500);   //延时(自我阻塞)500ms
	}
}

⑤task2任务函数:

void task2(void)
{
	while(1)     //task2永远不停歇(除非被其它任务删除)
	{
		LED2_Turn();      //LED2状态翻转
		vTaskDelay(500);   //延时(自我阻塞)500ms
	}
}

⑥task3任务函数:

void task3(void)
{
	uint8_t key = 0;
	while(1)
	{
		key = Key_GetNum();    //读取按键键值
		if(key == 1)
		{
			if(task1_handler != NULL)        //防止重复删除
			{
				vTaskDelete(task1_handler);
				task1_handler = NULL;
			}
			if(task2_handler != NULL)        //防止重复删除
			{
				vTaskDelete(task2_handler);
				task2_handler = NULL;
			}
		}
		if(key == 2)
		{
			taskENTER_CRITICAL();   //进入临界区
			//创建任务task1
			xTaskCreate((TaskFunction_t)task1,            //指向任务函数的指针
						"task1",                       //任务名字
						TASK1_STACK_SIZE,          //任务堆栈大小,单位为字
						NULL,                       //传递给任务函数的参数
						TASK1_PRIO,                 //任务优先级
						(TaskHandle_t *) &task1_handler  //任务句柄,就是任务的任务控制块
						);
			//创建任务task2
			xTaskCreate((TaskFunction_t)task2,             //指向任务函数的指针
						"task2",                        //任务名字
						TASK2_STACK_SIZE,           //任务堆栈大小,单位为字
						NULL,                        //传递给任务函数的参数
						TASK2_PRIO,                  //任务优先级
						(TaskHandle_t *) &task2_handler   //任务句柄,就是任务的任务控制块
						);
			taskEXIT_CRITICAL();   //退出临界区
		}
		vTaskDelay(10);  //延时(自我阻塞)10ms
	}
}

(6)在FreeRTOS_experiment.h文件中添加如下代码。

#ifndef __FREERTOS_EXPERIMENT
#define __FREERTOS_EXPERIMENT

void FreeRTOS_Test(void);

#endif

(7)在main.c文件中添加如下代码。

#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include "FreeRTOS.h"
#include "task.h"
#include "FreeRTOS_experiment.h"
#include "Key.h"
#include "LED.h"

int main(void)
{
	/*模块初始化*/
	OLED_Init();		   //OLED初始化
	Key_Init();          //Key初始化
	LED_Init();         //LED初始化
	
	FreeRTOS_Test();
	while (1)
	{
		
	}
}

(8)程序完善好后点击“编译”,然后将程序下载到开发板上。

3、程序执行流程

(1)main函数全流程:

①初始化OLED模块、按键模块、LED模块。

②调用FreeRTOS_Test函数。

(2)测试函数全流程:

①创建任务start_task。

②开启任务调度器。

(3)多任务调度执行阶段(发生在开启任务调度器以后):

①start_task任务函数首先进入临界区,在临界区中start_task任务不会被其它任务打断(否则创建task1后,由于task1的优先级较高,start_task任务会被打断),接着start_task任务依次创建任务task1、task2、task3,然后删除自身,接着退出临界区,让出CPU资源。

②三个任务中task3的优先级最高,故优先执行task3,假设先不按下任何按键,task3未接收到任何键码,不做出其它行为,直接自我阻塞10ms,此时task2的优先级最高(除开已阻塞的task3),故执行task2,LED2的状态翻转后,task2自我阻塞500ms,此时未阻塞的就绪任务仅剩task1,故执行task1,LED1的状态翻转后,task1自我阻塞500ms。待task3的自我阻塞时间结束,task3重新抢占CPU,task3自我阻塞后,待task2的自我阻塞时间结束,task2重新抢占CPU,待task1的自我阻塞时间结束,task1重新抢占CPU,以此往复,实现双LED灯闪烁。

③在某次执行task3时,按下按键1,task1和task2将会被删除,LED1和LED2会停止在任务删除前的状态,不再闪烁。(按下按键1的过程中因为按键消抖使用了vTaskDelay函数,task3会进行短暂的自我阻塞,图中未示出,不过由于task3的优先级最高,阻塞时间结束后task3可重新抢占CPU)

④在只有任务task3的情况下,按下按键2,task1和task2将会重新恢复,LED1和LED2将继续闪烁。(按下按键2的过程中因为按键消抖使用了vTaskDelay函数,task3会进行短暂的自我阻塞,图中未示出,不过由于task3的优先级最高,阻塞时间结束后task3可重新抢占CPU)

三、任务创建和删除的静态方法实验

1、原理图与实验目标

(1)原理图:

(2)实验目标:

①设计4个任务——start_task、task1、task2、task3:

[1]start_task:用于创建其它三个任务,然后删除自身。

[2]task1:实现LED1状态反转。

[3]task2:实现LED2状态反转。

[4]task3:按下按键1,删除task1、task2;按下按键2,创建task1、task2。

②预期实验现象:

[1]程序下载到板子上后,两个LED灯闪烁。

[2]按下按键1后,LED灯停止闪烁,接着按下按键2,LED灯恢复闪烁。

2、实验步骤

(1)将上一节“任务创建和删除的动态方法实验”的工程文件夹复制一份,在拷贝版中进行实验。

(2)在FreeRTOSConfig.h文件中将宏configSUPPORT_STATIC_ALLOCATION配置为1,将宏configMINMAL_STACK_SIZE配置为128,同时添加软件定时器相关配置,注意代码的添加位置(理论上保证能编译即可,不要被一些条件编译覆盖掉)。

#define configSUPPORT_STATIC_ALLOCATION  1        //支持静态申请内存
#define configMINMAL_STACK_SIZE           128      //空闲任务的栈空间大小

/* 软件定时器相关定义 */
#define configUSE_TIMERS                   1            //使能软件定时器
#define configTIMER_TASK_PRIORITY        (configMAX_PRIORITIES - 1) 
//定时器任务优先级
#define configTIMER_QUEUE_LENGTH        5            //定时器任务队列长度
#define configTIMER_TASK_STACK_DEPTH    (configMINIMAL_STACK_SIZE * 2)
//定时器任务栈深度

(3)在FreeRTOS_experiment.c中定义空闲任务和定时器任务的任务堆栈及TCB。

StaticTask_t idle_task_tcb;                               //定义空闲任务控制块
StackType_t idle_task_stack[configMINMAL_STACK_SIZE];  //定义空闲任务堆栈

StaticTask_t timer_task_tcb;                                  //定义定时器任务控制块
StackType_t timer_task_stack[configTIMER_TASK_STACK_DEPTH]; //定义定时器任务堆栈

(4)在FreeRTOS_experiment.c中实现接口函数vApplicationGetIdleTaskMemory( )以及vApplicationGetTimerTaskMemory ( )。

//空闲任务内存分配
void vApplicationGetIdleTaskMemory
( StaticTask_t ** ppxIdleTaskTCBBuffer,  //空闲任务控制块
StackType_t ** ppxIdleTaskStackBuffer,  //空闲任务堆栈
uint32_t * pulIdleTaskStackSize)        //空闲任务堆栈大小
{
	* ppxIdleTaskTCBBuffer = &idle_task_tcb;
	* ppxIdleTaskStackBuffer = idle_task_stack;
	* pulIdleTaskStackSize = configMINMAL_STACK_SIZE;
}

//软件定时器内存分配
void vApplicationGetTimerTaskMemory
(StaticTask_t ** ppxTimerTaskTCBBuffer,   //定时器任务控制块
StackType_t ** ppxTimerTaskStackBuffer,   //定时器任务堆栈
uint32_t * pulTimerTaskStackSize)         //定时器任务堆栈大小
{
	* ppxTimerTaskTCBBuffer = &timer_task_tcb;
	* ppxTimerTaskStackBuffer = timer_task_stack;
	* pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;
}

(5)在FreeRTOS_experiment.c中增加如下定义。

//任务堆栈
StackType_t start_task_stack[START_TASK_STACK_SIZE];  //start_task任务的堆栈
StackType_t task1_stack[TASK1_STACK_SIZE];            //task1任务的堆栈
StackType_t task2_stack[TASK2_STACK_SIZE];            //task2任务的堆栈
StackType_t task3_stack[TASK3_STACK_SIZE];            //task3任务的堆栈

//任务控制块
StaticTask_t start_task_tcb;        //start_task任务的控制块
StaticTask_t task1_tcb;             //task1任务的控制块
StaticTask_t task2_tcb;             //task2任务的控制块
StaticTask_t task3_tcb;             //task3任务的控制块

(6)将FreeRTOS_Test函数的实现做如下替换,主要是将动态任务创建函数替换为静态任务创建函数。

void FreeRTOS_Test(void)
{
	//创建任务start_task
	start_task_handler =                         //获取start_task的任务句柄
	xTaskCreateStatic((TaskFunction_t)start_task,    //指向任务函数的指针
                      "start_task",             //任务名字
                      START_TASK_STACK_SIZE,    //任务堆栈大小,单位为字
                      NULL,                //传递给任务函数的参数
                      START_TASK_PRIO,    //任务优先级
                      start_task_stack,         //任务堆栈,一般为数组,由用户分配
                      &start_task_tcb);        //任务控制块指针,由用户分配
	
	//开启任务调度器
	vTaskStartScheduler();
}

(7)将start_task函数和task3函数的实现做如下替换,主要是将动态任务创建函数替换为静态任务创建函数。

void start_task(void)
{
	//进入临界区(临界区保护,就是保护那些不想被打断的程序段)
	taskENTER_CRITICAL();
	
  	//创建任务task1
	task1_handler =                            //获取task1的任务句柄
	xTaskCreateStatic((TaskFunction_t)task1,       //指向任务函数的指针
                      "task1",                //任务名字
                      TASK1_STACK_SIZE,   //任务堆栈大小,单位为字
                      NULL,                //传递给任务函数的参数
                      TASK1_PRIO,          //任务优先级
                      task1_stack,            //任务堆栈,一般为数组,由用户分配
                      &task1_tcb);           //任务控制块指针,由用户分配
	
	//创建任务task2
	task2_handler =                           //获取task2的任务句柄
	xTaskCreateStatic((TaskFunction_t)task2,      //指向任务函数的指针
                      "task2",               //任务名字
                      TASK2_STACK_SIZE,  //任务堆栈大小,单位为字
                      NULL,               //传递给任务函数的参数
                      TASK2_PRIO,         //任务优先级
                      task2_stack,           //任务堆栈,一般为数组,由用户分配
                      &task2_tcb);          //任务控制块指针,由用户分配
	
	//创建任务task3
	task3_handler =                           //获取task3的任务句柄
	xTaskCreateStatic((TaskFunction_t)task3,      //指向任务函数的指针
                      "task3",               //任务名字
                      TASK3_STACK_SIZE,  //任务堆栈大小,单位为字
                      NULL,               //传递给任务函数的参数
                      TASK3_PRIO,         //任务优先级
                      task3_stack,           //任务堆栈,一般为数组,由用户分配
                      &task3_tcb);          //任务控制块指针,由用户分配
	
	//删除任务自身
	vTaskDelete(NULL);
	
	//退出临界区
	taskEXIT_CRITICAL();
}
void task3(void)
{
	uint8_t key = 0;
	while(1)
	{
		key = Key_GetNum();    //读取按键键值
		if(key == 1)
		{
			if(task1_handler != NULL)        //防止重复删除
			{  vTaskDelete(task1_handler);   task1_handler = NULL;  }
			
			if(task2_handler != NULL)        //防止重复删除
			{  vTaskDelete(task2_handler);   task2_handler = NULL;  }
		}
		if(key == 2)
		{
			//进入临界区
			taskENTER_CRITICAL();
			
			//创建任务task1
			task1_handler =                           //获取task1的任务句柄
			xTaskCreateStatic((TaskFunction_t)task1,      //指向任务函数的指针
                              "task1",                //任务名字
                              TASK1_STACK_SIZE,   //任务堆栈大小,单位为字
                              NULL,                //传递给任务函数的参数
                              TASK1_PRIO,          //任务优先级
                              task1_stack,    //任务堆栈,一般为数组,由用户分配
                              &task1_tcb);   //任务控制块指针,由用户分配
			
			//创建任务task2
			task2_handler =                           //获取task2的任务句柄
			xTaskCreateStatic((TaskFunction_t)task2,      //指向任务函数的指针
                              "task2",               //任务名字
                              TASK2_STACK_SIZE,  //任务堆栈大小,单位为字
                              NULL,               //传递给任务函数的参数
                              TASK2_PRIO,                 //任务优先级
                              task2_stack,    //任务堆栈,一般为数组,由用户分配
                              &task2_tcb);   //任务控制块指针,由用户分配
			//退出临界区
			taskEXIT_CRITICAL();
		}
		vTaskDelay(10);  //延时(自我阻塞)10ms
	}
}

(8)程序完善好后点击“编译”,然后将程序下载到开发板上。

3、程序执行流程

(1)main函数全流程:

①初始化OLED模块、按键模块、LED模块。

②调用FreeRTOS_Test函数。

(2)测试函数全流程:

①创建任务start_task。

②开启任务调度器。

(3)多任务调度执行阶段(发生在开启任务调度器以后):

①start_task任务函数首先进入临界区,在临界区中start_task任务不会被其它任务打断(否则创建task1后,由于task1的优先级较高,start_task任务会被打断),接着start_task任务依次创建任务task1、task2、task3,然后删除自身,接着退出临界区,让出CPU资源。

②三个任务中task3的优先级最高,故优先执行task3,假设先不按下任何按键,task3未接收到任何键码,不做出其它行为,直接自我阻塞10ms,此时task2的优先级最高(除开已阻塞的task3),故执行task2,LED2的状态翻转后,task2自我阻塞500ms,此时未阻塞的就绪任务仅剩task1,故执行task1,LED1的状态翻转后,task1自我阻塞500ms。待task3的自我阻塞时间结束,task3重新抢占CPU,task3自我阻塞后,待task2的自我阻塞时间结束,task2重新抢占CPU,待task1的自我阻塞时间结束,task1重新抢占CPU,以此往复,实现双LED灯闪烁。

③在某次执行task3时,按下按键1,task1和task2将会被删除,LED1和LED2会停止在任务删除前的状态,不再闪烁。(按下按键1的过程中因为按键消抖使用了vTaskDelay函数,task3会进行短暂的自我阻塞,图中未示出,不过由于task3的优先级最高,阻塞时间结束后task3可重新抢占CPU)

④在只有任务task3的情况下,按下按键2,task1和task2将会重新恢复,LED1和LED2将继续闪烁。(按下按键2的过程中因为按键消抖使用了vTaskDelay函数,task3会进行短暂的自我阻塞,图中未示出,不过由于task3的优先级最高,阻塞时间结束后task3可重新抢占CPU)

四、动态任务创建和删除详细过程(源码剖析)

1、xTaskCreate函数及其调用到的函数

(1)xTaskCreate函数本体——prvCreateTask函数:

static TCB_t * prvCreateTask( TaskFunction_t pxTaskCode,
                              const char * const pcName,
                              const configSTACK_DEPTH_TYPE uxStackDepth,
                              void * const pvParameters,
                              UBaseType_t uxPriority,
                              TaskHandle_t * const pxCreatedTask )
{
    TCB_t * pxNewTCB;

    #if ( portSTACK_GROWTH > 0 )   //判断栈的生长方向,分别做处理
    {
        pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );

        if( pxNewTCB != NULL )
        {
            ( void ) memset( ( void * ) pxNewTCB, 0x00, sizeof( TCB_t ) );

            pxNewTCB->pxStack = ( StackType_t * ) pvPortMallocStack( ( ( ( size_t ) uxStackDepth ) * sizeof( StackType_t ) ) );

            if( pxNewTCB->pxStack == NULL )
            {
                vPortFree( pxNewTCB );
                pxNewTCB = NULL;
            }
        }
    }
    #else /* STM32中的栈都是向下生长的(堆是向上生长的) */
    {
        StackType_t * pxStack;   //定义任务堆栈

        pxStack = pvPortMallocStack( ( ( ( size_t ) uxStackDepth ) * sizeof( StackType_t ) ) ); //FreeRTOS自动为任务申请栈空间

        if( pxStack != NULL )       //判断栈空间是否申请成功
        {
            pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );  //申请任务控制块专属内存

            if( pxNewTCB != NULL )  //判断任务控制块内存是否申请成功
            {
                ( void ) memset( ( void * ) pxNewTCB, 0x00, sizeof( TCB_t ) );

                pxNewTCB->pxStack = pxStack;  //初始化任务控制块中的pxStack成员
            }
            else
            {
                vPortFreeStack(pxStack);  //任务控制块申请失败,释放之前申请的栈空间
            }
        }
        else
        {
            pxNewTCB = NULL;    //栈空间申请失败,那么任务控制块也创建失败
        }
    }
    #endif /* portSTACK_GROWTH */

    if( pxNewTCB != NULL )   //判断任务控制块内存是否申请成功
    {
        #if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
        {
            pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
        }
        #endif /* tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE */

		//初始化任务控制块中的所有成员
        prvInitialiseNewTask( pxTaskCode, pcName, uxStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
    }

    return pxNewTCB;
}

(2)prvInitialiseNewTask函数:

static void prvInitialiseNewTask( TaskFunction_t pxTaskCode,
                                  const char * const pcName,
                                  const configSTACK_DEPTH_TYPE uxStackDepth,
                                  void * const pvParameters,
                                  UBaseType_t uxPriority,
                                  TaskHandle_t * const pxCreatedTask,
                                  TCB_t * pxNewTCB,
                                  const MemoryRegion_t * const xRegions )
{
    StackType_t * pxTopOfStack;
    UBaseType_t x;

    #if ( portUSING_MPU_WRAPPERS == 1 )
        BaseType_t xRunPrivileged;

        if( ( uxPriority & portPRIVILEGE_BIT ) != 0U )
        {
            xRunPrivileged = pdTRUE;
        }
        else
        {
            xRunPrivileged = pdFALSE;
        }
        uxPriority &= ~portPRIVILEGE_BIT;
    #endif /* portUSING_MPU_WRAPPERS == 1 */

    #if ( tskSET_NEW_STACKS_TO_KNOWN_VALUE == 1 )
    {
        //用一个值将堆栈填满,用于调试
        ( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) uxStackDepth * sizeof( StackType_t ) );
    }
    #endif /* tskSET_NEW_STACKS_TO_KNOWN_VALUE */

    #if ( portSTACK_GROWTH < 0 )   //判断栈的生长方向,分别做处理
    {
        pxTopOfStack = &( pxNewTCB->pxStack[ uxStackDepth - ( configSTACK_DEPTH_TYPE ) 1 ] );
        pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );  //栈顶指针

        configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0U ) );

        #if ( configRECORD_STACK_HIGH_ADDRESS == 1 )
        {
            pxNewTCB->pxEndOfStack = pxTopOfStack;
        }
        #endif /* configRECORD_STACK_HIGH_ADDRESS */
    }
    #else /* STM32中的栈都是向下生长的(堆是向上生长的),编译上面的代码块 */
    {
        pxTopOfStack = pxNewTCB->pxStack;
        pxTopOfStack = ( StackType_t * ) ( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) + portBYTE_ALIGNMENT_MASK ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );

        configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0U ) );

        pxNewTCB->pxEndOfStack = pxNewTCB->pxStack + ( uxStackDepth - ( configSTACK_DEPTH_TYPE ) 1 );
    }
    #endif /* portSTACK_GROWTH */

    if( pcName != NULL )
    {
        for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
        {
            pxNewTCB->pcTaskName[ x ] = pcName[ x ];   //任务名初始化(任务名有最大长度限制)

            if( pcName[ x ] == ( char ) 0x00 )
            {
                break;
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }

        pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1U ] = '\0';
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }

    configASSERT( uxPriority < configMAX_PRIORITIES );

    if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
    {
        uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }

    pxNewTCB->uxPriority = uxPriority;  //任务优先级初始化
    #if ( configUSE_MUTEXES == 1 )
    {
        pxNewTCB->uxBasePriority = uxPriority;
    }
    #endif /* configUSE_MUTEXES */

    vListInitialiseItem( &( pxNewTCB->xStateListItem ) );   //初始化状态列表项
    vListInitialiseItem( &( pxNewTCB->xEventListItem ) );  //初始化事件列表项

    listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );  //状态列表项的归属为该任务

	//如果几个任务都需要同一个事件唤醒,那么事件发生时会按照任务优先级选择首先唤醒的任务
    listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority );
    listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );  //事件列表项的归属为该任务

    #if ( portUSING_MPU_WRAPPERS == 1 )
    {
        vPortStoreTaskMPUSettings( &( pxNewTCB->xMPUSettings ), xRegions, pxNewTCB->pxStack, uxStackDepth );
    }
    #else
    {
        ( void ) xRegions;
    }
    #endif

    #if ( configUSE_C_RUNTIME_TLS_SUPPORT == 1 )
    {
        configINIT_TLS_BLOCK( pxNewTCB->xTLSBlock, pxTopOfStack );
    }
    #endif

    #if ( portUSING_MPU_WRAPPERS == 1 )
    {
        #if ( portHAS_STACK_OVERFLOW_CHECKING == 1 )
        {
            #if ( portSTACK_GROWTH < 0 )
            {
                pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxStack, pxTaskCode, pvParameters, xRunPrivileged, &( pxNewTCB->xMPUSettings ) );
            }
            #else /* portSTACK_GROWTH */
            {
                pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxEndOfStack, pxTaskCode, pvParameters, xRunPrivileged, &( pxNewTCB->xMPUSettings ) );
            }
            #endif /* portSTACK_GROWTH */
        }
        #else /* portHAS_STACK_OVERFLOW_CHECKING */
        {
            pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters, xRunPrivileged, &( pxNewTCB->xMPUSettings ) );
        }
        #endif /* portHAS_STACK_OVERFLOW_CHECKING */
    }
    #else /* portUSING_MPU_WRAPPERS */
    {
        //栈空间初始化
        #if ( portHAS_STACK_OVERFLOW_CHECKING == 1 )
        {
            #if ( portSTACK_GROWTH < 0 )
            {
                pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxStack, pxTaskCode, pvParameters );
            }
            #else /* portSTACK_GROWTH */
            {
                pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxEndOfStack, pxTaskCode, pvParameters );
            }
            #endif /* portSTACK_GROWTH */
        }
        #else /* portHAS_STACK_OVERFLOW_CHECKING */
        {
            pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
        }
        #endif /* portHAS_STACK_OVERFLOW_CHECKING */
    }
    #endif /* portUSING_MPU_WRAPPERS */

    #if ( configNUMBER_OF_CORES > 1 )
    {
        pxNewTCB->xTaskRunState = taskTASK_NOT_RUNNING;

        if( ( ( TaskFunction_t ) pxTaskCode == ( TaskFunction_t ) prvIdleTask ) || ( ( TaskFunction_t ) pxTaskCode == ( TaskFunction_t ) prvPassiveIdleTask ) )
        {
            pxNewTCB->uxTaskAttributes |= taskATTRIBUTE_IS_IDLE;
        }
    }
    #endif /* #if ( configNUMBER_OF_CORES > 1 ) */

    if( pxCreatedTask != NULL )
    {
        *pxCreatedTask = ( TaskHandle_t ) pxNewTCB;  //初始化任务句柄
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
}

(3)pxPortInitialiseStack函数:

①该函数用于初始化栈空间中的内容,如下图所示(图示中的“寄存器”并不是说申请的内存等于寄存器,而是指后续数据出栈时会传入相应的寄存器),pxTopOfStack指针从高地址的初始位置开始往下移动,同时往栈空间中写入相应的数据,然后将移动后的栈指针返回。

[1]寄存器xPSR被初始为0x01000000,其中bit24被置1,表示使用Thumb指令。

[2]寄存器PC被初始化为任务函数指针,假设是vTask_A(对应任务A),当某次任务切换后,任务A获得CPU控制权,任务函数vTask_A被出栈到PC寄存器,之后会执行任务A的代码。

[3]LR寄存器初始化为函数指针prvTaskExitError,这是由移植层提供的一个出错处理函数。

[4]子函数的调用通过寄存器R0~R3传递参数,创建任务时,传入的参数被保存到R0中,用来向任务传递参数。

②源码剖析:

StackType_t * pxPortInitialiseStack( StackType_t * pxTopOfStack,
                                     TaskFunction_t pxCode,
                                     void * pvParameters )
{
    /* Simulate the stack frame as it would be created by a context switch interrupt. */
    pxTopOfStack--;     /* Offset added to account for the way the MCU uses the stack on entry/exit of interrupts. */
    *pxTopOfStack = portINITIAL_XPSR;           /* xPSR的bit24位置1 */
    pxTopOfStack--;
    *pxTopOfStack = ((StackType_t) pxCode) & portSTART_ADDRESS_MASK; /* PC */
    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;
}

(4)prvAddNewTaskToReadyList函数:

static void prvAddNewTaskToReadyList( TCB_t * pxNewTCB )
{
    taskENTER_CRITICAL();
    {
        uxCurrentNumberOfTasks = ( UBaseType_t ) ( uxCurrentNumberOfTasks + 1U );  //任务数量+1

        if( pxCurrentTCB == NULL )     //判断新创建的任务是否是第一个任务
        {
            pxCurrentTCB = pxNewTCB;  //pxCurrentTCB指向新创建的任务

            if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 )  //再次确认新创建的任务是否是第一个任务
            {
				//初始化32个就绪列表(每个优先级各1个)、2个延时列表、1个等待就绪列表、1个等待删除列表、1个挂起列表
				prvInitialiseTaskLists();
			}
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        else
        {
            if( xSchedulerRunning == pdFALSE )  //判断调度器是否未启动
            {
                if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
                {
                    pxCurrentTCB = pxNewTCB;  //如果新创建的任务优先级较高,pxCurrentTCB要指向优先级更高的任务
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }

        uxTaskNumber++;  //任务标号自增

        #if ( configUSE_TRACE_FACILITY == 1 )
        {
            pxNewTCB->uxTCBNumber = uxTaskNumber;
        }
        #endif /* configUSE_TRACE_FACILITY */
        traceTASK_CREATE( pxNewTCB );

        prvAddTaskToReadyList( pxNewTCB );  //将新创建的任务添加进就绪列表中

        portSETUP_TCB( pxNewTCB );
    }
    taskEXIT_CRITICAL();

    if( xSchedulerRunning != pdFALSE )  //判断任务调度器是否开启
    {
		//如果当前任务的优先级非最高,执行一次任务切换,调度优先级最高的任务执行
        taskYIELD_ANY_CORE_IF_USING_PREEMPTION( pxNewTCB );
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
}

 

2、vTaskDelete函数本体

void vTaskDelete( TaskHandle_t xTaskToDelete )
{
    TCB_t * pxTCB;
    BaseType_t xDeleteTCBInIdleTask = pdFALSE;
    BaseType_t xTaskIsRunningOrYielding;

    traceENTER_vTaskDelete( xTaskToDelete );

    taskENTER_CRITICAL();  //进入临界区(关中断)
    {
        pxTCB = prvGetTCBFromHandle( xTaskToDelete );  //获取要删除的任务控制块

		//将任务移出状态列表,并判断移出后列表是否为空,是则将列表对应的位清零
        if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
            taskRESET_READY_PRIORITY( pxTCB->uxPriority );
        else
            mtCOVERAGE_TEST_MARKER();

        //判断该任务是否在等待某个事件,是则将其事件列表项移除
        if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
            ( void ) uxListRemove( &( pxTCB->xEventListItem ) );
        else
            mtCOVERAGE_TEST_MARKER();

        uxTaskNumber++;

        xTaskIsRunningOrYielding = taskTASK_IS_RUNNING_OR_SCHEDULED_TO_YIELD( pxTCB );

        //判断当前运行的任务是否是需要删除的任务(以及任务调度器是否已开启)
        if((xSchedulerRunning !=pdFALSE) && (xTaskIsRunningOrYielding !=pdFALSE))
        {
            vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xStateListItem ) );  //将任务移动至等待删除列表

            ++uxDeletedTasksWaitingCleanUp;  //等待删除列表任务数自增

            traceTASK_DELETE( pxTCB );

            xDeleteTCBInIdleTask = pdTRUE;

            #if ( configNUMBER_OF_CORES == 1 )
                portPRE_TASK_DELETE_HOOK( pxTCB, &( xYieldPendings[ 0 ] ) );
            #else
                portPRE_TASK_DELETE_HOOK( pxTCB, &( xYieldPendings[ pxTCB->xTaskRunState ] ) );
            #endif

            #if ( configNUMBER_OF_CORES > 1 )
            {
                if( taskTASK_IS_RUNNING( pxTCB ) == pdTRUE )
                {
                    if(pxTCB->xTaskRunState == ( BaseType_t )portGET_CORE_ID() )
                    {
                        configASSERT( uxSchedulerSuspended == 0 );
                        taskYIELD_WITHIN_API();
                    }
                    else
                    {
                        prvYieldCore( pxTCB->xTaskRunState );
                    }
                }
            }
            #endif /* #if ( configNUMBER_OF_CORES > 1 ) */
        }
        else
        {
            --uxCurrentNumberOfTasks;   //现有的任务总数自减
            traceTASK_DELETE( pxTCB );

            prvResetNextTaskUnblockTime();  //更新下一个任务的阻塞超时时间
        }
    }
    taskEXIT_CRITICAL();  //退出临界区(开中断)

    if( xDeleteTCBInIdleTask != pdTRUE ) //如果要删除的任务不是当前正在运行的任务
    {
        prvDeleteTCB( pxTCB );   //释放任务的堆栈内存
    }

    #if ( configNUMBER_OF_CORES == 1 )
    {
        if( xSchedulerRunning != pdFALSE )     //判断任务调度器是否已开启
        {
            if( pxTCB == pxCurrentTCB )      //判断需要删除的任务是否正在运行
            {
                configASSERT( uxSchedulerSuspended == 0 );
                taskYIELD_WITHIN_API();      //执行一次任务切换
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
    }
    #endif /* #if ( configNUMBER_OF_CORES == 1 ) */

    traceRETURN_vTaskDelete();
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zevalin爱灰灰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值