FreeRTOS基础入门——FreeRTOS时间管理(十一)

 个人名片:

🎓作者简介:嵌入式领域优质创作者
🌐个人主页:妄北y

📞个人QQ:2061314755

💌个人邮箱:[mailto:2061314755@qq.com]
📱个人微信:Vir2025WBY

🖥️个人公众号:科技妄北
🖋️本文为妄北y原创佳作,独家首发于CSDN🎊🎊🎊
💡座右铭:改造世界固然伟大,但改造自我更为可贵。

专栏导航:

妄北y系列专栏导航:

物联网嵌入式开发项目:大学期间的毕业设计,课程设计,大创项目,各种竞赛项目,全面覆盖了需求分析、方案设计、实施与调试、成果展示以及总结反思等关键环节。📚💼💡

QT基础入门学习:对QT的基础图形化页面设计进行了一个简单的学习与认识,利用QT的基础知识进行了翻金币小游戏的制作。🛠️🔧💭

Linux基础编程:初步认识什么是Linux,为什么学Linux,安装环境,进行基础命令的学习,入门级的shell编程。🍻🎉🖥️

深耕Linux应用开发:分享Linux的基本概念、命令行操作、文件系统、用户和权限管理等,网络编程相关知识,TCP/IP 协议、套接字(Socket)编程等,可以实现网络通信功能。常见开源库的二次开发,如libcurl、OpenSSL、json-c、freetype等💐📝💡

Linux驱动开发:Linux驱动开发是Linux系统不可或缺的组成部分,它专注于编写特殊的程序——驱动程序。这些程序承载着硬件设备的详细信息,并扮演着操作系统与硬件间沟通的桥梁角色。驱动开发的核心使命在于确保硬件设备在Linux系统上顺畅运作,同时实现与操作系统的无缝集成,为用户带来流畅稳定的体验。🚀🔧💻

Linux项目开发:Linux基础知识的实践,做项目是最锻炼能力的一个学习方法,这里我们会学习到一些简单基础的项目开发与应用,而且都是毕业设计级别的哦。🤸🌱🚀

非常期待与您一同在这个广阔的互联网天地里,携手探索知识的海洋,互相学习,共同进步。🌐💫🌱 熠熠星光,照亮我们的成长之路

✨✨ 欢迎订阅本专栏,对专栏内容任何问题都可以随时联系博主,共同书写属于我们的精彩篇章!✨✨

文章介绍:

📚本篇文章将深入剖析RTOS学习的精髓与奥秘,与您一同分享相关知识!🎉🎉🎉

若您觉得文章尚可入目,期待您能慷慨地送上点赞、收藏与分享的三连支持!您的每一份鼓励,都是我创作路上源源不断的动力。让我们携手并进,共同奔跑,期待在顶峰相见的那一天,共庆辉煌!🚀🚀🚀

🙏衷心感谢大家的点赞👍、收藏⭐和评论✍️,您的支持是我前进的动力!

目录:

目录:

一、系统延时函数

1.1 函数 vTaskDelay()

1.2 函数prvAddCurrentTaskToDelayedList()

1.3 函数vTaskDelayUntil()

二、系统时钟节拍:

2.1 函数xTaskIncrementTick()

2.2 函数xTaskResumeAll()


一、系统延时函数

在FreeRTOS中,延时函数提供了两种模式:相对模式和绝对模式。这两种模式分别由不同的函数实现。

1.1 函数 vTaskDelay()

相对延时函数:vTaskDelay()

  • 作用vTaskDelay()函数用于实现相对延时,即任务会延时指定的tick数。
  • 定义:在文件tasks.c中定义,使用此函数需要确保宏INCLUDE_vTaskDelay为1。
