深入剖析Linux内核定时器实现机制

转载 2013年03月22日 16:55:21

【摘要】本文详解了Linux内核的定时器实现机制。具体分析了定时器的分级组织结构,以及在此基础之上的插入、更新、扫描执行等过程。其动态刷新维护的机制值得借鉴。然后介绍了内核定时器相关的API

【关键字】内核定时器,分级结构,定时器迁移刷新,DEFINE_TIMERinit_timersetup_timeradd_timermod_timerdel_timer

 

1       内核定时器概述

Linux内核2.4版中去掉了老版本内核中的静态定时器机制,而只留下动态定时器。动态定时器与静态定时器这二个概念是相对于Linux内核定时器机制的可扩展功能而言的,动态定时器是指内核的定时器队列是可以动态变化的,然而就定时器本身而言,二者并无本质的区别。考虑到静态定时器机制的能力有限,因此Linux内核2.4版中完全去掉了以前的静态定时器机制。2.6内核为了支持SMPCPU热插拔,对定时器相关结构又做了改动。本文所有代码基于2.6.19内核http://lxr.linux.no/linux+v2.6.19

Linux

  11        struct list_head entry;

  12        unsigned long expires;

  13

  14        void (*function)(unsigned long);

  15        unsigned long data;

  16

  17        struct tvec_t_base_s *base;

  18}; 各数据成员的含义如下: 

²      双向链表元素entry:用来将多个定时器连接成一条双向循环队列。 

²      expires:指定定时器到期的时间,这个时间被表示成自系统启动以来的时钟滴答计数(也即时钟节拍数)。当一个定时器的expires值小于或等于jiffies变量时,我们就说这个定时器已经超时或到期了。在初始化一个定时器后,通常把它的expires域设置成当前expires变量的当前值加上某个时间间隔值(以时钟滴答次数计)。 

²      函数指针function:指向一个可执行函数。当定时器到期时,内核就执行function所指定的函数。

²      data域:被内核用作function函数的调用参数。 

²      base当前timer所属的base。由于考虑了SMP的情况,每个CPU都含有一个base

2       动态内核定时器的组织结构 

Linux是怎样为其内核定时器机制提供动态扩展能力的呢?其关键就在于定时器向量的概念。所谓定时器向量就是指这样一条双向循环定时器队列(队列中的每一个元素都是一个timer_list结构):对列中的所有定时器都在同一个时刻到期,也即对列中的每一个timer_list结构都具有相同的expires值。显然,可以用一个timer_list结构类型的指针来表示一个定时器向量。 

 

显然,定时器expires成员的值与jiffies变量的差值决定了一个定时器将在多长时间后到期。在32位系统中,这个时间差值的最大值应该是0xffffffff。因此如果是基于定时器向量基本定义,内核将至少要维护0xfffffffftimer_list结构类型的指针,这显然是不现实的。 

 

另一方面,从内核本身这个角度看,它所关心的定时器显然不是那些已经过期而被执行过的定时器(这些定时器完全可以被丢弃),也不是那些要经过很长时间才会到期的定时器,而是那些当前已经到期或者马上就要到期的定时器(注意!时间间隔是以滴答次数为计数单位的)。 

 

基于上述考虑,并假定一个定时器要经过interval个时钟滴答后才到期(intervalexpiresjiffies),则Linux采用了下列思想来实现其动态内核定时器机制:对于那些0≤interval≤255的定时器,Linux严格按照定时器向量的基本语义来组织这些定时器,也即Linux内核最关心那些在接下来的255个时钟节拍内就要到期的定时器,因此将它们按照各自不同的expires值组织成256个定时器向量。而对于那些256≤interval≤0xffffffff的定时器,由于他们离到期还有一段时间,因此内核并不关心他们,而是将它们以一种扩展的定时器向量语义(或称为松散的定时器向量语义)进行组织。所谓松散的定时器向量语义就是指:各定时器的expires值可以互不相同的一个定时器队列。 

各定时器向量数据结构定义在kernel/timer.c文件中,如下述代码段所示: 

/////////////////////////////////////////////// 2.4.19 内核  ///////////////////////////////////////////////

struct timer_vec {

        int index;

        struct list_head vec[TVN_SIZE];

};

 

struct timer_vec_root {

        int index;

        struct list_head vec[TVR_SIZE];

};

 

static struct timer_vec tv5;

static struct timer_vec tv4;

static struct timer_vec tv3;

static struct timer_vec tv2;

static struct timer_vec_root tv1;

 

static struct timer_vec * const tvecs[] = {

        (struct timer_vec *)&tv1, &tv2, &tv3, &tv4, &tv5

};

 

static struct list_head * run_timer_list_running;

static unsigned long timer_jiffies;

/* Initialize both explicitly - let's try to have them in the same cache line */

spinlock_t timerlist_lock = SPIN_LOCK_UNLOCKED;

