您的位置:首页 > 运维架构 > Linux

【嵌入式Linux学习七步曲之第五篇 Linux内核及驱动编程】深入剖析Linux内核定时器实现机制

2008-07-09 04:27 1016 查看
深入剖析Linux内核定时器实现机制
Sailor_forever sailing_9806@163.com 转载请注明 http://blog.csdn.net/sailor_8318/archive/2008/07/09/2627136.aspx
【摘要】本文详解了Linux内核的定时器实现机制。具体分析了定时器的分级组织结构,以及在此基础之上的插入、更新、扫描执行等过程。其动态刷新维护的机制值得借鉴。然后介绍了内核定时器相关的API。
【关键字】内核定时器,分级结构,定时器迁移刷新,DEFINE_TIMERinit_timersetup_timeradd_timermod_timerdel_timer

1 内核定时器概述

Linux内核2.4版中去掉了老版本内核中的静态定时器机制,而只留下动态定时器。动态定时器与静态定时器这二个概念是相对于Linux内核定时器机制的可扩展功能而言的,动态定时器是指内核的定时器队列是可以动态变化的,然而就定时器本身而言,二者并无本质的区别。考虑到静态定时器机制的能力有限,因此Linux内核2.4版中完全去掉了以前的静态定时器机制。2.6内核为了支持SMP及CPU热插拔,对定时器相关结构又做了改动。本文所有代码基于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。因此如果是基于“定时器向量”基本定义,内核将至少要维护0xffffffff个timer_list结构类型的指针,这显然是不现实的。

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

基于上述考虑,并假定一个定时器要经过interval个时钟滴答后才到期(interval=expires-jiffies),则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];
60} tvec_t;
61
62typedef struct tvec_root_s {
63 struct list_head vec[TVR_SIZE];
64} tvec_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值。
² tv1:0-255第一级定时器队列
² tv2:。。。。。

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

与2.4内核的区别:
² 无index域。利用timer_jiffies求余后即可自动获得每个tvi当前的index;
² 将零散的tvi变量组织到了一起,将数组tvecs更改为了新的结构体变量;
² 将lockrunning_timertimer_jiffies等变量封装在结构内部,体现了更好的面向对象的特性;
² 2.6内核支持CPU热插拔,此时定时器可以在各个CPU间转换,因此需要多组定时器结构变量。原有的单个变量形式无法满足需求。

3 定时器的组织原则

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

(2)而对于内核不关心的、interval值在[0xff,0xffffffff]之间的定时器,它们的到期紧迫程度也随其interval值的不同而不同。显然interval值越小,定时器紧迫程度也越高。因此在将它们以松散定时器向量进行组织时也应该区别对待。通常,定时器的interval值越小,它所处的定时器向量的松散度也就越低(也即向量中的各定时器的expires值相差越小);而interval值越大,它所处的定时器向量的松散度也就越大(也即向量中的各定时器的expires值相差越大)。

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

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

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

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

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

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

在内核动态定时器机制的实现中,有三个操作时非常重要的:
² 将一个定时器插入到它应该所处的定时器向量中。
² 定时器的迁移,也即将一个定时器从它原来所处的定时器向量迁移到另一个定时器向量中。
² 扫描并执行当前已经到期的定时器。

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

函数init_timers_cpu ()实现对动态定时器机制的初始化。该函数被sched_init()初始化例程所调用。动态定时器机制初始化过程的主要任务就是将tv1、tv2、…、tv5这5个成员变量中的定时器向量指针数组vec[]初始化为NULL。对于SMP,boot 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_bases, cpu) = 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_bases, cpu);
1382 }
1383
1384 spin_lock_init(&base->lock);
1385 lockdep_set_class(&base->lock, base_lock_keys + cpu);
1386
1387 for (j = 0; j < TVN_SIZE; j++) {
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_SIZE; j++)
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 past,then 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->entry, vec);
131}
从最小值开始,根据TVR_BITSTVN_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_jiffies%TVR_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]定时器向量中的定时器去填充tv2,tv1。
² 如此一直类推下去,直到tv5。

函数cascade_timers()完成定时器从tv(i+1) 到tvi-tv1层的迁移操作,三个参数:
² base,定时器所处的基队列;
² tv,待迁移的tv(i+1)层;
² index,tv(i+1)层中对应待迁移的向量。

因此函数实现base中tv(i+1)的tv(i+1)[ index]向量迁往tvi-tv1层。如下所示(kernel/timer.c):

383static int cascade(tvec_base_t *base, tvec_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(timer, tmp, &tv_list, entry) {
396 BUG_ON(timer->base != base);
397 internal_add_timer(base, timer);
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()函数的时间,因此jiffies与timer_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(jiffies, base->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->tv2, INDEX(0))) &&
427 (!cascade(base, &base->tv3, INDEX(1))) &&
428 !cascade(base, &base->tv4, INDEX(2)))
429 cascade(base, &base->tv5, INDEX(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(base, timer);
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 fn, preempt_count,
451 preempt_count());
452 BUG();
453 }
454 }
455 spin_lock_irq(&base->lock);
456 }
457 }
458 set_running_timer(base, NULL);
459 spin_unlock_irq(&base->lock);
460}
函数run_timer_list()的执行过程主要就是用一个大while{}循环来为时钟中断执行定时器服务,每一次循环服务一次时钟中断。因此一共要执行(jiffies-timer_jiffies+1)次循环。循环体所执行的服务步骤如下:
² 刷新定时器队列。首先,判断index是否为0,如果为0则需要从tv2中补充定时器到tv1中来。若tv2已经处理完最后一列向量,即(base->timer_jiffies >> (TVR_BITS + (N) * TVN_BITS)) & TVN_MASK) == 0,则需要从tv3补充,依次类推。若tv2中有未处理完的,则将部分补充到tv1中,此时tv3、tv4、tv5都不需要更新。注意&&的执行关系,前面为假后后面就不再执行。
² 执行到期的timer。list_replace_init获取当前待处理的向量列。将头保存到head中,删除所有定时器。while (!list_empty(head))循环若有到期的timer则获得待执行的函数地址及其参数,调用detach_timer从head中删除当前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对内核定时器的初始化进行了全面封装,用户无需知道内核的实现细节,只需要按照内核timer的API编程即可,接口是固定的,内部实现细节的变化对用户程序影响最小。

5.2 内部实现细节

5.2.1 时间比较

在定时器应用中经常需要比较两个时间值,以确定timer是否超时,所以Linux内核在timer.h头文件中定义了4个时间关系比较操作宏。这里我们说时刻a在时刻b之后,就意味着时间值a≥b。Linux强烈推荐用户使用它所定义的下列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->prev, entry->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(base, timer);
226 spin_unlock_irqrestore(&base->lock, flags);
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(timer, expires);
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->lock, flags);
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来描述一个内核定时器:
10struct timer_list {
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