资讯动态

FreeRTOS任务的创建与删除

发布时间:2026/9/8 17:50:47 来源:尧图企业网站定制
目录一、创建函数初步介绍1. 动态创建任务函数1.1 函数说明1.2 动态创建任务的步骤1.3 动态创建任务函数的内部实现2. 静态创建任务函数2.1 函数说明2.2 静态创建任务步骤2.3 静态创建任务的内部实现二、删除函数初步介绍1. 函数说明2. 删除流程3. 内部实现过程三、动态创建任务函数详细1. 项目的背景2. 编写代码3. 相关补充一、创建函数初步介绍API函数描述xTaskCreate()动态方式创建任务xTaskCreateStatic()静态方式创建任务vTaskDelete()删除任务动态创建任务任务的任务控制块TCB和任务的栈空间所需的内存均有FreeRTOS自己分配静态创建任务任务的任务控制块TCB和任务的栈空间所需的内存需用户分配提供。1. 动态创建任务函数1.1 函数说明BaseType_t xTaskCreate ( TaskFunction_t pxTaskCode, /* 指向任务函数的指针 */ const char * const pcName, /* 任务名字最大长度configMAX_TASK_NAME_LEN */ const configSTACK_DEPTH_TYPE usStackDepth, /* 任务堆栈大小默认单位4字节 1字4字节*/ void * const pvParameters, /* 传递给任务函数的参数 */ UBaseType_t uxPriority, /* 任务优先级范围0 ~ configMAX_PRIORITIES - 1 */ TaskHandle_t * const pxCreatedTask /* 任务句柄就是任务的任务控制块 */ )创建完成后参数会往任务控制块TCB中存放随后句柄存放此任务在TCB中的地址。函数的返回值说明pdPASS任务创建成功。errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY任务创建失败。1.2 动态创建任务的步骤将宏 configSUPPORT_DYNAMIC_ALLOCATION配置为 1定义函数入口参数。编写任务函数。至于为什么要将宏 configSUPPORT_DYNAMIC_ALLOCATION配置为1呢我们用 ctrl F在工程里面搜索将会在 FreeRTOS.h 中得到因为在工程中有的宏不是说一开始就有初始值的而是先经过一些初始化后发现没有自动定义赋初值的所以说宏 configSUPPORT_DYNAMIC_ALLOCATION一开始就是为1默认就是开启的。人话总结自己先定义好一个句柄类型的变量然后对该变量的成员进行赋值然后再将该句柄的地址交给函数函数就会在os里面申请tcb并填上对应的结构体成员的值注此函数创建的任务会立刻进入就绪状态由任务调度器调度运行。1.3 动态创建任务函数的内部实现申请堆栈内存任务控制块内存。TCB结构体成员赋值。添加新任务到就绪列表第一步申请栈内存和任务控制块内存是由FreeRTOS自动帮完成的然后接下来是第二部分重点TCB结构体成员的赋值其实上是我们在调用xTaskCreate()创建任务时就已经给TCB结构体成员赋值好了。但是有一个疑惑就是这两个结构体的成员也不一样啊但是为什么给xTaskCreate()赋值就会给TCB结构体赋值呢原因就在于xTaskCreate()层属于接口层面面向程序人员而TCB结构体属于实现层他们之间是一种映射关系而非一对一关系的。xTaskCreate 函数内部并不会傻傻地列出一行 tcb-参数1 传入的参数1。它会创建一个中间任务来调用一个私有初始化函数通常叫 prvInitialiseNewTask来把接口参数“填充”或“映射”到 TCB 结构体的对应成员中。通过xTaskCreate()创建好任务后任务就会自动处于就绪列表。TCB结构体成员介绍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. 静态创建任务函数2.1 函数说明TaskHandle_t xTaskCreateStatic ( TaskFunction_t pxTaskCode, /* 指向任务函数的指针 */ const char * const pcName, /* 任务函数名 */ const uint32_t ulStackDepth, /* 任务堆栈大小,单位是4字节 */ void * const pvParameters, /* 传递的任务函数参数 */ UBaseType_t uxPriority, /* 任务优先级 */ StackType_t * const puxStackBuffer, /* 任务堆栈一般为数组由用户分配 */ StaticTask_t * const pxTaskBuffer /* 任务控制块指针由用户分配 */ )跟动态的区别就是动态的句柄变为了两个成员变量因为静态是用户自己设置那么静态创建的任务句柄呢请看返回值返回值如下NULL用户没有提供相应的内存任务创建失败。其他值任务句柄任务创建成功。2.2 静态创建任务步骤将宏configSUPPORT_STATIC_ALLOCATION 配置为 1。定义空闲任务定时器任务的任务堆栈及TCB实现接口函数vApplicationGetIdleTaskMemory()vApplicationGetTimerTaskMemory()如果开启软件定时器定义函数入口参数。编写任务函数。我还没明白稍等此函数创建的任务会立刻进入就绪态由任务调度器调度运行。2.3 静态创建任务的内部实现TCB结构体成员变量赋值添加新任务到就绪列表二、删除函数初步介绍1. 函数说明/* 删除函数 * 参数为要删除任务的句柄 * TaskHandle_t表示任务句柄类型 */ void vTaskDelete( TaskHandle_t xTaskToDelete )当输入参数为NULL时表示删除任务自身当前正在运行的任务注参数是句柄是因为句柄是任务在TCB中的地址TCB是实现层删除是要删除TCB实现层。该函数用于删除已被创建的任务被删除的任务将从就绪态任务列表、阻塞态任务列表、挂起态任务列表和事件列表中移除。我们的删除函数实际上是标记删除真正删除是靠空闲任务来释放被删除任务中由系统分配的内存。需要注意的一点是由用户在任务中申请的内存则需要用户在任务删除前释放出来否则会导致内存泄漏。2. 删除流程使用删除任务函数需将宏INCLUDE_vTaskDelete 配置为 1入口参数输入需要删除的任务句柄NULL代表删除本身3. 内部实现过程获取所要删除任务的控制块句柄就是TCB控制块的地址通过传入的任务句柄判断所需要删除哪个任务NULL代表删除自身。将被删除任务移除所在列表将该任务在所在列表中移除包括就绪、阻塞、挂起、事件等列表。判断所需要删除的任务如果删除任务自身需先添加到等待删除列表因为删除函数实际上就是标记删除内存释放将在空闲任务执行如果删除其他任务释放内存任务数量 - 1。更新下个任务的阻塞时间更新下一个任务的阻塞超时时间以防被删除的任务就是下一个阻塞超时的任务。举例假如我在一个任务的中间执行删除函数由于删除函数是标记删除他会把自身任务添加到等待删除列表后面的代码继续执行。随后任务删除后等到空闲任务执行时会释放系统分配的内存。然后任务数量-1。三、动态创建任务函数详细1. 项目的背景PA8~11分别为KEY1~4PB3~5分别为LED1~3再打开usart2:2. 过程首先我们需要编写FreeRTOS_Demo.h文件还是.h文件的老套路#ifndef __FREERTOS_DEMO_H__ #define __FREERTOS_DEMO_H__ #endif仅此而已。我们来看FreeRTOS_Demo.c文件我们先导入一些RTOS必要的.h文件/* FreeRTOS最基本的头文件必须要有的 */ #include FreeRTOS.h #include task.h随后根据实际情况导入一些需要的头文件/* 需要用到的其他头文件根据实际情况添加 */ #include LED.h #include KEY.h #include usart.h #include FreeRTOS_Demo.h首先我们要先创建一个启动任务函数启动任务函数中需要创建一个启动任务和开启任务调度器我们要提前定义好启动任务需要用到的参数/* 启动任务函数的配置 */ #define TASK_Start_STACK 128 //任务栈大小 #define TASK_Start_PRIORITY 1 //任务优先级 TaskHandle_t Task_Start_Handle; //任务句柄 void Task_Start(void *pvParameters); //启动任务 /* 启动任务函数 */ void FreeRTOS_Start(void) { /* 1.创建一个启动任务 */ xTaskCreate( (TaskFunction_t)Task_Start, //任务启动函数的地址 (char *)Task_Start, //任务名称 (configSTACK_DEPTH_TYPE) TASK_Start_STACK, //任务栈大小以字为单位默认最小是128字 (void *) NULL, //任务形参由于没有参数就填NULL (UBaseType_t) TASK_Start_PRIORITY, //任务优先级范围是0 ~ configMAX_PRIORITIES-1 (TaskHandle_t *) Task_Start_Handle //任务句柄用于储存TCB任务控制块的地址 ); /* 2.启动调度器 */ vTaskStartScheduler(); }随后就是编写启动任务我们是通过这个启动任务来启动自己的任务的所以我们需要先提前定义好创建自己的任务时的相关参数/* 任务1的配置 */ #define TASK1_STACK 128 #define TASK1_PRIORITY 2 TaskHandle_t Task1_Handle; void Task1(void *pvParameters); /* 任务2的配置 */ #define TASK2_STACK 128 #define TASK2_PRIORITY 3 TaskHandle_t Task2_Handle; void Task2(void *pvParameters); /* 任务3的配置 */ #define TASK3_STACK 128 #define TASK3_PRIORITY 4 TaskHandle_t Task3_Handle; void Task3(void *pvParameters); void Task_Start(void *pvParameters) { /* 进入临界区防止其他任务在创建任务时被调度 */ taskENTER_CRITICAL(); //临界区有进入也有退出进入时要关闭中断退出时要开启中断 /* 创建任务1 */ xTaskCreate( (TaskFunction_t)Task1, (char *)Task1, (configSTACK_DEPTH_TYPE) TASK1_STACK, (void *) NULL, (UBaseType_t) TASK1_PRIORITY, (TaskHandle_t *) Task1_Handle ); /* 创建任务2 */ xTaskCreate( (TaskFunction_t)Task2, (char *)Task2, (configSTACK_DEPTH_TYPE) TASK2_STACK, (void *) NULL, (UBaseType_t) TASK2_PRIORITY, (TaskHandle_t *) Task2_Handle ); /* 创建任务3 */ xTaskCreate( (TaskFunction_t)Task3, (char *)Task3, (configSTACK_DEPTH_TYPE) TASK3_STACK, (void *) NULL, (UBaseType_t) TASK3_PRIORITY, (TaskHandle_t *) Task3_Handle ); /* 删除启动任务自身 */ vTaskDelete(NULL); /* 退出临界区 */ taskEXIT_CRITICAL(); }随后就是编写自己的实际任务了/* 任务1LED1闪烁 */ void Task1(void *pvParameters) { for(;;) { printf(Task1执行...\r\n); LED_Turn_Toggle(LED1_Pin); vTaskDelay(500); //这里的延时不能使用HAL_Delay()因为HAL_Delay不会使任务进入阻塞态需要用RTOS专用的延时函数 } } /* 任务2LED2闪烁 */ void Task2(void *pvParameters) { for(;;) { printf(任务2执行...\r\n); LED_Turn_Toggle(LED2_Pin); vTaskDelay(500); } } /* 任务3按键1按下就删除任务1 */ void Task3(void *pvParameters) { uint8_t key 0; while(1) { key Key_Detect(); if(key KEY1_PRESS) { /* 防止重复删除任务1 */ if(Task1_Handle ! NULL) { printf(任务3执行...\r\n); /* 删除任务1 */ vTaskDelete(Task1_Handle); } } vTaskDelay(500); /* 因为任务3的优先级最高所以需要延时500ms让他让出CPU资源给任务1和任务2 */ } }需要注意的点启动任务 void Task_Start(void *pvParameters) 的最后要删除自身 vTaskDelete(NULL);因为启动任务只能启动一次不能重复启动我们在自己的任务中使用延时函数时不能使用HAL_Delay()因为这个延时函数不能让任务进入阻塞态我们要使用RTOS提供的延时函数vTaskDelay()在启动任务的开始和结尾我们要添加进入临界区和退出临界区的函数 taskENTER_CRITICAL(); 和 taskEXIT_CRITICAL();这是为了防止中断或任务切换在系统核心数据结构还未完全初始化时介入从而导致系统崩溃随后来看一下LED和KEY的代码LED.c和LED.h/*---------------------LED.h begin---------------------*/ #ifndef __LED_H__ #define __LED_H__ #include gpio.h #define LED uint16_t void LED_Turn_ON(LED led); void LED_Turn_OFF(LED led); void LED_Turn_Toggle(LED led); void LED_Turn_Off_All(LED led[], LED len); #endif /*---------------------LED.h end-----------------------*/ /*---------------------LED.c begin----------------------*/ #include LED.h void LED_Turn_ON(LED led) { HAL_GPIO_WritePin(GPIOB, led, GPIO_PIN_SET); } void LED_Turn_OFF(LED led) { HAL_GPIO_WritePin(GPIOB, led, GPIO_PIN_RESET); } void LED_Turn_Toggle(LED led) { HAL_GPIO_TogglePin(GPIOB, led); } void LED_Turn_Off_All(LED led[], LED len) { LED i; for(i 0; i len; i) { LED_Turn_OFF(led[i]); } } /*---------------------LED.c end------------------------*/KEY.c和KEY.h/*---------------------KEY.h begin----------------------*/ #ifndef __KEY_H__ #define __KEY_H__ #include main.h #define KEY1 HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) #define KEY2 HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin) #define KEY3 HAL_GPIO_ReadPin(KEY3_GPIO_Port, KEY3_Pin) #define KEY4 HAL_GPIO_ReadPin(KEY4_GPIO_Port, KEY4_Pin) #define KEY1_PRESS 1 #define KEY2_PRESS 2 #define KEY3_PRESS 3 #define KEY4_PRESS 4 uint8_t Key_Detect(void); #endif /*---------------------KEY.h end------------------------*/ /*---------------------KEY.c begin----------------------*/ #include Key.h uint8_t Key_Detect(void) { uint8_t res 0; if(KEY1 GPIO_PIN_RESET || KEY2 GPIO_PIN_RESET || KEY3 GPIO_PIN_RESET || KEY4 GPIO_PIN_RESET) { HAL_Delay(10); if(KEY1 GPIO_PIN_RESET) res KEY1_PRESS; if(KEY2 GPIO_PIN_RESET) res KEY2_PRESS; if(KEY3 GPIO_PIN_RESET) res KEY3_PRESS; if(KEY4 GPIO_PIN_RESET) res KEY4_PRESS; } return res; } /*---------------------KEY.c end------------------------*/3. 相关补充我们的FreeRTOS_Start()启动任务相关代码void FreeRTOS_Start(void) { /* 1.创建一个启动任务 */ xTaskCreate( (TaskFunction_t)Task_Start, //任务启动函数的地址 (char *)Task_Start, //任务名称 (configSTACK_DEPTH_TYPE) TASK_Start_STACK, //任务栈大小以字为单位默认最小是128字 (void *) NULL, //任务形参由于没有参数就填NULL (UBaseType_t) TASK_Start_PRIORITY, //任务优先级范围是0 ~ configMAX_PRIORITIES-1 (TaskHandle_t *) Task_Start_Handle //任务句柄用于储存TCB任务控制块的地址 ); /* 2.启动调度器 */ vTaskStartScheduler(); } void Task_Start(void *pvParameters) { /* 进入临界区防止其他任务在创建任务时被调度 */ taskENTER_CRITICAL(); //临界区有进入也有退出进入时要关闭中断退出时要开启中断 /* 创建任务1 */ xTaskCreate( (TaskFunction_t)Task1, (char *)Task1, (configSTACK_DEPTH_TYPE) TASK1_STACK, (void *) NULL, (UBaseType_t) TASK1_PRIORITY, (TaskHandle_t *) Task1_Handle ); /* 创建任务2 */ xTaskCreate( (TaskFunction_t)Task2, (char *)Task2, (configSTACK_DEPTH_TYPE) TASK2_STACK, (void *) NULL, (UBaseType_t) TASK2_PRIORITY, (TaskHandle_t *) Task2_Handle ); /* 创建任务3 */ xTaskCreate( (TaskFunction_t)Task3, (char *)Task3, (configSTACK_DEPTH_TYPE) TASK3_STACK, (void *) NULL, (UBaseType_t) TASK3_PRIORITY, (TaskHandle_t *) Task3_Handle ); /* 删除启动任务自身 */ vTaskDelete(NULL); /* 退出临界区 */ taskEXIT_CRITICAL(); }我们创建好启动任务后会启动任务调度器然后任务调度器就会开始调度任务此时只有一个任务就是启动任务所以他就会开始执行启动任务然后创建三个任务。此刻我们有一个任务就是启动任务那么我们执行启动任务是先创建任务1再创建任务2最后创建任务3如果没有临界区的话那么就会导致刚创建好任务1时因为启动任务和任务1的优先级不同我们的案例的优先级排序是启动任务 任务1 任务2 任务3 。就会让CPU先执行任务1只有当任务1含有延时时才会让出CPU继续去执行启动任务再创建任务2随后还是一样的道理。有延时才会让出CPU去执行启动任务直到启动任务执行完毕。所以临界区是让启动任务把所有任务创建好后才被任务调度器嗲用而不是创建部分任务后就被任务调度器调用。四、静态创建任务函数详细1. 静态相较于动态的区别区别就在于这两个需要用户自己去实现我们需要先创建好然后把地址传进来2. 过程我们在启动函数中静态创建任务时输入xTaskCreateStatic()会发现没有代码提示并且还进不去函数。这是因为我们需要在 FreeRTOSConfig.h 里面定义一个宏#define configSUPPORT_STATIC_ALLOCATION 1这个宏的意思就是是否支持静态分配只有我们添加了这个宏后才会进入xTaskCreateStatic()函数内部注动态分配默认是开启的静态创建的函数因为静态和动态创建时前面几个参数都一样所以可以直接复制动态的参数。最后两个参数是任务栈的地址和TCB的地址栈链表或数组的实现栈和TCB都需要我们自己来实现随后把这两个参数赋值给静态创建的函数就好最后一步就是静态创建的返回值是一个句柄最终是这样/* 1.创建一个启动任务 */ Task_Start_Handle xTaskCreateStatic( (TaskFunction_t)Task_Start, //任务启动函数的地址 (char *)Task_Start, //任务名称 (uint32_t)(configSTACK_DEPTH_TYPE) TASK_Start_STACK, //任务栈大小以字为单位默认最小是128字 (void *) NULL, //任务形参由于没有参数就填NULL (UBaseType_t) TASK_Start_PRIORITY, //任务优先级范围是0 ~ configMAX_PRIORITIES-1 (StackType_t *)Task_Start_Stack, //任务栈的地址 (StaticTask_t *) Task_Start_Tcb //TCB地址 );然后就是把启动任务中三个创建任务的函数按照以上步骤创建就好最后的最后就是我们还需要实现两个函数我们就是通过vApplicationGetIdleTaskMemory接口函数来处理用户自己的资源的。而且当我们打开定时器任务时把宏 congigUSE_TIMERS赋值为1时会为我们创建一个定时器任务话不多说直接来实现两个接口函数在此之前要在FreeRTOSConfig.h中先添加定时器相关的配置/* 软件定时器的配置 */ #define configUSE_TIMERS 1 /* 1: 使能软件定时器, 默认: 0 */ #define configTIMER_TASK_PRIORITY ( configMAX_PRIORITIES - 1 ) /* 定义软件定时器任务的优先级, 无默认configUSE_TIMERS为1时需定义 */ #define configTIMER_QUEUE_LENGTH 5 /* 定义软件定时器命令队列的长度, 无默认configUSE_TIMERS为1时需定义 */ #define configTIMER_TASK_STACK_DEPTH ( configMINIMAL_STACK_SIZE * 2) /* 定义软件定时器任务的栈空间大小, 无默认configUSE_TIMERS为1时需定义 *//* 静态创建方式需要手动指定两个特殊任务的资源 */ /* 空闲任务的配置 */ StackType_t Idle_Task_Stack[configMINIMAL_STACK_SIZE]; //静态任务的栈以数组形式表示 StaticTask_t Idle_Task_Tcb; //静态任务的TCB结构体 /* 软件定时器的配置 */ StackType_t Timer_Task_Stack[configTIMER_TASK_STACK_DEPTH]; //静态任务的栈以数组形式表示 StaticTask_t Timer_Task_Tcb; //静态任务的TCB结构体 /* 分配空闲任务的资源*/ void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer, //TCB任务控制块的地址 StackType_t ** ppxIdleTaskStackBuffer, //任务空间栈的地址 uint32_t * pulIdleTaskStackSize ) //任务栈大小以字为单位 { *ppxIdleTaskTCBBuffer Idle_Task_Tcb; *ppxIdleTaskStackBuffer Idle_Task_Stack; *pulIdleTaskStackSize configMINIMAL_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; }

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价