volatile struct timer_list * volatile running_timer;

/////////////////////////////////////////////// 2.4.19 内核  ///////////////////////////////////////////////

 

/////////////////////////////////////////////// 2.6.19 内核  ///////////////////////////////////////////////   51#define TVN_BITS(CONFIG_BASE_SMALL ? 4 : 6)

  52#define TVR_BITS (CONFIG_BASE_SMALL ? 6 : 8)

  53#define TVN_SIZE (1 << TVN_BITS)

  54#define TVR_SIZE (1 << TVR_BITS)

  55#define TVN_MASK (TVN_SIZE - 1)

  56#define TVR_MASK (TVR_SIZE - 1)

  58typedef struct tvec_s {

  59        struct list_head vec[TVN_SIZE];

  60tvec_t;

  61

  62typedef struct tvec_root_s {

  63        struct list_head vec[TVR_SIZE];

  64tvec_root_t;

  65

  66struct tvec_t_base_s {

  67        spinlock_t lock;

  68        struct timer_list *running_timer;

  69        unsigned long timer_jiffies;

  70        tvec_root_t tv1;

  71        tvec_t tv2;

  72        tvec_t tv3;

  73        tvec_t tv4;

  74        tvec_t tv5;

  75____cacheline_aligned_in_smp;

  76

  77typedef struct tvec_t_base_s tvec_base_t;

  78

  79tvec_base_t boot_tvec_bases;

  80EXPORT_SYMBOL(boot_tvec_bases);

 

²      lock:由于内核动态定时器链表是一种系统全局共享资源,为了实现对它的互斥访问Linux定义了专门的自旋锁lock成员来保护。任何想要访问动态定时器链表的代码段都首先必须先持有该自旋锁,并且在访问结束后释放该自旋锁。

²      running_timer:用于SMP timer_jiffies定时器是在软中断中执行的,从触发到真正执行这段时间内可能会有几次时钟中断发生。因此内核必须记住上一次运行定时器机制是什么时候,也即内核必须保存上一次运行定时器机制时的jiffies

²      tv10255第一级定时器队列

²      tv2:。。。。。

/////////////////////////////////////////////// 2.6.19

 

 2.4内核的区别:

²      index域。利用timer_jiffies求余后即可自动获得每个tvi当前的index

²      将零散的tvi变量组织到了一起,将数组tvecs更改为了新的结构体变量;

²      lockrunning_timertimer_jiffies等变量封装在结构内部,体现了更好的面向对象的特性;

²      2.6内核支持CPU热插拔,此时定时器可以在各个CPU间转换,因此需要多组定时器结构变量。原有的单个变量形式无法满足需求。

3       定时器的组织原则

具体的组织方案可以分为两大部分:  1)对于内核最关心的、interval值在[0255]之间的前256个定时器向量,内核是这样组织它们的:这256个定时器向量被组织在一起组成一个定时器向量数组,并作为数据结构timer_vec_root的一部分。基于数据结构timer_vec_rootLinux定义了一个成员tv1,以表示内核所关心的前256个定时器向量。这样内核在处理是否有到期定时器时,它就只从定时器向量数组tv1.vec256]中的某个定时器向量内进行扫描。而利用timer_jiffiesTVR_SIZE求余后即可自动获得每个tv1当前处理的向量,也即tv1.vec[]数组的索引index,其初值为0,最大值为255(以256为模)。每个时钟节拍时timer_jiffies字段都会加1显然,index字段所指定的定时器向量tv1.vecindex]中包含了当前时钟节拍内已经到期的所有动态定时器。而定时器向量tv1.vecindexk]则包含了接下来第k个时钟节拍时刻将到期的所有动态定时器。当timer_jiffies求余后又重新变为0时,就意味着内核已经扫描了tv1变量中的所有256个定时器向量。在这种情况下就必须将那些以松散定时器向量语义来组织的定时器向量补充到tv1中来。  2)而对于内核不关心的、interval值在[0xff0xffffffff]之间的定时器,它们的到期紧迫程度也随其interval值的不同而不同。显然interval值越小,定时器紧迫程度也越高。因此在将它们以松散定时器向量进行组织时也应该区别对待。通常,定时器的interval值越小,它所处的定时器向量的松散度也就越低(也即向量中的各定时器的expires值相差越小);而interval值越大,它所处的定时器向量的松散度也就越大(也即向量中的各定时器的expires值相差越大)。 

 

内核规定,对于那些满足条件:0x100≤interval≤0x3fff的定时器,只要表达式(interval>>8)具有相同值的定时器都将被组织在同一个松散定时器向量中,即以18256为一个基本单位。因此,为组织所有满足条件0x100≤interval≤0x3fff的定时器,就需要2^664个松散定时器向量。同样地,为方便起见,这64个松散定时器向量也放在一起形成数组,并作为数据结构timer_vec的一部分。基于数据结构timer_vecLinux定义了成员tv2,来表示这64条松散定时器向量。如上述代码段所示。

 

