FreeRTOS任务管理
- 任务简介
- 任务调度器简介
- 任务状态简介
- 常用任务管理 API 函数
- 常用任务状态查询 API 函数
-
- vTaskGetInfo 获取单个任务信息
- xTaskGetCurrentTaskHandle 查询当前正在运行(调用)任务的句柄
- xTaskGetHandle 通过任务名查询任务句柄
- pcTaskGetName 通过任务句柄查询任务名
- xTaskGetIdleTaskHandle 查询空闲任务的句柄
- uxTaskGetStackHighWaterMark 查询任务剩余堆栈空间
- eTaskGetState 查询任务状态
- xTaskGetTickCount 查询Tick计数值(任务中使用)
- xTaskGetTickCountFromISR 查询Tick计数值 (中断中使用)
- xTaskGetSchedulerState 查询任务调度器状态
- uxTaskGetNumberOfTasks 查询任务数量
- vTaskList 查询每个任务的信息状态
- vTaskGetRunTimeStats 查询每个任务运行时间统计
- vTaskGetIdleRunTimeCounter 查询空闲任务运行时间
任务简介
从系统的角度看,任务是竞争系统资源的最小运行单元。FreeRTOS 是一个支持多任务的操作系统。
在 FreeRTOS 中,任务可以使用或等待 CPU、使用内存空间等系统资源,并独立于其它任务运行,任何数量的任务可以共享同一个优先级,如果宏 configUSE_TIME_SLICING 定义为 1,处于就绪态的多个相同优先级任务将会以时间片切换的方式共享处理器。
简而言之:FreeRTOS 的任务可认为是一系列独立任务的集合。每个任务在自己的环境中运行。
在任何时刻,只有一个任务得到运行,FreeRTOS 调度器决定运行哪个任务。调度器会不断的启动、停止每一个任务,宏观看上去所有的任务都在同时在执行。
作为任务,不需要对调度器的活动有所了解,在任务切入切出时保存上下文环境(寄存器值、堆栈内容)是调度器主要的职责。为了实现这点,每个 FreeRTOS 任务都需要有自己的栈空间。
当任务切出时,它的执行环境会被保存在该任务的栈空间中,这样当任务再次运行时,就能从堆栈中正确的恢复上次的运行环境,任务越多,需要的堆栈空间就越大,而一个系统能运行多少个任务,取决于系统的可用的 SRAM。
FreeRTOS 中的任务是抢占式调度机制,高优先级的任务可打断低优先级任务,低优先级任务必须在高优先级任务阻塞或结束后才能得到调度。同时FreeRTOS 也支持时间片轮转调度方式,只不过时间片的调度是不允许抢占任务的 CPU 使用权。
任务通常会运行在一个死循环中,也不会退出,如果一个任务不再需要,可以调用 FreeRTOS 中的任务删除 API 函数接口将其删除
任务调度器简介
FreeRTOS 中提供的任务调度器是基于优先级的全抢占式调度:在系统中除了中断处理函数、调度器上锁部分的代码和禁止中断的代码是不可抢占的之外,系统的其他部分都是可以抢占的。系统理论上可以支持无数个优先级(0 ~ N),
优先级数值越小的任务优先级越低,0 为最低优先级,分配给空闲任务使用,一般不建议用户来使用这个优先级。
假如使能了configUSE_PORT_OPTIMISED_TASK_SELECTION 这个宏(在 FreeRTOSConfig.h 文件定义),一般强制限定最大可用优先级数目为 32。在一些资源比较紧张的系统中,可以根据实际情况选择只支持 8 个或 32 个优先级的系统配置。在系统
中,当有比当前任务优先级更高的任务就绪时,当前任务将立刻被换出,高优先级任务抢占处理器运行。
FreeRTOS 内核中采用两种方法寻找最高优先级的任务,
第一种是通用的方法,在就绪链表中查找从高优先级往低查找 uxTopPriority,因为在创建任务的时候已经将优先级进行排序,查找到的第一个 uxTopPriority 就是我们需要的任务,然后通过 uxTopPriority 获取对应的任务控制块。
第二种方法则是特殊方法,利用计算前导零指令 CLZ,直接在 uxTopReadyPriority 这个 32 位的变
量中直接得出 uxTopPriority,这样子就知道哪一个优先级任务能够运行,这种调度算法比普通方法更快捷,但受限于平台(在 STM32 中我们就使用这种方法)。
FreeRTOS 内核中也允许创建相同优先级的任务。相同优先级的任务采用时间片轮转方式进行调度(也就是通常说的分时调度器),时间片轮转调度仅在当前系统中无更高优先级就绪任务存在的情况下才有效。为了保证系统的实时性,系统尽最大可能地保证高优先级的任务得以运行。任务调度的原则是一旦任务状态发生了改变,并且当前运行的任务优先级小于优先级队列组中任务最高优先级时,立刻进行任务切换(除非当前系统处于中断处理程序中或禁止任务切换的状态)。
任务状态简介
FreeRTOS 系统中的每一任务都有多种运行状态。
系统初始化完成后,创建的任务就可以在系统中竞争一定的资源,由内核进行调度。
任务状态通常分为以下四种:
就绪(Ready):该任务在就绪列表中,就绪的任务已经具备执行的能力,只等待调度器进行调度,新创建的任务会初始化为就绪态。
运行(Running):该状态表明任务正在执行,此时它占用处理器,FreeRTOS调度器选择运行的永远是处于最高优先级的就绪态任务,当任务被运行的一刻,它的任务状态就变成了运行态。
阻塞(Blocked):如果任务当前正在等待某个时序或外部中断,我们就说这个任务处于阻塞状态,该任务不在就绪列表中。包含任务被挂起、任务被延时、任务正在等待信号量、读写队列或者等待读写事件等。
挂起态(Suspended):处于挂起态的任务对调度器而言是不可见的,让一个任务进入挂起状态的唯一办法就是调用 vTaskSuspend()函数;
而把一个挂起状态的任务恢复的唯一途径就是调用 vTaskResume() 或 vTaskResumeFromISR()函数,
我们可以这么理解挂起态与阻塞态的区别,当任务有较长的时间不允许运行的时候,我们可以挂起任务,这样子调度器就不会管这个任务的任何信息,直到我们调用恢复任务的 API 函数;而任务处于阻塞态的时候,系统还需要判断阻塞态的任务是否超时,是否可以解除阻塞
常用任务管理 API 函数
vTaskDelay 相对延时
必须将 INCLUDE_vTaskDelay 定义为 1,此函数才可用。
#define INCLUDE_vTaskDelay 1
void vTaskDelay( const TickType_t xTicksToDelay );
参数:
xTicksToDelay 调用任务应阻塞的 tick 周期数。
用法示例:
void vTaskFunction( void * pvParameters )
{
/* Block for 500ms. */
const TickType_t xDelay = 500 / portTICK_PERIOD_MS;
for( ;; )
{
/* Simply toggle the LED every 500ms, blocking between each toggle. */
vToggleLED();
vTaskDelay( xDelay );
}
}
vTaskDelayUntil 绝对延时
INCLUDE_vTaskDelayUntil 必须被定义为 1 才能使用此函数
#define INCLUDE_vTaskDelayUntil 1
void vTaskDelayUntil( TickType_t *pxPreviousWakeTime,
const TickType_t xTimeIncrement );
参数:
pxPreviousWakeTime 指向一个变量的指针,该变量 任务上次被取消阻止。 该变量在第一次使用前 必须用当前时间进行初始化(见下方示例)。 在这之后,该变量 会在 vTaskDelayUntil() 中自动更新。
xTimeIncrement 周期时间段。 该任务将在 (*pxPreviousWakeTime + xTimeIncrement)时间解除阻塞。 配合相同的 xTimeIncrement 参数值 调用 vTaskDelayUntil 将导致任务 以固定的间隔期执行。
示例用法:
// Perform an action every 10 ticks.
void vTaskFunction( void * pvParameters )
{
TickType_t xLastWakeTime;
const TickType_t xFrequency = 10;
// Initialise the xLastWakeTime variable with the current time.
xLastWakeTime = xTaskGetTickCount();
for( ;; )
{
// Wait for the next cycle.
vTaskDelayUntil( &xLastWakeTime, xFrequency );
// Perform action here.
}
}
xTaskAbortDelay 强制任务离开阻塞状态
必须将 INCLUDE_xTaskAbortDelay 定义为 1,此函数才可用
#define INCLUDE_xTaskAbortDelay 1
BaseType_t xTaskAbortDelay( TaskHandle_t xTask );
参数:
xTask 将被强制退出阻塞状态的任务的句柄 。
要获得任务句柄,请使用 xTaskCreate() 创建任务并使用 pxCreatedTask 参数,或使用返回值创建任务 xTaskCreateStatic() 并存储该值,或在 xTaskGetHandle() 时使用任务的名字。
Returns:
如果 xTask 引用的任务不在“阻塞”状态, 则返回 pdFAIL。 否则返回 pdPASS。
用法示例:
void vTaskFunction( void * pvParameters )
{
/* Block for 500ms. */
const TickType_t xDelay = 500 / portTICK_PERIOD_MS;
for( ;; )
{
vTaskDelay( xDelay );
}
}
void vTaskAbortDelayFunction( void * pvParameters )
{
for( ;; )
{
xTaskAbortDelay( xTaskGetHandle( "vTaskFunction" ));
}
}
uxTaskPriorityGet 查询任务的优先级
必须将 INCLUDE_uxTaskPriorityGet 定义为 1,此函数才可用
#define INCLUDE_uxTaskPriorityGet 0
UBaseType_t uxTaskPriorityGet( TaskHandle_t xTask );
参数:
xTask 待查询的任务句柄。传递 NULL 句柄会导致返回调用任务的优先级。
Returns: XTask 的优先级。
用法示例:
void vAF