Linux驱动技术(七) _内核定时器与延迟工作

内核定时器

软件上的定时器最终要依靠硬件时钟来实现,简单的说,内核会在时钟中断发生后检测各个注册到内核的定时器是否到期,如果到期,就回调相应的注册函数,将其作为中断底半部来执行。实际上,时钟中断处理程序会触发TIMER_SOFTIRQ软中断,运行当前处理器上到期的所有定时器。

设备驱动程序如要获得时间信息以及需要定时服务,都可以使用内核定时器。

jiffies

要说内核定时器,首先就得说说内核中关于时间的一个重要的概念:jiffies变量,作为内核时钟的基础,jiffies每隔一个固定的时间就会增加1,称为增加一个节拍,这个固定间隔由定时器中断来实现,每秒中产生多少个定时器中断,由在<linux/param.h>中定义的HZ宏来确定,如此,可以通过jiffies获取一段时间,比如jiffies/HZ表示自系统启动的秒数。下两秒就是(jiffies/HZ+2),内核中用jiffies来计时,秒转换成的jiffies:seconds*HZ,所以以jiffiy为单位,以当前时刻为基准计时2秒:(jiffies/HZ+2)*HZ=jiffies+2*HZ如果要获取当前时间,可以使用do_gettimeofday(),该函数填充一个struct timeval结构,有着接近微妙的分辨率。


  1. //kernel/time/timekeeping.c 
  2. /** 
  3. * do_gettimeofday - Returns the time of day in a timeval 
  4. * @tv:         pointer to the timeval to be set 
  5. * NOTE: Users should be converted to using getnstimeofday() 
  6. */ 
  7. void do_gettimeofday(struct timeval *tv)  

驱动程序为了让硬件有足够的时间完成一些任务,常常需要将特定的代码延后一段时间来执行,根据延时的长短,内核开发中使用长延时和短延时两个概念。长延时的定义为:延时时间>多个jiffies,实现长延时可以用查询jiffies的方法:


  1. time_before(jiffies, new_jiffies); 
  2. time_after(new_jiffiesmjiffies); 

**短延时的定义为:延迟事件接近或短于一个jiffy,实现短延时可以调用


  1. udelay(); 
  2. mdelay(); 

这两个函数都是忙等待函数,大量消耗CPU时间,前者使用软件循环来延迟指定数目的微妙数,后者使用前者的嵌套来实现毫秒级的延时。

定时器

驱动可以注册一个内核定时器,来指定一个函数在未来某个时间来执行。定时器从注册到内核开始计时,达到指定的时间后会执行注册的函数。即超时值是一个jiffies值,当jiffies值大于timer->expires时,timer->function就会被执行。API如下


  1. //定一个定时器 
  2.  
  3. struct timer_list my_timer;//初始化定时器 
  4.  
  5. void init_timer(struct timer_list *timer); 
  6. mytimer.function = my_function; 
  7. mytimer.expires = jiffies +HZ;//增加定时器 
  8.  
  9. void add_timer(struct timer_list *timer);//删除定时器 
  10.  
  11. int del_tiemr(struct timer_list *timer);  