对于那些满足条件0x4000≤interval≤0xfffff的定时器,只要表达式(interval>>86)的值相同的定时器都将被放在同一个松散定时器向量中。同样,要组织所有满足条件0x4000≤interval≤0xfffff的定时器,也需要2^664个松散定时器向量。类似地,这64个松散定时器向量也可以用一个timer_vec结构来描述,相应地Linux定义了成员tv3来表示这64个松散定时器向量。

 

对于那些满足条件0x100000≤interval≤0x3ffffff的定时器,只要表达式(interval>>866)的值相同的定时器都将被放在同一个松散定时器向量中。同样,要组织所有满足条件0x100000≤interval≤0x3ffffff的定时器,也需要2^664个松散定时器向量。类似地,这64个松散定时器向量也可以用一个timer_vec结构来描述,相应地Linux定义了tv4成员来表示这64个松散定时器向量。

 

对于那些满足条件0x4000000≤interval≤0xffffffff的定时器,只要表达式(interval>>8666的值相同的定时器都将被放在同一个松散定时器向量中。同样,要组织所有满足条件0x4000000≤interval≤0xffffffff的定时器,也需要2^664个松散定时器向量。类似地,这64个松散定时器向量也可以用一个timer_vec结构来描述,相应地Linux定义了tv5成员来表示这64个松散定时器向量。

 

最后,为了引用方便,Linux定义了一个整体的数据结构tvec_base_t,以此统一处理各个定时器向量。

 

4       动态定时器的内部实现机制 

在内核动态定时器机制的实现中,有三个操作时非常重要的:

²      将一个定时器插入到它应该所处的定时器向量中。

²      定时器的迁移,也即将一个定时器从它原来所处的定时器向量迁移到另一个定时器向量中。

²      扫描并执行当前已经到期的定时器。 

4.1     动态定时器机制的初始化 

函数init_timers_cpu ()实现对动态定时器机制的初始化。该函数被sched_init()初始化例程所调用。动态定时器机制初始化过程的主要任务就是将tv1tv2tv55个成员变量中的定时器向量指针数组vec[]初始化为NULL。对于SMPboot CPU使用静态定义的boot_tvec_bases,而其他CPU都是动态申请的。如下所示(kernel/timer.c): 

 

static int __devinit init_timers_cpu(int cpu)

1351{

1352        int j;

1353        tvec_base_t *base;

1354        static char __devinitdata tvec_base_done[NR_CPUS];

1355

1356        if (!tvec_base_done[cpu]) {

1357                static char boot_done;

1358

1359                if (boot_done) {

1360                        /*

1361                         * The APs use this path later in boot

1362                         */

1363                        base = kmalloc_node(sizeof(*base), GFP_KERNEL,

1364                                                cpu_to_node(cpu));

1365                        if (!base)

1366                                return -ENOMEM;

1367                        memset(base, 0, sizeof(*base));

1368                        per_cpu(tvec_basescpu) = base;

1369                } else {

1370                        /*

1371                         * This is for the boot CPU - we use compile-time

1372                         * static initialisation because per-cpu memory isn't

1373                         * ready yet and because the memory allocators are not

1374                         * initialised either.

1375                         */

1376                        boot_done = 1;

1377                        base = &boot_tvec_bases;

1378                }

1379                tvec_base_done[cpu] = 1;

1380        } else {

1381                base = per_cpu(tvec_basescpu);

1382        }

1383

1384        spin_lock_init(&base->lock);

1385        lockdep_set_class(&base->lockbase_lock_keys + cpu);

1386

1387        for (j = 0; j < TVN_SIZEj++) {

1388                INIT_LIST_HEAD(base->tv5.vec + j);

1389                INIT_LIST_HEAD(base->tv4.vec + j);

1390                INIT_LIST_HEAD(base->tv3.vec + j);

1391                INIT_LIST_HEAD(base->tv2.vec + j);

1392        }

1393        for (j = 0; j < TVR_SIZEj++)

1394                INIT_LIST_HEAD(base->tv1.vec + j);

1395

1396        base->timer_jiffies = jiffies;

1397        return 0;

1398}

4.2     将一个定时器插入到链表中

函数internal_add_timer()用于将一个不处于任何定时器向量中的定时器插入到它应该所处的定时器向量中去(根据定时器的expires值来决定)。如下所示(kernel/timer.c):

 

static void internal_add_timer(tvec_base_t *base, struct timer_list *timer)

  92{

  93        unsigned long expires = timer->expires;

  94        unsigned long idx = expires - base->timer_jiffies;

  95        struct list_head *vec;

  96

  97        if (idx < TVR_SIZE) { //第一梯队

  98                int i = expires & TVR_MASK;

  99                vec = base->tv1.vec + i;

 100        } else if (idx < 1 << (TVR_BITS + TVN_BITS)) {//第二梯队

 101                int i = (expires >> TVR_BITS) & TVN_MASK;

 102                vec = base->tv2.vec + i;

 103        } else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS)) { //第三梯队

 104                int i = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK;

 105                vec = base->tv3.vec + i;

 106        } else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS)) {

 107                int i = (expires >> (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK;

 108                vec = base->tv4.vec + i;

 109        } else if ((signed long) idx < 0) {

 110                /*

 111                 * Can happen if you add a timer with expires == jiffies,

 112                 * or you set a timer to go off in the pastthen return current timer list

 113                 */

 114                vec = base->tv1.vec + (base->timer_jiffies & TVR_MASK);

 115        } else {

 116                int i;

 117                /* If the timeout is larger than 0xffffffff on 64-bit

 118                 * architectures then we use the maximum timeout:

 119                 */

 120                if (idx > 0xffffffffUL) {

 121                        idx = 0xffffffffUL;

 122                        expires = idx + base->timer_jiffies;

 123                }

 124                i = (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK;

 125                vec = base->tv5.vec + i;

 126        }

 127        /*

 128         * Timers are FIFO:

 129         */

 130        list_add_tail(&timer->entryvec);

 131}

 

从最小值开始,根据TVR_BITS TVN_BITS的值依次求商(通过移位实现),来获得对应分组的list首地址,然后将定时器添加到对应list的尾部。详细流程如下:

²      首先,计算定时器的expires值与timer_jiffies的差值(注意!这里应该使用动态定时器自己的时间基准),这个差值就表示这个定时器相对于上一次运行定时器机制的那个时刻还需要多长时间间隔才到期。局部变量idx保存这个差值。

²      根据idx的值确定这个定时器应被插入到哪一个定时器分组中。而由expires确定的i值决定了对应定时器分组的哪个向量中。定时器向量的头部指针vec表示这个定时器应该所处的定时器向量链表头部,其指针域指向有效的定时器

²      最后,调用list_add()函数将定时器插入到vec指针所指向的定时器队列的尾部。

 

4.3     定时器迁移

由于一个定时器的interval值会随着时间的不断流逝(即jiffies值的不断增大)而不断变小,因此那些原本到期紧迫程度较低的定时器会随着jiffies值的不断增大而成为即将马上到期的定时器。比如定时器向量tv2.vec[0]中的定时器在经过256个时钟滴答后会成为未来256个时钟滴答内会到期的定时器。因此,定时器在内核动态定时器链表中的位置也应相应地随着改变。改变的规则是:

²      timer_jiffiesTVR_SIZE重新变为0时,则意味着tv1中的256个定时器向量都已被内核扫描一遍了,从而使tv1中的256个定时器向量变为空(此处不考虑中途添加的定时器),此时需要用tv2.vec(timer_jiffies >> TVR_BITS) & TVN_MASK]定时器向量中的定时器去填充tv1

²      随着timer_jiffies增加TVR_SIZE后,timer_jiffies >> TVR_BITS) & TVN_MASK自动加1,当timer_jiffies >> TVR_BITS) & TVN_MASK == TVN_MASK时,意味着tv2中的64个定时器向量都已经被全部填充到tv1中去了,从而使得tv2变为空,则用tv3.vec(timer_jiffies >> (TVR_BITS +TVN_BITS) & TVN_MASK]定时器向量中的定时器去填充tv2tv1

