FreeRTOS时间管理

server/2024/11/14 22:54:57/

FreeRTOS时间管理

主要要了解延时函数:
在这里插入图片描述
相对延时:指每次延时都是从执行函数vTaskDelay()开始,直到延时指定的时间结束。
绝对延时:指将整个任务的运行周期看成一个整体,适用于需要按照一定频率运行的任务。
函数 vTaskDelayUntil()是绝对模式(绝对延时函数)。函数 vTaskDelay()在文件 tasks.c 中有定义,要使用此函数的话宏 INCLUDE_vTaskDelay 必须为 1,

void vTaskDelay( const TickType_t xTicksToDelay )

具体的函数代码如下:

void vTaskDelay( const TickType_t xTicksToDelay )//{BaseType_t xAlreadyYielded = pdFALSE;/* A delay time of zero just forces a reschedule. */if( xTicksToDelay > ( TickType_t ) 0U ){configASSERT( uxSchedulerSuspended == 0 );vTaskSuspendAll();/*通过调用vTaskSuspendAll挂起所有任务,这是为了安全地更新任务的状态和延迟列表,防止在操作过程中发生中断导致的数据不一致。*/{traceTASK_DELAY();prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );/*调用prvAddCurrentTaskToDelayedList将当前任务添加到延迟列表中,xTicksToDelay指定了延迟的时间,pdFALSE表示此任务在延迟期满时不需要立即运行。*/}xAlreadyYielded = xTaskResumeAll();/*通过调用xTaskResumeAll尝试恢复之前挂起的任务。如果在挂起期间有任务变为就绪状态,xTaskResumeAll会返回pdTRUE,表示已经触发了任务切换,否则返回pdFALSE。*/}else{mtCOVERAGE_TEST_MARKER();}if( xAlreadyYielded == pdFALSE ){portYIELD_WITHIN_API();/*如果xTaskResumeAll返回pdFALSE,这意味着在挂起所有任务和恢复任务切换的过程中,没有其他任务变为就绪状态,从而没有自动触发任务切换。但是,当前任务通过调用vTaskDelay已经表达了它愿意让出CPU。为了确保这种意愿得到尊重,即使xTaskResumeAll没有触发任务切换,也通过调用portYIELD_WITHIN_API强制进行一次任务调度。这样做确保了调度器会重新评估哪个任务应该运行,即使当前任务的延迟时间为0,也会按照优先级选择另一个任务运行,如果有的话。*/}else{mtCOVERAGE_TEST_MARKER();}}

接下来让我们来看这个函数prvAddCurrentTaskToDelayedList(),该函数就是用于将当前任务添加到等待列表。
函数声明:static void prvAddCurrentTaskToDelayedList(TickType_t xTicksToWait, const BaseType_t xCanBlockIndefinitely)声明了一个静态函数,接受两个参数:xTicksToWait(任务应该被延迟的tick数)和xCanBlockIndefinitely(一个布尔值,指示任务是否可以无限期地阻塞)