void vTaskDelay(const TickType_t xTicksToDelay)
{
    BaseType_t xAlreadyYielded = pdFALSE;

    /* 延迟时间为零时,仅强制重新调度。 */
    if (xTicksToDelay > (TickType_t)0U)
    {
        configASSERT(uxSchedulerSuspended == 0);
        vTaskSuspendAll();
        {
            traceTASK_DELAY();

            /* 在调度器挂起时,从事件列表中移除的任务不会被放入
            就绪列表或从阻塞列表中移除,直到调度器恢复运行。

            这个任务不能在事件列表中,因为它是当前正在执行的任务。 */
            prvAddCurrentTaskToDelayedList(xTicksToDelay, pdFALSE);
        }
        xAlreadyYielded = xTaskResumeAll();
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }

    /* 如果xTaskResumeAll尚未强制重新调度,则强制重新调度,
    因为我们可能已经将自己置于睡眠状态。 */
    if (xAlreadyYielded == pdFALSE)
    {
        portYIELD_WITHIN_API();
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
}

1. 延迟时间为零时,仅强制重新调度: 如果xTicksToDelay为零,则不进行实际的延时操作,只是强制触发一次任务重新调度。

2. 确保调度器未挂起:onfigASSERT(uxSchedulerSuspended == 0);:确保调度器在调用延时函数时未被挂起。

3. 挂起调度器 vTaskSuspendAll();:挂起任务调度器,防止在延时操作期间发生任务切换。

4. 跟踪任务延时事件 traceTASK_DELAY();:记录任务延时事件,通常用于调试和跟踪。

5. 从事件列表中移除任务的说明:当调度器挂起时,从事件列表中移除的任务不会立即被放入就绪列表或阻塞列表,而是等到调度器恢复运行后才处理。同时,当前任务不能在事件列表中,因为它是正在执行的任务。

6. 将当前任务添加到延时列表prvAddCurrentTaskToDelayedList(xTicksToDelay, pdFALSE);:将当前任务添加到延时列表中,并指定延时时间。

7. 恢复调度器: xAlreadyYielded = xTaskResumeAll();:恢复任务调度器,并检查是否已经触发了任务重新调度。

8. 强制重新调度以防止处于睡眠状态: 如果xTaskResumeAll()尚未触发重新调度(即xAlreadyYieldedpdFALSE),则调用portYIELD_WITHIN_API();强制重新调度,确保任务不会长时间处于睡眠状态。

1.2 函数prvAddCurrentTaskToDelayedList()

以下是缩减后的 prvAddCurrentTaskToDelayedList() 函数,函数在文件 tasks.c中有定义,它用于将当前任务添加到等待列表中:

static void prvAddCurrentTaskToDelayedList(TickType_t xTicksToWait, const BaseType_t xCanBlockIndefinitely)
{
    TickType_t xTimeToWake;
    const TickType_t xConstTickCount = xTickCount;

    #if (INCLUDE_xTaskAbortDelay == 1)
    {
        /* 即将进入延时列表,因此确保将 ucDelayAborted 标志重置为 pdFALSE,
        这样当任务离开阻塞状态时可以检测到其已被设置为 pdTRUE。 */
        pxCurrentTCB->ucDelayAborted = pdFALSE;
    }
    #endif

    /* 在将任务添加到阻塞列表之前,将其从就绪列表中移除,
    因为同一个列表项用于这两个列表。 */
    if (uxListRemove(&(pxCurrentTCB->xStateListItem)) == (UBaseType_t)0)
    {
        /* 当前任务必须在就绪列表中,因此不需要检查,并且可以直接调用端口重置宏。 */
        portRESET_READY_PRIORITY(pxCurrentTCB->uxPriority, uxTopReadyPriority);
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }

    #if (INCLUDE_vTaskSuspend == 1)
    {
        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();
                }
            }
        }
    }
    #else /* INCLUDE_vTaskSuspend */
    {
        /* 计算任务应该被唤醒的时间,如果事件没有发生。这可能会溢出,但这无关紧要,内核会正确管理它。 */
        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();
            }
        }

        /* 当 INCLUDE_vTaskSuspend 不为 1 时,避免编译器警告。 */
        (void)xCanBlockIndefinitely;
    }
    #endif /* INCLUDE_vTaskSuspend */
}

1. 初始化和配置

  • 获取当前的系统 tick 计数 xTickCount 并存储在 xConstTickCount 中。
  • 如果定义了 INCLUDE_xTaskAbortDelay,则重置当前任务的 ucDelayAborted 标志为 pdFALSE,以便在任务离开阻塞状态时可以检测到延时是否被中止。

