[STM32F1]

[MsgOS]高效的延时处理机制

[复制链接]
658|0
手机看帖
扫描二维码
随时随地手机跟帖
科技猎人|  楼主 | 2016-6-19 12:26 | 显示全部楼层 |阅读模式
[MsgOS]MsgOS源码来了
https://bbs.21ic.com/forum.php?mo ... amp;fromuid=1131700
(出处: 21ic电子技术论坛)


[MsgOS]让系统跑起来
https://bbs.21ic.com/forum.php?mo ... amp;fromuid=1131700
(出处: 21ic电子技术论坛)


[MsgOS]shell初体验
https://bbs.21ic.com/forum.php?mo ... amp;fromuid=1131700
(出处: 21ic电子技术论坛)



[MsgOS]hello world!
https://bbs.21ic.com/forum.php?mo ... amp;fromuid=1131700
(出处: 21ic电子技术论坛)




因为MsgOS发送消息时具有延时选项,具备其他系统里软硬件timer的功能,也就不再有单独的timer组件了。
当msg_send函数中的time参数不为0时,说明该消息要延时发送,单位为1毫秒,误差为一个单位,由于time为无符号32位数,则可延时的范围为1毫秒到约49天,无论定时精度还是跨度都是很出众的。当opt参数具备MSG_OPT_PERIODIC或MSG_OPT_REPEAT属性时,定时将会是周期性的(二者区**面再讲),否则为单次定时。
定时检查处理是在系统滴答定时器的中断中进行的,MsgOS系统心跳周期固定为1毫秒,而其他家系统一般是可变的,1毫秒的系统心跳既有足够的分辨精度又不至于占用太多CPU时间,最关键的是所有基于MsgOS的应用代码心跳都是1毫秒非常便于代码阅读和移植。


最初的定时实现方式是每次心跳中断都检查所有消息块,看计时值是否为0,不为0则减减处理,减减后若为0则说明计时时间到,调用相关处理。这种方式原理很简单但时间复杂度为O(n),消息块越多检查耗时越多。
后来又改为了需要延时处理的消息块放在数组前面,每次心跳中断只对前面的延时消息快进行逐个判断。这样时间消耗明显减小,但当时对消息块数组进行排列插入操作的实现较为复杂。
后来系统从Linux源码中借鉴了双向循环链表的处理方法,消息块的插入删除等操作就变得简单高效了。
此后看到了别人的一种软件定时处理方法,基本思路是这样的:
1.添加第一个定时器(TA)时,计时时间就是参数输入值,如TA=100,然后每次心跳中断减减直到为0
2.添加第二个定时器(TB),如果延时时间小于当前第一个的值,如延时为TB=40,此时第一个计时已为TA=80,则将其插入到第一个前面,计时为TB=40,而TA=80-40。如果TB延时时间小于当前第一个的值,如TB=120,TA=80,则将TB排在TA后面,此时TA=80,TB=120-80.
3.再有新定时器添加时类似上面处理,即根据当前延时值从小到大排列进行插入处理,并对队列后面的定时值进行修正。
4.每次心跳中断都只对第一个定时块进行计时减减处理,直到其减到为0定时时间到,删除该块把下一个块作为队列头继续处理。
这样处理的好处是心跳中断时间复杂度为O(1),每次耗时都很短且和延时任务数目无关。但每次在添加定时时需要进行一次队列比较和插入处理,还要修改其他定时块的计时值。


结合上面这一思路,MsgOS目前的计时处理又进化成了这样。
有一个64位的计时值clock从0开始,每次心跳中断加1处理,对于一个64位单位为1毫秒的计时值来讲,要到576584650年后才能溢出,所以我对其溢出后的错误处理采取了不处理的严谨做法:lol
同时对这个64位数据还要按照高32位和低32位来访问,低32位表示此次计时周期中当前系统时间,高32位表示低32位计时溢出次数。
为了方便,构造一个联合体对此数据进行访问,如下所示
union
{
        uint64    total;
        struct
        {
                uint32 current;
                uint32 overflow;
        }part;   
}clock;



再定义两个定时链表timer[0]和timer[1],用于接收添加的定时消息块。且以计时周期溢出次数的0位值来指示哪个链表是当前延时链表,也就是在每次计时溢出后都会切换一次当前延时链表。
当添加一个延时消息时,以time+current(即tick)做为参考,在当前定时链表中按照从小到大排列,不会修改已有消息块的计时值,每次心跳中断时只比较current和表头tick值,如果tick<=current则认为计时时间到,进行相应处理后删除首节点,再以下一个节点为首进行相同处理。这样通过比较系统时间轴上前后位置来判定计时时间是否到达就避免了减减计数方式带来的定时块间的关联耦合。

这里有一个问题就是time和current都是无符号32位数据,出现加值溢出怎么办?
办法就是如果tick< current即出现加值溢出,则将该消息块以同样的方式添加到非当前定时链表中。在定时链表切换后自然就会得到准确处理。

这样在系统心跳中断中只做一次数值比较,时间复杂度为O(1),而在添加延时时只需要一次队列比较和插入操作。而且系统心跳计时clock的加入为系统还带来了跟多的功能和好处(好处以后在讲)。


怎么样,是不是非常高效呢?

下面是系统心跳中断延时处理函数源码
void  msg_irq(void)
{
        msg_st    *msg;
        link_st   *link;
        link_st   *node;

        scb.mcb.clock.total++;/**/
        link =&scb.mcb.timers[scb.mcb.clock.part.overflow & 0x01];/**/   

        while(!link_isempty(link))/**/
        {
                node = link->next;/**/      
                msg =(msg_st  *)node;/**/         
                if(msg->tick <= scb.mcb.clock.part.current)/**/
                {            
                        link_remove(node);/**/
                        if(msg->opt & MSG_OPT_IRQ)/**/
                        {
                                msg->state=MSG_STATE_RUNING;/**/
                                (msg->msgf)(msg->data,msg->ptr);/**/
                                if(msg->opt & MSG_OPT_PERIODIC)/**/
                                {              
                                        msg_enqueue_delay(msg);/**/                                                          
                                }
                                else
                                {
                                        link_insert_before(&scb.mcb.free,node);/**/
                                        msg->state=MSG_STATE_FREE;/**/
                                }
                        }
                        else
                        {
                                msg_enqueue_thread(msg);/**/                                                       
                        }
                }
                else
                {
                        break;/**/
                }                           
        }   
}





发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

31

主题

192

帖子

8

粉丝