转自:http://blog.csdn.net/wowuyinglingluan/article/details/45720151

版权声明:本文为博主原创文章,未经博主允许不得转载。

 
 

随着各种嵌入式设备上采用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不能反应实际的情况了

关于hrtimer_forward小段代码的分析【转】的更多相关文章

  1. js中闭包来实现bind函数的一段代码的分析

    今天研究了一下bind函数,发现apply和call还可以有这样的妙用,顺便巩固复习了闭包. var first_object = { num: 42 }; var second_object = { ...

  2. matlab小段代码学习

    matlab读hdf文件到txt filename='E:\data\H1BDLD10110607231863921.L2B.HDF'; h=hdftool(filename); Latitude = ...

  3. SVNKIT一段代码的分析

    打印SVNkit版本库中的结构: 函数如下: 调用方法如下: listEntries(repository, ""); System.out.println("XXXXX ...

  4. python优势之通过一段代码来了解python的强大之处

    晚上闲暇之余随意翻了一下博客,看到https://www.jianshu.com/p/69bf0ed0b5cc作者提到了一段代码,刚开始看没啥感觉,仔细深入后引起了我的注意.里面使用了python最简 ...

  5. flappy pig小游戏源码分析(1)——主程序初探

    闲逛github发现一个javascript原生实现的小游戏,源码写的很清晰,适合想提高水平的同学观摩学习.读通源码后,我决定写一系列的博客来分析源码,从整体架构到具体实现细节来帮助一些想提高水平的朋 ...

  6. 小程序代码包压缩 策略&方案

    微信小程序自推出以来,逐渐发展,目前正受到越来越多的青睐.其中很重要的一点得益于小程序的轻量级特性,每个小程序最多不超过2MB,招之即来挥之即去,相比于几十上百兆的APP,用户进入小程序,或者说,小程 ...

  7. HashMap的小总结 + 源码分析

    一.HashMap的原理 所谓Map,就是关联数组,存的是键值对——key&value. 实现一个简单的Map,你也许会直接用两个LIst,一个存key,一个存value.然后做查询或者get ...

  8. STM32启动代码详细分析

    最近需要学习iap的功能,因此离不开stm32的启动代码的分析,以前看了很多遍,都看不懂,读书百遍,其义自见,因此我有看了一遍,下面的文章,挺好的,因此转载: 在上电复位后,我们都知道会先运行启动代码 ...

  9. 微信小程序应用安全分析及设计

    针对微信关于小程序安全设计的分析 针对微信小程序开发配置及部分配置机制分析微信小程序安全设计: AppSecret 管理员生成AppSecret,在与微信后台交互过程中部分接口使用,如 auth.co ...

随机推荐

  1. Selenium 启动无头浏览器,只有chrome 和 firefox的,没有IE

    使用无头浏览器,可以避免掉不确定的弹出框对脚本稳定性的影响,还能在脚本执行过程中省略掉了css 加载的时间. 以下是Selenium 启动无头浏览器的方法,只有chrome 和 firefox的. p ...

  2. Python全栈 MongoDB 数据库(数据的修改)

    修改操作符的使用   $set 修改一个域的值,增加一个域   阿哲年龄修改为33 db.class1.update({name:'阿哲'},{$set:{age:33}})   如果sex域不存在则 ...

  3. annoy安装

    yum install gcc-c++ #linux下需安装c++编译器 sudo pip install annoy

  4. ubuntu中 VI 方向键、删除键问题

    这两天重新装的ubuntu系统,发觉使用VI时,方向键按下去后变成ABCD,删除键无效.网上搜寻一番,应该是VI软件本身的问题,顾卸载重装即可,步骤如下: 1.执行命令 sudo apt-get re ...

  5. JavaSE复习(三)异常与多线程

    异常 分类 编译时期异常:checked异常. 在编译时期,就会检查,如果没有处理异常,则编译失败.(如日期格式化异常) 运行时期异常:runtime异常. 在运行时期,检查异常.在编译时期,运行异常 ...

  6. get? post? put? delete? head? trace? options? http请求方法

    http1.1协议里面定义了八种请求方法: get:用作获取,读取数据 post:向指定的资源提交数据 put:更新,向指定的资源上传一个内容,比如说:更新一个用户的头像或者替换掉已有的一个视频 de ...

  7. SSH答疑解惑系列(一)——spring容器是如何启动的

    SSH框架十分受欢迎,其中有一个原因就是spring可以和Struts2框架无缝整合.在使用spring时,无需手动创建web容器,而是通过配置文件声明式创建spring容器. 在web应用中,创建s ...

  8. Chromium之文件类型

    .grp: Generate your project. 是由Json(JavaScript Object Notation)(or Python?)来解析,根据环境(OS,Compiler..)来生 ...

  9. Bootstrap中的Affix插件

    我们为什么要用bootstrap?因为懒!哦....不,是因为方便,呃...意思差不多. 今天来说说Affix这个插件,它可以使导航栏固定,免去了自己手写的麻烦,用着非常方便,废话不多说,下面是用法. ...

  10. Netscaler立身之本—NAT

    Netscaler立身之本—NAT http://blog.51cto.com/caojin/1926579 一.前言 ADC的主要作用是作为服务器的反向代理来进行应用发布的,介于客户端和服务器端之间 ...