2. 从就绪列表中移除任务

  • 调用 uxListRemove() 函数将当前任务从就绪列表中移除。
  • 如果任务成功从就绪列表中移除,则调用 portRESET_READY_PRIORITY() 宏重置任务的就绪优先级。

3. 处理延时任务

  • 如果定义了 INCLUDE_vTaskSuspend
    • 检查 xTicksToWait 是否为 portMAX_DELAY 并且 xCanBlockIndefinitely 不为 pdFALSE。如果是,则将任务添加到挂起任务列表 xSuspendedTaskList 中,以确保任务无限期阻塞。
    • 否则,计算任务的唤醒时间 xTimeToWake,并将其插入到适当的延时任务列表中:
      • 如果 xTimeToWake 小于 xConstTickCount,表示唤醒时间已溢出,将任务插入到溢出延时任务列表 pxOverflowDelayedTaskList 中。
      • 否则,将任务插入到当前延时任务列表 pxDelayedTaskList 中。
      • 如果任务被插入到延时任务列表的头部,则更新 xNextTaskUnblockTime

4. 未定义 INCLUDE_vTaskSuspend 的情况

  • 计算任务的唤醒时间 xTimeToWake,并将其插入到适当的延时任务列表中(类似于上述步骤)。
  • 避免编译器警告,将 xCanBlockIndefinitely 强制转换为 void

prvAddCurrentTaskToDelayedList() 函数的核心思路是根据任务的延时时间将其从就绪列表中移除,并插入到适当的延时或挂起任务列表中。通过计算任务的唤醒时间并处理可能的溢出情况,确保任务在预期的时间点被唤醒。如果任务需要无限期阻塞且 INCLUDE_vTaskSuspend 被定义,则将任务添加到挂起任务列表,以避免因定时事件而被唤醒。这样可以灵活管理任务的延时和阻塞行为。 

1.3 函数vTaskDelayUntil()