²      如此一直类推下去,直到tv5

 

函数cascade_timers()完成定时器从tv(i+1) tvitv1层的迁移操作,三个参数:

²      base,定时器所处的基队列;

²      tv,待迁移的tv(i+1)层;

²      indextv(i+1)层中对应待迁移的向量。

 

因此函数实现basetv(i+1)tv(i+1)[ index]向量迁往tvitv1层。如下所示(kernel/timer.c):

 

 383static int cascade(tvec_base_t *basetvec_t *tv, int index)

 384{

 385        /* cascade all the timers from tv up one level */

 386        struct timer_list *timer, *tmp;

 387        struct list_head tv_list;

 388

 389        list_replace_init(tv->vec + index, &tv_list);

 390

 391        /*

 392         * We are removing _all_ timers from the list, so we

 393         * don't have to detach them individually.

 394         */

 395        list_for_each_entry_safe(timertmp, &tv_listentry) {

 396                BUG_ON(timer->base != base);

 397                internal_add_timer(basetimer);

 398        }

 399

 400        return index;

 401}

 

相应流程如下:

²      首先,list_replace_init将原有的tv->vec + index向量的头赋给tv_list,然后将其头部初始化为NULL,即从原始队列中删除了所有的定时器。

²      然后,将tv_list所链接的所有定时器list_for_each_entry_safe宏获得,然后调用internal_add_timer()将其添加到队列中。由于定时器到点时刻jiffiesx未变,而timer_jiffies增大,二者差值缩小,故其在队列中的新位置发送变化。

 

4.4     扫描更新并执行当前已经到期的定时器

函数__run_timers完成这个功能。和2.4内核的时钟中断的Bottom Half不同,2.6内核采用了定时器软中断,该函数是被run_timer_softirq函数所调用的,其由TIMER_SOFTIRQ定时器软中断触发。

 

timer_jiffies表示了内核上一次执行run_timer_list()函数的时间,因此jiffiestimer_jiffies的差值就表示了自从上一次处理定时器以来,期间一共发生了多少次时钟中断,显然run_timer_list()函数必须为期间所发生的每一次时钟中断补上定时器服务。但通常每次时钟中断后的某一时刻就会执行run_timer_softirq。该函数的源码如下(kernel/timer.c):

 

 403#define INDEX(N) ((base->timer_jiffies >> (TVR_BITS + (N) * TVN_BITS)) &TVN_MASK当前待处理的定时器向量

 404

 405/**

 406 * __run_timers - run all expired timers (if any) on this CPU.

 407 * @base: the timer vector to be processed.

 408 *

 409 * This function cascades all vectors and executes all expired timer vectors.

 411 */

 412static inline void __run_timers(tvec_base_t *base)

 413{

 414        struct timer_list *timer;

 415

 416        spin_lock_irq(&base->lock);

 417        while (time_after_eq(jiffiesbase->timer_jiffies)) {

 418                struct list_head work_list;

 419                struct list_head *head = &work_list;

 420                int index = base->timer_jiffies & TVR_MASK;

 421

 422                /*

 423                 * Cascade timers:

 424                 */

 425                if (!index &&

 426                        (!cascade(base, &base->tv2INDEX(0))) &&

 427                                (!cascade(base, &base->tv3INDEX(1))) &&

 428                                        !cascade(base, &base->tv4INDEX(2)))

 429                        cascade(base, &base->tv5INDEX(3));

 430                ++base->timer_jiffies;

 

 431                list_replace_init(base->tv1.vec + index, &work_list);

 432                while (!list_empty(head)) {

 433                        void (*fn)(unsigned long);

 434                        unsigned long data;

 435

 436                        timer = list_entry(head->next,struct timer_list,entry);

 437                        fn = timer->function;

 438                        data = timer->data;

 439

 440                        set_running_timer(basetimer);

 441                        detach_timer(timer, 1);

 442                        spin_unlock_irq(&base->lock);

 443                        {

 444                                int preempt_count = preempt_count();

 445                                fn(data);

 446                                if (preempt_count != preempt_count()) {

 447                                        printk(KERN_WARNING "huh, entered %p "

 448                                               "with preempt_count %08x, exited"

 449                                               " with %08x?/n",

 450                                               fnpreempt_count,

 451                                               preempt_count());

 452                                        BUG();

 453                                }

 454                        }

 455                        spin_lock_irq(&base->lock);

 456                }

 457        }

 458        set_running_timer(baseNULL);

 459        spin_unlock_irq(&base->lock);

 460}

 

函数run_timer_list()的执行过程主要就是用一个大while{}循环来为时钟中断执行定时器服务,每一次循环服务一次时钟中断。因此一共要执行(jiffiestimer_jiffies1)次循环。循环体所执行的服务步骤如下:

²      刷新定时器队列。首先,判断index是否为0,如果为0则需要从tv2中补充定时器到tv1中来。若tv2已经处理完最后一列向量,即(base->timer_jiffies >> (TVR_BITS + (N) * TVN_BITS)) &TVN_MASK== 0则需要从tv3补充,依次类推。若tv2中有未处理完的,则将部分补充到tv1中,此时tv3tv4tv5都不需要更新。注意&&的执行关系,前面为假后后面就不再执行。

²      执行到期的timerlist_replace_init获取当前待处理的向量列。将头保存到head中,删除所有定时器。while (!list_empty(head))循环若有到期的timer则获得待执行的函数地址及其参数,调用detach_timerhead中删除当前timer。依次执行head链中所有的timer

 

5       内核定时器的API

5.1     初始化

5.1.1      静态初始化

  20extern struct tvec_t_base_s boot_tvec_bases;

  21

  22#define TIMER_INITIALIZER(_function_expires_data) { /

  23                .function = (_function),        /

  24                .expires = (_expires),          /

  25                .data = (_data),               /

  26                .base = &boot_tvec_bases,      /

  27        }

TIMER_INITIALIZER构造一个内核timer元素,当其他结构体中包含一个内核timer时,此宏可以直接内嵌在结构体中。Linux内核中常见的数据结构都采用了此方法,如自旋锁,等待队列等。

 

  29#define DEFINE_TIMER(_name_function_expires_data)  /

  30        struct timer_list _name =                       /

  31          TIMER_INITIALIZER(_function_expires_data)

静态定义一个内核timer变量,同时对各个元素进行初始化。好处在于用户无需知道定时器的实现细节,同时可以防止用户忘记初始化导致的问题。

5.1.2      动态初始化init_timer

  33void fastcall init_timer(struct timer_list * timer);

//////////////////////////////

#define fastcall __attribute__((regparm(3)))   //通过寄存器传递参数可以提高性能,内核中的大部分函数都有此修饰符

#define asmlinkage __attribute__((regparm(0))) 函数定义前加宏asmlinkage,表示这些函数通过堆栈而不是通过寄存器传递参数。

gcc编译器在汇编过程中调用c语言函数时传递参数有两种方法:一种是通过堆栈,另一种是通过寄存器。缺省时采用寄存器,假如你要在你的汇编过程中调用c语言函数,并且想通过堆栈传递参数,你定义的c函数时要在函数前加上宏asmlinkage

//////////////////////////////

 

内核函数init_timer()用来初始化一个定时器。实际上,这个初始化函数仅仅将结构中的list成员初始化为空并获得对应CPU上的base。如下所示(/kernel/timer.c):   140void fastcall init_timer(struct timer_list*timer)

 141{

 142        timer->entry.next = NULL;

 143        timer->base = __raw_get_cpu_var(tvec_bases);

 144}

 145EXPORT_SYMBOL(init_timer);

 

5.1.3      完全初始化setup_timer

  34

  35static inline void setup_timer(struct timer_list * timer,

  36                                void (*function)(unsigned long),

  37                                unsigned long data)

  38{

  39        timer->function = function;

  40        timer->data = data;

  41        init_timer(timer);

  42}

动态初始化一个内核定时器,经过setup_timer的调用后,内核timer的各个域都有初始值了;该函数也可以对已经初始化的定时器重新初始化。

 

2.4的内核代码中还没有setup_timer函数,通常动态初始化时先用init_timer,接着对function域及data域赋值。这样用户直接操作结构体成员变量,若内核timer结构改变,则可移植性就降低了。因此2.6内核通过DEFINE_TIMERsetup_timer对内核定时器的初始化进行了全面封装,用户无需知道内核的实现细节,只需要按照内核timerAPI编程即可,接口是固定的,内部实现细节的变化对用户程序影响最小。

5.2     内部实现细节

5.2.1      时间比较

在定时器应用中经常需要比较两个时间值,以确定timer是否超时,所以Linux内核在timer.h头文件中定义了4个时间关系比较操作宏。这里我们说时刻a在时刻b之后,就意味着时间值a≥bLinux强烈推荐用户使用它所定义的下列4个时间比较操作宏(include/linux/jiffies.h),还是一句话,封装可以改善移植性: 

 106#define time_after(a,b)         /

 107        (typecheck(unsigned long, a) && /

 108         typecheck(unsigned long, b) && /

 109         ((long)(b) - (long)(a) < 0))

 110#define time_before(a,b)        time_after(b,a)

 111

 112#define time_after_eq(a,b)      /

 113        (typecheck(unsigned long, a) && /

 114         typecheck(unsigned long, b) && /

 115         ((long)(a) - (long)(b) >= 0))

 116#define time_before_eq(a,b)     time_after_eq(b,a)

 

5.2.2      挂起判断

由于定时器通常被连接在一个双向循环队列中等待执行,此时我们说定时器处于pending状态。因此函数time_pending()就可以用entry成员是否为空来判断一个定时器是否处于pending状态。如下所示 include/linux/timer.h):     44/***

Callers must ensure serialization wrt. other operations done to this timer, eg. interrupt contexts, or other CPUs on SMP.

return value: 1 if the timer is pending, 0 if not.

  53 */

  54static inline int timer_pending(const struct timer_list * timer)

  55{

  56        return timer->entry.next != NULL;

  57}

 

5.2.3      锁定定时器base

 158/*

 159 * We are using hashed locking: holding per_cpu(tvec_bases).lock

 160 * means that all timers which are tied to this base via timer->base are

 161 * locked, and the base itself is locked too.

 166 * When the timer's base is locked, and the timer removed from list, it is

 167 * possible to set timer->base = NULL and drop the lock: the timer remains

 168 * locked.

 169 */

 170static tvec_base_t *lock_timer_base(struct timer_list *timer,

 171                                        unsigned long *flags)

 172        __acquires(timer->base->lock)

 173{

 174        tvec_base_t *base;

 175

 176        for (;;) {

 177                base = timer->base;

 178                if (likely(base != NULL)) {

 179                        spin_lock_irqsave(&base->lock, *flags);

 180                        if (likely(base == timer->base))

 181                                return base;

 182                        /* The timer has migrated to another CPU */

 183                        spin_unlock_irqrestore(&base->lock, *flags);

 184                }

 185                cpu_relax();

 186        }

 187}

5.2.4      内部删除

