在开发驱动过程中在与硬件交互的过程中,不可避免的需要使用到延迟技术,例如在设置外设寄存器后,由于硬件延迟等原因 设置之后并不会立即马上上生效,需要等待延迟一小段时间以便设置能够生效,驱动开发人员不可避免的需要使用到内核delay技术。

采用何种延迟技术一直都是是很多开发人员都没有注意到,Robert Love在《Linux Kernel Development》一书中专门对delay技术做出了比较详细的说明。

Busy Looping

Busy Looping即使用使用一个循环计数进行忙等待,直到满足条件。在满足需延迟的时间之后 退出循环,内核中提供了一种使用tick 计数方法,通过控制延迟多少个tick数目,来控制整个延迟时间,方法如下:

unsigned long timeout = jiffies + 10; /* ten ticks */while (time_before(jiffies, timeout))
;

jifferes为内核种获取当前clock数目,当获取到的clock数目超过10个时退出循环,time_before函数其本质上就是一个数目比较函数:

#define time_after(a,b)      \(typecheck(unsigned long, a) && \typecheck(unsigned long, b) && \((long)((b) - (a)) < 0))
#define time_before(a,b)    time_after(b,a)

通过上述宏展开 可知timer_before()就是比较a的计数是否在b前面,如果a<b则返回true,否则返回fase 。

上述jiffies是在进入循环前获取到当前系统时钟滴答tick数目,当tick数目超过进入timeout数目之后将会退出循环,意思就是延迟10个滴答数目。jiffies获取到的是系统当前的滴答数目,为了防止缓存等影响不能获取最新的当前系统滴答数目, 该变量使用volatie变量。

由于每个cpu的频率不一致,例如如果当前系统频率为1000HZ,则延迟10个滴答数目则为10ms,所以每个cpu延迟的时间并不一致,为了可以增加程序可移值性,延迟具体某个时间可以将要延迟的滴答数目* HZ(系统时钟)方法,下面程序可以修改为:

unsigned long delay = jiffies + 2*HZ; /* two seconds */while (time_before(jiffies, delay))
;

上述用例采用2*HZ意味这就是延迟2s。

Busy Looping危害

采用上述busy looping方法,使用忙等待的方式,会长期占用cpu资料,使其他任务得不到调度,为了增加防止在忙等待时候可以调度更好优先级得任务,可以在循环种加入cond_resched,其方法如下:

unsigned long delay = jiffies + 5*HZ;while (time_before(jiffies, delay))cond_resched();

在忙等待时间,如果有更高优先级任务,可以将cpu资源让给其他任务,提高整体性能。但是采用cond_resched(),意味着该方法不能在中断函数种使用。其延迟时间也将会不准确,因为其他任务调度时间可能超过延迟时间。在时间要求不准确的场景可以使用上述方法。

Small Delays

busy loops在时间延迟要求为tick整数倍或者时间要求不是很准确场景下使用,如果要求延迟时间段,且时间精确的场景下,内核提供了一系列的函数分别针对microsecond(微秒), nanosecond(纳秒), and millisecond(毫秒)函数,分别为:

void udelay(unsigned long usecs)
void ndelay(unsigned long nsecs)
void mdelay(unsigned long msecs)

udelay是微秒级的延迟,ndelay是纳秒级的延迟,mdelay是毫秒级别的延迟。特别要指出的是其使用的延迟参照最好不要超过该函数的级别,比如微秒级的延迟最好使用udelay,为了防止数据溢出,其参数最好不要超过微妙级别。

The udelay() function should be called only for small delays because larger delays on fast machines might result in overflow.As a rule, do not use udelay() for delays more than one millisecond in duration. For longer durations, mdelay() works fine。

除了上述几个函数之前,还有几个函数可以用于非atomic场景:

usleep_range(unsigned long min, unsigned long max)
msleep(unsigned long msecs)
msleep_interruptible(unsigned long msecs)

schedule_timeout()

除了上述方法之外,内核还从调度角度提供延迟方法:schedule_timeout()

该函数可以将当前进程进行休眠,将cpu资源让给其他任务进行调度,直到所设置的延迟时间达到,唤醒。由于当前进行会休眠所以延迟的时间并不是很精确。使用方法如下:

set_current_state(TASK_INTERRUPTIBLE);schedule_timeout(s * HZ);

延迟s秒,再设置之前需要将当前任务状态设置为TASK_INTERRUPTIBLE,意思是当任务被标记为TASK_INTERRUPTIBLE,且处于延迟休眠状态时,如果该任务收到一个signal时,该任务也会被唤醒(即使延迟时间还未到),如果在延迟休眠时间段内不想收到signal唤醒打扰可以设置为一下:

