FreeRTOS源码解析(2)任务调度器挂起与恢复

1.任务调度器挂起,防止进行任务切换

复制代码
void vTaskSuspendAll( void )
{
    /* A critical section is not required as the variable is of type
     * BaseType_t.  Please read Richard Barry's reply in the following link to a
     * post in the FreeRTOS support forum before reporting this as a bug! -
     * https://goo.gl/wu4acr */

    /* portSOFTWARE_BARRIER() is only implemented for emulated/simulated ports that
     * do not otherwise exhibit real time behaviour. */
    portSOFTWARE_BARRIER();

    /* The scheduler is suspended if uxSchedulerSuspended is non-zero.  An increment
     * is used to allow calls to vTaskSuspendAll() to nest. */
    /* 通过递增计数器挂起调度器,以禁止任务切换。
     * uxSchedulerSuspended == 0 时,调度器正常运行,允许切换。
     * uxSchedulerSuspended > 0 时,调度器挂起,禁止一切任务切换。
     * 使用计数器而非布尔标志,是为了支持对 vTaskSuspendAll() 的嵌套调用。 */
    ++uxSchedulerSuspended;

    /* Enforces ordering for ports and optimised compilers that may otherwise place
     * the above increment elsewhere. */
    portMEMORY_BARRIER();
}

2.任务调度器恢复