函数detach_timer()如下所示(kernel/timer.c):

 

 147static inline void detach_timer(struct timer_list *timer,

 148                                        int clear_pending)

 149{

 150        struct list_head *entry = &timer->entry;

 151

 152        __list_del(entry->preventry->next);

 153        if (clear_pending)

 154                entry->next = NULL;

 155        entry->prev = LIST_POISON2; //0的防范值,操作时将导致页表异常

 156}

 

函数detach_timer()用来将一个定时器从相应的内核定时器队列中删除。前提条件,该定时器处于定时器队列中。首先将起从链表中删除,根据clear_pending标志是否清除entry->next

5.2.5      内部修部timer

 189int __mod_timer(struct timer_list *timer, unsigned long expires)

 190{

 191        tvec_base_t *base, *new_base;

 192        unsigned long flags;

 193        int ret = 0;

 194

 195        BUG_ON(!timer->function);

////

#define BUG_ON(condition) do { if (condition) ; } while(0)

/////

 196

 197        base = lock_timer_base(timer, &flags); // 获得timer所在的根base,同时锁定

 198

 199        if (timer_pending(timer)) {

 200                detach_timer(timer, 0);

 201                ret = 1;

 202        }

 203

 204        new_base = __get_cpu_var(tvec_bases);

 205

 206        if (base != new_base) {

 207                /*

 208                 * We are trying to schedule the timer on the local CPU.

 213                 */

 214                if (likely(base->running_timer != timer)) {

 215                        /* See the comment in lock_timer_base() */

 216                        timer->base = NULL;

 217                        spin_unlock(&base->lock);

 218                        base = new_base;

 219                        spin_lock(&base->lock);

 220                        timer->base = base;

 221                }

 222        }

 223

 224        timer->expires = expires;

 225        internal_add_timer(basetimer);

 226        spin_unlock_irqrestore(&base->lockflags);

 227

 228        return ret;

 229}

 230

 231EXPORT_SYMBOL(__mod_timer);

 

该函数首先判断回调函数是否设置否则返回。然后获得timer所在的根base,判断是否已经是否已经添加,若已经添加则调用detach_timer()函数将该定时器从它原来所属的链表中删除。接着获得更新的timer所在的根base,最后调用internal_add_timer()函数将该定时器根据它新的expires值重新插入到相应的链表中。

 

5.3     添加定时器到内核中add_timer

函数add_timer()用来将参数timer指针所指向的定时器插入到一个合适的定时器链表中。它首先调用timer_pending()函数判断所指定的定时器是否已经位于在某个定时器向量中等待执行。如果是,则不进行任何操作,只是打印一条内核告警信息就返回了;如果不是,则调用__mod_timer函数完成实际的插入操作。其源码如下(kernel/timer.h):

 

static inline void add_timer(struct timer_list *timer)

{

        BUG_ON(timer_pending(timer));

        __mod_timer(timer, timer->expires);

}

 

对于add_timer来说,if (timer_pending(timer))判断无意义,但对于mod_timer来说此句有意义,为了最大限度的代码复用,基本上__mod_timer实现了所有timer的更改。add_timer只是mod_timer的一个特例而已,无需删除原来的链而已,但都需要插入到新的队列中。

5.4     修改定时器的expires值mod_timer

当一个定时器已经被插入到内核动态定时器链表中后,我们还可以修改该定时器的expires值。函数mod_timer()实现这一点。如下所示(kernel/timer.c):

 253/**

 258 * mod_timer is a more efficient way to update the expire field of an

 259 * active timer (if the timer is inactive it will be activated)

 260 *

 261 * mod_timer(timer, expires) is equivalent to:

 262 *

 263 *     del_timer(timer); timer->expires = expires; add_timer(timer);

 264 *

 265 * Note that if there are multiple unserialized concurrent users of the

 266 * same timer, then mod_timer() is the only safe way to modify the timeout,

 267 * since add_timer() cannot modify an already running timer.

 272 */

 273int mod_timer(struct timer_list *timer, unsigned long expires)

 274{

 275        BUG_ON(!timer->function);

 276

 277        /*

 278         * 如果定时器修改为自身,则直接返回,避免无谓的修改,很多地方需要用 279      到这个再浅显不过的道理

 281         */

 282        if (timer->expires == expires && timer_pending(timer))

 283                return 1;

 284

 285        return __mod_timer(timerexpires);

 286}

 287

 288EXPORT_SYMBOL(mod_timer);

 

实际上该函数有以下功能:

²      已经激活的timer,即timer_pending返回1,若修改值为自身,则直接返回;

²      已经激活的timer,真正的修改其值,则修改,函数本意;

²      未激活的timer,无论timer->expires多少,都添加到队列中,激活了timer,此时相当于add_timer

5.5     删除一个定时器del_timer

