FreeRTOS系统延时函数分析

news2024/9/25 9:30:07

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

相对延时:指每次延时都是从执行函数vTaskDelay()开始,直到延时指定的时间结束。

绝对延时:指将整个任务的运行周期看作一个整体,适用于需要按照一定频率运行的任务。

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

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

  1. 为其他任务运行

相对延时函数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执行一个需要20毫秒才能完成的任务。

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

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

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

小结

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

操作系统闲谈07——系统启动

操作系统闲谈07——系统启动 一、BIOS启动 BIOS程序不需要由谁加载&#xff0c;本身便固化在ROM只读存储器中。 开机的一瞬间 cs:ip 便被初始化为0xf000 : 0xfff0。开机的时候处于实模式&#xff0c;其等效地址为0xffff0&#xff0c;如上图所示此地址为BIOS的入口地址。 建立…

C/C++每日一练(20230218)

目录 1. 整数转罗马数字 2. 跳跃游戏 II 3. 买卖股票的最佳时机 IV 1. 整数转罗马数字 罗马数字包含以下七种字符&#xff1a; I&#xff0c; V&#xff0c; X&#xff0c; L&#xff0c;C&#xff0c;D 和 M。 字符 数值 I 1 V 5 X …

物联网中RocketMQ的使用

物联网中RocketMQ的使用 1. 背景 随着物联网行业的发展、智能设备数量越来越多&#xff0c;很多常见的智能设备都进入了千家万户&#xff1b;随着设备数量的增加&#xff0c;也对后台系统的性能提出新的挑战。 在日常中&#xff0c;存在一些特定的场景&#xff0c;属于高并发请…

Java数据类型、基本与引用数据类型区别、装箱与拆箱、a=a+b与a+=b区别

文章目录1.Java有哪些数据类型2.Java中引用数据类型有哪些&#xff0c;它们与基本数据类型有什么区别&#xff1f;3.Java中的自动装箱与拆箱4.为什么要有包装类型&#xff1f;5.aab与ab有什么区别吗?1.Java有哪些数据类型 8种基本数据类型&#xff1a; 6种数字类型(4个整数型…

java ssm志愿者信息服务平台springmvc

志愿者服务平台&#xff0c;采用ssm框架技术&#xff0c;java语言&#xff0c;jsp前端页面以及js&#xff0c;DIVCSS&#xff0c;html5等技术综合应用开发实现。志愿者服务平台&#xff0c;采用了前台后台的模式开发&#xff0c;前台用于志愿者在线项目培训&#xff0c;视频学习…

CUDA编程接口

编程接口 文章目录编程接口3.1利用NVCC编译3.1.1编译流程3.1.1.1 离线编译3.1.1.2 即时编译3.1.2 Binary 兼容性注意&#xff1a;仅桌面支持二进制兼容性。 Tegra 不支持它。 此外&#xff0c;不支持桌面和 Tegra 之间的二进制兼容性。3.1.3 PTX 兼容性3.1.4 应用程序兼容性3.1…

Kafka框架快速入门及异步通知

文章目录1、异步通信原理1.1 观察者模式1.2 生产者消费者模式1.3 缓冲区1.4 数据单元2、消息系统原理2.1 点对点消息传递2.2 发布订阅消息传递3、Kafka简介3.1 设计目标3.2 Kafka的优点4、Kafka系统架构4.1 Broker4.2 Topic4.3 Partition4.4 Leader4.5 Follower4.6 replication…

ACL与NAT

ACL---访问控制列表&#xff0c;是一种策略控制工具 功能&#xff1a;1.定义感兴趣流量&#xff08;数据层面 &#xff09; 2.定义感兴趣路由&#xff08;控制层面&#xff09; ACL 条目表项组成&#xff1a; 编号规则&#xff1a;步数或者跳数默认值为5&#xff0c;…

Rancher 部署 MySQL

文章目录创建 pvc部署 MySQL前置条件&#xff1a;安装 rancher&#xff0c;可参考文章 docker 部署 rancher 创建 pvc MySQL 数据库是需要存储的&#xff0c;所以必须先准备 pvc 创建 pvc 自定义 pvc 名称选择已经新建好的 storageclass&#xff0c;storageclass 的创建可参考…

Mac os如何安装绿盾客户端

