• 关于hrtimer_forward小段代码的分析【转】


     
     

    随着各种嵌入式设备上采用linux,特别是Android系统的广泛应用,linux的hrtimer高精度模式开始被广泛支持。当然,虽说可以支持到ns精度,具体实现依赖于硬件定时器和内核编译条件,不过,一般情况下,几十us的定时精度都是支持的。这给编写驱动带来了很大的方便。 
    之前有一篇非常强大的博文 Linux时间子系统之六:高精度定时器(HRTIMER)的原理和实现,已经将hrtimer的基本原理和hrtimer的应用方法做了清晰详尽的剖析,这里针对在应用hrtimer时,往往导致定时器没有按照设计精度执行的问题,分析一下hrtimer_forword的内部的小段代码。

    整段代码

    817 u64 hrtimer_forward(struct hrtimer *timer, ktime_t now, ktime_t interval)
    818 {
    819         u64 orun = 1;
    820         ktime_t delta;
    821 
    822         delta = ktime_sub(now, hrtimer_get_expires(timer));
    823 
    824         if (delta.tv64 < 0)
    825                 return 0;
    826 
    827         if (interval.tv64 < timer->base->resolution.tv64)
    828                 interval.tv64 = timer->base->resolution.tv64;
    829 
    830         if (unlikely(delta.tv64 >= interval.tv64)) {
    831                 s64 incr = ktime_to_ns(interval);
    832 
    833                 orun = ktime_divns(delta, incr);
    834                 hrtimer_add_expires_ns(timer, incr * orun);
    835                 if (hrtimer_get_expires_tv64(timer) > now.tv64)
    836                         return orun;
    837                 /*
    838                  * This (and the ktime_add() below) is the
    839                  * correction for exact:
    840                  */
    841                 orun++;
    842         }
    843         hrtimer_add_expires(timer, interval);
    844 
    845         return orun;
    846 }

    关于无效的forward

    以上是3.4版本内核中的hrtimer_forward的实现,我们来分析一下这里面的几个关键环节: 
    首先时给出的now,这个时间很重要,

    822         delta = ktime_sub(now, hrtimer_get_expires(timer));
    823 
    824         if (delta.tv64 < 0)
    825                 return 0;

    因为如上我们可以看到,如果当前时间比定时器到期时间要早,说明定时器上还有一个工作需要在当前时间完成,因此,不会进行任何操作。直接返回,也就是说,如果我们希望这次hrtimer_forward起作用,那么实际上,不会起任何作用。

    关于定时精度问题

    接下来看看定时精度,如果我们看一下针对某一嵌入式系统的linux源码中hrtimer的实现,我们就会看到,良好的实现均准确地设置了resolution,这个resolution是定时器能够实现的最高精度,当然这一般不是硬件决定的(一个主频48Mhz的MCU,其硬件定时器的定时精度都在20ns),而是嵌入式系统设计者根据系统的整体性能考虑权衡的结果。

    827         if (interval.tv64 < timer->base->resolution.tv64)
    828                 interval.tv64 = timer->base->resolution.tv64;

    因此我们看到,如果我们给出的interval如果比这个精度要高,那么,我们只能得到最短等于该精度的定时。

    精确调整和overrun问题

    接下去的代码,还有点儿让对gcc不太了解的人糊涂,就是

    830         if (unlikely(delta.tv64 >= interval.tv64)) {

    这句话的关节在于unlikely宏,其实这是在提醒gcc编译器,一般情况下,delta.tv64 >= interval.tv64的条件按是不成立的,这可以让gcc编译出更加高效的代码。并不影响if内部的判断,我们可以直接理解为:

    if (delta.tv64 >= interval.tv64) {

    而在这个条件为真的时候,会对expires作较为精密的外科手术。delta是一个由于hrtimer自身遍历以及我们callback函数内部的业务处理,耗费了一定时间,已经超过了 interval,那么则认为出现了overrun现象,即下一次定时已经被覆盖掉了。这时,内核希望做一些补救工作,不过也做不了多少,就是告诉你按照你给出interval,已经超时了几次了,并且看看是否可以根据这个来微调后续的定时时间。这里的代码挺有意思,我们连起来看一下

    /*这是delta怎么来的*/
    822         delta = ktime_sub(now, hrtimer_get_expires(timer));
    831                 s64 incr = ktime_to_ns(interval);
    832 /*这是一个将当前的expires加上detal所能包含的interval整数倍的时间的计算
    833                 orun = ktime_divns(delta, incr);
    834                 hrtimer_add_expires_ns(timer, incr * orun);

    那么,如果hrtimer的处理,是在硬件中断上的原子性操作,且ktime_divns只是简单的整数除法,以下的代码绝对不可能成立:

    835                 if (hrtimer_get_expires_tv64(timer) > now.tv64)
    836                         return orun;

    那么,实际上,它是一个通过损失精度来完成计算的除法,如果除数大于32位,那么除数与被除数一起损失精度到除数小于等于32位为止,以下是其函数定义:

    u64 ktime_divns(const ktime_t kt, s64 div)
    313 {
    314         u64 dclc;
    315         int sft = 0;
    316 
    317         dclc = ktime_to_ns(kt);
    318         /* Make sure the divisor is less than 2^32: */
    319         while (div >> 32) {
    320                 sft++;
    321                 div >>= 1;
    322         }
    323         dclc >>= sft;
    324         do_div(dclc, (unsigned long) div);
    325 
    326         return dclc;
    327 }

    因此,如果inteval是一个大于2^32的大数的时候,还真有可能让条件成立滴^_^。这是返回值和加载expires上的值都对,没有什么要讲的了。 
    如果不成立,则

    841                 orun++;
    842         }
    843         hrtimer_add_expires(timer, interval);

    存疑

    那么在这样的一个状态下,很不幸,expires被加上了(incr * orun+interval)这么多的时间,我们可以理解为,此时,内核认为你希望得到的定时时间为interval给定定时时间的整数倍,然后你还知道有几次overrun,这个主意不坏,但是我们带入实际的数值,就会感觉比较好玩了。 
    假设: 
    delta=5ns,interval=4ns 
    则: 
    行833计算结果:orrun=1 
    行834在原expires上加上了4ns 
    那么,由于行835此时条件不会为真那么在: 
    行841,orrun变成了2 
    然后在行843,expires的值加上了4ns 
    至此,expires一共加上了8ns,如果所有其他操作瞬间完成的话,将在3ns后得到下一次callback的机会。 
    如果按照这样的逻辑,我们看到返回的结果凌乱了,明明overrun了一次,结果会返回2。如果我没有理解错误的话,此时的overrun不能反应实际的情况了

  • 相关阅读:
    Java设计模式(Design Patterns)
    P2213 [USACO14MAR]懒惰的牛The Lazy Cow_Sliver
    P3120 [USACO15FEB]牛跳房子(金)Cow Hopscotch (Gold)
    P4818 [USACO15DEC]Bessie's Dream 贝西的梦
    P3667 [USACO17OPEN]Bovine Genomics
    P4379 [USACO18OPEN]Lemonade Line
    P4378 [USACO18OPEN]Out of Sorts S
    P4089 [USACO17DEC]The Bovine Shuffle
    P4269 [USACO18FEB]Snow Boots G
    P4086 [USACO17DEC]My Cow Ate My Homework
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/5413985.html
Copyright © 2020-2023  润新知