RTOS下的任务管理
2.3 RTOS下的任务管理(***)
RTOS的任务管理主要是进行哪些功能?
RTOS的任务管理的多任务管理是怎样进行与实现的?
任务管理中FreeRTOS如何给每个任务分配CPU时间?
文章目录
- 2.3 RTOS下的任务管理(***)
- 2.3.0 任务概述
- 2.3.1任务的创建与删除
- 2.3.2 任务的优先级和Tick
- 2.3.4 任务状态
- 2.3.5 任务相关的Delay函数
- 2.3.6 调度算法(**)
2.3.0 任务概述
任务管理算是FreeRTOS中核心以及重点内容。简而言之,后续代码移植嫁接便是在任务管理的基础上进行的。对于任务管理的重中之重则是在于多任务管理。任务具有一系列状态以及优先级(Priority)等相关属性。任务的一系列相关数据是在栈中保存的,多个任务之间的协作处理[^231]称之为任务调度(即CPU按照某种算法策略进行不同任务间的切换)。
任务即需要完成的一件件功能,也称之为“线程(thread)”,需要注意的RTOS中一个完整的任务由任务栈(Stack)+任务控制块(TCB)组成。
任务的几大相关状态:
- 运行态
- 非运行态:
- 就绪态(ready)
- 阻塞(blocked)
- 挂起(suspended)
2.3.1任务的创建与删除
RTOS任务相关的函数有哪些?怎样创建任务
在RTOS中任务的本质即一个函数,其这个函数不能有返回,一个任务式的函数通常如下:
- 其传递的形参通常为指针类型
- 各自任务存在各自栈的备份(副本)
void ATaskFunction( void *pvParameters )
{
/* 对于不同的任务,局部变量放在任务的栈里,有各自的副本 */
int32_t lVariableExample = 0;
/* 任务函数通常实现为一个无限循环 */
for( ;; )
{
/* 任务的代码 */
}
/* 如果程序从循环中退出,一定要使用vTaskDelete删除自己
* NULL表示删除的是自己
*/
vTaskDelete( NULL );
/* 程序不会执行到这里, 如果执行到这里就出错了 */
}
RTOS进行任务创建时主要有两种函数:
-
动态分配内存创建相关结构体
/** *@return:成功则返回相关pass */ BaseType_t xTaskCreate( /*函数指针,也就是任务体*/ TaskFunction_t pxTaskCode, /*任务名*/ const char * const pcName, /*设置任务栈大小,单位为word(字),一个字占4个字节,大多时候估计方法,精确值的话看反汇编*/ const configSTACK_DEPTH_TYPE usStackDepth, /*调用pvTaskCode(任务函数)是用到的参数 ,并将相关参数传送到对应的任务函数指针中*/ void * const pvParameters, /*任务优先级,越小优先级越高*/ UBaseType_t uxPriority, /*用于保存任务创建后的返回结构体 task handle(句柄),可用于后续的任务属性相关修改,或是进行任务删除相关*/ TaskHandle_t * const pxCreatedTask )
-
静态分配(手动分配)内存创建
/** * @return: 创建成功则返回任务句柄 */ TaskHandle_t xTaskCreateStatic ( TaskFunction_t pxTaskCode, // 函数指针, 任务函数 const char * const pcName, // 任务的名字 const uint32_t ulStackDepth, // 栈大小,单位为word,10表示40字节 void * const pvParameters, // 调用任务函数时传入的参数 UBaseType_t uxPriority, // 优先级,数值越大优先级越高 StackType_t * const puxStackBuffer, // 静态分配的栈内存大小,就是一个buffer StaticTask_t * const pxTaskBuffer // 静态分配的任务结构体的指针,用它来操作这个任务, );
创建时相关代码(freertos.c):
/*LED的任务创建的句柄*/
static TaskHandle_t xLightTaskHandle;
static StackType_t g_pucStackOfLightTask[128];
static StaticTask_t g_TCBofLightTask;
void MX_FREERTOS_Init(void) {
/* USER CODE BEGIN Init */
TaskHandle_t xTaskHandle;
BaseType_t ret;
/* USER CODE END Init */
defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
//动态内存创建任务
ret = xTaskCreate(Led_Test, "LEDTask", 128, NULL, osPriorityNormal, &xLightTaskHandle);
//静态内存创建任务
xLightTaskHandle = xTaskCreateStatic(Led_Test, "LEDTask2", 128, NULL, osPriorityNormal, g_pucStackOfLightTask, &g_TCBofLightTask);
/* USER CODE BEGIN RTOS_EVENTS */
/* add events, ... */
/* USER CODE END RTOS_EVENTS */
}
LED相关代码
#include "driver_led.h"
#include "gpio.h"
/**
* @brief: LED引脚初始化
* @param: NULL
* @date: 2024年12月10日14:58:42
*/
int Led_init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOC_CLK_ENABLE();
/*Configure GPIO pin : PC13 */
GPIO_InitStruct.Pin = GPIO_PIN_13;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
return 0;
}
/**
* @brief:控制LED的亮灭
* @param:on-状态 1亮 0灭
* @return: 0-成功 其他值-失败
*/
int Led_Control(int on)
{
if(on)
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, RESET);
else
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, SET);
return 0;
}
/**
* @brief:LED1的亮灭相关测试
* @return:NULL
*/
void Led_Test(void)
{
Led_init();
while (1)
{
/* code */
Led_Control(1);
HAL_Delay(300);
Led_Control(0);
HAL_Delay(300);
}
}
使用任务参数创建多任务
这里可能出现明明创建了多个任务,但最终却只执行一个任务的情况是怎么回事?
这里使用的是动态分配内存方式创建相同任务体的多任务,其中各自任务的相关参数g_Task1Info
会被传入到任务体中,这里的if (g_OLEDCanUse)
及其函数体内部g_OLEDCanUse
变量改变主要是用于防范多任务时,出现的当前显示任务未完成就去下个显示任务,最终造成的逻辑混乱,这里也可以将OLED显示资源视作公共资源,而该操作就是简单的一种同步机制(但效果有限)。
void OledPrintTask(void *params)
{
struct TaskPrintInfo *pInfo = params;
uint32_t count = 0;
//uint8_t len;
while (1)
{
/* code */
if (g_OLEDCanUse)
{
/* code */
g_OLEDCanUse = 0;
OLED_ShowString(pInfo->x, pInfo->y, pInfo->name);
//OLED_ShowString(1, 1, "Task1:");
OLED_ShowNum(pInfo->x, pInfo->y+6, count++, 4);
g_OLEDCanUse = 1;
}
HAL_Delay(300);
}
}
/******************/
//最后一个参数则是用于设置句柄
xTaskCreate(OledPrintTask, "task1", 128,&g_Task1Info, osPriorityNormal, NULL);
xTaskCreate(OledPrintTask, "task2", 128,&g_Task2Info, osPriorityNormal, NULL);
xTaskCreate(OledPrintTask, "task3", 128,&g_Task3Info, osPriorityNormal, NULL);
需要说明的是若不加HAL_Delay(300);
,则显示器上只会出现Task3相关信息,原因在于CPU多任务时间调度太短,也就是说Task3第一次执行完后第二次还在OLED_ShowString
时发送调度去执行Task1或2直接便导致if判断失败进而回到Task3然后继续执行并再次发生调度而CPU下次发生调度时也是循环执行到task3中红线框选区域,进而导致task1和2永远无法显示,简单解决办法是加入的延时超过整体的调度时间。
2.3.2 任务的优先级和Tick
Tick是什么?有什么用?
与优先级相关的函数有哪些,分别有些什么作用?
任务优先级的数值越高,优先级越高。FreeRTOS确保高优先级的先运行,对于同优先级则轮流执行。需要注意的是同级的轮流执行就运用到了Tick(时钟节拍)。Tick之与单片机,就犹如人心脏一般,且与中断息息相关,而两次中断之间的间隔时间称之为时间片。FreeRTOS中的Tick由configTICK_RATE_HZ
设置。
关于同级的任务切换过程:1.触发中断后切换到别的同级任务 2.别的任务时间片用完触发中断切换下一同级运行。
相关函数
除创建任务时的优先级属性设置,还有
获取任务优先级
UBaseType_t uxTaskPriorityGet( const TaskHandle_t xTask );
设置任务优先级
void vTaskPrioritySet( TaskHandle_t xTask, UBaseType_t uxNewPriority );
2.3.4 任务状态
FreeRTOS中任务状态有哪几种?
阻塞态与挂起等待态的区别是什么?
怎样才能让阻塞态变为就绪态?
简而言之,常见任务状态有以下几种,:
- 运行态
- 非运行态:
- 就绪态(Ready) : 随时可以运行,只是当前时间片还未到该任务
- 阻塞态(Blocked) :因某种资源未能满足需求而导致一直不能运行的状态,主要由于被动原因造成等待,这里的等待可以是资源的等待,也可以是同步事件的来源等待 。
- 等待态(Suspended):主动去暂停某项任务,只能通过唯一函数
void vTaskSuspend( TaskHandle_t xTaskToSuspend );
进行,退出的话也只能主动去退出(vTaskResume( TaskHandle_t xTaskToSuspend )
)。实际开发中暂停等待态用得并不多。
需要说明的是,阻塞态的本质对事情的“等待”,只有当等待的事情满足后才能转为就绪态,而这里等待的事情主要有两大种类:
- 基于时间的等待事件:就像设置了超时时间一般,达到某个时间点后即转换后就绪态,属于一种低级的阻塞。
- 基于同步的等待事件:这里是指某个阻塞的任务需要等待其他任务或是中断程序给它”发送消息“,发送消息的方法包括但不限于
任务通知(task notification)、队列(queue)、信号量(semaphoe)、互斥量(mutex)。
2.3.5 任务相关的Delay函数
任务相关的两大Delay函数是什么,各自使用方法是怎样的?
-
void vTaskDelay( const TickType_t n )
: 等待n个时间片(这里的时间片时间与设置频率有关)中断(需要注意的是这里只是等待中断,而不能达到精确的等待时间)。 -
BaseType_t vTaskDelayUntil( TickType_t * const pxPreviousWakeTime,const TickType_t xTimeIncrement );
2.3.6 调度算法(**)
调度算法的定义是什么?
FreeRTOS调度算法是怎么进行设置的?
调度算法是指存在多个任务的系统中,某一时刻指定给某一任务获得处理器资源的算法。调度器用于都是挑选最高优先级的就绪态任务并让其进入运行状态。
需要注意的是,目前多任务执行时的CPU均是在内核态上进行执行的,而只有当CPU空闲时(无事可做),才会执行用户态相关程序任务。RTOS中的调度思想满足:高优先级的抢占,同优先级的时间片轮转(具体的时间片又和频率相关)。其设置在FreeRTOS.h
和FreeRTOSConfig.h
两个头文件中。
#define configUSE_PREEMPTION 1 //可抢占式运行
//同级轮流调度
#define configUSE_TIME_SLICING 1
//CPU空闲时允许用户态介入
#define configIDLE_SHOULD_YIELD 1