现在的位置: 首页 > 综合 > 正文

Linux timer定时器

2013年08月17日 ⁄ 综合 ⁄ 共 4517字 ⁄ 字号 评论关闭

定时器,有时也称为动态定时器或内核定时器,是管理内核时间的基础。内核经常要推后执行某些代码,比如下半部机制就是为了将工作推后执行。我们需要一种工具,使工作能够在指定时间点上执行,正好在希望的时间点上,内核定时器正是这样一种工具。

定时器使用简单,只须执行一些初始化工作,设置一个超时时间,指定超时发生后执行的函数,然后激活定时器就可以了。

注意,定时器并不周期运行,它在超时后就自行销毁,这就是这种定时器被称为动态定时器的原因。动态定时器不断地创建和销毁,而且它的运行次数也不受限制

使用定时器

定时器由结构timer_list表示:在<Timer.h(include/linux)>中

struct timer_list {
     struct list_head entry; //定时器链表的入口
     unsigned long expires; //定时器超时时的节拍数
     void (*function)(unsigned long ); //定时器处理函数
     unsigned long data; //传给定时器处理函数的长整型参数
     struct tvec_t_base_s *base; //定时器内部值,用户不要使用
#ifdef CONFIG_TIMER_STATS
     void *start_site;
     char start_comm[16];
     int start_pid;
#endif
};
在<Timer.c(kernel)>中
struct tvec_t_base_s {
     spinlock_t lock;
     struct timer_list *running_timer;
     unsigned long timer_jiffies;
     tvec_root_t tv1;
     tvec_t tv2;
     tvec_t tv3;
     tvec_t tv4;
     tvec_t tv5;
} ____cacheline_aligned_in_smp;

 

内核提供了一组与定时器相关的接口用来简化管理定时器的操作。所有这些接口都声明在<linux/Timer.h>中,大多数接口在<kernel/timer.c>中的到实现。

创建定时器首先要先定义它,然后通过一个辅助函数初始化定时器数据结构的内部值,初始化必须在使用其他定时器管理函数之前完成:

 

struct timer_list my_timer;
init_timer(&my_timer);// 初始化定时器
my_timer.expires = jiffies + delay; //
my_timer.data = 0; //
my_timer.function = my_function; //
add_timer(&my_timer); //最后,激活定时器

【补充】

也可以这样调用:

1、setup_timer(struct timer_list, function,data); //初始化timer并赋值func和data

2、mod_timer();修改并启动之。另外,定时值可以这样设定:msecs_to_jiffies(50);

调用方式:

mod_timer(&mytimer, jiffies + msecs_to_jiffies(50) );

 

处理函数的原型:

 

void my_timer_function(unsigned long data);

 

data参数可以使你利用同一个处理函数注册多个定时器,只须通过该参数就能区别对待他们。如果你不需要这个参数,可以简单传递0或任何其他值给处理函数。

当前节拍计数等于或大于指定的超时时,内核就开始执行定时器处理函数。因为内核有可能延误定时器的执行,所以不能用定时器来实现任何硬实时任务。

有时可能需要更改已经激活的定时器超时时间,内核通过mod_timer()来实现该功能。当定时器已经初始化,但未激活,mod_timer()就激活它。一旦从该函数返回,定时器都被激活而且设置了新的定时值

int mod_timer( struct timer_list *timer, unsigned long expires)
{
     BUG_ON(!timer->function);
     timer_stats_timer_set_start_info(timer);
     /*
      * This is a common optimization triggered by the
      * networking code - if the timer is re-modified
      * to be the same thing then just return:
      */
     if (timer->expires == expires && timer_pending(timer))
         return 1;
     return __mod_timer(timer, expires);
}

如果要在定时器超时之前停止定时器,使用del_timer()函数:

int del_timer( struct timer_list *timer)
{
     tvec_base_t *base;
     unsigned long flags;
     int ret = 0;
     timer_stats_timer_clear_start_info(timer);
     if (timer_pending(timer)) {
         base = lock_timer_base(timer, &flags);
         if (timer_pending(timer)) {
             detach_timer(timer, 1);
             ret = 1;
         }
         spin_unlock_irqrestore(&base->lock, flags);
     }
     return ret;
}