实例


  1. static struct timer_list tm; 
  2. struct timeval oldtv;void callback(unsigned long arg){ 
  3.     struct timeval tv; 
  4.     char *strp = (char*)arg; 
  5.     do_gettimeofday(&tv); 
  6.     printk("%s: %ld, %ld\n", __func__, 
  7.         tv.tv_sec - oldtv.tv_sec, 
  8.         tv.tv_usec- oldtv.tv_usec); 
  9.     oldtv = tv; 
  10.     tm.expires = jiffies+1*HZ; 
  11.     add_timer(&tm); 
  12. static int __init demo_init(void){ 
  13.     init_timer(&tm); 
  14.     do_gettimeofday(&oldtv); 
  15.     tm.function= callback; 
  16.     tm.data    = (unsigned long)"hello world"; 
  17.     tm.expires = jiffies+1*HZ; 
  18.     add_timer(&tm); 
  19.     return 0; 
  20. }  

延迟工作

除了使用内核定时器完成定时延迟工作,Linux内核还提供了一套封装好的"快捷方式"-delayed_work,和内核定时器类似,其本质也是利用工作队列和定时器实现,


  1. //include/linux/workqueue.h 
  2.  struct work_struct {            
  3.          atomic_long_t data; 
  4.          struct list_head entry; 
  5.          work_func_t func; 
  6.  #ifdef CONFIG_LOCKDEP 
  7.          struct lockdep_map lockdep_map; 
  8.  #endif 
  9.  }; 
  10.  struct delayed_work {              114         struct work_struct work; 
  11.          struct timer_list timer; 
  12.  
  13.   /* target workqueue and CPU ->timer uses to queue ->work */ 
  14.          struct workqueue_struct *wq; 
  15.          int cpu; 
  16.  };  

--103-->需要延迟执行的函数, typedef void (work_func_t)(struct work_struct work);

至此,我们可以使用一个delayed_work对象以及相应的调度API实现对指定任务的延时执行


  1. //注册一个延迟执行 
  2.  
  3. 591 static inline bool schedule_delayed_work(struct delayed_work *dwork,unsigned long delay)//注销一个延迟执行 
  4. 2975 bool cancel_delayed_work(struct delayed_work *dwork)     

和内核定时器一样,延迟执行只会在超时的时候执行一次,如果要实现循环延迟,只需要在注册的函数中再次注册一个延迟执行函数。


  1. schedule_delayed_work(&work,msecs_to_jiffies(poll_interval)); 

本文作者:佚名

来源:51CTO

时间: 2024-11-02 08:20:37

Linux驱动技术(七) _内核定时器与延迟工作的相关文章

Linux驱动技术(八) _并发控制技术

为了实现对临界资源的有效管理,应用层的程序有原子变量,条件变量,信号量来控制并发,同样的问题也存在与驱动开发中,比如一个驱动同时被多个应用层程序调用,此时驱动中的全局变量会同时属于多个应用层进程的进程空间,这种情况下也要使用一些技术来实现对并发的控制.本文将讨论内核中下述并发控制技术的技术特点和应用场景. 1.中断屏蔽 2.原子操作  a.原子变量操作  b.原子位操作 3.自旋锁  a.传统自旋锁  b.读写自旋锁  c.顺序锁  d.RCU 4.信号量  a.传统信号量  b.读写信号量  

Linux驱动技术(五) _设备阻塞/非阻塞读写

等待队列是内核中实现进程调度的一个十分重要的数据结构,其任务是维护一个链表,链表中每一个节点都是一个PCB(进程控制块),内核会将PCB挂在等待队列中的所有进程都调度为睡眠状态,直到某个唤醒的条件发生.应用层的阻塞IO与非阻塞IO的使用我已经在Linux I/O多路复用一文中讨论过了,本文主要讨论驱动中怎么实现对设备IO的阻塞与非阻塞读写.显然,实现这种与阻塞相关的机制要用到等待队列机制.本文的内核源码使用的是3.14.0版本 设备阻塞IO的实现 当我们读写设备文件的IO时,最终会回调驱动中相应

Linux驱动技术(二) _访问I/O内存

ARM是对内存空间和IO空间统一编址的,所以,通过读写SFR来控制硬件也就变成了通过读写相应的SFR地址来控制硬件.这部分地址也被称为I/O内存.x86中对I/O地址和内存地址是分开编址的,这样的IO地址被称为I/O端口.本文只讨论IO内存的访问. IO内存访问流程 我们知道,为了管理最重要的系统资源并让物理地址对进程透明,Linux使用了内存映射机制,就是一个进程如果想访问一个物理内存地址(eg.SFR地址),那么首先就是将其映射成虚拟地址.   IO内存申请/归还 Linux提供一组函数用于

Linux驱动技术(三) _DMA编程

DMA即Direct Memory Access,是一种允许外设直接存取内存数据而没有CPU参与的技术,当外设对于该块内存的读写完成之后,DMAC通过中断通知CPU,这种技术多用于对数据量和数据传输速度都有很高要求的外设控制,比如显示设备等. DMA和Cache一致性 我们知道,为了提高系统运行效率,现代的CPU都采用多级缓存结构,其中就包括使用多级Cache技术来缓存内存中的数据来缓解CPU和内存速度差异问题.在这种前提下,显而易见,如果DMA内存的数据已经被Cache缓存了,而外设又修改了其

linux驱动开发--内核定时器

1.内核定时器 时钟中断:由系统的定时硬件以周期性的时间间隔发生,这个间隔(也就是频率)由内核根据常数HZ来确定. HZ常数:她是一个与体系结构无关的常数,可以配置50-1200之间,可以在内核中配置 tick:她是HZ的倒数,也就是每发生一次硬件定时器中断的事件间隔.如HZ为200,tick为5毫秒. jiffies核心变数:它是linux核心变数(32位变数,unsigned long),它被用记录自开机以来,已经过多少个tick.每发生一次硬件定时器中断,jiffies变数会被加1. 定时

Linux驱动开发必看详解神秘内核(完全转载)

Linux驱动开发必看详解神秘内核 完全转载-链接:http://blog.chinaunix.net/uid-21356596-id-1827434.html   IT168 技术文档]在开始步入Linux设备驱动程序的神秘世界之前,让我们从驱动程序开发人员的角度看几个内核构成要素,熟悉一些基本的内核概念.我们将学习内核定时器.同步机制以及内存分配方法.不过,我们还是得从头开始这次探索之旅.因此,本章要先浏览一下内核发出的启动信息,然后再逐个讲解一些有意思的点. 2.1 启动过程 图2-1显示

《Linux设备驱动开发详解 A》一一3.2 Linux 2.6后的内核特点

3.2 Linux 2.6后的内核特点 Linux 2.6内核是Linux开发者群落一个寄予厚望的版本,从2003年12月直至2011年7月,内核重新进行了版本的编号,从而过渡到Linux 3.x版本直到成书时的Linux 4.0-rc1. Linux 2.6相对于Linux 2.4有相当大的改进,主要体现在如下几个方面. 1.?新的调度器 Linux 2.6以后版本的Linux内核使用了新的进程调度算法,它在高负载的情况下有极其出色的性能,并且当有很多处理器时也可以很好地扩展.在Linux内核

Linux Platform驱动模型(二) _驱动方法【转】

转自:http://www.cnblogs.com/xiaojiang1025/archive/2017/02/06/6367910.html 在Linux设备树语法详解和Linux Platform驱动模型(一) _设备信息中我们讨论了设备信息的写法,本文主要讨论平台总线中另外一部分-驱动方法,将试图回答下面几个问题: 如何填充platform_driver对象? 如何将驱动方法对象注册到平台总线中? 正文前的一点罗嗦 写驱动也有一段时间了,可以发现,其实驱动本质上只做了两件事:向上提供接口,

5-3 Linux内核计时、延时函数与内核定时器【转】

转自:http://www.xuebuyuan.com/510594.html 5-3 Linux内核计时.延时函数与内核定时器  计时 1. 内核时钟 1.1   内核通过定时器(timer)中断来跟踪时间流 1.2   硬件定时器以周期性的间隔产生时间中断,这个间隔(即频率)由内核根据HZ来确定,HZ是一个与体系结构无关的常数. 1.3   这个时间间隔通常取1ms到10ms. 2. jiffies计算器 2.1每次当定时器中断发生时,内核内部通过一个64位的变量jiffies_64做加一计