static void prvAddCurrentTaskToDelayedList( TickType_t xTicksToWait,const BaseType_t xCanBlockIndefinitely )
{TickType_t xTimeToWake;const TickType_t xConstTickCount = xTickCount;/*获取当前的tick计数(xTickCount),这是系统启动以来经过的tick数。*/#if ( INCLUDE_xTaskAbortDelay == 1 ){pxCurrentTCB->ucDelayAborted = pdFALSE;}/*重置延迟中止标志(如果启用了INCLUDE_xTaskAbortDelay):这部分代码通过将pxCurrentTCB->ucDelayAborted设置为pdFALSE,确保当任务被移动到延迟列表时,任何之前的延迟中止请求都被清除。*/#endifif( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 ){portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority );}/*将当前任务从就绪列表中移除以后还要取消任务在 uxTopReadyPriority中的就绪标记。也就是将 uxTopReadyPriority 中对应的 bit 清零。*/else{mtCOVERAGE_TEST_MARKER();}#if ( INCLUDE_vTaskSuspend == 1 ){/*这部分代码检查任务是否请求无限期等待(xTicksToWait == portMAX_DELAY)。portMAX_DELAY通常定义为可表示的最大延时,意味着任务希望无限期挂起。同时,它检查xCanBlockIndefinitely标志,确保任务允许无限期阻塞。如果两个条件都满足,任务会被加入到挂起任务列表(xSuspendedTaskList)的末尾。这意味着任务将不会被调度,直到明确地被唤醒。*/if( ( xTicksToWait == portMAX_DELAY ) && ( xCanBlockIndefinitely != pdFALSE ) ){listINSERT_END( &xSuspendedTaskList, &( pxCurrentTCB->xStateListItem ) );}/*如果任务不是无限期挂起,那么它请求有限的延时。这部分代码计算任务应当被唤醒的时间点(xTimeToWake),并将这个时间设置为任务状态列表项的值。*/else{xTimeToWake = xConstTickCount + xTicksToWait;listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );if( xTimeToWake < xConstTickCount ){vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );}else{vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );/*这里检查是否存在时间溢出的情况。如果xTimeToWake小于当前的xConstTickCount,说明发生了溢出,任务被插入到溢出延时任务列表(pxOverflowDelayedTaskList)。否则,任务插入到正常的延时任务列表(pxDelayedTaskList)。*/if( xTimeToWake < xNextTaskUnblockTime ){xNextTaskUnblockTime = xTimeToWake;}/*当任务进入阻塞状态时(例如,等待一个事件或延时),它的唤醒时间会被计算并设置。这时,系统会检查这个唤醒时间是否早于当前的xNextTaskUnblockTime:如果早于:这意味着系统中有一个新的最早唤醒时间,因此需要更新xNextTaskUnblockTime为这个新时间。这样可以确保调度器能够在正确的时间唤醒任务。如果晚于或等于:xNextTaskUnblockTime不需要更新,因为已经存在一个更早或相同时间的任务需要被唤醒。*/else{mtCOVERAGE_TEST_MARKER();}}}}#else /* INCLUDE_vTaskSuspend */{xTimeToWake = xConstTickCount + xTicksToWait;/* The list item will be inserted in wake time order. */listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );if( xTimeToWake < xConstTickCount ){vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );}else{vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );if( xTimeToWake < xNextTaskUnblockTime ){xNextTaskUnblockTime = xTimeToWake;}else{mtCOVERAGE_TEST_MARKER();}}( void ) xCanBlockIndefinitely;}#endif /* INCLUDE_vTaskSuspend */
}

1: if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )在FreeRTOS中,uxListRemove函数用于从列表中移除一个项,并返回该项所在列表中的剩余项数。这个函数的返回值在某些情况下用于判断是否需要进行额外的操作,比如更新调度器的状态或做一些清理工作。具体到if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )这行代码的意义,我们来详细解释一下:

  1. 移除任务从就绪列表:这行代码的主要目的是从就绪列表中移除当前任务的状态列表项(pxCurrentTCB->xStateListItem)。在FreeRTOS中,每个任务都有一个与之关联的列表项,用于将该任务链接到不同的任务列表中,例如就绪列表、延迟列表等。当任务需要被延迟或阻塞时,它必须首先从就绪列表中移除。

  2. 判断列表项是否是列表中的最后一个:通过检查uxListRemove的返回值是否为0,这行代码实际上是在判断移除操作后,原列表是否为空。如果返回值为0,意味着在移除当前任务之前,它是列表中的唯一任务项。这种情况下,就绪列表变为空,可能需要进行一些额外的操作,比如调整就绪任务的优先级位图。

  3. 调整优先级位图:如果当前任务是其优先级队列中的唯一任务,移除它后,该优先级队列变为空。在这种情况下,需要调用portRESET_READY_PRIORITY宏(或类似的操作),来在就绪优先级位图中清除相应优先级的位。这是因为,如果一个优先级队列为空,那么调度器在选择下一个要运行的任务时,就不应该考虑这个优先级了。

  4. 保持调度器的正确性:这个判断和随后的操作确保了调度器能够正确地反映当前系统的状态,避免在选择下一个要运行的任务时,考虑到已经没有任务的优先级队列。

2:listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );在FreeRTOS中,listSET_LIST_ITEM_VALUE是一个宏,用于设置列表项的值。这个宏通常用于与任务控制块(TCB)相关的列表项,以跟踪特定的信息,如任务的唤醒时间。
这行代码的作用是设置当前任务(由pxCurrentTCB指向)的状态列表项(xStateListItem)的值为xTimeToWake。这里,xTimeToWake是计算出的任务应当被唤醒的时间点。

  • pxCurrentTCB: 是指向当前任务控制块(Task Control Block)的指针。每个任务在FreeRTOS中都有一个TCB,其中包含了管理和调度任务所需的所有信息。

  • xStateListItem: 是TCB中的一个成员,是一个ListItem_t结构体。这个结构体用于将任务链接到不同的列表中,例如就绪列表、延时列表等。通过这种方式,FreeRTOS的调度器可以管理和调度多个任务。

  • listSET_LIST_ITEM_VALUE: 这个宏接受两个参数,第一个参数是列表项的地址,第二个参数是要设置的值。在这个上下文中,它用于设置任务的唤醒时间。这个值随后用于确定何时将任务从延时列表移动到就绪列表,以便调度器可以重新调度该任务。

函数 vTaskDelayUntil()

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

    BaseType_t xTaskDelayUntil( TickType_t * const pxPreviousWakeTime,const TickType_t xTimeIncrement )/*这是xTaskDelayUntil函数的定义,接受两个参数:pxPreviousWakeTime是指向上一次唤醒时间的指针,xTimeIncrement是两次唤醒之间的时间间隔。*/{TickType_t xTimeToWake;BaseType_t xAlreadyYielded, xShouldDelay = pdFALSE;/*定义局部变量xTimeToWake来存储下一次唤醒的时间点,xAlreadyYielded用于指示是否已经进行了任务切换,xShouldDelay标志是否需要延迟。*/configASSERT( pxPreviousWakeTime );configASSERT( ( xTimeIncrement > 0U ) );configASSERT( uxSchedulerSuspended == 0 );vTaskSuspendAll();//调用vTaskSuspendAll来暂停所有任务调度,这是为了防止在更新计数器和计算下一次唤醒时间时发生中断。{/* Minor optimisation.  The tick count cannot change in this* block. */const TickType_t xConstTickCount = xTickCount;//获取当前的tick计数并保存到xConstTickCount中,这个值在这个代码块中不会改变,用于后续的时间计算。/* 计算下一次唤醒的时间点,即上一次唤醒时间加上时间间隔。 */xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;/* 计算下一次唤醒的时间点,即上一次唤醒时间加上时间间隔。 */if( xConstTickCount < *pxPreviousWakeTime ){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;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();}return xShouldDelay;}

下面我们要分析计数器溢出的几种情况,为了更好理解溢出的几种情况。可以根据下面这个图更好去理解这个过程:
在这里插入图片描述

            if( xConstTickCount < *pxPreviousWakeTime ){if( ( xTimeToWake < *pxPreviousWakeTime ) && ( xTimeToWake > xConstTickCount ) ){xShouldDelay = pdTRUE;}else{mtCOVERAGE_TEST_MARKER();}}

根据图 12.3.1 可以看出,理论上 xConstTickCount 要大于 pxPreviousWakeTime 的,但是也有一种情况会导致 xConstTickCount 小于 pxPreviousWakeTime,那就是 xConstTickCount 溢出了!既然 xConstTickCount 都溢出了,那么计算得到的任务唤醒时间点肯定也是要溢出的,并且 xTimeToWake 肯定也是要大于 xConstTickCount 的。接下来就是分情况去讨论:
在这里插入图片描述
还有其他两种情况,一:只有 xTimeToWake 溢出,二:都没有溢出。只有 xTimeToWake溢出的话如图 12.3.3 所示:
在这里插入图片描述
其实使用函数 vTaskDelayUntil()延时的任务也不一定就能周期性的运行,使用函数vTaskDelayUntil()只能保证你按照一定的周期取消阻塞,进入就绪态。如果有更高优先级或者中断的话你还是得等待其他的高优先级任务或者中断服务函数运行完成才能轮到你。这个绝对延时只是相对于 vTaskDelay()这个简单的延时函数而言的。


http://www.ppmy.cn/server/11663.html

相关文章

基于SSM的计算机课程实验管理系统的设计与实现(内附设计LW + PPT+ 源码下载)

基于SSM的计算机课程实验管理系统的设计与实现 项目名称&#xff1a; 基于SSM的计算机课程实验管理系统的设计与实现 项目技术栈 该项目采用了以下核心技术栈&#xff1a; 后端框架/库&#xff1a; SSM (Spring Spring MVC MyBatis)数据库&#xff1a; MySQL前端技术&…

去雾笔记01-SRKTDN: Applying Super Resolution Method to Dehazing Task

文章目录 Abstract1. Introduction2. Related Work3. Method3.1. Network Architecture Abstract 们提出了一种结合超分辨方法和知识转移方法的模型。我们的模型由一个教师网络、一个去雾网络和一个超分辨率网络组成。 1. Introduction ECNU KT团队提出了一个知识蒸馏[20]模…

第四届大数据工程与教育国际会议(BDEE 2024)即将召开!

第四届大数据工程与教育国际会议&#xff08;BDEE 2024&#xff09;将于2024年8月9-11日在泰国清迈举行。数据驱动教育变革&#xff0c;智慧点亮未来课堂&#xff01;BDEE 2024是专注于大数据工程与教育领域的重要学术会议&#xff0c;全球大数据与教育精英齐聚&#xff0c;在数…

盲人辅助设备赋能视障人士,实时导航与避障打造无障碍生活

随着科技日新月异的发展&#xff0c;我们见证了一个又一个创新产品如何深刻改变人们的生活。在关爱特殊群体、推动社会包容性发展的道路上&#xff0c;一款名为蝙蝠避障的盲人辅助设备脱颖而出&#xff0c;以其卓越的实时导航与精准避障功能&#xff0c;成功破除了视障人士出行…

15 php学习:表单验证

表单验证 表单验证在网页和应用程序开发中起着至关重要的作用&#xff0c;其主要目的是确保用户输入的数据符合预期的格式和规则&#xff0c;以提升用户体验、数据准确性和系统安全性。以下是表单验证的主要作用&#xff1a; 数据准确性&#xff1a;通过表单验证&#xff0c;可…

科技驱动未来,提升AI算力,GPU扩展正当时

要说这两年最火的科技是什么&#xff1f;我想“AI人工智能”肯定是最有资格上榜的&#xff0c;尤其ChatGPT推出后迅速在社交媒体上走红&#xff0c;短短5天&#xff0c;注册用户数就超过100万&#xff0c;2023年一月末&#xff0c;ChatGPT的月活用户更是突破1亿&#xff0c;成为…

【springBoot】资源文件的变量替换

在Spring Boot项目中&#xff0c;可以从application.yml或application.properties中获取pom.xml中定义的变量。为了实现在application.yml中使用pom.xml中的属性&#xff0c;通常需要在构建过程中将这些属性注入到资源文件中。以下是实现这一目标的几种方法&#xff1a; 方法1…

上汽大通:依托电子签网络,升级产业供应链协同

2023年12月&#xff0c;法大大发布了中国首部《汽车行业合同数智化白皮书》&#xff08;点击阅读及下载&#xff1a;中国首部&#xff01;《汽车行业合同数智化白皮书》重磅发布 | 附下载&#xff09;。该白皮书中基于法大大自身参与汽车行业合同数智化建设的实践和思考&#x…