FreeRTOS系统延时函数分析

   一、概述

       FreeRTOS提供了两个系统延时函数,相对延时函数vTaskDelay()和绝对延时函数vTaskDelayUntil()。相对延时是指每次延时都是从任务执行函数vTaskDelay()开始,延时指定的时间结束,绝对延时是指每隔指定的时间,执行一次调用vTaskDealyUntil()函数的任务,换句话说,就是任务以固定的频率执行。

  • 相对延时:指每次延时都是从执行函数vTaskDelay()开始,直到延时指定的时间结束。
  • 绝对延时:指将整个任务的运行周期看作一个整体,适用于需要按照一定频率运行的任务。

  1. 为任务主体,也就是任务真正要做的工作
  2. 是任务函数中调用vTaskDelayUntil()对任务进行延时
  3. 为其他任务运行

 二、相对延时函数vTaskDelay()

考虑下面的任务,任务A在执行任务主体代码后,调用相对延时函数vTaskDelay()进入阻塞态。系统中除了任务A外,还有其他任务,但是任务A的优先级最高。

void vTaskA( void * pvParameters )  {  /* 阻塞500ms. 注:宏pdMS_TO_TICKS用于将毫秒转成节拍数,FreeRTOS V8.1.0及以上版本才有这个宏,如果使用低版本,可以使用 500 / portTICK_RATE_MS */  const portTickType xDelay = pdMS_TO_TICKS(500);  for( ;; )  {  //  ...//  这里为任务主体代码//  .../* 调用系统延时函数,阻塞500ms */vTaskDelay( xDelay );  }  
}  

对于这样一个任务,执行过程的图示如下所示,当任务A获取CPU使用权后,先执行任务A的主体代码,之后调用系统延时函数vTaskDelay()进入阻塞态。任务进入阻塞态后,其他任务得以执行。FreeRTOS内核会周期的检查任务A的阻塞是否达到,在滴答定时中断中进行解除阻塞态,如果阻塞时间达到,则将任务A设置为就绪态,由于任务A的优先级最高,会抢占CPU,再次执行任务主体代码,不断循环。

从图中可以看出,如果执行任务A的过程中发生中断,那么任务A执行的周期就会变长,所以使用相对延时函数vTaskDelay(),不能周期的执行任务A。

 void vTaskDelay( const TickType_t xTicksToDelay ){BaseType_t xAlreadyYielded = pdFALSE;/*如果延时时间为0,则不会将当前任务加入延时列表. */if( xTicksToDelay > ( TickType_t ) 0U ){configASSERT( uxSchedulerSuspended == 0 );vTaskSuspendAll();{traceTASK_DELAY();/* 将当前任务从就绪列表中移除,并根据当前系统节拍计数器值计算唤醒时间,然后将任务加入延时列表 */prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );}xAlreadyYielded = xTaskResumeAll();}else{mtCOVERAGE_TEST_MARKER();}/* 强制执行一次上下文切换 */if( xAlreadyYielded == pdFALSE ){portYIELD_WITHIN_API();}else{mtCOVERAGE_TEST_MARKER();}}

如上述代码,如果延时大于0,则会将当前任务从就绪列表删除,然后加入到延时列表。是调用函数prvAddCurrentTaskToDelayedList()完成这一过程的,tasks.c中定义了很多局部变量,其中有一个变量xTickCount定义如下所示: 

static volatile TickType_t xTickCount = (TickType_t) 0U;

这个变量用来计数,记录系统节拍中断的次数它在启动调度器时被清零,在每次系统节拍时钟发生中断之后加1.相对延时函数会使用到这个变量,xTickCount表示了当前的系统节拍中断次数,这个值加上参数规定的延时时间(以系统节拍数表示)xTickToDelay,就是下次唤醒任务的时间,xTickCount + xTicksToDelay会被记录到任务TCB中,随着任务一起被挂接到延时列表。

我们直到变量xTickCount是TickType_t类型的,它会溢出。在32位架构中,当xTicksToDelay达到0xFFFF_FFFF后再增加,就会溢出变成0.为了解决xTickCount溢出问题,FreeRTOS使用了两个延时列表:xDelayedTaskList1和xDelayedTaskList2,并使用两个列表指针类型变量pxDelayedTaskList和pxOverflowDelayedTaskList分别指向上面的延时列表1和延时列表2(在创建任务时将延时列表指针指向延时列表)。顺便说一下,上面的两个延时列表指针变量和两个延时列表变量都是在tasks.c中定义的静态局部变量。

如果内核判断出xTickCount+xTicksToDelay溢出,将当前任务挂在列表指针pxOverflowDelayedTaskList指向的列表中,否则就挂接到列表指针pxDelayedTaskList指向的列表中。

每次系统节拍时钟中断,中断服务函数都会检查这两个延时 列表,查看延时的任务是否到期,如果时间到期,则将任务从延时列表中删除,重新加入就绪列表。如果新加入就绪列表的任务优先级大于当前任务,则会触发一次上下文切换(保护现场和恢复现场)。

