@(嵌入式)
简述
FreeRTOS 的任务调度在 Source/include/task.c 中实现,包含了任务的创建、切换、挂起、延时和删除等所有功能。涉及到的链表组织见文章 FreeRTOS 任务调度 List 组织 。任务切换实现代码量比较大,因此关于任务调度这一块会分几个文章来描述,这一篇主要分析任务的创建的调用与实现。
分析的源码版本是 v9.0.0
(为了方便查看,github 上保留了一份源码Source目录下的拷贝)
任务状态
系统运行过程,任务可能处于以下各种状态,各个状态之间切换的关系如上图所示。
* Running
运行状态, 当前正在执行,占有处理器的任务
* Ready
就绪状态,准备被运行的任务,没有被挂起和阻塞,但不是当前正在执行的任务,等待更高优先级任务或者同等级任务时间片结束释放处理器
* Blocked
阻塞状态,任务在等待一个事件而进入阻塞状态,比如延时、获取信号量等
* Suspended
挂起状态,任务由于调用 vTaskSuspend()
而被挂起不能被执行, 直到调用 xTaskResume()
重新恢复
使用示例
FreeRTOS 中创建任务并开始调度的基本框架如下 :
void vATaskFunction( void *pvParameters )
{
for( ;; )
{
// -- 任务代码 --
}
// 任务不能有任何 返回
// 对自行结束的任务,退出前需要自行清理
vTaskDelete( NULL );
}
void main(void)
{
static unsigned char ucParameterToPass;
xTaskHandle xHandle;
xTaskCreate( vATaskFunction, /*任务实现函数*/
"TASK_NAME", /*任务名,方便调试*/
STACK_SIZE, /*任务堆栈大小 *StackType_t*/
&ucParameterToPass, /*任务运行时的参数*/
tskIDLE_PRIORITY, /*任务优先级*/
&xHandle ); /*回传任务句柄,供其他地方引用任务*/
// 其他任务和拉拉杂杂的初始化
// 启动任务调度器 loop ....
}
任务创建函数中, 设置的栈大小单位由使用平台的 StackType_t
决定,不同平台栈指针对齐有自己的要求。
回传的句柄(指向TCB的指针)一般用于在其他任务中发送消息通知给任务,或者删除任务时引用。
任务成功创建后返回 pdPASS
, 否则失败回传错误码。
另外,删除任务,可以通过其他任务中调用 voidvTaskDelete
进行删除,此时该任务会从各种链表中移除,并且内存会被马上回收; 但是如果是任务自己调用删除,则其内存回收需要由空闲任务来完成(毕竟当前正在使用这些资源)。
使用 voidvTaskDelete
的前提是在 FreeRTOSConfig.h 设置 INCLUDE_vTaskDelete
为1(Tips !! API 在使用前最后需要看看是否需要设置对应的宏定义)。
叙述完上层的调用,后续介绍背后具体是如何实现的。
数据结构
TCB
任务调度离不开任务控制块(TCB), 用于存储任务的状态信息、运行时环境等。源代码见 tskTaskControlBlock, 以下具体介绍下这个数据结构。
typedef struct tskTaskControlBlock
{
// 任务栈顶指针
volatile StackType_t *pxTopOfStack;
// 启用MPU 的情况下设置
#if ( portUSING_MPU_WRAPPERS == 1 )
// 设置任务访问内存的权限
xMPU_SETTINGS xMPUSettings;
#endif
// 状态链表项(Ready, Blocked, Suspended)
// 任务处于不同状态 该项会被插入到对应的链表, 供链表引用任务
ListItem_t xStateListItem;
// 事件链表项
// 比如任务延时挂起等,被插入到延时链表中,到时间或事件发生,链表引用唤醒任务
ListItem_t xEventListItem;
// 任务优先级 0 最低
UBaseType_t uxPriority;
// 任务栈内存起始地址
StackType_t *pxStack;
// 任务名, 字符串, 一般供调试时使用
char pcTaskName[ configMAX_TASK_NAME_LEN ];
// 对于向上生长的栈, 用于指明栈的上边界,用于判断是否溢出
#if ( portSTACK_GROWTH > 0 )
StackType_t *pxEndOfStack;
#endif
// 边界嵌套计数
#if ( portCRITICAL_NESTING_IN_TCB == 1 )
UBaseType_t uxCriticalNesting;
#endif
#if ( configUSE_TRACE_FACILITY == 1 )
// 调试, 标识这个任务是第几个被创建
// 每创建一个任务, 系统有个全局变量就会加一, 并赋值给这个新任务
UBaseType_t uxTCBNumber;
// 调试 供用户设置特定数值
UBaseType_t uxTaskNumber;
#endif
#if ( configUSE_MUTEXES == 1 )
// 涉及互斥锁下的优先级继承(避免优先级反转), queue 那边介绍
// 当优先级被临时提高(继承了拿锁被堵的高优先级任务)时,这个变量保存任务实际的优先级
UBaseType_t uxBasePriority;
UBaseType_t uxMutexesHeld;
#endif
#if ( configUSE_APPLICATION_TASK_TAG == 1 )
TaskHookFunction_t pxTaskTag;
#endif
#if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
// 存储一些本地数据的指针
void *pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
#endif
#if( configGENERATE_RUN_TIME_STATS == 1 )
// 记录任务运行状态下的总时间
uint32_t ulRunTimeCounter;
#endif
#if ( configUSE_NEWLIB_REENTRANT == 1 )