- 前言
- 3.1 任务概念
- 3.2 任务状态
- 3.3 任务优先级
- 3.4 空闲任务和空闲任务钩子
- 3.4.1 空闲任务
- 3.4.2 空闲任务钩子
- 3.4.3 创建空闲钩子
- 3.5 创建任务
- 3.5.1 任务参数相关概念
- 3.5.2 创建静态内存任务
- 3.5.2.1 配置静态内存
- 3.5.2.2 实现空闲任务堆栈函数
- 3.5.2.3 实现定时器任务堆栈函数
- 3.5.2.4 配置内存对齐
- 3.5.2.5 分配静态内存
- 3.5.2.6 创建任务原型
- 3.5.2.7 创建任务
- 3.5.3 创建动态内存任务
- 3.5.3.1 配置动态内存
- 3.5.3.2 任务句柄
- 3.5.3.3 创建任务原型
- 3.5.3.4 创建任务
- 3.6 删除任务
- 3.6.1 配置删除任务
- 3.6.2 删除任务原型
- 3.7 实战
资源:
- https://www.freertos.org/taskandcr.html
- 李柱明博客
进程:进程是程序执行的过程,是程序在执行过程中分配和管理资源的基本单位。拥有独立的虚拟地址空间。
线程:线程是CPU调度和分派的基本单位。与其它同一进程的线程共享当前进程资源。
协程:比线程更加轻量级的存在,不是由操作系统内核管理,而是由程序控制的。其实就是在同一线程内时分地执行不同的子程序。(注意:不是函数调用)
还有管程、纤程。
并发:多个任务看起来是同时进行, 这是一种假并行。
并行:并行是指令同一时刻一起运行。
对于目前主流的RTOS的任务,大部分都属于并发的线程。
因为MCU上的资源每个任务都是共享的,可以认为是单进程多线程模型。
3.2 任务状态freertos有四种状态,每种状态都有对应的状态链表管理。
运行态:占用CPU使用权时的状态。
就绪态:能够运行(没有被阻塞和挂起),但是当前没有运行的任务的状态。
阻塞态:由于等待信号量、消息队列、事件标志组、调用延迟函数等而处于的状态被称之为阻塞态。
挂起态:调用函数vTaskSuspend()对指定任务进行挂起,挂起后这个任务将不被执行。
- 调用函数xTaskResume()可退出挂起状态。
- 不可以指定超时周期事件(不可以通过设定超时事件而退出挂起状态)
任务状态转换图:
3.3 任务优先级每个任务被分配一个从0到(configMAX_PRIORITIES
- 1)的优先级。
configMAX_PRIORITIES
是在 FreeRTOSConfig.h
文件中被定义。
优先级数值越高,优先级越高。
idle任务的优先级为0。
多个任务可以共享一个任务优先级。
如果在FreeRTOSConfig.h文件中配置宏定义configUSE_TIME_SLICING
为1,或者没有配置此宏定义,时间片调度都是使能的。
使能时间片后,处于就绪态的多个相同优先级任务将会以时间片切换的方式共享处理器。
如果硬件架构支持CLZ指令,可以使用该特性,使能配置如下:
- 将
FreeRTOSConfig.h
中configUSE_PORT_OPTIMISED_TASK_SELECTION
设置为1; - 最大优先级数目
configMAX_PRIORITIES
不能大于CPU位数。
空闲任务是启动RTOS调度器时由内核自动创建的任务,其优先级为0,确保系统中至少有一个任务在运行。
空闲任务可用来释放RTOS分配给被删除任务的内存。
3.4.2 空闲任务钩子空闲任务钩子是一个函数,每一个空闲任务周期被调用一次。
空闲任务钩子应该满足一下条件:
- 不可以调用可能引起空闲任务阻塞的API函数;
- 不应该陷入死循环,需要留出部分时间用于系统处理系统资源回收。
在FreeRTOSConfig.h
头文件中设置configUSE_IDLE_HOOK
为1;
定义一个函数,名字和参数原型如下所示:
void vApplicationIdleHook( void ); // FreeRTOS 规定了函数的名字和参数
一般设置CPU进入低功耗模式都是使用空闲任务钩子函数实现的。
3.5 创建任务任务的创建有两种:创建静态内存任务和创建动态内存任务。
3.5.1 任务参数相关概念任务入口函数:即是任务函数,是该任务需要跑的函数。
任务名称:即是任务名,主要用于调试。
任务堆栈大小:即是任务栈大小,单位是word。
任务入口函数参数:传递给任务入口函数的参数。在任务函数里,通过形参获得。
任务控制块:主要用于内核管理任务,记录任务信息。
任务句柄:用于区分不同的任务,用于找到该任务的任务控制块。
3.5.2 创建静态内存任务xTaskCreateRestrictedStatic()
,该函数不讲解,因为需要MPU,想研究的同学可以参考:freertos官网API
创建静态内存任务需要先实现以下内容:
-
需要在
FreeRTOSConfig.h
打开configSUPPORT_STATIC_ALLOCATION
宏,开启静态内存。 -
开启静态内存的同时需要实现两个函数:(使用静态内存分配任务堆栈和任务控制块内存)
vApplicationGetIdleTaskMemory()
:空闲任务堆栈函数。vApplicationGetTimerTaskMemory()
:定时器任务堆栈函数。
-
注意静态内存对齐。
实现该函数是为了给内核提供空闲任务关于空闲任务控制块和空闲任务堆栈的相关信息。
/* 空闲任务控制块 */
static StaticTask_t Idle_Task_TCB;
/* 空闲任务任务堆栈 */
static StackType_t Idle_Task_Stack[configMINIMAL_STACK_SIZE];
/** @brief vApplicationGetIdleTaskMemory
* @details 获取空闲任务的任务堆栈和任务控制块内存
* @param
* @retval
* @author lizhuming
*/
void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer,
StackType_t **ppxIdleTaskStackBuffer,
uint32_t *pulIdleTaskStackSize)
{
*ppxIdleTaskTCBBuffer = &Idle_Task_TCB; /* 任务控制块内存 */
*ppxIdleTaskStackBuffer = Idle_Task_Stack; /* 任务堆栈内存 */
*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE; /* 任务堆栈大小 */
}
3.5.2.3 实现定时器任务堆栈函数
实现该函数是为了给内核创建定时器任务时提供定时器任务控制块和定时器任务堆栈的相关信息。
/* 定时器任务控制块 */
static StaticTask_t Timer_Task_TCB;
/* 定时器任务堆栈 */
static StackType_t Timer_Task_Stack[configTIMER_TASK_STACK_DEPTH];
/** @brief vApplicationGetTimerTaskMemory
* @details 获取定时器任务的任务堆栈和任务控制块内存
* @param
* @retval
* @author lizhuming
*/
void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer,
StackType_t **ppxTimerTaskStackBuffer,
uint32_t *pulTimerTaskStackSize)
{
*ppxTimerTaskTCBBuffer = &Timer_Task_TCB;/* 任务控制块内存 */
*ppxTimerTaskStackBuffer = Timer_Task_Stack;/* 任务堆栈内存 */
*pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;/* 任务堆栈大小 */
}
3.5.2.4 配置内存对齐
内存对齐的配置在portmacro.h
里面的portBYTE_ALIGNMENT
宏,按自己需求配置即可。
在任务堆栈初始化时会把栈顶指针纠正为内存对齐。参考下列代码:
pxTopOfStack = &( pxNewTCB->pxStack[ ulStackDepth - ( uint32_t ) 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 ) == 0UL ) );
3.5.2.5 分配静态内存
静态内存分配是有编译器决定的。
在freertos中,创建任务需要分配的内存主要是任务控制块和任务堆栈。
/* 任务控制快 */
static StaticTask_t lzmStaticTestTaskTCB = {0};
/* 任务堆栈 */
static StackType_t lzmStaticTestTaskStack[256] = {0};
3.5.2.6 创建任务原型
创建任务函数原型:
TaskHandle_t xTaskCreateStatic( // 返回任务句柄
TaskFunction_t pxTaskCode, // 任务入口函数
const char * const pcName, // 任务名称
const uint32_t ulStackDepth, // 任务堆栈大小
void * const pvParameters, // 传递给任务入口函数的参数
UBaseType_t uxPriority, // 任务优先级
StackType_t * const puxStackBuffer, // 任务堆栈
StaticTask_t * const pxTaskBuffer ) // 任务控制块
3.5.2.7 创建任务
/* 创建静态内存任务 */
lzmStaticTestTaskHandle = xTaskCreateStatic((TaskFunction_t) lzmStaticTestTask, // 任务入口函数
(const char*) "lzm static test task", // 任务函数名
(uint32_t )256, // 任务堆栈大小
(void* )NULL, // 传递给任务入口函数的参数
(UBaseType_t)5, // 任务优先及
(StackType_t* )lzmStaticTestTaskStack, // 任务堆栈地址
(StaticTask_t* )&lzmStaticTestTaskTCB); // 任务控制块地址
3.5.3 创建动态内存任务
3.5.3.1 配置动态内存
动态内存配置是在FreeRTOSConfig.h
配置的,这些内存主要供给FreeRTOS动态内存分配函数使用。
#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 32 * 1024 ) ) // 系统总堆大小
而freertos的动态内存管理是有文件heap_x.c
实现的,具体实现算法,后面讲到内存时会分析。
uint8_t ucHeap[ configTOTAL_HEAP_SIZE ]; // 系统总堆
3.5.3.2 任务句柄
static TaskHandle_t lzmTestTaskHandle = NULL;
3.5.3.3 创建任务原型
创建任务函数原型:
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 ) // 任务控制块指针
3.5.3.4 创建任务
/* 创建动态内存任务 */
xReturn = xTaskCreate((TaskFunction_t) lzmTestTask, // 任务入口函数
(const char*) "lzm test task", // 任务函数名
(uint16_t )256, // 任务堆栈大小
(void* )NULL, // 传递给任务入口函数的参数
(UBaseType_t)5, // 任务优先及
(TaskHandle_t* )&lzmTestTaskHandle); // 任务句柄
3.6 删除任务
3.6.1 配置删除任务
在文件FreeRTOSConfig.h
中,必须定义宏INCLUDE_vTaskDelete
为 1,删除任务的API才会失效。
调用API删除任务后,将会从就绪、阻塞、暂停和事件列表中移除该任务。
如果是动态内存创建任务,删除任务后,其占用的空间资源有空闲任务释放,所以删除任务后尽量保证空闲任务获取一定的CPU时间。
如果是静态内存创建任务,删除任务后,需要自己处理释放任务占用的空间资源。
3.6.2 删除任务原型void vTaskDelete( TaskHandle_t xTaskToDelete ); // 参数为任务句柄
注意:传入的参数为任务句柄,当出入的参数为NULL时,表示删除调用者当前的任务。
3.7 实战源码:拉取 freertos_on_linux_task_01 文件夹
结果: