熟悉又陌生的udelay

申请CSDN博客认证专家通过,着实让我受宠若惊,自己还是有这份自知之明,与专家 大牛这些词汇还是有很长距离。
不过认证通过给了自己一份动力,在博客上分享更多自己的所学,与大家学习交流。

内核开发中经常用到延时函数,最熟悉的是mdelay msleep。虽然经常会使用,但是具体实现却不了解,今天来研究下。

这2个函数在实现上有着天壤之别。

msleep实现是基于调度,延时期间调用schedule_timeout产生调度,待时间到期后继续运行,该函数实现在kernel/timer.c中。

由于linux内核不是实时系统,因此涉及调度的msleep肯定不会精确。

今天不细说msleep,有时间再来分析它,今天重点来学习mdelay。
mdelay是使用最多的延时函数。它的实现是忙循环,利用了内核loop_peer_jiffy,延时相对于msleep更加准确。

mdelay ndelay都是基于udelay来实现的。在include/linux/delay.h中,如下:

#ifndef MAX_UDELAY_MS
#define MAX_UDELAY_MS   5
#endif

#ifndef mdelay
#define mdelay(n) (    (__builtin_constant_p(n) && (n)<=MAX_UDELAY_MS) ? udelay((n)*1000) :     ({unsigned long __ms=(n); while (__ms--) udelay(1000);}))
#endif

#ifndef ndelay
static inline void ndelay(unsigned long x)
{
    udelay(DIV_ROUND_UP(x, 1000));
}
#define ndelay(x) ndelay(x)
#endif

#define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d))

gcc的内建函数__builtin_constant_p用于判断n是否为编译时常数,如果n是常数,返回 1,否则返回 0。
mdelay实现,如果参数为常数,且小于5,则直接调用udelay,说明udelay最大支持5000us延时。否则则循环调用udelay达到延时目的。

ndelay实现可以看出非常不精确,经过计算调用udelay。因此ndelay最少也是延时1us。

所以接下来来看udelay实现。这里讨论基于ARM处理器架构的实现,udelay实现在arch/arm/include/asm/delay.h中。

#define MAX_UDELAY_MS 2

#define udelay(n)                               (__builtin_constant_p(n) ?                        ((n) > (MAX_UDELAY_MS * 1000) ? __bad_udelay() :                  __const_udelay((n) * ((2199023U*HZ)>>11))) :          __udelay(n))

最终会调用__const_udelay或者__udelay,2者实现在arch/arm/lib/delay.s中,如下:

.LC0:       .word   loops_per_jiffy
.LC1:       .word   (2199023*HZ)>>11

/*
 * r0  <= 2000
 * lpj <= 0x01ffffff (max. 3355 bogomips)
 * HZ  <= 1000
 */

ENTRY(__udelay)
        ldr r2, .LC1
        mul r0, r2, r0
ENTRY(__const_udelay)               @ 0 <= r0 <= 0x7fffff06
        mov r1, #-1
        ldr r2, .LC0
        ldr r2, [r2]        @ max = 0x01ffffff
        add r0, r0, r1, lsr #32-14
        mov r0, r0, lsr #14     @ max = 0x0001ffff
        add r2, r2, r1, lsr #32-10
        mov r2, r2, lsr #10     @ max = 0x00007fff
        mul r0, r2, r0      @ max = 2^32-1
        add r0, r0, r1, lsr #32-6
        movs    r0, r0, lsr #6
        moveq   pc, lr

上面这段汇编运算规则可以总结为下面这个计算公式,n为传入参数:
loops = ( ( (n *((2199023*HZ)>>11)) >> 14 ) * (loops_per_jiffy >> 10) ) >> 6 

/*
 * loops = r0 * HZ * loops_per_jiffy / 1000000
 *
 * Oh, if only we had a cycle counter...
 */

@ Delay routine
ENTRY(__delay)
        subs    r0, r0, #1
        bhi __delay
        mov pc, lr
ENDPROC(__udelay)
ENDPROC(__const_udelay)
ENDPROC(__delay)

__udelay的实现利用了loop_per_jiffy,该变量是内核全局变量,在内核启动时调用calibrate_delay计算得出,表示处理器在一个jiffy中loop数。
calibrate-delay实现之前写过一篇文章来分析,链接如下:
http://blog.csdn.net/skyflying2012/article/details/16367983

loop_per_jiffy内核下转换为bogoMIPS反馈给用户,我们执行命令cat /proc/cpuinfo,可以看到bogoMIPS,表征处理器每秒执行百万指令数,是一个cpu性能测试数。

根据上面汇编实现可以看出,先计算出延时us所需的loop数,最后调用__delay循环递减完成延时,很明显,udelay实现最终就是一个处理器忙循环。

这里需要注意一个细节,calibrate_delay实现中也是通过调用__delay来实现,参数即为loops_per_jiffy。
loops_per_jiffy的单位即为__delay,也就是说一个loop就是一个__delay。
__delay实现就是将参数一直subs递减,反复跳转。
所以我的理解,一个loop就是一条arm递减指令+跳转指令。

但是对于__udelay实现最大的疑问在于有一个奇怪的数字(2199023*HZ)>>11是什么意思,并且汇编中实现的计算规则各种移位又是什么意思呢。

首先最常规的方式,借助loop_per_jiffy根据延时us计算loop数,计算公式应该是汇编注释中那样:
 loops = n * HZ * loops_per_jiffy / 1000000
 HZ表征内核每秒jiffy个数,则HZ*loops_per_jiffy/1000000代表了1us中的loop数。

查找各种资料找到原因,对于处理器这个公式有一个极大的缺陷,如果处理器没有浮点处理单元,即非浮点处理器(整型处理器),运行时,这个公式计算很容易变为0。
因为除数1000000极大,loops_per_jiffy * HZ / 1000000=0。无能你想要延迟多少微秒,总为0。
内核的解决方法是,除1000000变为乘1/1000000,为保持精度,1/1000000要先左移30位, 变为
(1/1000000)<<30  =  2^30 / 1000000 = 2199023U>>11

这就明白了(2199023*HZ)>>11来源啦。

汇编中出现的反复移位则是为了把2199023U>>11实现中向左移的30位移回来。考虑到溢出,所以分成了>>14 , >>10, >>6,最后等同于 >>30 。

到此处就彻底明白汇编实现的loops计算公式的巧妙之处了,也就明白了arm的udelay实现方法。

可以看出内核在处理大数据除法运算时不直接除,而是运用了移位运算,我理解原因可能有两点:
(1)如上面遇到的问题,精度问题,除数很大,计算结果可能出现0.
(2)之前驱动开发中遇到的一种情况,内核编译时编译器对于除法会替换为gcc.so库的数学运算函数__aeabi_ldivmod,但是内核编译不依赖任何库,所以会出现编译错误。倒是可以使用内核提供的do_div替换。

udelay分析就到这里,2点小启发:
(1)内核的delay函数实现的确就是个忙循环。不同于sleep函数。
(2)内核开发中使用除法运算时要考虑清楚哦。

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

时间: 2024-10-18 07:49:53

熟悉又陌生的udelay的相关文章

一座熟悉而陌生的城市--一个程序员的成长史(7)

回到重庆,已经是两天以后了.代是雄的老家在农村,但他并没有急着回去,而是先到一个亲戚家住下,借此机会来重新看看重庆这张"熟悉而陌生"的面孔. 代是雄是土生土长的重庆人,但老家在农村,在上高中之前的大部分时光都是在他们那个小镇上度过的,自己的足迹也几乎仅限于以他家老房子为中心的方圆10公里以内.在他的印象中,在初中三年时间里,他到过两次县城,那是去参加所谓的初中数学竞赛.他当时只是觉得县城与小镇比起来,要漂亮很多.他印象最深的就是在县城里面吃了一碗凉粉,那个味道真的是好极了,似乎自己之前

黑马程序员——冒泡排序和选择排序——熟悉又陌生的排序方法

------Java培训.Android培训.iOS培训..Net培训.期待与您交流! ------- 昨天做基础测试题,遇到了个排序问题,写完后脑子里突然跳出了“冒泡排序法”这个名词.“冒泡排序法”和“选择排序法”是每个程序初学者都会学到的两个排序方法,但是好多人对这两个排序方法是既熟悉又陌生,熟悉是因为凡是遇到排序一般都会想到这两个方法,陌生是因为细细一想对这两个方法的原理和区别又不是很清楚.不较真还好,初学者一较真就很容易被这两个方法搞晕了,我当时也被这两个方法搞晕了,就又认认真真一步一步

