高效软件定时器的设计

软件定时器在协议栈等很多场景都有广泛的应用,有时候会有大量的定时器同时处于工作状态,它们的超时时间各异,要高效的保证每个定时器都能够较为准确的超时并执行到其回调函数并不是一件易事。本文分析嵌入式实时操作系统Nucleus的定时器方案,它巧妙的管理了一条按照相对时间来排序的双向链表,避免每次tick中断都要遍历链表检查超时和更新剩余时间,实现了一种相当高效的软件定时器。

结构体TM_TCB来表示动态创建的定时器,其定义如下

typedef struct TM_TCB_STRUCT

{

/*Nucleus的定时器分为应用定时器和task内置两种type  */

INT                 tm_timer_type;

/* 剩余时间,可动态改变  */

UNSIGNED            tm_remaining_time;

/*tm_information指向TM_APP_TCB,其保存了超时函数等定时器参数  */

VOID               *tm_information;

struct TM_TCB_STRUCT

*tm_next_timer,           /* Next timer in list    */

*tm_previous_timer;     /* Previous timer in list*/

} TM_TCB;

每个字段都有注释,和本文相关的字段已经标红。

定时器结构体TM_TCB之间使用双向循环链表的方式组织在一起,即tm_next_timer和tm_previous_timer指针。Nucleus里有个TM_TCB    *TMD_Active_Timers_List全局指针,其指向该链表的头部,管理这所有处于激活状态的定时器。

假设当前系统里没有定时器,我们的应用在初始化时要连续创建和激活5个周期性的定时器,其周期超时时间为:5,8,8,12,20(单位都是tick).

假设是按照顺序创建,那么链表的形成过程如下所示(方框里的数字就是TM_TCB的tm_remaining_time):

 //第1个定时器会在5个tick之后超时

 // 第2个定时器会在8个tick之后超时,相对于前一个,它的超时时间多3

 // 第3个定时器会在8个tick之后超时,相对于前一个,它的超时时间多0

 // 第4个定时器会在12个tick之后超时,相对于前一个,它的超时时间多4

 // 第5个定时器会在20个tick之后超时,相对于前一个,它的超时时间多8

看起来很容易。

现在打乱其创建的顺序,例如12,8,20,5,8,那么链表的形成过程如下所示(蓝色表示新插入的节点,棕色表示受影响的节点):

        //第1个定时器会在12个tick之后超时

      // 第1个定时器会在8个tick之后超时,第2个相对于前一个,它的超时时间多4

    // 第3个定时器会在20个tick之后超时,相对于前一个,它的超时时间多8

    //链表重新排序,并更新当前插入的定时器的下一个元素的相对时间

     //链表重新排序,并更新当前插入的定时器的下一个元素的相对时间

总之,无论创建的顺序如何,结果链表组织方式和顺序都是一样的

有了这个排序的记录有相对时间的双向链表后,下一个问题就是时间的递减怎么记录?

需要引入一个重要的全局变量

UNSIGNED TMD_Timer

它记录了当前距离超时最近的定时器的剩余时间。例如在上面的例子里,当前TMD_Timer值为5。Nucleus的底层实现了一个中断处理函数INT_Timer_Interrupt,它是由硬件定时器触发的,当有处于激活状态的timer时,每个tick触发一次,在这个函数里,会对TMD_Timer减1并判断是否为0,如果为0,说明有定时器超时,开始激活TMD_HISR,其回调函数就是用于处理所有定时器超时的入口。

那么,有定时器超时了以后会发生什么?

假设现在已经过了5个tick(这个5个tick里,不需要对定时器链表有任何的操作),那么首节点的超时函数会被执行,首节点被从链表头部摘除,TMD_Timer会被改写为下一个首节点的剩余时间重新计数,即3.且链表会更新成(蓝色表示新插入的节点,棕色表示受影响的节点):

原首节点定时器是周期性的,所以需要重新寻找合适的位置插入链表,由于它的下次超时时间是5个tick后.所以应该放在3之后,且相对超时时间为2。同时受到影响的还有它的下一个节点,原来的相对剩余时间是4,由于加入了一个新的节点,相对的剩余时间变成了2。

