1.任务调度器挂起
【任务调度器挂起,防止任务切换】
1.1 流程图
软件屏障:
portSOFTWARE_BARRIER()仅用于模拟/仿真端口,保证不具备实时行为的仿真环境中的执行顺序。递增挂起计数:
uxSchedulerSuspended自增。当其值非零时,调度器被挂起,内核不再进行任务切换。使用计数器而非布尔量,支持vTaskSuspendAll()的嵌套调用(必须调用相同次数的xTaskResumeAll()才能恢复)。内存屏障:
portMEMORY_BARRIER()防止编译器或硬件将上述增量操作重排序到错误位置,确保挂起状态对所有寄存器/内存访问立即可见。
1.2 函数解析
功能:挂起任务调度器,禁止任务切换。通过递增内部计数器支持嵌套调用,需与xTaskResumeAll()配对使用来恢复调度
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.任务调度器恢复
2.1 流程图
解除挂起:先断言调度器确实处于挂起状态,进入临界区后递减
uxSchedulerSuspended。若尚未归零(嵌套调用),直接跳过后续处理。迁移挂起期间就绪任务:若计数器归零且系统有任务,遍历
xPendingReadyList,将其中的任务从事件/状态列表中移除,正式加入其优先级对应的就绪链表。同时将任何优先级高于或等于当前任务的任务标记为需要切换 (xYieldPending=TRUE)。重新计算唤醒时间:如果至少处理了一个挂起期间就绪的任务,调用
prvResetNextTaskUnblockTime校正下一次任务唤醒的时间点。补处理延迟时钟:执行在调度器挂起期间累积的
xPendedTicks次xTaskIncrementTick,确保系统时钟不落后,并根据每次模拟结果更新xYieldPending。触发切换:若最终
xYieldPending为真且启用抢占式调度,设置返回值xAlreadyYielded=TRUE并立即调用taskYIELD_IF_USING_PREEMPTION请求任务切换。退出:退出临界区,返回
xAlreadyYielded指示是否已进行或请求了切换。
2.2 函数解析
功能:递减调度器挂起计数器。当计数器归零时,恢复任务调度:将挂起期间积累的就绪任务移入正式就绪列表,补处理挂起期间的时钟节拍,必要时触发任务切换。
返回值
若恢复调度后已触发或请求了任务切换,返回
pdTRUE;否则返回pdFALSE。
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中断或任务调度器挂起态恢复时执行】
3.1 流程图
调度器状态检查:若调度器挂起,仅递增
xPendedTicks并可能调用 tick 钩子,直接返回pdFALSE,不处理任务唤醒。节拍更新:递增全局
xTickCount;若发生回绕(归零),交换延时任务列表和溢出延时列表,确保链表正确。到期任务唤醒:若当前节拍达到或超过
xNextTaskUnblockTime,遍历延时链表。对每个到期任务,从延时和事件列表中移除,加入就绪列表;若该任务优先级高于当前任务,置xSwitchRequired = pdTRUE。记录链表中第一个未到期任务的时间为新的xNextTaskUnblockTime。时间片轮转:若同时开启了抢占和时间片,且当前优先级就绪列表多于1个任务,标记需要切换。
挂起的 yield 请求:若
xYieldPending为真,强制标记切换。Tick 钩子:在正常 tick 处理且无累积挂起 tick 时调用
vApplicationTickHook()。返回:返回
xSwitchRequired,指示是否需要 PendSV 上下文切换。
3.2 函数解析
功能:每次系统节拍中断调用。递增节拍计数器,处理延时链表中的到期任务并将其移至就绪链表,根据抢占和时间片轮转策略决定是否需要任务切换。
返回值
若需要上下文切换(有高优先级任务就绪、时间片到期或挂起的 yield 请求),返回
pdTRUE;否则返回pdFALSE。
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 * */【以上内容为个人在学习FreeRTOS过程中的源码解读笔记,欢迎大家在评论区讨论指正。】
【如果本篇内容对你有帮助,不妨点个关注,你的支持是我持续更新的动力!】