FreeRTOS学习 任务管理(二)

任务管理(二)

多任务系统在同一时刻中,只有一个任务占有CPU的使用权。一个任务长期占有CPU会影响对系统的效应能力,故任务需要在空闲的时候,通过延时/挂起来让出CPU的使用权给其他任务,这样任务调度器才能调度其他任务运行。

一、任务延时 vTaskDelay

任务延时就是当前任务将CPU让出一定时间,将任务插入延时链表中,等到延时时间到达才将任务恢复到就绪状态,等待调度运行。

其实质就是将任务移动到延时链表/挂起链表。

注意进入延时函数时,会挂起调度器,这是为了防止在插入就绪链表过程中,由于中断产生打乱了代码逻辑

void vTaskDelay(const TickType_t xTicksToDelay)
{
    BaseType_t xAlreadyYielded = pdFALSE;
    
    if (xTicksToDelay > (TickType_t)0U)
    {
        configASSERT(uxSchedulerSuspended == 0);
        //TODO 挂起调度器、防止中断导致任务插入就绪链表、扰乱代码逻辑
        vTaskSuspendAll();
        {
            traceTASK_DELAY();

            //当调度器挂起时,任务不能插入就绪表
            //添加当前任务到延时链表
            prvAddCurrentTaskToDelayedList(xTicksToDelay, pdFALSE);
        }
        //恢复调度器、若恢复调度器时、已经完成一次任务调度、则接下来不需要再产生一次任务调度
        xAlreadyYielded = xTaskResumeAll();
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }

    //若调度器恢复时未进行任务调度,则启动一次任务调度
    if (xAlreadyYielded == pdFALSE)
    {
        portYIELD_WITHIN_API();
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
}
static void prvAddCurrentTaskToDelayedList(TickType_t xTicksToWait,
                                           const BaseType_t xCanBlockIndefinitely)
{
    TickType_t xTimeToWake;
    const TickType_t xConstTickCount = xTickCount; //保存当前时钟计数值为常量

    //进入延时链表、设置标记
    pxCurrentTCB->ucDelayAborted = pdFALSE;


    //首先将当前任务移出就绪链表 若返回为0说明该优先级链表为空 需要重置最高优先级
    if (uxListRemove(&(pxCurrentTCB->xStateListItem)) == (UBaseType_t)0)
    {

        portRESET_READY_PRIORITY(pxCurrentTCB->uxPriority, uxTopReadyPriority);
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }


    {
        //若延时时间最大且无限期等待,则直接加入挂起链表 任务调度器不对其管理
        if ((xTicksToWait == portMAX_DELAY) && (xCanBlockIndefinitely != pdFALSE))
        {

            vListInsertEnd(&xSuspendedTaskList, &(pxCurrentTCB->xStateListItem));
        }
        else   //插入延时链表
        {
            //计算任务下次由时钟中断唤醒的时间戳 注意时钟溢出(当前时间+需要等待的时间=下次醒来的时间点)
            xTimeToWake = xConstTickCount + xTicksToWait;

            //将时间戳设置为任务的状态节点值
            listSET_LIST_ITEM_VALUE(&(pxCurrentTCB->xStateListItem), xTimeToWake);

            if (xTimeToWake < xConstTickCount)
            {
                //延时时间溢出、任务插入延时溢出链表
                vListInsert(pxOverflowDelayedTaskList, &(pxCurrentTCB->xStateListItem));
            }
            else
            {
                //未溢出,插入当前延时链表
                vListInsert(pxDelayedTaskList, &(pxCurrentTCB->xStateListItem));

                //更新xNextTaskUnblockTime
                if (xTimeToWake < xNextTaskUnblockTime)
                {
                    xNextTaskUnblockTime = xTimeToWake;
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
        }
    }

        /* Avoid compiler warning when INCLUDE_vTaskSuspend is not 1. */
        (void)xCanBlockIndefinitely;
    }

}

二、绝对任务延时 xTaskDelayUntil

另一种延时方式:延时一个任务直到指定的时间点到来。经常用于周期性的运行一个任务。

使用vTaskDelay()由于任务执行的时间是不确定的(可能被中断打断),故相对延时无法实现周期信运行一个任务。而使用绝对延时xTaskDelayUntil()能保证任务在指定的时间点唤醒,延时时间由上一次运行时的时间点与所需延时频率计算。

该函数不同之处在于计算延时时间、最后都同样的调用prvAddCurrentTaskToDelayedList函数。

BaseType_t xTaskDelayUntil(TickType_t *const pxPreviousWakeTime,
                           const TickType_t xTimeIncrement)
{
    TickType_t xTimeToWake;
    BaseType_t xAlreadyYielded, xShouldDelay = pdFALSE;

    configASSERT(pxPreviousWakeTime);
    configASSERT((xTimeIncrement > 0U));
    configASSERT(uxSchedulerSuspended == 0);
    //TODO 挂起调度器、防止中断导致任务插入就绪链表、扰乱代码逻辑
    vTaskSuspendAll();
    {
        //保证xTickCount在本代码块内不会被修改
        const TickType_t xConstTickCount = xTickCount;

        //计算下次唤醒时间
        xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;

        if (xConstTickCount < *pxPreviousWakeTime)
        {
            // -----cur++++++pre----- 只有当xTimeToWake在+号时才能进入延时
            if ((xTimeToWake < *pxPreviousWakeTime) && (xTimeToWake > xConstTickCount))
            {
                xShouldDelay = pdTRUE;
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        else
        {
            // +++++pre-----cur++++++ 只有当xTimeToWake在+号时才能进入延时
            if ((xTimeToWake < *pxPreviousWakeTime) || (xTimeToWake > xConstTickCount))
            {
                xShouldDelay = pdTRUE;
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }

        //更新pxPreviousWakeTime
        *pxPreviousWakeTime = xTimeToWake;

        if (xShouldDelay != pdFALSE)
        {
            traceTASK_DELAY_UNTIL(xTimeToWake);

            //prvAddCurrentTaskToDelayedList需要的是相对延时时间 故是xTimeToWake - xConstTickCount
            prvAddCurrentTaskToDelayedList(xTimeToWake - xConstTickCount, pdFALSE);
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    //恢复调度器、执行任务调度
    xAlreadyYielded = xTaskResumeAll();

    if (xAlreadyYielded == pdFALSE)
    {
        portYIELD_WITHIN_API();
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }

    return xShouldDelay;
}

三、终止延时

有开始、就会有结束。其他任务可唤醒处于延时中的函数。其实质就是将任务移动到就绪链表、并进行任务调度。

BaseType_t xTaskAbortDelay(TaskHandle_t xTask)
{
    TCB_t *pxTCB = xTask;
    BaseType_t xReturn;

    configASSERT(pxTCB);
    //挂起任务调度器
    vTaskSuspendAll();
    {
        //任务的状态为阻塞时,才能解锁
        if (eTaskGetState(xTask) == eBlocked)
        {
            xReturn = pdPASS;

            //从延时链表中移除
            //TODO  当调度器挂起时、中断不会修改任务的xStateListItem.所以在这里的操作是安全的
            (void)uxListRemove(&(pxTCB->xStateListItem));

            //从事件链表中移除 (中断可能修改xEventListItem 故需要进入临界区)
            taskENTER_CRITICAL();
            {
                if (listLIST_ITEM_CONTAINER(&(pxTCB->xEventListItem)) != NULL)
                {
                    (void)uxListRemove(&(pxTCB->xEventListItem));
                    //记录他已经解除挂起
                    pxTCB->ucDelayAborted = pdTRUE;
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            taskEXIT_CRITICAL();

            //退出延时后,进入就绪态(因为中断中不能访问就绪链表,故当前函数不能在中断中调用)
            prvAddTaskToReadyList(pxTCB);

            {
                //解锁任务的优先级高于当前任务优先级,发生抢占
                if (pxTCB->uxPriority > pxCurrentTCB->uxPriority)
                {
                    //挂起一次任务调度(因为当前调度器挂起了)
                    xYieldPending = pdTRUE;
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }

        }
        else
        {
            xReturn = pdFAIL;
        }
    }
    (void)xTaskResumeAll();

    return xReturn;
}

四、任务挂起

将指定任务挂起、该任务不会被恢复运行,除非用户代码恢复该任务。其代码逻辑就是将任务移动到挂起链表、并判断是否需要任务调度。

void vTaskSuspend(TaskHandle_t xTaskToSuspend)
{
    TCB_t *pxTCB;
    //进入临界区
    taskENTER_CRITICAL();
    {
        //获取任务tcb
        pxTCB = prvGetTCBFromHandle(xTaskToSuspend);

        traceTASK_SUSPEND(pxTCB);

        //从就绪/阻塞链表中移除任务
        if (uxListRemove(&(pxTCB->xStateListItem)) == (UBaseType_t)0)
        {
            taskRESET_READY_PRIORITY(pxTCB->uxPriority);
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }

        //从事件链表中移除任务
        if (listLIST_ITEM_CONTAINER(&(pxTCB->xEventListItem)) != NULL)
        {
            (void)uxListRemove(&(pxTCB->xEventListItem));
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
        //将任务插入挂起链表
        vListInsertEnd(&xSuspendedTaskList, &(pxTCB->xStateListItem));
    }
    taskEXIT_CRITICAL();

    if (xSchedulerRunning != pdFALSE)
    {
        //更新下次任务解锁时间。刚刚挂起的任务可能是下个解锁的任务
        taskENTER_CRITICAL();
        {
            prvResetNextTaskUnblockTime();
        }
        taskEXIT_CRITICAL();
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }

    //挂起任务是正在运行的任务
    if (pxTCB == pxCurrentTCB)
    {
        if (xSchedulerRunning != pdFALSE)
        {
            //当前任务被挂起,执行一次任务调度
            configASSERT(uxSchedulerSuspended == 0);
            portYIELD_WITHIN_API();
        }
        else    //todo 调度器关闭所以必须调整pxCurrentTCB
        {
            
            if (listCURRENT_LIST_LENGTH(&xSuspendedTaskList) == uxCurrentNumberOfTasks)
            {
                //所有任务都被挂起 pxCurrentTCB为null,下次创建新任务时,就执行新任务
                pxCurrentTCB = NULL;
            }
            else
            {
                //更新pxCurrentTCB
                vTaskSwitchContext();
            }
        }
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
}

五、恢复任务

挂起的任务只有得到恢复才能运行。以下是函数在任务函数中调用。

void vTaskResume(TaskHandle_t xTaskToResume)
{
    TCB_t *const pxTCB = xTaskToResume;

    configASSERT(xTaskToResume);

    //恢复的任务不可能是null 并且 不可能是正在运行的任务
    if ((pxTCB != pxCurrentTCB) && (pxTCB != NULL))
    {
        taskENTER_CRITICAL();
        {
            //判断是否真的在挂起链表中
            if (prvTaskIsTaskSuspended(pxTCB) != pdFALSE)
            {
                traceTASK_RESUME(pxTCB);

                //移出挂起链表、加入就绪链表
                (void)uxListRemove(&(pxTCB->xStateListItem));
                prvAddTaskToReadyList(pxTCB);

                //恢复任务的优先级较高、进行任务调度
                if (pxTCB->uxPriority >= pxCurrentTCB->uxPriority)
                {

                    taskYIELD_IF_USING_PREEMPTION();
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        taskEXIT_CRITICAL();
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
}

六、从中断中恢复任务

与上一次函数的区别是:

  • 该函数只能在中断中调用。

  • 在中断中进入临界区使用的函数也不一样。同时,还需要注意当调度器挂起时,不能在中断中直接修改就绪链表,而是先将任务缓存到xPendingReadyList,当调度器恢复时,再将任务移动到就绪链表。

  • 在中断中不能进行任务调度,所以该函数的返回值表示是否需要在退出中断后进行任务调度

BaseType_t xTaskResumeFromISR(TaskHandle_t xTaskToResume)
{
    BaseType_t xYieldRequired = pdFALSE; //任务调度标记 由于中断中不能执行上下文切换、故使用他来代替上下文切换
    TCB_t *const pxTCB = xTaskToResume;
    UBaseType_t uxSavedInterruptStatus;

    configASSERT(xTaskToResume);

    portASSERT_IF_INTERRUPT_PRIORITY_INVALID();
    //进入中断级临界区
    uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR(); 
    {
        //判断是否真的挂起
        if (prvTaskIsTaskSuspended(pxTCB) != pdFALSE)
        {
            traceTASK_RESUME_FROM_ISR(pxTCB);

            if (uxSchedulerSuspended == (UBaseType_t)pdFALSE)
            {
                //调度器运行中、若任务优先级更高、则产生任务调度
                if (pxTCB->uxPriority >= pxCurrentTCB->uxPriority)
                {
                    xYieldRequired = pdTRUE; //需要进行任务调度

                    xYieldPending = pdTRUE; //一个任务调度请求被挂起
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
                //将任务移动到就绪链表
                (void)uxListRemove(&(pxTCB->xStateListItem));
                prvAddTaskToReadyList(pxTCB);
            }
            else
            {
                //调度器挂起 不能在中断中直接修改就绪链表,先将任务缓存到xPendingReadyList
                vListInsertEnd(&(xPendingReadyList), &(pxTCB->xEventListItem));
            }
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    portCLEAR_INTERRUPT_MASK_FROM_ISR(uxSavedInterruptStatus);

    return xYieldRequired;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值