被激活或未被激活的定时器都可以使用该函数。注意,不要为已经超时的定时器调用该函数,因为它们会自动被删除。

删除定时器时要小心,可能存在潜在的竞争条件。多处理器机器上定时器中断可能已经在其他处理器上运行了,所以删除定时器时需要等待可能在其他处理器上运行的定时器处理程序都退出,这时要使用del_timer_sync()函数执行删除工作:

int del_timer_sync( struct timer_list *timer)
{
     for (;;) {
         int ret = try_to_del_timer_sync(timer);
         if (ret >= 0)
             return ret;
         cpu_relax();
     }
}

和del_timer()函数不同,del_timer_sync()函数不能在中断上下文中使用。

定时器竞争条件

定时器与当前执行代码是异步的,因此有可能存在潜在的竞争条件。

首先,绝不能用下列代码来代替mod_timer()函数:

del_timer(my_timer);
my_timer->expires = jiffies + new_delay;
add_timer(my_timer);

其次,使用del_timer_sync()函数取代del_timer()函数。

最后,因为内核异步执行中断处理程序,所以应该重点保护定时器中断处理程序中的共享数据。

实现定时器

内核在时钟中断发生后执行定时器,定时器作为软中断在下半部上下文中执行。

具体来说,时钟中断处理程序会执行update_process_timers()函数,该函数随即调用run_local_timers()函数:

 

run_timer_softirq()函数处理软中断TIMER_SOFTIRQ,从而在当前处理器上运行所有的超时定时器:

 

所有定时器都以链表形式存放在一起,为了提高搜索效率,内核将定时器按它们的超时时间划分为五组。当定时器超时时间接近时,定时器将随组一起下移。采用分组定时器的方法可以在执行软中断的多数情况下,确保内核尽可能减少搜索超时定时器所带来的负担。

jiffies:

全局变量jiffies用来记录自系统启动以来产生的节拍的总数。启动时,内核将该变量初始化为0,此后,每次时钟中断处理程序都会增加该变量的值。一秒内时钟中断的次数等于Hz,所以jiffies一秒内增加的值也就是Hz。

系统运行时间以秒为单位,等于jiffies/Hz。

注意,jiffies类型为无符号长整型(unsigned long),其他任何类型存放它都不正确。

将以秒为单位的时间转化为jiffies:

seconds * Hz

将jiffies转化为以秒为单位的时间:

jiffies / Hz

相比之下,内核中将秒转换为jiffies用的多些。

* jiffies的内部表示

jiffies定义于文件<linux\Jiffies.h>中:

1. /*
2. * The 64-bit value is not atomic - you MUST NOT read it
3. * without sampling the sequence number in xtime_lock.
4. * get_jiffies_64() will do this for you as appropriate.
5. */
6. extern u64 __jiffy_data jiffies_64;
7. extern unsigned long volatile __jiffy_data jiffies;

ld(1)脚本用于连接主内核映像(在x86上位于arch/i386/kernel/vmlinux.lds.S中),然后用jiffies_64变量的初值覆盖jiffies变量。因此jiffies取整个jiffies_64变量的低32位。

访问jiffies的代码只会读取jiffies_64的低32位,通过get_jiffies_64()函数就可以读取整个64位的值。在64位体系结构上,jiffies_64和jiffies指的是同一个变量。

1. #if (BITS_PER_LONG < 64)
2. u64 get_jiffies_64(void);
3. #else
4. static inline u64 get_jiffies_64(void)
5. {
6. return (u64)jiffies;
7. }
8. #endif

1. 在<Time.c(kernel)>中
2. #if (BITS_PER_LONG < 64)
3. u64 get_jiffies_64(void)
4. {
5. unsigned long seq;
6. u64 ret;
7.
8. do {
9. seq = read_seqbegin(&xtime_lock);
10. ret = jiffies_64;
11. } while (read_seqretry(&xtime_lock, seq));
12. return ret;
13. }

* jiffies的回绕wrap around

当jiffies的值超过它的最大存放范围后就会发生溢出。对于32位无符号长整型,最大取值为(2^32)-1,即429496795。如果节拍计数达到了最大值后还要继续增加,它的值就会回绕到0

抱歉!评论已关闭.