通过分析任务切换,我们知道任务切换的两种方法:系统节拍器中断、调用portYIELD产生PendSV中断。
在系统节拍器中断中,如果调度器被挂起,仅仅将调度器挂起时间加一(在解除挂起后需要补偿这些节拍),并不会检查是否有任务需要切换。
/* 系统节拍加一 */
BaseType_t xTaskIncrementTick(void)
{
BaseType_t xSwitchRequired = pdFALSE;
/* 调度器没有被挂起 */
if(uxSchedulerSuspended == (UBaseType_t)pdFALSE)
{
......
}
/* 调度器被挂起 */
else
{
/* 挂起时间加一 */
++uxPendedTicks;
}
/* 前面有程序因为各种原因,要求延迟到现在切换 */
if(xYieldPending != pdFALSE)
{
/* 请求切换任务,最终进入PendSV异常,是否切换上下文还是在于PendSV */
xSwitchRequired = pdTRUE;
}
return xSwitchRequired;
}
在PendSV中断中,如果调度器被挂起,则不进行上下文切换,通过xYieldPending将任务切换延迟到下一个节拍。
/* 任务切换上下文 */
void vTaskSwitchContext(void)
{
/* 调度器被挂起 */
if(uxSchedulerSuspended != (UBaseType_t)pdFALSE)
{
/* 等到下一次节拍的时候再切换上下文 */
xYieldPending = pdTRUE;
}
/* 调度器没有挂起 */
else
{
......
}
}
也就是说,只要将调度器挂起,就肯定不会进行任务切换。
挂起调度器非常简单,只要让uxSchedulerSuspended不等于pdFALSE(0)即可。调度器可以多次挂起,但是对应的也要进行多次解除挂起。
/* 挂起调度器 */
void vTaskSuspendAll(void)
{
/* 挂起层数加一 */
++uxSchedulerSuspended;
}
当完全解除调度器挂起时,需要进行如下工作:
检查在调度器挂起期间是否有任务进入就绪态,有则要将其从挂起期间就绪任务列表中移除,重新挂接到就绪任务列表
更新下一个需要解除阻塞的任务,的解除时间
要对之前调度器挂起期间产生的节拍进行补偿
对于延迟切换任务到下一个节拍的请求,在这里提供一次切换机会
/* 解除调度器挂起 */
BaseType_t xTaskResumeAll( void )
{
TCB_t *pxTCB = NULL;
BaseType_t xAlreadyYielded = pdFALSE;
configASSERT(uxSchedulerSuspended);
/* 进入临界区 */
taskENTER_CRITICAL();
{
/* 调度器挂起层数减一 */
--uxSchedulerSuspended;
/* 调度器挂起完全解除 */
if(uxSchedulerSuspended == (UBaseType_t)pdFALSE)
{
/* 系统当前任务数大于0 */
if(uxCurrentNumberOfTasks > (UBaseType_t)0U)
{
/* 挂起时进入就绪的任务列表不为空 */
while(listLIST_IS_EMPTY(&xPendingReadyList) == pdFALSE)
{
/* 从挂起时进入就绪的任务列表中取出一个任务 */
pxTCB = listGET_OWNER_OF_HEAD_ENTRY((&xPendingReadyList));
/* 将任务从事件列表中移除 */
(void)uxListRemove(&(pxTCB->xEventListItem));
/* 将任务从状态列表中移除 */
(void)uxListRemove(&(pxTCB->xStateListItem));
/* 将任务加入就绪任务列表 */
prvAddTaskToReadyList(pxTCB);
/* 如果任务优先级高于当前任务优先级,则请求在下一个节拍时切换 */
if(pxTCB->uxPriority >= pxCurrentTCB->uxPriority)
{
xYieldPending = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
/* 有任务在挂起期间就绪 */
if(pxTCB != NULL)
{
/* 更新下一个要解除阻塞的时间 */
prvResetNextTaskUnblockTime();
}
{
/* 调度器挂起时间 */
UBaseType_t uxPendedCounts = uxPendedTicks;
/* 调度器挂起时间大于1个节拍 */
if(uxPendedCounts > (UBaseType_t)0U)
{
/* 将所有节拍重新补上 */
do
{
/* 节拍数加一,如果需要切换任务,则请求在下一个节拍时切换 */
if(xTaskIncrementTick() != pdFALSE)
{
xYieldPending = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
--uxPendedCounts;
}while(uxPendedCounts > (UBaseType_t)0U);
uxPendedTicks = 0;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
/* 原来请求在下一个节拍时切换的任务,在这里直接请求切换 */
if(xYieldPending != pdFALSE)
{
#if (configUSE_PREEMPTION != 0)
{
/* 已经切换过任务 */
xAlreadyYielded = pdTRUE;
}
#endif
taskYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
/* 退出临界区 */
taskEXIT_CRITICAL();
/* 返回是否已经切换过任务 */
return xAlreadyYielded;
}