charlie2008 发表于 2010-11-2 13:38:34

ffxz 可能有更好的方法解决定时器溢出(回绕)问题

Linux内核中提供了以下四个宏,可有效地解决由于jiffies溢出而造成程序逻辑出错的情况。下面是从Linux Kernel 2.6.7版本中摘取出来的代码:
/*
* These inlines deal with timer wrapping correctly. You are
* strongly encouraged to use them
* 1. Because people otherwise forget
* 2. Because if the timer wrap changes in future you won't have to
* alter your driver code.
*
* time_after(a,b) returns true if the time a is after time b.
*
* Do this with "<0" and ">=0" to only test the sign of the result. A
* good compiler would generate better code (and a really good compiler
* wouldn't care). Gcc is currently neither.
*/
#define time_after(a,b) \
(typecheck(unsigned long, a) && \
typecheck(unsigned long, b) && \
((long)(b) - (long)(a) < 0))
#define time_before(a,b) time_after(b,a)

#define time_after_eq(a,b) \
(typecheck(unsigned long, a) && \
typecheck(unsigned long, b) && \
((long)(a) - (long)(b) >= 0))
#define time_before_eq(a,b) time_after_eq(b,a)

time_after等比较时间先后的宏背后的原理 (摘自网上的分析!)

上述time_after等比较时间先/后的宏为什么能够解决jiffies溢出造成的错误情况呢?
我们仍然以8位无符号整型(unsigned char)为例来加以说明。仿照上面的time_after宏,我们可以给出简化的8位无符号整型对应的after宏:
#define uc_after(a, b) ((char)(b) - (char)(a) < 0)

设a和b的数据类型为unsigned char,b为临近8位无符号整型最大值附近的一个固定值254,下面给出随着a(设其初始值为254)变化而得到的计算值:
a b (char)(b) - (char)(a)
254 254 0
255 - 1
0 - 2
1 - 3
...
124 -126
125 -127
126 -128
127 127
128 126
...
252 2
253 1

从上面的计算可以看出,设定b不变,随着a(设其初始值为254)不断增长1,a的取值变化为:
254, 255, (一次产生溢出)
0, 1, ..., 124, 125, 126, 127, 126, ..., 253, 254, 255, (二次产生溢出)
0, 1, ...
...

而(char)(b) - (char)(a)的变化为:
0, -1,
-2, -3, ..., -126, -127, -128, 127, 126, ..., 1, 0, -1,
-2, -3, ...
...

从上面的详细过程可以看出,当a取值为254,255, 接着在(一次产生溢出)之后变为0,然后增长到127之前,uc_after(a,b)的结果都显示a是在b之后,这也与我们的预期相符。但在a取值为127之后,uc_after(a,b)的结果却显示a是在b之前。
从上面的运算过程可以得出以下结论:
使用uc_after(a,b)宏来计算两个8位无符号整型a和b之间的大小(或先/后,before/after),那么a和b的取值应当满足以下限定条件:
. 两个值之间相差从逻辑值来讲应小于有符号整型的最大值。
. 对于8位无符号整型,两个值之间相差从逻辑值来讲应小于128。
从上面可以类推出以下结论:
对于time_after等比较jiffies先/后的宏,两个值的取值应当满足以下限定条件:
两个值之间相差从逻辑值来讲应小于有符号整型的最大值。
对于32位无符号整型,两个值之间相差从逻辑值来讲应小于2147483647。
对于HZ=100,那么两个时间值之间相差不应当超过2147483647/100秒 = 0.69年 = 248.5天。对于HZ=60,那么两个时间值之间相差不应当超过2147483647/60秒 = 1.135年。在实际代码应用中,需要比较先/后的两个时间值之间一般都相差很小,范围大致在1秒 ~1天左右,所以以上time_after等比较时间先/后的宏完全可以放心地用于实际的代码中。

=================================================================================
用这种方法实现就会轻松解决了定时器的回绕问题使程序逻辑出错,代码也简洁很多!

ffxz 发表于 2010-11-2 15:08:37

没看明白。

上面说的是a与b的比较,但RT-Thread中的tick比较并不是这样的。如果按照这个方法,能否直接提供个OS Tick问题的patch?

charlie2008 发表于 2010-11-2 16:06:24

