Chinaunix首页 | 论坛 | 博客
  • 博客访问: 237995
  • 博文数量: 51
  • 博客积分: 235
  • 博客等级: 入伍新兵
  • 技术积分: 25
  • 用 户 组: 普通用户
  • 注册时间: 2011-02-16 23:16
文章分类

全部博文(51)

文章存档

2016年(3)

2015年(35)

2014年(12)

2013年(1)

分类: LINUX

2015-08-28 16:50:54

Linux 内核定时器及使用方法

一.度量时间差

     时钟中断是由系统的定时硬件以周期性的时间间隔产生,这个间隔(即频率)由内核根据HZ来确定,HZ是一个与体系结构无关的常量(定义在),可配置(50-1200),X86平台,默认值为1000.HZ的含义是系统每秒钟产生的时钟中断的次数.

每当时钟中断发生时,全局变量jiffies(一个32位的unsigned long 变量,定义在jiffies.h>)就加1,因此jiffies记录了字linux系统启动后时钟中断发生的次数.驱动程序常利用jiffies来计算不同事件间的时间间隔.

内核提供了一组宏用来比较时间量:

  1. #include<linux/jiffies.h>
  2. int time_after(unsigned long a, unsigned long b);
  3. int time_before(unsigned long a, unsigned long b);
  4. int time_after_eq(unsigned long a, unsigned long b);
  5. int time_after_eq(unsigned long a, unsigned long b);

这几个宏可以理解为 a 宏名 b? 1:0.


获取当前时间:

  1.     #include <linux/time.h>
  2.     struct timeval {
  3.      time_t tv_sec; /* seconds */
  4.      suseconds_t tv_usec; /* microseconds */
  5.     };
  6.     void do_gettimeofday(struct timeval *tv)

二.内核定时器

     内核定时器用于控制某个函数(定时器处理函数)在未来的某个特定时间执行.内核定时器注册的处理函数只执行一次.处理过后即失效.

     当内核定时器被调度运行时,几乎可以肯定其不会在注册这些函数的进程正在执行时.相反,它会异步的执行.这种异步类似于硬件中断发生时的情景.实际上,内核定时器是被"软件中断"调度运行的.因此,其运行于原子上下文中.这点和tasklet很类似.处于原子上下文的进程有一些运行时的限制:

1. 不能访问用户空间.因为没有进程上下文.无法与特定的进程与用户关联

2. 不能执行调度或休眠.

3. Current指针在原子模式下无意义.

     内核定时器被组织成双向链表,使用struct timer_list结构描述.

  1. struct time_list{
  2. /*...*/
  3.    unsigned long expires; //超时的jiffies值
  4.    void(*function)(unsigned long) ; //注册的定时器处理函数
  5.    unsigned long data; //定时器处理函数的参数
  6. }

3个字段表示,当jiffies等于expires时,内核会调度function函数运行.data是传递给function的参数的指针,如果function函数需要不止一个参数,那么可以将这几个参数组成一个结构体,并将结构体的指针赋值给data.

三.管理定时器的接口 

  void init_timer(struct time_list *timer);

  初始化定时器队列结构.timer_list结构在使用前必须初始化,这是要保证结构体中其他的成员能正确赋值.

  void add_timer(struct time_list *timer);

  启动定时器.

  int del_timer(struct time_list *timer);

  在定时器超时前将定时器删除.当定时器超时后,系统会自动将其删除.

四.内核定时器的使用方法

  1. #include<linux/timer.h> // for timer_list API
  2.    #include<linux/param.h> // for HZ
  3.     #include<linux/jiffies.h> // for jiffies
  4.    
  5.    /*首先定义一个定时器结构并调用init_timer()将其初始化.*/
  6.    struct timer_list my_timer;
  7.    init_timer(&my_timer);
  8.    /*在init_timer()函数中,内核会初始化其entry成员和base成员,其他3个成员
  9.     需要在程序中显示初始化.
  10.    */
  11.    my_timer.expires = jiffies + delay_sec*HZ; //延时delay_sec秒
  12.    my_timer.data = **; //给定时器处理函数传入的参数
  13.    my_timer.function = my_function; //定时器超时时调用的函数
  14.    
  15.    /*最后,激活定时器*/
  16.    add_timer(&my_timer);
  17.    /*如果需要改变已经激活的定时器的超时时间,可以调用mod_timer()函数,mod_timer()函数也可以操作已经初始化,但没有激活的定时器.如果调用时定时器未被激活,则函数返回0,否则返回1*/
  18.    mod_timer(&my_timer);
  19.    /*如果需要在定时器超时之前停止定时器,可以使用del_timer()函数.*/
  20.    result = del_timer(&my_timer);
  21.    /*如果定时器还未激活,则result为0.否则为1*/

    

当删除定时器时,必须小心一个潜在的竞争条件。当del_timer()返回后,可以保证的只是: 定时器不会再被激活(也就是,将来不会执行),但是在多处理机器上定时器中断可能已经在其他处理器上运行了,所以删除定时器时需要等待可能在其他处理器上 运行的定时器处理程序都退出,这时就要使用del_timer_sync()函数执行删除工作:
    del_timer_sync(&my_timer);
    和del_timer()函数不同,del_timer_sync()函数不能在中断上下文中使用.(这点不是很理解)


五. 内核定时器的实现

    ...待完善

:内核中的中断上下文:

       内核的一个基本原则就是:在中断或者说原子上下文中,内核不能访问用户空间,而且内核是不能睡眠的。也就是说在这种情况下,内核是不能调用有可能引起睡眠的任何函数。一般来讲原子上下文指的是在中断或软中断中,以及在持有自旋锁的时候。内核提供了四个宏来判断是否处于这几种情况里:


  1. #define in_irq() (hardirq_count()) //在处理硬中断中
  2. #define in_softirq() (softirq_count()) //在处理软中断中
  3. #define in_interrupt() (irq_count()) //在处理硬中断或软中断中
  4. #define in_atomic() ((preempt_count() & ~PREEMPT_ACTIVE) != 0) //包含以上所有情况


这四个宏所访问的count都是thread_info->preempt_count。这个变量其实是一个位掩码。最低8位表示抢占计数,通常由spin_lock/spin_unlock修改,或程序员强制修改,同时表明内核容许的最大抢占深度是256
815位表示软中断计数,通常由local_bh_disable/local_bh_enable修改,同时表明内核容许的最大软中断深度是256
位1627是硬中断计数,通常由enter_irq/exit_irq修改,同时表明内核容许的最大硬中断深度是4096
第28位是PREEMPT_ACTIVE标志。用代码表示就是:

PREEMPT_MASK: 0x000000ff
SOFTIRQ_MASK: 0x0000ff00
HARDIRQ_MASK: 0x0fff0000

凡是上面4个宏返回1得到地方都是原子上下文,是不容许内核访问用户空间,不容许内核睡眠的,不容许调用任何可能引起睡眠的函数。而且代表thread_info->preempt_count不是0,这就告诉内核,在这里面抢占被禁用。

但是,对于in_atomic()来说,在启用抢占的情况下,它工作的很好,可以告诉内核目前是否持有自旋锁,是否禁用抢占等。但是,在没有启用抢占的情况下,spin_lock根本不修改preempt_count,所以即使内核调用了spin_lock,持有了自旋锁,in_atomic()仍然会返回0,错误的告诉内核目前在非原子上下文中。所以凡是依赖in_atomic()来判断是否在原子上下文的代码,在禁抢占的情况下都是有问题的。 

阅读(836) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~