2.4内核下的意义有点差别。detach_timer是内核内部调用的函数,而del _timer是对外的API首先调用timer_pending()来判断指定的定时器是否已经处于某个链表中,如果定时器原来就不处于任何链表中,则del _timer()函数什么也不做,直接返回0值,表示失败。否则,就调用detach_timer函数将定时器从它原来所处的链表中摘除,返回1成功。如下所示(kernel/timer.c):

 

 290/**

 291 * del_timer - deactive a timer.

 292 * @timer: the timer to be deactivated

 293 *

 294 * del_timer() deactivates a timer - this works on both active and inactive

 295 * timers.

 300 */

 301int del_timer(struct timer_list *timer)

 302{

 303        tvec_base_t *base;

 304        unsigned long flags;

 305        int ret = 0;

 306

 307        if (timer_pending(timer)) {

 308                base = lock_timer_base(timer, &flags);

 309                if (timer_pending(timer)) {

 310                        detach_timer(timer, 1);

 311                        ret = 1;

 312                }

 313                spin_unlock_irqrestore(&base->lockflags);

 314        }

 315

 316        return ret;

 317}

 318

 319EXPORT_SYMBOL(del_timer);

 

上述函数中的宏TVN_SIZE是指timer_vec结构类型中的定时器向量指针数组vec[]的大小,值为64。宏TVR_SIZE是指timer_vec_root结构类型中的定时器向量数组vec[]的大小,值为256 内核  ///////////////////////////////////////////////include/linux/timer.h头文件中定义了数据结构timer_list来描述一个内核定时器。




本文转自:http://blog.csdn.net/sailor_8318/archive/2008/07/09/2627136.aspx


http://blog.csdn.net/sailor_8318/archive/2008/07/09/2627136.aspx

深入剖析Linux内核定时器实现机制

【摘要】本文详解了Linux内核的定时器实现机制。具体分析了定时器的分级组织结构,以及在此基础之上的插入、更新、扫描执行等过程。其动态刷新维护的机制值得借鉴。然后介绍了内核定时器相关的API。 【关...
  • tianmo2010
  • tianmo2010
  • 2013年03月22日 16:55
  • 5473

Linux内核时钟系统和定时器实现

1. Linux内核时钟系统和定时器实现Linux 2.6.16之前,内核只支持低精度时钟,内核定时器的工作方式: 系统启动后,会读取时钟源设备(RTC, HPET,PIT…),初始化当前系统时间; ...
  • anonymalias
  • anonymalias
  • 2016年07月25日 13:27
  • 4742

linux kernel 内核定时器

1.涉及函数// 1. 初始化定时器队列结构 init_timer(&buttons_timer);// 2. 定时器超时函数 buttons_timer.function = but...
  • u012719256
  • u012719256
  • 2016年09月26日 11:27
  • 2719

嵌入式 深入剖析Linux内核定时器实现机制

【摘要】本文详解了Linux内核的定时器实现机制。具体分析了定时器的分级组织结构,以及在此基础之上的插入、更新、扫描执行等过程。其动态刷新维护的机制值得借鉴。然后介绍了内核定时器相关的API。 【关...
  • skdkjxy
  • skdkjxy
  • 2014年04月25日 11:30
  • 858

深入剖析Linux内核定时器实现机制

深入剖析Linux内核定时器实现机制 Sailor_foreversailing_9806@163.com转载请注明 http://blog.csdn.net/sailor_8318/archiv...
  • im1020110542
  • im1020110542
  • 2013年04月22日 15:20
  • 684

Linux内核进程调度以及定时器实现机制

【摘要】本文简单介绍了任务的各种状态和PCB的结构,分析了几种任务调度策略,详解了schedule,并分析了如何进行进程上下文切换;随后分析了2.6内核如何优化了任务调度算法;最后介绍了内核定时器的实...
  • Tommy_wxie
  • Tommy_wxie
  • 2013年03月15日 14:21
  • 1443

论文《Linux内核中一种高精度定时器的设计与实现》

  • 2010年03月23日 00:26
  • 298KB
  • 下载

Linux内核模块的实现机制

  • 2011年07月16日 01:15
  • 295KB
  • 下载

《Linux内核设计与实现》读书笔记(十一)- 定时器和时间管理

系统中有很多与时间相关的程序(比如定期执行的任务,某一时间执行的任务,推迟一段时间执行的任务),因此,时间的管理对于linux来说非常重要。   主要内容: 系统时间定时器定时器相关...
  • dang_guoying
  • dang_guoying
  • 2017年01月11日 10:55
  • 160

Linux内核系统定时器TIMER实现过程分析

转自http://blog.csdn.net/yyplc/article/details/7065722 Linux系统定时器,在内核中扮演着重要角色。内核的许多重要实现如任务调度,工作队列等均...
  • hongkangwl
  • hongkangwl
  • 2013年09月29日 17:35
  • 1157
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深入剖析Linux内核定时器实现机制
举报原因:
原因补充:

(最多只允许输入30个字)