环境&#xff1a; Apple Mac mini 八核M1芯片 8G 256G Mac os 11.0 问题描述&#xff1a; Mac os如何安装绿盾客户端 解决方案&#xff1a; 一、关闭系统保护 1.关机电脑&#xff0c;按住“开机键”不放直到屏幕上出现“选项” 点击“继续”&#xff0c;等待进入恢复模…

github 使用

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录一、git与github二、出错的地方1.GitHub没有css样式2、git clone出现错误3、明明创建了responsibility 但git 不显示一、git与github 这个博客写的很好&#xff01;…

剔除绿化后的比较可靠的评价模型

sheet4权重实际评分&#xff08;越高越不好&#xff09;&#xff08;0-5分&#xff09;正向化&#xff08;5-X&#xff09;后分别与权重相乘得到该地的总分总分高&#xff0c;排名高为好&#xff0c;即污染小排序后

【Kubernetes】第三篇 - ci-server 构建节点 Docker、Jenkins 环境搭建

一&#xff0c;前言 上一篇&#xff0c;主要介绍了阿里云服务器的采购和简单配置&#xff1a; 三台服务器规划如下&#xff1a; 服务配置内网IP外网IP说明ci-server2c4g172.17.178.104182.92.4.158Jenkins Nexus Dockerk8s-master2c4g172.17.178.10547.93.9.45Kubernetes …

ARM uboot 源码分析5 -启动第二阶段

一、start_armboot 解析6 1、console_init_f (1) console_init_f 是 console&#xff08;控制台&#xff09;的第一阶段初始化。_f 表示是第一阶段初始化&#xff0c;_r 表示第二阶段初始化。有时候初始化函数不能一次一起完成&#xff0c;中间必须要夹杂一些代码&#xff0c;…

ccc-pytorch-回归问题(1)

文章目录1.简单回归实战&#xff1a;2.手写数据识别1.简单回归实战&#xff1a; 用 线性回归拟合二维平面中的100个点 公式&#xff1a;ywxbywxbywxb 损失函数&#xff1a;∑(yreally−y)2\sum(y_{really}-y)^2∑(yreally​−y)2 迭代方法&#xff1a;梯度下降法&#xff0c;…

【QA】[Vue/复选框全选] v-model绑定每一项的赋初值问题

发生场景&#xff1a;不只是复选框的状态改变&#xff0c;还有的功能要用到复选框的选中状态&#xff0c;比如&#xff1a;购物车计算总价&#xff0c;合计等等。 引入&#xff1a;复选框 checkbox 在使用时&#xff0c;需要用v-model绑定布尔值&#xff0c;来获取选中状态&…

一台电脑安装26个操作系统(windows,macos,linux,chromeOS,Android,静待HarmonyOS)

首先看看安装了哪些操作系统1-4: windows系统 四个5.Ubuntu6.deepin7.UOS家庭版8.fydeOS9.macOS10.银河麒麟11.红旗OS12.openSUSE Leap13.openAnolis14.openEuler(未安装桌面UI)15.中标麒麟&#xff08;NeoKylin&#xff09;16.centos17.debian Edu18.fedora19.oraclelinux(特别…

Rust Web入门(一):TCP 和 HTTP Server

本教程笔记来自 杨旭老师的 rust web 全栈教程&#xff0c;链接如下&#xff1a; https://www.bilibili.com/video/BV1RP4y1G7KF?p1&vd_source8595fbbf160cc11a0cc07cadacf22951 学习 Rust Web 需要学习 rust 的前置知识可以学习杨旭老师的另一门教程 https://www.bili…

【原创】java+swing+mysql图书管理系统设计与实现

图书管理系统是一个比较常见的系统&#xff0c;今天我们主要介绍如何使用javaswiingmysql去开发一个cs架构的图书管理系统&#xff0c;方便学生进行图书借阅。 功能分析&#xff1a; 宿舍报修管理系统的使用角色&#xff0c;一般分为管理员和学生&#xff0c;管理员主要进行学…

学习OpenGL图形2D/3D编程

环境&#xff1a;WindowsVisual Studio 2019最流行的几个库&#xff1a;GLUT&#xff0c;SDL&#xff0c;SFML和GLFWGLFWGLAD库查看显卡OPENGL支持情况VS2019glfwgladopenGL3.3顶点着色器片段着色器VAO-VBO-(EBO)->渲染VAO-VBO-EBO->texture纹理矩阵matrix对图形transfor…