else{/* Calculate the time at which the task should be woken if the event* does not occur.  This may overflow but this doesn't matter, the* kernel will manage it correctly. */xTimeToWake = xConstTickCount + xTicksToWait;/* The list item will be inserted in wake time order. */listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );if( xTimeToWake < xConstTickCount ){/* Wake time has overflowed.  Place this item in the overflow* list. */vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );}else{/* The wake time has not overflowed, so the current block list* is used. */vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );/* If the task entering the blocked state was placed at the* head of the list of blocked tasks then xNextTaskUnblockTime* needs to be updated too. */if( xTimeToWake < xNextTaskUnblockTime ){xNextTaskUnblockTime = xTimeToWake;}else{mtCOVERAGE_TEST_MARKER();}}

三、绝对延时函数vTaskDelayUntil() 

考虑下面的任务B,任务B首先调用绝对延时函数vTaskDelayUntil()进入阻塞态,阻塞时间到后,执行任务主体代码,系统中除了任务B外,还有其它任务,但是任务B的优先级最高。

void vTaskB( void * pvParameters )  
{  static portTickType xLastWakeTime;  const portTickType xFrequency = pdMS_TO_TICKS(500);  // 使用当前时间初始化变量xLastWakeTime ,注意这和vTaskDelay()函数不同 xLastWakeTime = xTaskGetTickCount();  for( ;; )  {  /* 调用系统延时函数,周期性阻塞500ms */        vTaskDelayUntil( &xLastWakeTime,xFrequency );  //  ...//  这里为任务主体代码,周期性执行.注意这和vTaskDelay()函数也不同//  ...}  
}  

对于这样一个任务,执行过程如下图所示,当任务B获取CPU使用权后,先调用系统延时函数vTaskDelayUntil()使任务进入阻塞态。任务B进入阻塞态后,其他任务得以执行,FreeRTOS内核会周期性的检查任务A的阻塞是否达到,如果阻塞时间达到,则将任务A设置为就绪态,由于任务B的优先级最高,会抢占CPU,接下来执行任务主体代码。任务主体代码执行完毕后,会继续调用系统延时函数vTaskDelayUntil()使任务进入阻塞态,周而复始。

从图可以看出,从调用函数vTaskDelayUntil()开始,每隔固定周期,任务B的主体代码就会执行一次,即使任务B在执行过程中发生中断,也不会影响这个周期性,只是会缩短其他任务的执行时间,所以这个函数被称之为绝对延时函数,它可以用于周期性的执行任务A的主体代码。

 函数vTaskDelayUntil()是如何做到周期性的呢,看下面源码:

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 );vTaskSuspendAll();{/* 保存系统节拍中断次数计数器 */const TickType_t xConstTickCount = xTickCount;/* 计算任务下次话u女性时间(以系统节拍中断次数表示). */xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;/* pxPreviousWakeTime中保存的是上次唤醒时间,唤醒后需要一定时间执行任务主体代码,如果上次唤醒时间大于当前时间,说明节拍计数器溢出了*/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;}

与相对延时函数vTaskDelay不同,本函数增加了一个参数pxPreviousWakeTime用于指向一个变量,变量保存上次任务解除阻塞时间。这个变量在任务开始时必须被设置成当前系统节拍中断次数,此后函数vTaskDelayUntil()在内部自动更新这个变量

由于变量xTickCount可能会溢出,所以程序必须检查各种溢出情况,并且要保证延时周期不得小于任务主体代码执行时间。这很好理解,就是不可能出现每5ms执行一个需要20ms才能完成的任务。

如果我们以横坐标表示变量xTickCount的范围,则横坐标左端为0,右端为变量xTickCount所能表示的最大值,在上图所示的三种情况下,才可以将任务加入延时列表。如上图,*pxPreviousWakeTime和xTimeToWake之间表示任务周期性延时时间,*pxPreviousWakeTime和xConstTickCount之间表示任务B主体代码执行时间。

图中第一种情况处理系统节拍中断计数器(xConstTickCount)和唤醒时间计数器(xTimeToWake)溢出情况;第二种情况处理唤醒时间计数器(xTimeToWake)溢出情况;第三种情况处理常规无溢出的情况。从图中可以看出,不管是溢出还是无溢出,都要求在下次唤醒任务之前,当前任务主体代码必须被执行完。表现在图中,就是变量xTimeToWake总是大于变量xConstTickCount(每溢出一次的话相当于加上一次最大值Max)。

计算的唤醒时间合法后,就将当前任务加入延时列表,同样延时列表也有两个。每次系统节拍中断,中断服务函数都会检查这两个延时列表,查看延时的任务是否到期,如果时间到期,则将任务从延时列表中删除,重新加入就绪列表。如果新加入就绪列表的任务优先级大于当前任务,则会触发一次上下文切换。

四、总结

       调用系统延时的任务都是最高优先级,这是为了便于分析而特意为之的,实际上的任务可不一定能设置为最高优先级。对于相对延时,如果任务不是最高优先级,则任务执行周期更不可测,这个问题不大,我们本来也不会使用它作为精确延时;对于绝对延时函数,如果任务不是最高优先级,则仍然能周期性的将任务解除阻塞,但是解除阻塞的任务不一定能获得CPU权限,因此任务主体代码也不会总是精确周期性执行。