哈哈,由你更改会好一些,我对你的系统不太熟悉!还等着你的radio板来学学!
我举个例子:下面是rrt的Timer代码一部分;
void rt_timer_check(void)
{
        struct rt_timer *t;
        rt_tick_t current_tick;   //rt_tick_t为uint32_t
        register rt_base_t level;

#ifdef RT_TIMER_DEBUG
        rt_kprintf("timer check enter\n");
#endif

        current_tick = rt_tick_get();

        /* disable interrupt */
        level = rt_hw_interrupt_disable();

        while (!rt_list_isempty(&rt_timer_list))
        {
                t = rt_list_entry(rt_timer_list.next, struct rt_timer, list);
                if (current_tick >= t->timeout_tick)//此处就定时器溢出的逻辑错误(当t->timeout_tick产生回绕时)虽然时间相当长。可以改成:if( (rt_int32_t)current_tick - (rt_int32_t)t->timeout_tick >= 0 ) 通过强制成有符号量进行比较就没有问题了.
                {
#ifdef RT_USING_HOOK
                        if (rt_timer_timeout_hook != RT_NULL) rt_timer_timeout_hook(t);
#endif

                        /* remove timer from timer list firstly */
                        rt_list_remove(&(t->list));

                        /* call timeout function */
                        t->timeout_func(t->parameter);

                        /* re-get tick */
                        current_tick = rt_tick_get();

#ifdef RT_TIMER_DEBUG
                        rt_kprintf("current tick: %d\n", current_tick);
#endif

                        if ((t->parent.flag & RT_TIMER_FLAG_PERIODIC) &&
                                        (t->parent.flag & RT_TIMER_FLAG_ACTIVATED))
                        {
                                /* start it */
                                t->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
                                rt_timer_start(t);
                        }
                        else
                        {
                                /* stop timer */
                                t->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
                        }
                }
                else break;
        }

        /* enable interrupt */
        rt_hw_interrupt_enable(level);

        /* increase soft timer tick */
#ifdef RT_USING_TIMER_SOFT
        rt_soft_timer_tick_increase ( );
#endif

#ifdef RT_TIMER_DEBUG
        rt_kprintf("timer check leave\n");
#endif
}

//////////////////////////////////////////////////////////////////////////////

把if (current_tick >= t->timeout_tick)改成:if( (rt_int32_t)current_tick - (rt_int32_t)t->timeout_tick >= 0 )
时,当t->timeout_tick 溢出产生回绕时,用符号运算还能正常得出结果。其原因是current_tick - t->timeout_tick会产生大于
2147483647的数,对有符号运算来说,其值为负值。其结果还是对的。

这样两个值的取值应当满足以下限定条件即可:
两个值之间相差从逻辑值来讲应小于有符号整型的最大值。
对于32位无符号整型,两个值之间相差从逻辑值来讲应小于2147483647。
对于小于2147483647值来说,是很长很长的定时,

ffxz 发表于 2010-11-2 16:17:12

有些明白了,多谢。我再仔细思考下应该如何修改

charlie2008 发表于 2010-11-2 16:59:16

不客气!^:^

snoopyzz 发表于 2010-11-2 17:13:48

改成 if ( (rt_int32_t)(rt_uint32_t)(current_tick-t->timeout_tick) >= 0 )
效率可能好一点,原理是一样的

charlie2008 发表于 2010-11-2 17:16:28

看不出对效率有何改善?snoopyzz说说!

snoopyzz 发表于 2010-11-2 17:24:28

对于 有符号数的减法运算,编译器生成的代码和无符号数是不一样的

实事上,由于数字在ram存储是没有符号的,负数是补码形式保存,所以无符号数相减得到的差值 就是差值,
只要不产生数据类型所能表示的最大范围差值即可,
我们需要比较的仅仅是后一个tick比前一个大,那么,只需要将这个差值转为有符号数判断符号即可,


LS可以反汇编看看结果是不是我的写法效率高些,8位机做久了...效率一向太重视了,优化写法是家常便饭 -_-

这种比较方式我经常用

不过 ,arm是不是有 有符号32位减法指令?我对arm指令集不熟,有的话,估计效率差不了太多

snoopyzz 发表于 2010-11-2 17:33:09

测试了一下,果然,在arm中两种写法生成的汇编一样-_-
我多事了...

charlie2008 发表于 2010-11-2 17:36:00

哦!我试试看.

charlie2008 发表于 2010-11-2 17:37:38

一样的,我就不用试了!^:^

charlie2008 发表于 2010-11-2 21:08:30

我试着更改了clock.c和timer.c的代码,不知到是否有问题,没有样板板试。我想最好比较做成宏较好,外部也能使用!
clock.c和Timer.c代码ourdev_594716QE00TO.rar(文件大小:4K) (原文件名:clock&Timer.rar)

eworker 发表于 2010-11-2 22:00:17

学习

feng200808 发表于 2010-11-4 14:45:33

学习

32446975 发表于 2013-7-7 23:28:06

收藏!好帖!学习了.
页: [1]
查看完整版本: ffxz 可能有更好的方法解决定时器溢出(回绕)问题