Python : 熟悉又陌生的字符编码(转自Python 开发者)

Python : 熟悉又陌生的字符编码 字符编码是计算机编程中不可回避的问题,不管你用 Python2 还是 Python3,亦或是 C++, Java 等,我都觉得非常有必要厘清计算机中的字符编码概念.本文主要分以下几个部分介绍: 基本概念 常见字符编码简介 Python 的默认编码 Python2 中的字符类型 UnicodeEncodeError & UnicodeDecodeError 根源 基本概念 字符(Character) 在电脑和电信领域中,字符是一个信息单位,它是各种文字和符号

CSS总结篇—熟悉而陌生的文档流

文档流 我相信,在大家刚学css的时候,一定对这个名词熟悉而又陌生.在查阅了相关资料后,总结了下对文档流的认识. 文档流定义:将窗体自上而下分成一行一行,并在每行中按从左至右的挨次排放元素,即为文档流 每个非浮动块级元素都独有一行, 浮动元素则按规则浮在行的一端. 若当时行容不下, 则另起新行再浮动.内联元素也不会独有一行. 一切元素(包括块级,内联和列表元素)均可生成子行, 用于摆放子元素.有三种状况将使得元素离开文档流而存在,分别是浮动.绝对定位.固定定位.然则在IE中浮动元素也存在于文档流

五分钟 知晓Eclipse不为人知的秘密(越熟悉 越陌生)

大家好,这篇博客的目的是总结一下Eclipse这个软件中一些不为常用的功能.与大家分享.谢谢~ 1.利用one hour看了一下Eclipse的使用,用two hour写了这篇blog. 2.在现实项目中,活学活用,才会真正对你有利,否则你浪费时间看了本博客,对你毫无帮助. 本博客结构:目录 + 按目录分述 目录: 1.给Eclipse添加书签. 2.通过Attach File查看源码和系统函数.也可以通过open Type:Open Type Hierarchy:Open resources查

熟悉又陌生 彪悍徐茂栋的双面人生

"该出手时出手,该冷静时冷静."唯快不破和专注极致,是互联网时代的两个重要铁律,作为一个20年成功的连续创业者,时任窝窝商城董事长兼CEO徐茂栋的创业经历,即是这样的体现. 外界对徐的熟悉,源于2011年团购大战时,带领窝窝团从行业排名20多位后,快速上升成为第-,面对血风腥雨的激烈竞争,徐茂栋是有仇必报,手段强硬,让对手闻之胆寒,在很长一段时间,外界对徐的认知停留在"彪悍" 二字. 但徐茂栋这位山东汉子的另一面却不为人知,比如在春节时会一个个拜访员工,不忙的时候在

MySQL 8.0发布,你熟悉又陌生的Hash Join?

昨天下午在查资料的时候,无意间点到了MySQL的官网.发现MySQL发布了一个新版本. Mysql这个数据库有没有人不熟悉?不用的?没有吧. 2019年末,MySQL发布的8.0.18 GA版本,带来了一些新特性和增强功能.其中最引人注目的莫过于多表连接查询支持Hash Join. 还是老样子,建议英文好的同学直接看这里: https://dev.mysql.com/doc/refman/8.0/en/hash-joins.html 关于MySQL Hash Join的特性介绍: 1.对于大数据

那些熟悉又陌生的 css2、css3 样式,持续复习

initial关键字:    除了 Internet Explorer,其他的主流浏览器都支持 initial 关键字. Opera 15 之前的版本不支持 initial 关键字. initial 关键字用于设置 CSS 属性为它的默认值. initial 关键字可用于任何 HTML 元素上的任何 CSS 属性. overflow : 所有主流浏览器都支持 overflow 属性. 注释:任何的版本的 Internet Explorer (包括 IE8)都不支持属性值 "inherit&quo

Android:全面解析 熟悉而陌生 的Application类使用

前言 Applicaiton类在 Android开发中非常常见,可是你真的了解Applicaiton类吗? 本文将全面解析Applicaiton类,包括特点.方法介绍.应用场景和具体使用,希望你们会喜欢. 目录 示意图 1. 定义 代表应用程序(即 Android App)的类,也属于Android中的一个系统组件 继承关系:继承自 ContextWarpper 类 示意图 2. 特点 2.1 实例创建方式:单例模式 每个Android App运行时,会首先自动创建Application 类并实