如果要想精确周期性执行某个任务,可以使用系统节拍钩子函数vApplicationTickHook(),它在系统节拍中断服务函数中被调用,因此这个函数中的代码必须简洁。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/255345.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

浅析AI智能视频监控技术在城市交通中的作用及意义

城市交通作为整个城市的整体脉络&#xff0c;每天都发挥着重要作用&#xff0c;为了最大程度地避免城市交通堵塞、提高城市交通效率&#xff0c;智能视频监控系统发挥了重要作用。具体表现在以下几个方面&#xff1a; 1、交通违规监管&#xff1a;TSINGSEE青犀智能视频监控系统…

【ESP32】手势识别实现笔记:红外温度阵列 | 双三次插值 | 神经网络 | TensorFlow | ESP-DL

目录 一、开发环境搭建与新建工程模板1.1、开发环境搭建与卸载1.2、新建工程目录1.3、自定义组件 二、驱动移植与应用开发2.1、I2C驱动移植与AMG8833应用开发2.2、SPI驱动移植与LCD应用开发2.3、绘制温度云图2.4、启用PSRAM&#xff08;可选&#xff09;2.5、画面动静和距离检测…

Linux基础作业1

课后练习&#xff1a; 案例1&#xff1a;ls命令练习 1.查看根目录下内容 2. 显示/etc目录内容 3. 显示/boot目录内容的 4. 显示/root的内容 5. 显示/bin/bash程序 6. 显示/opt目录内容 案例2&#xff1a;查看文件内容练习 查看/etc/passwd文件内容查看/etc/default/useradd…

Java集合大总结——Map的简单使用

现实需求 现实生活与开发中&#xff0c;我们常会看到这样的一类集合&#xff1a;用户ID与账户信息、学生姓名与考试成绩、IP地址与主机名等&#xff0c;这种一一对应的关系&#xff0c;就称作映射。Java提供了专门的集合框架用来存储这种映射关系的对象&#xff0c;即java.uti…

Java程序员,你掌握了多线程吗?

文章目录 01 多线程对于Java的意义02 为什么Java工程师必须掌握多线程03 Java多线程使用方式04 如何学好Java多线程写作末尾 摘要&#xff1a;互联网的每一个角落&#xff0c;无论是大型电商平台的秒杀活动&#xff0c;社交平台的实时消息推送&#xff0c;还是在线视频平台的流…

k8s-service 7

由控制器来完成集群的工作负载&#xff0c;service&#xff08;微服务&#xff09;是将工作负载的应用暴露出去&#xff0c;从而解决访问问题 作用&#xff1a;无论是在集群内还是集群外&#xff0c;都可以访问pod上的应用&#xff0c;其实现对集群内的应用pod自动发现和负载均…

欧洲原料药认证注册信息查询方法-CEP数据库

欧盟是全球最大、最重要的药品国际市场之一&#xff0c;药品需求市场非常庞大。中国药企要进入欧盟市场&#xff0c;必须获得CEP认证。 CEP认证与COS认证等同&#xff0c;均代表欧洲药典适应性证书 COS&#xff08;Certificate of Suitability&#xff09;是指欧洲药典适用性认…

Course2-Week3-使用机器学习的建议

Course2-Week3-使用机器学习的建议 文章目录 Course2-Week3-使用机器学习的建议1. 拆分原始训练集1.1 如何改进模型1.2 二拆分&#xff1a;训练集、测试集1.3 三拆分&#xff1a;训练集、验证集、测试集 2. 避免高偏差和高方差2.1 使用训练误差和验证误差进行分析2.2 选择合适的…

校园外卖小程序源码系统 附带完整的搭建教程

随着大学生消费水平的提高&#xff0c;对于外卖服务的需求也在不断增加。很多学生都面临着课业繁重、时间紧张等问题&#xff0c;无法亲自到餐厅就餐。因此&#xff0c;开发一款适合校园外卖市场的应用软件&#xff0c;将为广大学生提供极大的便利。 以下是部分代码示例&#…

APP兼容性测试,这几个面试硬技能,包教包会

兼容性测试主要通过人工或自动化的方式&#xff0c;在需要覆盖的终端设备上进行功能用例执行&#xff0c;查看软件性能、稳定性等是否正常。 对于需要覆盖的终端设备&#xff0c;大型互联网公司&#xff0c;像 BAT&#xff0c;基本都有自己的测试实验室&#xff0c;拥有大量终…

期末速成数据库极简版【创建】(1)

目录 前言 【1】T-SQL语句创建数据库 【2】T-SQL语句删除数据库 【3】T-SQL语句创建表 完整性约束 数据类型 例子 【4】T-SQL语句修改表 【5】T-SQL语句删除表 关于数据库&#xff0c;在我们学习Linux网络编程后面会详细学习到&#xff0c;为了应付期末考试&#xff0…