假设又继续过了3个tick,现在的第一、二个节点会超时(在一次激活HISR的函数里执行两个定时器的超时函数),TMD_Timer会被改写为下一个首节点的剩余时间重新计数,即2,链表会被更新为

再过2个tick,下个节点超时,以此类推。

还有一个问题,前面创建几个定时器的时候都是同时创建的,如果不是同时创建会怎么样?

如上面的分析,回到整个用例开始的时候,在5个定时器组成的链表建立好之后,在接下来的5个tick内,没有定时器会超时,同时链表里的数据也不会有任何更新。

假设在3个tick之后,需要加入一个新的定时器,超时时间为10,应该怎么办?

如果继续按照上面组织链表的办法,10 = 5 + 3 + 2,应该放在第4位,且相对前一个定时器的超时时间加2,如下所示:

但是请注意,当前已经距离开始时间已经过去了3个tick,第一个定时器会在2个tick后超时,第二和第三个会在5秒后超时,所以如果按照这种方式,新加入的这个定时器在7个tick后就超时了,这显然是不正确的。

这个地方还是需要TMD_Timer,它记录了当前距离超时最近的定时器的剩余时间,也就是等于当前正确的链表首节点的剩余时间(开始是5,过了3个tick之后,变成了2)

正确的做法是先更新头节点的剩余时间之后,再插入新的节点,更新完之后的链表如下所示

这样,链表里的每个定时器节点都会有正确的超时时间了

有了前面的分析,停止定时器时的链表操作就很简单了,紧接着上面的例子,假设1个tick后,应用程序停止了第5个timer(图中的剩余时间为1),链表更新如下:

停止定时器最重要的工作就是将其从TMD_Active_Timers_List链表上摘下,摘节点时不需要更新头节点的剩余时间,但需要更新要摘除的节点的下一个节点的剩余时间8 = 7 + 1

最后还有两个问题:

1 这个定时器的设计把耗时的操作都放到了start 和stop timer的时刻。当timer个数很多时,这个新timer节点的插入以及整个链表的会不会比较费时?

答:1 前面的这些讨论都是关于在激活状态的定时器,实际使用中,在同一时间里,会有相当部分的定时器是处于非激活状态,它们不需要用链表组织起来(应用程序拥有其指针可对其进行操作)。所以激活定时器组成的链表长度不会太长。

2将start timer的操作分解开来,就可以看出它只是干了几件事(stop也是类似的):

a)更新头节点的剩余超时时间为TMD_Time。固定时间,几条指令即可完成

b)遍历链表,找到合适的插入位置。这是一个相对耗时的操作,时间复杂度是O(n)

c)更新这个插入位置的下一个节点的剩余时间,也就是图里面的棕色节点, 固定时间,几条指令即可完成

2 这个定时器的设计还能再优化吗?

答:软件层面上,这是楼主见过的最优秀的定时器设计方案,硬件上还有一点优化空间。前面对于硬件定时计数的描述是这样的

每个tick触发一次,在这个函数里,会对TMD_Timer减1并判断是否为0,如果为0,说明有定时器超时,开始激活TMD_HISR

这是一个反复比较的过程,每个tick的中断里,会做不多的数字累加和比较操作,这个定时器中断的操作可以省下来。每次链表头节点更新的时候,可以启动一个硬件定时器,将其超时时间设置为TMD_Timer,时间一到,就代表着头节点已超时,而不用反复的在每个tick都做着重复的工作

时间: 2024-10-21 01:49:13

高效软件定时器的设计的相关文章

实现自己的软件定时器