set_current_state(TASK_UNINTERRUPTIBLE);schedule_timeout(s * HZ);

特别要说明的时,在使用schedule_timeout()函数之前,必须将task状态设置为TASK_INTERRUPTIBLE或者TASK_UNINTERRUPTIBLE。

具体schedule_timeout()函数说明可以详见《Linux Kernel Development》

参考资料

《Linux Kernel Development》

linux内核那些事之delay延迟技术相关推荐

  1. linux内核那些事之buddy(慢速申请内存__alloc_pages_slowpath)(5)

    内核提供__alloc_pages_nodemask接口申请物理内存主要分为两个部分:快速申请物理内存get_page_from_freelist(linux内核那些事之buddy(快速分配get_p ...

  2. linux内核那些事之buddy(anti-fragment机制)(4)

    程序运行过程中,有些内存是短暂的驻留 用完一段时间之后就可以将内存释放以供后面再次使用,但是有些内存一旦申请之后,会长期使用而得不到释放.长久运行有可能造成碎片.以<professional l ...

  3. linux内核那些事之mmap_region流程梳理

    承接<linux内核那些事之mmap>,mmap_region()是申请一个用户进程虚拟空间 并根据匿名映射或者文件映射做出相应动作,是实现mmap关键函数,趁这几天有空闲时间 整理下mm ...

  4. linux内核那些事之buddy

    buddy算法是内核中比较古老的一个模块,很好的解决了相邻物理内存碎片的问题即"内碎片问题",同时有兼顾内存申请和释放效率问题,内核从引入该算法之后一直都能够在各种设备上完好运行, ...

  5. linux内核那些事之pg_data_t、zone结构初始化

    free_area_init 继续接着<linux内核那些事之ZONE>,分析内核物理内存初始化过程,zone_sizes_init()在开始阶段主要负责对各个类型zone 大小进行计算, ...

  6. linux内核那些事之Sparse vmemmap

    <inux内核那些事之物理内存模型之SPARCE(3)>中指出在传统的sparse 内存模型中,每个mem_section都有一个属于自己的section_mem_map,如下图所示: 而 ...

  7. linux内核那些事之buddy(anti-fragment机制-steal page)(5)

    继<linux内核那些事之buddy(anti-fragment机制)(4)>,在同一个zone内指定的migrate type中没有足够内存,会启动fallback机制,从fallbac ...

  8. linux内核那些事之 VMA Gap

    rb_subtree_gap kernel为了提高对VMA 查找.插入速度,采用了很多技术,例如VMA采用RB tree和 双向链表两种组织形式,另外对VMA结构对cache line对齐从而提高ca ...

  9. linux内核那些事之用户空间管理

    内核主要数据结构 linux内核将用户空间抽象成struct vm_area_struct进行管理,每申请以个用户空间在内核中都会抽象成对应的vm_are_struct进行管理,同时为了区别不同进程的 ...

最新文章

  1. JavaScript计算两个文本框内数据的乘积(四舍五入保留两位小数)
  2. Effective Java读书笔记完结啦
  3. Codeforces Round #762 (Div. 3)
  4. java学习(111):日期时间格式化
  5. kk服务器信息及端口,kk服务器设置
  6. java 数据字典使用_java中数据字典怎么用?图文详解
  7. YOLOv3中Anchor理解
  8. 决策树--CART算法
  9. 找回丢失的QQ之普及知识全套
  10. Python moviepy 快速视频剪辑编辑神器
  11. Web测试要点(功能、性能、可用性、兼容、安全)
  12. 心脏支架手术后遗症 做完心脏支架手术留下后遗症
  13. 周易六十四卦——豫卦
  14. 大企业喜欢使用外包人员驻场开发软件的原因解析
  15. wps加载项开发和使用
  16. 论文解读:U-Net: Convolutional Networks for Biomedical Image Segmentation
  17. 三、Maven-单一架构案例(搭建环境:辅助功能,业务功能:登录)
  18. 其它基本扩展-URLs-base64加密
  19. Java缩放PDF内容_如何使用Itext缩放现有的pdf边距
  20. div设置背景图background:url

热门文章

  1. 剖析如何自己写框架Struts和Mybatis,学其原理而非重复造轮子
  2. 如何用好 Chrome devtools
  3. java+nginx+tomcat+memcache
  4. 项目开发优化技巧总结
  5. MediaCodec的使用和若干问题处理
  6. Redis-字典(hash)基础
  7. krpano使用总结-地图路线
  8. canvas 粒子效果 - 手残实践纪录
  9. 命令行下 mysql 不是内部或外部命令排查方法
  10. 多线程的等待唤醒机制