函数VTaskDelayUntil()会阻塞任务,阻塞时间是一个绝对时间,那些需要按照一定的频率运行的任务可以使用函数VTaskDelayUntilO)。此函数再文件tasks..c中有如下定义:

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

    configASSERT( pxPreviousWakeTime );
    configASSERT( ( xTimeIncrement > 0U ) );
    configASSERT( uxSchedulerSuspended == 0 );

    vTaskSuspendAll();
    {
        /* 小优化。在这段代码块中,tick 计数不会改变。 */
        const TickType_t xConstTickCount = xTickCount;

        /* 生成任务希望唤醒的 tick 时间。 */
        xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;

        if (xConstTickCount < *pxPreviousWakeTime)
        {
            /* 自上次调用此函数以来,tick 计数已溢出。在这种情况下,
            唯一需要真正延迟的情况是唤醒时间也已溢出,并且唤醒时间大于 tick 时间。
            当这种情况发生时,就好像两者都没有溢出一样。 */
            if ((xTimeToWake < *pxPreviousWakeTime) && (xTimeToWake > xConstTickCount))
            {
                xShouldDelay = pdTRUE;
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        else
        {
            /* tick 时间没有溢出。在这种情况下,如果唤醒时间已溢出,和/或 tick 时间小于唤醒时间,我们将延迟。 */
            if ((xTimeToWake < *pxPreviousWakeTime) || (xTimeToWake > xConstTickCount))
            {
                xShouldDelay = pdTRUE;
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }

        /* 更新唤醒时间,为下次调用做准备。 */
        *pxPreviousWakeTime = xTimeToWake;

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

            /* prvAddCurrentTaskToDelayedList() 需要阻塞时间,而不是唤醒时间,所以减去当前的 tick 计数。 */
            prvAddCurrentTaskToDelayedList(xTimeToWake - xConstTickCount, pdFALSE);
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    xAlreadyYielded = xTaskResumeAll();

    /* 如果 xTaskResumeAll 没有导致重新调度,我们可能已进入睡眠状态,强制重新调度。 */
    if (xAlreadyYielded == pdFALSE)
    {
        portYIELD_WITHIN_API();
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
}

1. pxPreviousWakeTime:指向任务上一次延时结束时被唤醒的时间点。任务第一次调用 vTaskDelayUntil() 时需要初始化为任务进入 while(1) 循环体的时间点值。在以后的运行中,vTaskDelayUntil() 会自动更新 pxPreviousWakeTime

2. xTimeIncrement:任务需要延时的时间节拍数(相对于 pxPreviousWakeTime 本次延时的节拍数)。

挂起任务调度器

vTaskSuspendAll();

记录进入函数 vTaskDelayUntil() 的时间点值,并保存在 xConstTickCount

const TickType_t xConstTickCount = xTickCount;

根据延时时间 xTimeIncrement 计算任务下一次要唤醒的时间点

计算出唤醒时间 xTimeToWake,这个时间是相对于 pxPreviousWakeTime 的。

xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;

处理可能的 tick 计数溢出情况并决定是否需要延迟:

if (xConstTickCount < *pxPreviousWakeTime)
{
    // 处理 tick 计数溢出
    if ((xTimeToWake < *pxPreviousWakeTime) && (xTimeToWake > xConstTickCount))
    {
        xShouldDelay = pdTRUE;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
}
else
{
    // 处理正常情况
    if ((xTimeToWake < *pxPreviousWakeTime) || (xTimeToWake > xConstTickCount))
    {
        xShouldDelay = pdTRUE;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
}

更新 pxPreviousWakeTime 为新的唤醒时间 xTimeToWake

*pxPreviousWakeTime = xTimeToWake;

根据是否需要延迟决定下一步操作

如果需要延迟,则调用 prvAddCurrentTaskToDelayedList() 将当前任务添加到延时列表:

if (xShouldDelay != pdFALSE)
{
    traceTASK_DELAY_UNTIL(xTimeToWake);
    prvAddCurrentTaskToDelayedList(xTimeToWake - xConstTickCount, pdFALSE);
}
else
{
    mtCOVERAGE_TEST_MARKER();
}

恢复任务调度器并根据需要强制重新调度

恢复任务调度器:

xAlreadyYielded = xTaskResumeAll();

如果调度器没有导致重新调度,则强制重新调度:

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

参数关系图

  • pxPreviousWakeTime:上一次任务被唤醒的时间点。
  • xTimeIncrement:任务需要延时的时间节拍数。
  • xConstTickCount:记录进入函数 vTaskDelayUntil() 的当前系统时间。
  • xTimeToWake:任务下一次要唤醒的时间点。

详细关系如图1-1: 

图1-1 变量之间关系 

(1)为任务主体,也就是任务真正要做的工作

(2)是任务函数中调用vTaskDelayUntil()对任务进行延时

(3)为其他任务在运行。

vTaskDelayUntil() 函数用于在 FreeRTOS 中实现任务的绝对延时任务的延时时间 xTimeIncrement 是相对于上一次任务唤醒时间 pxPreviousWakeTime 计算的。

因此,任务的总执行时间必须小于 xTimeIncrement,以确保任务能够按固定的周期运行。

这意味着任务的执行周期始终为 xTimeIncrement,并且任务必须在这个时间内完成执行。通过这种方式,可以保证任务以恒定的频率运行,因此 vTaskDelayUntil() 被称为绝对延时函数。

(4)、理论上xConstTickCount要大于pxPreviousWakeTime的,但是也有一种情况会导致xConstTickCount小于pxPreviousWakeTime,那就是xConstTickCount溢出了!

(5)、既然xConstTickCount都溢出了,那么计算得到的任务唤醒时间点肯定也是要溢出的,并且xTimeToWake肯定也是要大于xConstTickCount的。这种情况如图 1-2 所示:

图1-2 变量溢出 

(6)、如果满足(5)条件的话就将pdTRUE赋值给xShouldDelay,标记允许延时。

(7)、还有其他两种情况,一:只有xTimeToWake溢出,二:都没有溢出。只有xTimeToWake溢出的话如图1-3 所示:

图1-3 xTimeToWake 溢出

如果都不溢出则如图1-1 所示,这俩种情况都会运行延时。

(8)、将pdTRUE赋值给xShouldDelay,标记允许延时。

(9)、更新pxPreviousWakeTime的值,更新为xTimeToWake,为本函数的下次执行做准备。

(10)、经过前面的判断,允许进行任务延时。

(11)、调用函数prvAddCurrentTaskToDelayedList()进行延时。函数的第一个参数是设置任务的阻塞时间,前面我们已经计算出了任务下一次唤醒时间点了,那么任务还需要阻塞的时间就是下一次唤醒时间点xTimeToWake减去当前的时间xConstTickCount。而在函数vTaskDelay()中只是简单的将这参数设置为xTicksToDelay。

(12)、调用函数xTaskResumeAll()恢复任务调度器。

函数vTaskDelayUntil()的使用方法如下:

void TestTask(void *pvParameters) {
    // 定义变量,用于存储上次唤醒时间
    TickType_t PreviousWakeTime;

    // 延时50ms,但由于 vTaskDelayUntil() 的参数需要设置为节拍数,因此利用 pdMS_TO_TICKS 函数将时间转换为节拍数
    const TickType_t TimeIncrement = pdMS_TO_TICKS(50);

    // 获取当前的系统节拍值,初始化上次唤醒时间
    PreviousWakeTime = xTaskGetTickCount();

    for (;;) {
        /***********************************************************
        ************************* 任务主体 **************************
        ***********************************************************/

        // 调用函数 vTaskDelayUntil 进行延时
        vTaskDelayUntil(&PreviousWakeTime, TimeIncrement);
    }
}

其实使用函数vTaskDelayUntil()延时的任务也不一定就能周期性的运行,使用函数vTaskDelayUntil()只能保证你按照一定的周期取消阻塞,进入就绪态。

如果有更高优先级或者中断的话你还是得等待其他的高优先级任务或者中断服务函数运行完成才能轮到你。这个绝对延时只是相对于vTaskDelay()这个简单的延时函数而言的。

二、系统时钟节拍:

2.1 函数xTaskIncrementTick()

不管是什么系统,运行都需要有个系统时钟节拍,前面已经提到多次了,xTickCount就是FreeRTOS的系统时钟节拍计数器。每个滴答定时器中断中xTickCount就会加一, xTickCount的具体操作过程是在函数xTaskIncrementTick()中进行的,此函数在文件 tasks.c中有定义,如下:

BaseType_t xTaskIncrementTick( void )
{
    TCB_t * pxTCB;
    TickType_t xItemValue;
    BaseType_t xSwitchRequired = pdFALSE;

    /* 被可移植层调用,每次发生节拍中断时调用。
       增加节拍计数,然后检查新的节拍值是否会导致任何任务被取消阻塞。 */
    traceTASK_INCREMENT_TICK( xTickCount );
    
    if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
    {
        /* 小优化。在此代码块中节拍计数不会更改。 */
        const TickType_t xConstTickCount = xTickCount + 1;

        /* 增加 RTOS 节拍计数,如果发生溢出则切换延迟和溢出延迟列表。 */
        xTickCount = xConstTickCount;

        if( xConstTickCount == ( TickType_t ) 0U )
        {
            taskSWITCH_DELAYED_LISTS();
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }

        /* 检查当前节拍是否使得某个超时到期。任务按唤醒时间顺序存储在队列中,
           这意味着一旦找到一个任务其阻塞时间尚未到期,就不需要再继续检查列表中的其他任务。 */
        if( xConstTickCount >= xNextTaskUnblockTime )
        {
            for( ;; )
            {
                if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
                {
                    /* 延迟列表为空。将 xNextTaskUnblockTime 设置为最大可能值,
                       这样非常不可能在下次通过测试时 ( xTickCount >= xNextTaskUnblockTime ) 条件成立。 */
                    xNextTaskUnblockTime = portMAX_DELAY; /*lint !e961 MISRA 例外,因为在某些移植中,类型转换仅仅是多余的。 */
                    break;
                }
                else
                {
                    /* 延迟列表不为空,获取延迟列表头部条目的值。
                       这是延迟列表头部任务必须从阻塞状态移除的时间。 */
                    pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
                    xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );

                    if( xConstTickCount < xItemValue )
                    {
                        /* 尚未到达取消阻塞该项的时间,但是项值是延迟列表头部任务必须从阻塞状态移除的时间,
                           因此将项值记录到 xNextTaskUnblockTime。 */
                        xNextTaskUnblockTime = xItemValue;
                        break;
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }

                    /* 到了移除该项阻塞状态的时间。 */
                    ( void ) uxListRemove( &( pxTCB->xStateListItem ) );

                    /* 任务是否还在等待事件?如果是,则将其从事件列表中移除。 */
                    if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
                    {
                        ( void ) uxListRemove( &( pxTCB->xEventListItem ) );
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }

                    /* 将取消阻塞的任务放入适当的就绪列表。 */
                    prvAddTaskToReadyList( pxTCB );

                    /* 如果抢占被关闭,任务被取消阻塞不能导致即时上下文切换。 */
                    #if (  configUSE_PREEMPTION == 1 )
                    {
                        /* 抢占已开启,但仅当被取消阻塞的任务优先级等于或高于当前执行任务的优先级时才应执行上下文切换。 */
                        if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
                        {
                            xSwitchRequired = pdTRUE;
                        }
                        else
                        {
                            mtCOVERAGE_TEST_MARKER();
                        }
                    }
                    #endif /* configUSE_PREEMPTION */
                }
            }
        }

        /* 如果抢占已开启,并且应用程序编写者未显式关闭时间片,则与当前运行任务优先级相等的任务将共享处理时间(时间片)。 */
        #if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )
        {
            if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 )
            {
                xSwitchRequired = pdTRUE;
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        #endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) */

        #if ( configUSE_TICK_HOOK == 1 )
        {
            /* 防止在挂起节拍计数时调用 tick 钩子(当调度器被解锁时)。 */
            if( uxPendedTicks == ( UBaseType_t ) 0U )
            {
                vApplicationTickHook();
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        #endif /* configUSE_TICK_HOOK */
    }
    else
    {
        ++uxPendedTicks;

        /* 即使调度器被锁定,tick 钩子仍会在规则间隔被调用。 */
        #if ( configUSE_TICK_HOOK == 1 )
        {
            vApplicationTickHook();
        }
        #endif
    }

    #if ( configUSE_PREEMPTION == 1 )
    {
        if( xYieldPending != pdFALSE )
        {
            xSwitchRequired = pdTRUE;
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    #endif /* configUSE_PREEMPTION */

    return xSwitchRequired;
}
  • traceTASK_INCREMENT_TICK: 记录节拍增加的跟踪。
  • uxSchedulerSuspended: 检查调度器是否暂停。
  • xConstTickCount: 增加的节拍计数。
  • xNextTaskUnblockTime: 下一个任务取消阻塞的时间。
  • prvAddTaskToReadyList: 将任务添加到就绪列表。
  • configUSE_PREEMPTION: 抢占设置。
  • configUSE_TIME_SLICING: 时间片设置。
  • configUSE_TICK_HOOK: Tick 钩子设置。

1. 判断任务调度器是否被挂起。

2. 将时钟节拍计数器 xTickCount 加一,并将结果保存在 xConstTickCount 中,下一行程序会将 xConstTickCount 赋值给 xTickCount,相当于给 xTickCount 加一。

3. xConstTickCount 为 0,说明发生了溢出!

4. 如果发生了溢出的话使用函数 taskSWITCH_DELAYED_LISTS 将延时列表指针 pxDelayedTaskList 和溢出列表指针 pxOverflowDelayedTaskList 所指向的列表进行交换,函数 taskSWITCH_DELAYED_LISTS() 本质上是个宏,在文件 tasks.c 中有定义,将这两个指针所指向的列表交换以后还需要更新 xNextTaskUnblockTime 的值。

5. 变量 xNextTaskUnblockTime 保存着下一个要解除阻塞的任务的时间点值,如果 xConstTickCount 大于 xNextTaskUnblockTime 的话就说明有任务需要解除阻塞了。

6. 判断延时列表是否为空。

7. 如果延时列表为空的话就将 xNextTaskUnblockTime 设置为 portMAX_DELAY。

8. 延时列表不为空,获取延时列表第一个列表项对应的任务控制块。

9. 获取(8)中获取到的任务控制块中的状态列表项值。

10. 任务控制块中的状态列表项值保存了任务的唤醒时间点,如果这个唤醒时间点值大于当前的系统时钟(时钟节拍计数器值),说明任务的延时时间还未到。

11. 任务延时时间还未到,而且 xItemValue 已经保存了下一个要唤醒的任务的唤醒时间点,所以需要用 xItemValue 来更新 xNextTaskUnblockTime。

12. 任务延时时间到了,所以将任务先从延时列表中移除。

13. 检查任务是否还等待某个事件,比如等待信号量、队列等。如果还在等待的话就将任务从相应的事件列表中移除,因为超时时间到了。

14. 将任务从相应的事件列表中移除。

15. 任务延时时间到了,并且任务已经从延时列表或者事件列表中已经移除,所以这里需要将任务添加到就绪列表中。

16. 延时时间到的任务优先级高于正在运行的任务优先级,所以需要进行任务切换,标记 xSwitchRequired 为 pdTRUE,表示需要进行任务切换。

17. 如果使能了时间片调度的话,还要处理跟时间片调度有关的工作。

18. 如果使能了时间片钩子函数的话就执行时间片钩子函数 vApplicationTickHook(),函数的具体内容由用户自行编写。

19. 如果调用函数 vTaskSuspendAll() 挂起了任务调度器的话在每个滴答定时器中断就不会更新 xTickCount 了。取而代之的是用 uxPendedTicks 来记录调度器挂起过程中的时钟节拍数。

这样在调用函数 xTaskResumeAll() 恢复任务调度器的时候就会调用 uxPendedTicks 次函数 xTaskIncrementTick(),这样 xTickCount 就会恢复,并且那些应被取消阻塞的任务都会取消阻塞。

2.2 函数xTaskResumeAll()

函数xTaskResumeAll()中相应的处理代码如下:

BaseType_t xTaskResumeAll( void )
{
TCB_t *pxTCB = NULL;
BaseType_t xAlreadyYielded = pdFALSE;

    // 如果 uxSchedulerSuspended 为零,则此函数不匹配之前对 vTaskSuspendAll() 的调用。
    configASSERT( uxSchedulerSuspended );

    // 可能在调度器挂起期间,一个中断服务例程导致一个任务从事件列表中被移除。
    // 如果发生这种情况,移除的任务将被添加到 xPendingReadyList。
    // 一旦调度器恢复,就可以安全地将所有待定的就绪任务从该列表移动到它们各自的就绪列表中。
    taskENTER_CRITICAL();
    {
        --uxSchedulerSuspended;

        if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
        {
            if( uxCurrentNumberOfTasks > ( UBaseType_t ) 0U )
            {
                // 将所有已就绪的任务从待定列表移到适当的就绪列表中。
                while( listLIST_IS_EMPTY( &xPendingReadyList ) == pdFALSE )
                {
                    pxTCB = ( TCB_t * ) 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; // 非易失性副本。

                    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;
}
  • configASSERT(uxSchedulerSuspended):确保在调用 xTaskResumeAll 前,调度器确实被挂起过。
  • taskENTER_CRITICAL() / taskEXIT_CRITICAL():进入/退出临界区,确保不会被中断打断。
  • --uxSchedulerSuspended:减少挂起计数。
  • listLIST_IS_EMPTY( &xPendingReadyList ):在挂起期间,将任何从事件列表中移除的任务移到就绪列表中。
  • prvResetNextTaskUnblockTime():重新计算下一个取消阻塞时间,防止不必要地退出低功耗状态。
  • xTaskIncrementTick():处理在挂起期间累积的节拍。
  • xYieldPending:如果有优先级较高的任务需要运行,则设置为 pdTRUE
  • taskYIELD_IF_USING_PREEMPTION():如果启用了抢占式调度,则让出当前任务。

📝大佬觉得本文有所裨益,不妨轻点一下👍给予鼓励吧!

❤️❤️❤️本人虽努力,但能力尚浅,若有不足之处,恳请各位大佬不吝赐教,您的批评指正将是我进步的动力!😊😊😊

💖💖💖若您认为此篇文章对您有所帮助,烦请点赞👍并收藏🌟,您的支持是我前行的最大动力!

🚀🚀🚀任务在默默中完成,价值在悄然间提升。让我们携手共进,一起加油,迎接更美好的未来!🌈🌈🌈

  • 19
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

妄北y

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值