为什么要实现软件定时器: 在芯片平台上,地址空间也是相当宝贵的,如果保留了更多的硬件定时器的话,就需要更多的地址空间,那么我们能不能作个折中方案呢?答案是肯定的,我们可以使用一个硬件定时器,来模拟实现一个软件定时器,可以满足更多的定时需求,需要注意的一点就是软件定时器精度可能会有稍微误差,因为会涉及到任务调度.锁中断等,在对定时精度要求不高的场景,可以考虑使用软件定时器.Linux内核中的timer_list精度为10ms,这里我们来实现一套精度为1ms的软件定时器(当然可以实现精度为微秒级的,

高效软件开发习惯总结

高效软件开发习惯总结: 一.  编程与开发 1.      充分重视需求,理解需求,明确需求,确认需求: 2.      提供尽可能多的方案,选择符合约束的最佳方案: 3.      编程前,尽可能将设计与实现方案思考成熟: 4.      编程时,写好配套的文档及必要的注释: 5.      编程后,认真完成周密苛刻的单元测试: 6.      修改代码时,同步更新文档及注释: 7.      程序风格保持一致: 8.      善于复用并改进: 9.      解决问题后,深入思考内部机制,

毫秒,微妙级别软件定时器

单片机开发中,软件定时器是常用的工具.定时执行特定任务和延时功能,都可以用软件定时器实现. 常见的延时函数的实现做法有: 1. 使用空指令进行延时,通过控制空指令的执行次数,进行延时.优点:不需要占用系统外设.缺点:系统运行指定个空指令的时间不稳定,中途出现的中断处理会严重影响计时的精确性. 2.使用单片机的定时器外设,设定特定的时间产生中断,进行计时.优点:计时准确,不受其他中断影响计时.缺点:浪费单片机外设资源,并且延时处理不能嵌套调用,灵活性不够. 这里要介绍的是利用单片机内部的sysTi

软件顾问可视设计的得力助手——PowerMockup

你可能是一位从事信息化的软件顾问,你也可能是一位软件设计师,你需要通过图形直观的向客户表达你的设计意图. 你可能已经积累了很多的Powerpoint图形元素,但每次都要从以往的文件中到处寻找,浪费您宝贵的时间. 现在,我要向您推荐这一款软件顾问可视设计的得力助手--PowerMockup,他是一款Powerpoint插件,具备以下特性: 1.支持采用Powerpoint快速设计软件界面草图,让用户快速认可你的设计方案.PowerMockup内置有 78种完全可编辑的用户界面元素和84种线框图图标

软件编写和设计中的18大原则

软件编写和设计中的18大原则写在这里,自己经常看一看.作者Diggins是加拿大一位有25年编程经验的资深技术人员,曾效力于Microsoft和Autodesk,并创办过两家赢利的互联网公司. 下面的13和14合起来也被称作Shy原则. 1.避免重复原则(DRY - Don’t repeat yourself) 编程的最基本原则是避免重复.在程序代码中总会有很多结构体,如循环.函数.类等等.一旦你重复某个语句或概念,就会很容易形成一个抽象体. 2.抽象原则(Abstraction Princip

谈谈编程器软件开发与设计

*************************************************************************************************************************** 作者:EasyWave                                                   时间:2014.05.24 类别:协议标准-编程器简介                                  声明:

软件定时器-闹钟提醒我们该吃饭吃饭,该睡觉睡觉

闹钟提醒我们该吃饭吃饭,该睡觉睡觉 原文地址:http://blog.csdn.net/u011833609/article/details/28862125 softwaretimer.h #ifndef _SOFTWARETIMER_H_ #define _SOFTWARETIMER_H_ typedef enum{z_false = 0, z_true = !z_false} z_bool; typedef unsigned char z_uchar; typedef unsigned c

μC/OS-II中使用软件定时器

在试着将μC/OS-II移植到ARM7芯片(LPC2138)上的过程中,发现使用OSTmrCreate创建的OSTmr始终都不能执行CallbackFunction,OS版本是v2.85,最后是这么解决的. 在文档<uCOS-II-RefMan.PDF>中找到了关于“OSTmrSignal()”这个函数的一段描述: OSTmrSignal() is called either by a task or an ISR to indicate that it’s time to update th

敏捷开发随笔(一)高效软件开发之道

敏捷的方法值关注真正重要的事情,少关注那些占用大量时间而无甚裨益的不重要的事情 这是一组把以人为本,团队合作,快速响应变化和可工作的软件作为宗旨的开发方法 敏捷意味着可以快速地适应变化 土耳其谚语-不管路走了多远,错了就要重新返回 敏捷开发宣言 1个体和交互胜过过程和工具 2可工作的软件胜过面面俱到的文档 3客户协作胜过合同谈判 4响应变化胜过遵循计划 开发需要持续不断,切勿时续时断 敏捷开发就是在一个高度协作的环境中,不断地使用反馈进行自我调整和完善 先难后易,把简单的问题留到最后 可工作的软