任务管理
任务的管理主要依据调度器实现,调度器的主要职责就是从就绪链表中找到优先级最高的任务,然后进行上下文切换,执行该任务。在查找优先级最高的任务过程中有两种方式,第一种是在就绪链表中从高优先级往低优先级查找,另外一种是计算前导零,直接在这个变量中uxTopReadyPriority获取最高优先级的任务。
任务状态
- 就绪态
任务创建成功后,会将其挂载到对应优先级的就绪链表中,等到调度器的执行;
对应的链表定义为:
c
PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];
- 挂起态
如果将任务挂起,则对于调度器来讲,任务不可见,操作的函数为:
c
void vTaskSuspend( TaskHandle_t xTaskToSuspend ); //挂起任务
void vTaskResume( TaskHandle_t xTaskToResume ) //恢复任务
对应的链表为
c
PRIVILEGED_DATA static List_t xSuspendedTaskList;
- 运行态
运行态表示任务此时正在运行,占用处理器的资源;
- 阻塞态
当任务调用延时函数,获取去获取某一个通信变量时会导致进行阻塞态;进入阻塞态的任务如果条件满足,会进行就绪态准备调度;
对应的链表为:
c
PRIVILEGED_DATA static List_t xDelayedTaskList1;
PRIVILEGED_DATA static List_t xDelayedTaskList2;
任务状态迁移过程

