Linux定时器的使用.doc

上传人:hw****26 文档编号:3842656 上传时间:2019-08-07 格式:DOC 页数:13 大小:72.50KB
下载 相关 举报
Linux定时器的使用.doc_第1页
第1页 / 共13页
Linux定时器的使用.doc_第2页
第2页 / 共13页
Linux定时器的使用.doc_第3页
第3页 / 共13页
Linux定时器的使用.doc_第4页
第4页 / 共13页
Linux定时器的使用.doc_第5页
第5页 / 共13页
点击查看更多>>
资源描述

1、Linux 定时器的使用内核定时器是内核用来控制在未来某个时间点(基于 jiffies)调度执行某个函数的一种机制,其实现位于 和 kernel/timer.c 文件中。被调度的函数肯定是异步执行的,它类似于一种“软件中断” ,而且是处于非进程的上下文中,所以调度函数必须遵守以下规则:1) 没有 current 指针、不允许访问用户空间。因为没有进程上下文,相关代码和被中断的进程没有任何联系。2) 不能执行休眠(或可能引起休眠的函数)和调度。3) 任何被访问的数据结构都应该针对并发访问进行保护,以防止竞争条件。内核定时器的调度函数运行过一次后就不会再被运行了(相当于自动注销) ,但可以通过在被

2、调度的函数中重新调度自己来周期运行。在 SMP 系统中,调度函数总是在注册它的同一 CPU 上运行,以尽可能获得缓存的局域性。内核定时器的数据结构struct timer_list struct list_head entry;unsigned long expires;void (*function)(unsigned long);unsigned long data;struct tvec_base *base;/* . */;其中 expires 字段表示期望定时器执行的 jiffies 值,到达该 jiffies 值时,将调用 function 函数,并传递 data 作为参数。当一个

3、定时器被注册到内核之后,entry 字段用来连接该定时器到一个内核链表中。base 字段是内核内部实现所用的。需要注意的是 expires 的值是 32 位的,因为内核定时器并不适用于长的未来时间点。初始化在使用 struct timer_list 之前,需要初始化该数据结构,确保所有的字段都被正确地设置。初始化有两种方法。方法一:DEFINE_TIMER(timer_name, function_name, expires_value, data);该宏会定义一个名叫 timer_name 内核定时器,并初始化其 function, expires, name 和 base 字段。方法二:s