复制代码
BaseType_t xTaskResumeAll( void )
{
    /* 在调度器挂起期间,如果直接创建了一个高优先级任务(而非通过中断事件唤醒),该任务会通过 prvAddTaskToReadyList 直接加入就绪列表,
     * 但不会进入 xPendingReadyList【因为此时的调度器是停止的,不会触发这个高优先级任务】。
     * 因此,在 xTaskResumeAll() 中,这个任务不会被 while 循环处理,也不会因此置位 xYieldPending。*/
    TCB_t * pxTCB = NULL;
    BaseType_t xAlreadyYielded = pdFALSE;

    /* If uxSchedulerSuspended is zero then this function does not match a
     * previous call to vTaskSuspendAll(). */
    /* 确保调度器当前确实处于挂起状态 */
    configASSERT( uxSchedulerSuspended );   //参数检查,但实际并未实现,用户可根据自己需求实现

    /* It is possible that an ISR caused a task to be removed from an event
     * list while the scheduler was suspended.  If this was the case then the
     * removed task will have been added to the xPendingReadyList.  Once the
     * scheduler has been resumed it is safe to move all the pending ready
     * tasks from this list into their appropriate ready list. */
    taskENTER_CRITICAL();       //进入临界段
    {
        /* 通过变量uxSchedulerSuspended操作,解除任务调度器挂起,响应上一次的任务调度器挂起 */
        --uxSchedulerSuspended;

        /* 判断当前任务调度器是否彻底解除挂起状态 */
        if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )       //彻底解除挂起
        {
            /* 检查是否有任务存在 */
            if( uxCurrentNumberOfTasks > ( UBaseType_t ) 0U )       //存在
            {
                /* Move any readied tasks from the pending list into the
                 * appropriate ready list. */
                /* 遍历任务调度器挂起期间存放就绪任务的链表 */
                while( listLIST_IS_EMPTY( &xPendingReadyList ) == pdFALSE )     //存在任务
                {
                    /* 挂起期间就绪任务列表的第一个任务控制块 */
                    pxTCB = listGET_OWNER_OF_HEAD_ENTRY( ( &xPendingReadyList ) ); /*lint !e9079 void * is used as this macro is used with timers and co-routines too.  Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */
                    /* 从该任务的事件列表项(用于将任务挂起在事件上)中移除该列表项 */
                    listREMOVE_ITEM( &( pxTCB->xEventListItem ) );
                    /* 内存屏障,确保移除操作在硬件层面完成,防止编译器或 CPU 乱序优化导致问题 */
                    portMEMORY_BARRIER();
                    /* 从该任务的状态列表项(用于表示任务当前状态,如就绪、阻塞、挂起等)中移除 */
                    listREMOVE_ITEM( &( pxTCB->xStateListItem ) );
                    /* 将任务正式加入其优先级对应的就绪链表 */
                    prvAddTaskToReadyList( pxTCB );

                    /* If the moved task has a priority higher than or equal to
                     * the current task then a yield must be performed. */
                    /* 如果当前从挂起期间就绪任务列表移出的任务优先级高于或等于当前正在执行的任务,则标记需要一次任务切换,让调度器重新选择最高优先级任务运行 */
                    if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
                    {
                        xYieldPending = pdTRUE; //标记恢复调度后进行一次任务切换
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();   //用于代码覆盖率分析测试,本质为空
                    }
                }

                /* 判断提取的任务控制块是否为空 */
                if( pxTCB != NULL )     //不为空
                {
                    /* A task was unblocked while the scheduler was suspended,
                     * which may have prevented the next unblock time from being
                     * re-calculated, in which case re-calculate it now.  Mainly
                     * important for low power tickless implementations, where
                     * this can prevent an unnecessary exit from low power
                     * state. */
                    /* 当调度器处于暂停状态时,某个任务得以解除阻塞。
                     * 这可能导致后续的解除阻塞时间无法重新计算,此时应重新计算该时间。
                     * 这主要适用于低功耗无滴答模式实现方式,因为这样可以避免不必要的从低功耗状态退出操作。 */
                    /* 重置下一个任务解除阻塞时间 */
                     prvResetNextTaskUnblockTime();
                }

                /* If any ticks occurred while the scheduler was suspended then
                 * they should be processed now.  This ensures the tick count does
                 * not  slip, and that any delayed tasks are resumed at the correct
                 * time. */
                /* 将任务调度器挂起期间发生的Systick计数在任务调度器恢复的时候补上,避免系统时钟异常,影响其他任务阻塞时间 */
                {
                    TickType_t xPendedCounts = xPendedTicks; /* Non-volatile copy. */

                    if( xPendedCounts > ( TickType_t ) 0U )
                    {
                        do
                        {
                            /* 模拟递增时钟节拍 */
                            if( xTaskIncrementTick() != pdFALSE )       
                            {
                                xYieldPending = pdTRUE;
                            }
                            else
                            {
                                mtCOVERAGE_TEST_MARKER();
                            }
                            /* 完成一次模拟,记录的节拍计数减1 */
                            --xPendedCounts;
                        } while( xPendedCounts > ( TickType_t ) 0U );

                        xPendedTicks = 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;
}

3.Tick节拍数增加

说明:Systick中断或任务调度器挂起态恢复时执行

复制代码
BaseType_t xTaskIncrementTick( void )
{
    TCB_t * pxTCB;
    TickType_t xItemValue;
    BaseType_t xSwitchRequired = pdFALSE;

    /* Called by the portable layer each time a tick interrupt occurs.
     * Increments the tick then checks to see if the new tick value will cause any
     * tasks to be unblocked. */
    traceTASK_INCREMENT_TICK( xTickCount );     //调试宏,暂未实现,用户可根据需求实现

    /* 根据任务调度器状态判断,如果调度器被挂起则不处理任务调度,只是相关累计变量加1 */
    if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
    {
        /* Minor optimisation.  The tick count cannot change in this
         * block. */
        /* 当前代码块中,tickCount不会改变,tick计数加1并保存到局部变量,进一步更新全局计数值xTickCount */
        const TickType_t xConstTickCount = xTickCount + ( TickType_t ) 1;

        /* Increment the RTOS tick, switching the delayed and overflowed
         * delayed lists if it wraps to 0. */
        /* 增加RTOS tick,如果tick计数回绕为0,说明计数溢出,则交换延迟列表和溢出延迟列表 */
        xTickCount = xConstTickCount;

        if( xConstTickCount == ( TickType_t ) 0U ) /*lint !e774 'if' does not always evaluate to false as it is looking for an overflow. */
        {
            taskSWITCH_DELAYED_LISTS();     //交换延迟列表和溢出延迟列表
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();       //用于代码覆盖率分析测试,本质为空
        }

        /* See if this tick has made a timeout expire.  Tasks are stored in
         * the  queue in the order of their wake time - meaning once one task
         * has been found whose block time has not expired there is no need to
         * look any further down the list. */
        /* 检查本次时钟节拍是否已使某个超时到期。任务在队列中按其唤醒时间排序存储,
         * 这意味着一旦找到一个阻塞时间尚未到期的任务,就无需继续查看列表后续的任务。*/
        if( xConstTickCount >= xNextTaskUnblockTime )
        {
            for( ; ; )
            {
                if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )     //判断延时任务列表为空
                {
                    /* The delayed list is empty.  Set xNextTaskUnblockTime
                     * to the maximum possible value so it is extremely
                     * unlikely that the
                     * if( xTickCount >= xNextTaskUnblockTime ) test will pass
                     * next time through. */
                    /* 设置下一个需要被解除阻塞的任务唤醒tick */
                    xNextTaskUnblockTime = portMAX_DELAY; /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
                    break;
                }
                else                                                        //延时任务列表不为空
                {
                    /* The delayed list is not empty, get the value of the
                     * item at the head of the delayed list.  This is the time
                     * at which the task at the head of the delayed list must
                     * be removed from the Blocked state. */
                    /* 从延时任务列表中获取任务控制块,即该延时任务列表中的第一项 */
                    pxTCB = listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList ); /*lint !e9079 void * is used as this macro is used with timers and co-routines too.  Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */
                    /* 获取该任务的唤醒时间 */
                    xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );

                    /* 进一步判断当前时间点是否小于获取的到任务唤醒时间,即当前tick是否到达该任务唤醒时间 */
                    if( xConstTickCount < xItemValue )      //记录下个任务阻塞时间,并结束循环
                    {
                        /* It is not time to unblock this item yet, but the
                         * item value is the time at which the task at the head
                         * of the blocked list must be removed from the Blocked
                         * state -  so record the item value in
                         * xNextTaskUnblockTime. */
                        xNextTaskUnblockTime = xItemValue;
                        break; /*lint !e9011 Code structure here is deemed easier to understand with multiple breaks. */
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();       //用于代码覆盖率分析测试,本质为空
                    }

                    /* It is time to remove the item from the Blocked state. */
                    /* 将任务从阻塞列表中移除 */
                    listREMOVE_ITEM( &( pxTCB->xStateListItem ) );

                    /* Is the task waiting on an event also?  If so remove
                     * it from the event list. */
                    /* 判断该任务是否有等待事件,如果是则从事件列表中移除,避免后续事件响应重复退出阻塞,导致系统崩溃【双重唤醒会破坏列表】 */
                    if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
                    {
                        listREMOVE_ITEM( &( pxTCB->xEventListItem ) );
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();       //用于代码覆盖率分析测试,本质为空
                    }

                    /* Place the unblocked task into the appropriate ready
                     * list. */
                    /* 将该任务加入到对应优先级的就绪任务列表中 */
                    prvAddTaskToReadyList( pxTCB );

                    /* A task being unblocked cannot cause an immediate
                     * context switch if preemption is turned off. */
                    /* 如果抢占式调度被关闭,则任务解除阻塞不会立即引起上下文切换 */
                    #if ( configUSE_PREEMPTION == 1 )
                    {
                        /* Preemption is on, but a context switch should
                         * only be performed if the unblocked task's
                         * priority is higher than the currently executing
                         * task.
                         * The case of equal priority tasks sharing
                         * processing time (which happens when both
                         * preemption and time slicing are on) is
                         * handled below.*/
                        /* 如果使能了抢占式调度(configUSE_PREEMPTION == 1),
                         * 则检查被解除阻塞的任务优先级是否高于当前正在执行的任务(pxCurrentTCB)。
                         * 如果是,则设置 xSwitchRequired = pdTRUE,表示需要触发任务切换。 */
                        if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
                        {
                            xSwitchRequired = pdTRUE;
                        }
                        else
                        {
                            mtCOVERAGE_TEST_MARKER();       //用于代码覆盖率分析测试,本质为空
                        }
                    }
                    #endif /* configUSE_PREEMPTION */
                }
            }
        }

        /* Tasks of equal priority to the currently running task will share
         * processing time (time slice) if preemption is on, and the application
         * writer has not explicitly turned time slicing off. */
        /* 如果同时使能了抢占和时间片轮转(configUSE_TIME_SLICING == 1),
         * 则在每个 tick 中断中检查当前优先级就绪列表中的任务数量。
         * 如果数量大于 1,说明有多个同优先级任务就绪,需要时间片轮转,
         * 因此设置 xSwitchRequired = pdTRUE,以便在 tick 中断退出时切换到下一个同优先级任务(通常由调度器在 PendSV 中处理)。
         * 注意:这里仅仅设置标志,实际切换发生在中断退出时。*/
        #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 ) ) */

        /* 如果配置了 tick 钩子,则在每个 tick 中断中调用应用程序定义的钩子函数 vApplicationTickHook()。
         * 这里有一个保护条件:仅在 xPendedTicks == 0 时调用。因为在调度器挂起期间,tick 被累积到 xPendedTicks,
         * 当调度器恢复时(xTaskResumeAll)会逐个处理这些 tick,此时如果再次调用 tick 钩子可能导致重复调用。
         * 所以只在正常 tick 处理时调用。 */
        #if ( configUSE_TICK_HOOK == 1 )
        {
            /* Guard against the tick hook being called when the pended tick
             * count is being unwound (when the scheduler is being unlocked). */
            if( xPendedTicks == ( TickType_t ) 0 )
            {
                vApplicationTickHook();
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        #endif /* configUSE_TICK_HOOK */

        /* 检查是否有待处理的 yield 请求 */
        /* xYieldPending 是一个标志,表示是否有外部请求(如任务调用了 taskYIELD() 或中断中请求了切换)尚未处理。
         * 如果该标志为真,则强制设置 xSwitchRequired = pdTRUE,以确保进行一次上下文切换 */
        #if ( configUSE_PREEMPTION == 1 )
        {
            if( xYieldPending != pdFALSE )
            {
                xSwitchRequired = pdTRUE;
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        #endif /* configUSE_PREEMPTION */
    }
    else        //如果调度器挂起,仅累加 xPendedTicks 并调用 tick 钩子(如果使能)。不进行任何任务列表操作
    {
        ++xPendedTicks;

        /* The tick hook gets called at regular intervals, even if the
         * scheduler is locked. */
        #if ( configUSE_TICK_HOOK == 1 )
        {
            vApplicationTickHook();
        }
        #endif
    }

    return xSwitchRequired;
}

4.声明

(1)Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.

(2)文中代码来自FreeRTOS,遵循MIT许可证,许可证可参考:https://opensource.org/licenses/MIT

复制代码
/*
 * FreeRTOS Kernel V10.5.1
 * Copyright (C) 2021 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * SPDX-License-Identifier: MIT
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * https://www.FreeRTOS.org
 * https://github.com/FreeRTOS
 *
 */
相关推荐
飘飘叶2 小时前
[FRP]Windows 安装 frpc 客户端,以及P2P方式ssh配置
windows·frp
承渊政道2 小时前
用群晖部署OmniBox+pansou:把分散的影视资源全聚合到一个界面里
服务器·windows·网络协议·https·ip·视频·持续部署
阿昭L2 小时前
Windows中的I/O完成通知与事件内核对象
windows·多线程
l1t12 小时前
DeepSeek辅助解决windows 11 wsl2中Linux版Dbeaver显示中文
linux·运维·windows
love530love13 小时前
Clink 调校指南:让 Windows CMD 拥有现代终端的便捷体验
人工智能·windows·python·cmd·clink
奋斗的小青年I18 小时前
Proxmox VE Ceph 超融合集群落地实战
windows·ceph·vmware·pve·超融合·proxmox
不做超级小白19 小时前
一行命令修复 WSL 中 VS Code 报 `Exec format error`
windows
CyrusCJA19 小时前
在Windows系统上将Redis注册为系统服务使其实现开机自启
数据库·windows·redis·缓存
星辰徐哥1 天前
OpenCV入门:Windows系统下OpenCV的安装与环境配置
人工智能·windows·opencv