几个不经常理解的状态迁移过程:
- 运行到就绪态:CPU使用权被高优先级任务抢占,此时的任务会进行就绪态;
- 阻塞到就绪:如果阻塞的任务条件满足,则变为就绪态,如果优先级高于此时的优先级,则再次变为运行态;
- 挂起到就绪:通过API操作,注意如果这个任务优先级比运行的任务优先级高,则会发生切换,变成运行态;
问题
-
如何管理这些状态?
通过状态链表管理,在每个任务的TCB中有一个成员变量,指示着当前任务处于什么状态链表中;
ctypedef struct tskTaskControlBlock { volatile StackType_t *pxTopOfStack; ListItem_t xStateListItem; //用于记录当前任务处于什么链表之中 /* 省略部分代码 */ }tskTCB; -
有多少状态链表?
在prvAddNewTaskToReadyList函数中,如果是第一次执行,则会调用prvInitialiseTaskLists函数初始化状态链表;
cstatic void prvInitialiseTaskLists( void ) { for( uxPriority = ( UBaseType_t ) 0U; uxPriority < ( UBaseType_t ) configMAX_PRIORITIES; uxPriority++ ) { vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) ); //就绪链表,每个优先级对应一个 } vListInitialise( &xDelayedTaskList1 ); vListInitialise( &xDelayedTaskList2 ); vListInitialise( &xPendingReadyList ); //记录中断中解除阻塞的任务 #if ( INCLUDE_vTaskDelete == 1 ) { vListInitialise( &xTasksWaitingTermination ); //等待删除的链表 } #endif /* INCLUDE_vTaskDelete */ #if ( INCLUDE_vTaskSuspend == 1 ) { vListInitialise( &xSuspendedTaskList ); //挂起链表 } #endif /* INCLUDE_vTaskSuspend */ /* Start with pxDelayedTaskList using list1 and the pxOverflowDelayedTaskList using list2. */ pxDelayedTaskList = &xDelayedTaskList1; pxOverflowDelayedTaskList = &xDelayedTaskList2; }
常用函数分析
-
任务挂起,需要将INCLUDE_vTaskSuspend置1
cvoid vTaskSuspend( TaskHandle_t xTaskToSuspend ) /* 有删减 */ { TCB_t *pxTCB; taskENTER_CRITICAL(); { /* 1.获取任务的TCB,如果为null,则返回当前任务的TCB */ pxTCB = prvGetTCBFromHandle( xTaskToSuspend ); /* 2. 移除任务的链表状态 */ uxListRemove( &( pxTCB->xStateListItem ) ) /* 3.移除任务的事件状态 */ listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) /* 4. 将其插入到挂起链表 */ vListInsertEnd( &xSuspendedTaskList, &( pxTCB->xStateListItem ) ); } taskEXIT_CRITICAL(); /* 5. 重置阻塞时间 */ prvResetNextTaskUnblockTime() /* 6. 判断是否为当前任务 */ if( pxTCB == pxCurrentTCB ) { if( xSchedulerRunning != pdFALSE ) { portYIELD_WITHIN_API(); /* 立即调度 */ } else { vTaskSwitchContext(); /* 在就绪链表中查找,更新pxCurrentTCB */ } } }- 任务挂起就是将任务的状态从延时链表或其他链表中移除,将其加入到挂起链表中;
- vTaskSuspendAll函数是将所有任务挂起,他的本质是挂起调度器uxSchedulerSuspended;
-
任务恢复
cvoid vTaskResume( TaskHandle_t xTaskToResume ) { TCB_t * const pxTCB = ( TCB_t * ) xTaskToResume; if( ( pxTCB != NULL ) && ( pxTCB != pxCurrentTCB ) ) { taskENTER_CRITICAL(); { if( prvTaskIsTaskSuspended( pxTCB ) != pdFALSE ) { /*1. 从挂起链表中移除 */ ( void ) uxListRemove( &( pxTCB->xStateListItem ) ); /*2. 加入到就绪链表 */ prvAddTaskToReadyList( pxTCB ); /* 3. 优先级判断 */ /* We may have just resumed a higher priority task. */ if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority ) { taskYIELD_IF_USING_PREEMPTION(); /* 立即调度 */ } } } taskEXIT_CRITICAL(); }- 任务的恢复就是将任务从挂起链表删除,加入到就绪链表中;
- 如果恢复的任务优先级高,则直接调度执行;
- xTaskResumeFromISR函数是在中断中调度的,区别就是内部不会进行任务切换,如果需要切换,会返回true。
- xTaskResumeAll是恢复所有的挂起任务;
-
任务删除
cvoid vTaskDelete( TaskHandle_t xTaskToDelete ) { TCB_t *pxTCB; taskENTER_CRITICAL(); { /* 1. 获取删除任务的TCB */ pxTCB = prvGetTCBFromHandle( xTaskToDelete ); /* 2. 移除任务的链表和事件状态 */ uxListRemove( &( pxTCB->xStateListItem ) ) listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) /* 3. 任务数量加加 */ uxTaskNumber++; /* 4. 删除的是当前任务 */ if( pxTCB == pxCurrentTCB ) { /* 4.1 将任务状态链表加入到等待删除链表 */ vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xStateListItem ) ); /* 4.2 等待删除的任务数量++;*/ ++uxDeletedTasksWaitingCleanUp; /* 4.3 执行删除之前的动作 */ portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending ); } else { /* 任务数量-- */ --uxCurrentNumberOfTasks; prvDeleteTCB( pxTCB ); /*删除任务TCB,包括TCB的空间和栈空间 */ prvResetNextTaskUnblockTime(); /* 复位阻塞时间 */ } } taskEXIT_CRITICAL(); /* 5. 如果调度器在运行,要删除的任务是当前任务,则执行一次切换 */ if( xSchedulerRunning != pdFALSE ) { if( pxTCB == pxCurrentTCB ) { portYIELD_WITHIN_API(); /* 切换 */ } } }- 任务的删除就是将任务的TCB和栈空间进行释放;
- 如果要删除的任务是当前任务,那么就将其加入到待删除链表xTasksWaitingTermination,在IDLE任务中会将任务的TCB和栈空间进行删除;
- 如果删除的不是当前任务,则直接删除TCB和栈空间;
-
任务延时
cvoid vTaskDelay( const TickType_t xTicksToDelay ) { BaseType_t xAlreadyYielded = pdFALSE; if( xTicksToDelay > ( TickType_t ) 0U ) { vTaskSuspendAll(); /* 挂起所有任务 */ { /* 添加到延时链表 */ prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE ); } xAlreadyYielded = xTaskResumeAll(); /* 恢复所有 */ } if( xAlreadyYielded == pdFALSE ) /* 如果没有要切换的,则强行切换 */ { portYIELD_WITHIN_API(); } }- 整个过程首先是调用vTaskSuspendAll函数,将任务进行挂起,挂起的操作本质是将uxSchedulerSuspended变量加1。然后将这个任务添加到延时链表中,插入的过程中会计算这个任务的延时时间,并将这个时间写入到xStateListItem中的xItemValue成员变量中。然后将当前任务加入到延时链表中,插入中会根据xItemValue变量寻找插入的位置,在延时链表中会按照延时时间升序排列,即越快到时间的结点,越靠近头部。后面调用xTaskResumeAll函数进行恢复,此时当前任务已经挂载到了延时链表中。后面就是调度切换,这里有两方面,如果xPendedTicks大于0,则会加一次tick,并判断是否需要调度,如果需要调度会将xYieldPendings写成true,进而调用taskYIELD_TASK_CORE_IF_USING_PREEMPTION函数进行调度。如果xPendedTicks等于0,则xAlreadyYielded为FALSE,调用这个 taskYIELD_WITHIN_API函数完成任务切换。这里的切换均是采用的系统调用的方式。
- vTaskDelayUntil是绝对延时函数;
实验
创建两个任务,一个任务是LED会进行1秒一次的闪烁,另外一个是按键任务,一个按键负责挂起LED任务,另外一个按键负责恢复LED任务。实验的现象为LED挂起后,将不会再闪烁,LED任务恢复后,LED继续闪烁。
c
static void KEY_Task(void* parameter)
{
uint8_t key = 0;
while (1) {
key=KEY_Scan(0);
if(key)
{
switch(key)
{
case WKUP_PRES:
printf("挂起LED 任务!\n");
vTaskSuspend(LED_Task_Handle); /* 挂起 */
break;
case KEY0_PRES:
printf("恢复LED 任务!\n");
vTaskResume(LED_Task_Handle); /* 恢复 */
break;
}
}
vTaskDelay(20);/* 延时20 个tick */
}
}
任务创建
函数代码
c
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
const char * const pcName,
const uint16_t usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask )
{
TCB_t *pxNewTCB;
BaseType_t xReturn;
StackType_t *pxStack;
pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );
if( pxStack != NULL )
{
/* Allocate space for the TCB. */
pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
if( pxNewTCB != NULL )
{
pxNewTCB->pxStack = pxStack;
}
else
{
vPortFree( pxStack );
}
}
if( pxNewTCB != NULL )
{
prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
prvAddNewTaskToReadyList( pxNewTCB );
xReturn = pdPASS;
}
return xReturn;
}
首先xTaskCreate函数进行创建任务,在创建任务的过程中传入任务的名称,栈的大小,参数,优先级和任务对应的执行函数。该函数会调用prvCreateTask创建任务对应的TCB信息,如果TCB信息创建正常,则会将该任务通过prvAddNewTaskToReadyList函数添加到就绪链表中。
创建任务TCB的过程为:首先从堆中申请一定的内存空间,例如这里的任务栈大小为1024,单位是8个字节,那么总的空间大小为8KB。然后从堆中申请一块空间用作TCB_t变量,将TCB变量中的stack指针指向刚刚申请的栈空间,然后调用prvInitialiseNewTask函数初始化TCB变量,包括pxTopOfStack、pcTaskName、uxPriority、StateListItem,其中pxTopOfStack变量在调用pxPortInitialiseStack函数后重新赋值,pxPortInitialiseStack函数会将栈中头部34个字节的空间用于保存CPU的状态,在创建任务时会初始化这34个字节的空间。

添加就绪链表的具体过程为:首先会先初始化RTOS用到的一些链表,包括每个中断优先级下的就绪链表,阻塞链表和挂起链表,例如如果支持5个优先级,则会创建5个就绪链表。最后会将任务挂载到对应优先级的链表中,等待调度器的执行。
c
#define prvAddTaskToReadyList( pxTCB ) \
traceMOVED_TASK_TO_READY_STATE( pxTCB ); \
taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority ); \
vListInsertEnd( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) ); \
tracePOST_MOVED_TASK_TO_READY_STATE( pxTCB )
使用方法
c
xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate, /* 任务入口函数 */
(const char* )"AppTaskCreate",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL,/* 任务入口函数参数 */
(UBaseType_t )1, /* 任务的优先级 数值越大,优先级越高 */
(TaskHandle_t* )&AppTaskCreate_Handle);/* 任务控制块指针 */
总结
任务是RTOS中最基本的调度单元,任务的创建与管理是非常重要的内容,任务的创建分为静态和动态两种方法,一般在实际项目中使用的均为动态方法。