4、truct timer_list mytimer;setup_timer(mytimer.expires = jiffies + 5*HZ;注意,无论用哪种方法初始化,其本质都只是给字段赋值,所以只要在运行 add_timer() 之前,expires, function 和 data 字段都可以直接再修改。关于上面这些宏和函数的定义,参见 include/linux/timer.h。注册定时器要生效,还必须被连接到内核专门的链表中,这可以通过 add_timer(struct timer_list *timer) 来实现。重新注册要修改一个定时器的调度时间,可以通过调用 mod_timer(

5、struct timer_list *timer, unsigned long expires)。mod_timer() 会重新注册定时器到内核,而不管定时器函数是否被运行过。注销注销一个定时器,可以通过 del_timer(struct timer_list *timer) 或 del_timer_sync(struct timer_list *timer)。其中 del_timer_sync 是用在 SMP 系统上的(在非 SMP 系统上,它等于 del_timer) ,当要被注销的定时器函数正在另一个 cpu 上运行时,del_timer_sync() 会等待其运行完,所以这个函数会休眠

6、。另外还应避免它和被调度的函数争用同一个锁。对于一个已经被运行过且没有重新注册自己的定时器而言,注销函数其实也没什么事可做。int timer_pending(const struct timer_list *timer)这个函数用来判断一个定时器是否被添加到了内核链表中以等待被调度运行。注意,当一个定时器函数即将要被运行前,内核会把相应的定时器从内核链表中删除(相当于注销)一个简单的例子#include #include #include struct timer_list mytimer;static void myfunc(unsigned long data)printk(“%sn“,

7、 (char *)data);mod_timer(static int _init mytimer_init(void)setup_timer(mytimer.expires = jiffies + HZ;add_timer(return 0;static void _exit mytimer_exit(void)del_timer(module_init(mytimer_init);module_exit(mytimer_exit); *-761 Linux 内核对定时器的描述 Linux 内核 2.4 版中去掉了老版本内核中的静态定时器机制,而只留下动态定时器。相应地在 timer_bh(

8、)函数中也不再通过 run_old_timers()函数来运行老式的静态定时器。动态定时器与静态定时器这二个概念是相对于 Linux 内核定时器机制的可扩展功能而言的,动态定时器是指内核的定时器队列是可以动态变化的,然而就定时器本身而言,二者并无本质的区别。考虑到静态定时器机制的能力有限,因此 Linux 内核 2.4 版中完全去掉了以前的静态定时器机制。 timer_create(2): 创建了一个定时器。timer_settime(2): 启动或者停止一个定时器。timer_gettime(2): 返回到下一次到期的剩余时间值和定时器定义的时间间隔。出现该接口的原因是,如果用户定义了一个

9、1ms 的定时器,可能当时系统负荷很重,导致该定时器实际山 10ms 后才超时,这种情况下,overrun=9ms 。timer_getoverrun(2): 返回上次定时器到期时超限值。timer_delete(2): 停止并删除一个定时器。上面最重要的接口是 timer_create(2),其中,clockid 表明了要使用的时钟类型,在 POSIX 中要求必须实现 CLOCK_REALTIME 类型的时钟。 evp 参数指明了在定时到期后,调用者被通知的方式。该结构体定义如下 :Linux 在 include/linux/timer.h 头文件中定义了数据结构 timer_list 来描

10、述一个内核定时器: struct timer_list struct list_head list; unsigned long expires; unsigned long data; void (*function)(unsigned long); ;各数据成员的含义如下: (1)双向链表元素 list:用来将多个定时器连接成一条双向循环队列。 (2)expires:指定定时器到期的时间,这个时间被表示成自系统启动以来的时钟滴答计数(也即时钟节拍数) 。当一个定时器的 expires 值小于或等于 jiffies 变量时,我们就说这个定时器已经超时或到期了。在初始化一个定时器后,通常把它的

11、 expires 域设置成当前expires 变量的当前值加上某个时间间隔值(以时钟滴答次数计) 。 (3)函数指针 function:指向一个可执行函数。当定时器到期时,内核就执行 function 所指定的函数。而 data 域则被内核用作 function 函数的调用参数。 内核函数 init_timer()用来初始化一个定时器。实际上,这个初始化函数仅仅将结构中的 list成员初始化为空。如下所示(include/linux/timer.h): static inline void init_timer(struct timer_list * timer) timer-list.nex

12、t = timer-list.prev = NULL; 由于定时器通常被连接在一个双向循环队列中等待执行(此时我们说定时器处于 pending状态) 。因此函数 time_pending()就可以用 list 成员是否为空来判断一个定时器是否处于pending 状态。如下所示 (include/linux/timer.h): static inline int timer_pending (const struct timer_list * timer) return timer-list.next != NULL; 时间比较操作 在定时器应用中经常需要比较两个时间值,以确定 timer 是否

13、超时,所以 Linux 内核在timer.h 头文件中定义了 4 个时间关系比较操作宏。这里我们说时刻 a 在时刻 b 之后,就意味着时间值 ab。Linux 强烈推荐用户使用它所定义的下列 4 个时间比较操作宏(include/linux/timer.h): #define time_after(a,b) (long)(b) - (long)(a) = 0) #define time_before_eq(a,b) time_after_eq(b,a)762 动态内核定时器机制的原理 Linux 是怎样为其内核定时器机制提供动态扩展能力的呢?其关键就在于“定时器向量”的概念。所谓“定时器向量”

14、 就是指这样一条双向循环定时器队列(对列中的每一个元素都是一个 timer_list 结构):对列中的所有定时器都在同一个时刻到期,也即对列中的每一个timer_list 结构都具有相同的 expires 值。显然,可以用一个 timer_list 结构类型的指针来表示一个定时器向量。 显然,定时器 expires 成员的值与 jiffies 变量的差值决定了一个定时器将在多长时间后到期。在 32 位系统中,这个时间差值的最大值应该是 0xffffffff。因此如果是基于“定时器向量”基本定义,内核将至少要维护 0xffffffff 个 timer_list 结构类型的指针,这显然是不现实的。

15、 另一方面,从内核本身这个角度看,它所关心的定时器显然不是那些已经过期而被执行过的定时器(这些定时器完全可以被丢弃) ,也不是那些要经过很长时间才会到期的定时器,而是那些当前已经到期或者马上就要到期的定时器(注意!时间间隔是以滴答次数为计数单位的) 。 基于上述考虑,并假定一个定时器要经过 interval 个时钟滴答后才到期(intervalexpiresjiffies) ,则 Linux 采用了下列思想来实现其动态内核定时器机制:对于那些 0interval255 的定时器,Linux 严格按照定时器向量的基本语义来组织这些定时器,也即 Linux 内核最关心那些在接下来的 255 个时钟

16、节拍内就要到期的定时器,因此将它们按照各自不同的 expires 值组织成 256 个定时器向量。而对于那些 256interval0xffffffff 的定时器,由于他们离到期还有一段时间,因此内核并不关心他们,而是将它们以一种扩展的定时器向量语义(或称为“松散的定时器向量语义”)进行组织。所谓“松散的定时器向量语义”就是指:各定时器的 expires 值可以互不相同的一个定时器队列。 具体的组织方案可以分为两大部分: (1)对于内核最关心的、interval 值在0,255之间的前 256 个定时器向量,内核是这样组织它们的:这 256 个定时器向量被组织在一起组成一个定时器向量数组,并作

17、为数据结构 timer_vec_root 的一部分,该数据结构定义在 kernel/timer.c 文件中,如下述代码段所示: /* * Event timer code */ #define TVN_BITS 6 #define TVR_BITS 8 #define TVN_SIZE (1 8)具有相同值的定时器都将被组织在同一个松散定时器向量中。因此,为组织所有满足条件 0x100interval0x3fff 的定时器,就需要 2664 个松散定时器向量。同样地,为方便起见,这 64 个松散定时器向量也放在一起形成数组,并作为数据结构timer_vec 的一部分。基于数据结构 timer_

18、vec,Linux 定义了全局变量 tv2,来表示这 64 条松散定时器向量。如上述代码段所示。 对于那些满足条件 0x4000interval0xfffff 的定时器,只要表达式(interval8 6)的值相同的定时器都将被放在同一个松散定时器向量中。同样,要组织所有满足条件0x4000interval0xfffff 的定时器,也需要 2664 个松散定时器向量。类似地,这 64 个松散定时器向量也可以用一个 timer_vec 结构来描述,相应地 Linux 定义了 tv3 全局变量来表示这 64 个松散定时器向量。 对于那些满足条件 0x100000interval0x3ffffff

19、的定时器,只要表达式(interval866)的值相同的定时器都将被放在同一个松散定时器向量中。同样,要组织所有满足条件 0x100000interval0x3ffffff 的定时器,也需要 2664 个松散定时器向量。类似地,这 64 个松散定时器向量也可以用一个 timer_vec 结构来描述,相应地 Linux定义了 tv4 全局变量来表示这 64 个松散定时器向量。 对于那些满足条件 0x4000000interval0xffffffff 的定时器,只要表达式(interval8666)的值相同的定时器都将被放在同一个松散定时器向量中。同样,要组织所有满足条件 0x4000000int

20、erval0xffffffff 的定时器,也需要 2664 个松散定时器向量。类似地,这 64 个松散定时器向量也可以用一个 timer_vec 结构来描述,相应地Linux 定义了 tv5 全局变量来表示这 64 个松散定时器向量。 最后,为了引用方便,Linux 定义了一个指针数组 tvecs ,来分别指向 tv1、tv2 、tv5结构变量。如上述代码所示。 763 内核动态定时器机制的实现 在内核动态定时器机制的实现中,有三个操作时非常重要的:(1)将一个定时器插入到它应该所处的定时器向量中。 (2)定时器的迁移,也即将一个定时器从它原来所处的定时器向量迁移到另一个定时器向量中。 (3)

21、扫描并执行当前已经到期的定时器。 7631 动态定时器机制的初始化 函数 init_timervecs()实现对动态定时器机制的初始化。该函数仅被 sched_init()初始化例程所调用。动态定时器机制初始化过程的主要任务就是将 tv1、tv2 、tv5 这 5 个结构变量中的定时器向量指针数组 vec初始化为 NULL。如下所示(kernel/timer.c): void init_timervecs (void) int i; for (i = 0; i expires;unsignedlongidx=expires-timer_jiffies;structlist_head*vec;i

22、f(idxTVR_BITS)vec=tv2.vec i;elseif(idx(TVR_BITS TVN_BITS)vec=tv3.vec i;elseif(idx(TVR_BITS 2*TVN_BITS)vec=tv4.vec i;elseif(signedlong)idx(TVR_BITS 3*TVN_BITS)vec=tv5.vec i;else/*Canonlygethereonarchitectureswith64-bitjiffies*/INIT_LIST_HEAD(return;/*TimersareFIFO!*/list_add( 对该函数的注释如下:(1)首先,计算定时器的 e

23、xpires 值与 timer_jiffies 的插值(注意!这里应该使用动态定时器自己的时间基准) ,这个差值就表示这个定时器相对于上一次运行定时器机制的那个时刻还需要多长时间间隔才到期。局部变量 idx 保存这个差值。 (2)根据 idx 的值确定这个定时器应被插入到哪一个定时器向量中。其具体的确定方法我们在7.6.2 节已经说过了,这里不再详述。最后,定时器向量的头部指针 vec 表示这个定时器应该所处的定时器向量链表头部。 (3)最后,调用 list_add()函数将定时器插入到 vec 指针所指向的定时器队列的尾部。7635 修改一个定时器的 expires 值当一个定时器已经被插入

24、到内核动态定时器链表中后,我们还可以修改该定时器的 expires 值。函数 mod_timer()实现这一点。如下所示(kernel/timer.c): intmod_timer(structtimer_list*timer,unsignedlongexpires)intret;unsignedlongflags;spin_lock该函数首先根据参数 expires 值更新定时器的 expires 成员。然后调用 detach_timer()函数将该定时器从它原来所属的链表中删除。最后调用 internal_add_timer()函数将该定时器根据它新的 expires 值重新插入到相应的链

25、表中。函数 detach_timer()首先调用 timer_pending()来判断指定的定时器是否已经处于某个链表中,如果定时器原来就不处于任何链表中,则detach_timer()函数什么也不做,直接返回 0 值,表示失败。否则,就调用 list_del()函数将定时器从它原来所处的链表中摘除。如下所示(kernel/timer.c): staticinlineintdetach_timer(structtimer_list*timer)if(!timer_pending(timer)return0;list_del(return1; 7636 删除一个定时器函数 del_timer()

26、用来将一个定时器从相应的内核定时器队列中删除。该函数实际上是对 detach_timer()函数的高层封装。如下所示(kernel/timer.c): intdel_timer(structtimer_list*timer)intret;unsignedlongflags;spin_lock_irqsave(ret=detach_timer(timer);timer-list.next=timer-list.prev=NULL;spin_unlock_irqrestore(returnret; 软件开发网7637 定时器迁移操作由于一个定时器的 interval 值会随着时间的不断流逝(即 j

27、iffies 值的不断增大)而不断变小,因此那些原本到期紧迫程度较低的定时器会随着 jiffies 值的不断增大而成为既将马上到期的定时器。比如定时器向量 tv2.vec0中的定时器在经过 256 个时钟滴答后会成为未来 256 个时钟滴答内会到期的定时器。因此,定时器在内核动态定时器链表中的位置也应相应地随着改变。改变的规则是:当 tv1.index 重新变为 0 时(意味着 tv1中的 256 个定时器向量都已被内核扫描一遍了,从而使 tv1 中的 256 个定时器向量变为空),则用 tv2.vecindex定时器向量中的定时器去填充 tv1,同时使 tv2.index 加 1(它以 64

28、 为模)。当 tv2.index 重新变为 0(意味着 tv2中的 64 个定时器向量都已经被全部填充到 tv1 中去了,从而使得 tv2 变为空),则用 tv3.vecindex定时器向量中的定时器去填充 tv2。如此一直类推下去,直到 tv5。函数 cascade_timers()完成这种定时器迁移操作,该函数只有一个timer_vec 结构类型指针的参数 tv。这个函数将把定时器向量 tv-vectv-index中的所有定时器重新填充到上一层定时器向量中去。如下所示(kernel/timer.c): staticinlinevoidcascade_timers(structtimer_v

29、ec*tv)/*cascadeallthetimersfromtvuponelevel*/structlist_head*head,*curr,*next;head=tv-vec tv-index;curr=head-next;/*Weareremoving_all_timersfromthelist,sowedonthaveto*detachthemindividually,justclearthelistafterwards.*/while(curr!=head)structtimer_list*tmp;tmp=list_entry(curr,structtimer_list,list);next=curr-next;list_del(curr);/notneededinternal_add_timer(tmp);curr=next;INIT_LIST_HEAD(head);tv-index=(tv-index 1)

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 实用文档资料库 > 策划方案

Copyright © 2018-2021 Wenke99.com All rights reserved

工信部备案号浙ICP备20026746号-2  

公安局备案号:浙公网安备33038302330469号

本站为C2C交文档易平台,即用户上传的文档直接卖给下载用户,本站只是网络服务中间平台,所有原创文档下载所得归上传人所有,若您发现上传作品侵犯了您的权利,请立刻联系网站客服并提供证据,平台将在3个工作日内予以改正。