ImageVerifierCode 换一换
格式:DOCX , 页数:16 ,大小:28.38KB ,
资源ID:8688347      下载积分:3 金币
快捷下载
登录下载
邮箱/手机:
温馨提示:
快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。 如填写123,账号就是123,密码也是123。
特别说明:
请自助下载,系统不会自动发送文件的哦; 如果您已付费,想二次下载,请登录后访问:我的下载记录
支付方式: 支付宝    微信支付   
验证码:   换一换

加入VIP,免费下载
 

温馨提示:由于个人手机设置不同,如果发现不能下载,请复制以下地址【https://www.bdocx.com/down/8688347.html】到电脑端继续下载(重复下载不扣费)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录   QQ登录  

下载须知

1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。
2: 试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。
3: 文件的所有权益归上传用户所有。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 本站仅提供交流平台,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

版权提示 | 免责声明

本文(linux内核定时器实现机制剖析.docx)为本站会员(b****6)主动上传,冰豆网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知冰豆网(发送邮件至service@bdocx.com或直接QQ联系客服),我们立即给予删除!

linux内核定时器实现机制剖析.docx

1、linux内核定时器实现机制剖析内核定时器,分级结构,定时器迁移刷 新,DEFINE_TIMER,init_timer,setup_timer,add_timer,mod_timer,del_timer1 内核定时器概述Linux内核2.4版中去掉了老版本内核中的静态定时器机制,而只留下动态定时器。动态定时器与静态定时器这二个概念是相对于Linux内核定时器机制 的可扩展功能而言的,动态定时器是指内核的定时器队列是可以动态变化的,然而就定时器本身而言,二者并无本质的区别。考虑到静态定时器机制的能力有限,因 此Linux内核2.4版中完全去掉了以前的静态定时器机制。2.6内核为了支持SMP及CP

2、U热插拔,对定时器相关结构又做了改动。本文所有代码基于 2.6.19内核(摘自)Linux11 struct list_head entry;12 unsigned long expires;1314 void (*function)(unsigned long);15 unsigned long data;1617 struct tvec_t_base_s *base;18; 各数据成员的含义如下: 双向链表元素entry:用来将多个定时器连接成一条双向循环队列。 expires:指定定时器到期的时间,这个时间被表示成自系统启动以来的时钟滴答计数(也即时钟节拍数)。当一个 定时器的expir

3、es值小于或等于jiffies变量时,我们就说这个定时器已经超时或到期了。在初始化一个定时器后,通常把它的expires域设置 成当前expires变量的当前值加上某个时间间隔值(以时钟滴答次数计)。 函数指针function:指向一个可执行函数。当定时器到期时,内核就执行function所指定的函数。data域:被内核用作function函数的调用参数。 base:当前timer所属的base。由于考虑了SMP的情况,每个CPU都含有一个base。2 动态内核定时器的组织结构 Linux是怎样为其内核定时器机制提供动态扩展能力的呢?其关键就在于“定时器向量”的概念。所谓“定时器向量”就是指这

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

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

6、值组织成256个定时器向量。而对于那 些256interval0xffffffff的定时器,由于他们离到期还有一段时间,因此内核并不关心他们,而是将它们以一种扩展的定时器向量语义 (或称为“松散的定时器向量语义”)进行组织。所谓“松散的定时器向量语义”就是指:各定时器的expires值可以互不相同的一个定时器队列。 各定时器向量数据结构定义在kernel/timer.c文件中,如下述代码段所示: / 2.4.19 内核 /struct timer_vec int index;struct list_head vecTVN_SIZE;struct timer_vec_root int index

7、;struct list_head vecTVR_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_

8、list_running;static unsigned long timer_jiffies;/* Initialize both explicitly - lets 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

9、#define TVR_BITS (CONFIG_BASE_SMALL ? 6 : 8)53#define TVN_SIZE (1 54#define TVR_SIZE (1 55#define TVN_MASK (TVN_SIZE - 1)56#define TVR_MASK (TVR_SIZE - 1)58typedef struct tvec_s 59 struct list_head vecTVN_SIZE;60 tvec_t;6162typedef struct tvec_root_s 63 struct list_head vecTVR_SIZE;64 tvec_root_t;65

10、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;7677typedef struct tvec_t_base_s tvec_base_t;7879tvec_base_t boot_tvec_bases;80EXPO

11、RT_SYMBOL(boot_tvec_bases);lock:由于内核动态定时器链表是一种系统全局共享资源,为了实现对它的互斥访问,Linux定义了专门的自旋锁lock成员来保 护。任何想要访问动态定时器链表的代码段都首先必须先持有该自旋锁,并且在访问结束后释放该自旋锁。running_timer:用于SMP timer_jiffies:定时器是在软中断中执行的,从触发到真正执行这段时间内可能会有几次时钟中断发生。因此内核必须记住上一次运行定时器机制是 什么时候,也即内核必须保存上一次运行定时器机制时的jiffies值。 tv1:0255第一级定时器队列 tv2:。/ 2.6.19 与2.4

12、内核的区别:无index域。利用timer_jiffies求余后即可自动获得每个tvi当前的index;将零散的tvi变量组织到了一起,将数组tvecs更改为了新的结构体变量;将lock、running_timer、timer_jiffies等变量封装在结构内部,体现了更好的面向对象的特性; 2.6内核支持CPU热插拔,此时定时器可以在各个CPU间转换,因此需要多组定时器结构变量。原有的单个变量形式无法满足需求。3 定时器的组织原则具体的组织方案可以分为两大部分: (1)对于内核最关心的、 interval值在0,255之间的前256个定时器向量,内核是这样组织它们的:这256个定时器向量被组

13、织在一起组成一个定时器向量数组,并作 为数据结构timer_vec_root的一部分。基于数据结构timer_vec_root,Linux定义了一个成员tv1,以表示内核所关心的前 256个定时器向量。这样内核在处理是否有到期定时器时,它就只从定时器向量数组tv1.vec256中的某个定时器向量内进行扫描。而利用 timer_jiffies对TVR_SIZE求余后即可自动获得每个tv1当前处理的向量,也即tv1.vec数组的索引index,其初值为0, 最大值为255(以256为模)。每个时钟节拍时timer_jiffies字段都会加1。显然,index字段所指定的定时器向量 tv1.veci

14、ndex中包含了当前时钟节拍内已经到期的所有动态定时器。而定时器向量tv1.vecindexk则包含了接下来第k个时钟 节拍时刻将到期的所有动态定时器。当timer_jiffies求余后又重新变为0时,就意味着内核已经扫描了tv1变量中的所有256个定时器向量。在 这种情况下就必须将那些以松散定时器向量语义来组织的定时器向量补充到tv1中来。 (2)而对于内核不关心的、interval值在 0xff,0xffffffff之间的定时器,它们的到期紧迫程度也随其interval值的不同而不同。显然interval值越小,定时器紧迫程 度也越高。因此在将它们以松散定时器向量进行组织时也应该区别对待。

15、通常,定时器的interval值越小,它所处的定时器向量的松散度也就越低(也即向 量中的各定时器的expires值相差越小);而interval值越大,它所处的定时器向量的松散度也就越大(也即向量中的各定时器的expires值 相差越大)。 内核规定,对于那些满足条件:0x100interval0x3fff的定时器,只要表达式(interval8)具有相同值的定时 器都将被组织在同一个松散定时器向量中,即以18256为一个基本单位。因此,为组织所有满足条件0x100interval0x3fff的定时 器,就需要2664个松散定时器向量。同样地,为方便起见,这64个松散定时器向量也放在一起形成数

16、组,并作为数据结构timer_vec的一部分。 基于数据结构timer_vec,Linux定义了成员tv2,来表示这64条松散定时器向量。如上述代码段所示。对于那些满足条件0x4000interval0xfffff的定时器,只要表达式(interval86)的值相同的定时器都将 被放在同一个松散定时器向量中。同样,要组织所有满足条件0x4000interval0xfffff的定时器,也需要2664个松散定时器向 量。类似地,这64个松散定时器向量也可以用一个timer_vec结构来描述,相应地Linux定义了成员tv3来表示这64个松散定时器向量。对于那些满足条件0x100000interva

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

18、0xffffffff的定时器,也 需要2664个松散定时器向量。类似地,这64个松散定时器向量也可以用一个timer_vec结构来描述,相应地Linux定义了tv5成员来表示 这64个松散定时器向量。最后,为了引用方便,Linux定义了一个整体的数据结构tvec_base_t,以此统一处理各个定时器向量。4 动态定时器的内部实现机制 在内核动态定时器机制的实现中,有三个操作时非常重要的: 将一个定时器插入到它应该所处的定时器向量中。 定时器的迁移,也即将一个定时器从它原来所处的定时器向量迁移到另一个定时器向量中。 扫描并执行当前已经到期的定时器。 4.1 动态定时器机制的初始化 函数init_

19、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)13511352 int j;1353 tvec_base_t *base;1354 static char _devinitdata

20、tvec_base_doneNR_CPUS;13551356 if (!tvec_base_donecpu) 1357 static char boot_done;13581359 if (boot_done) 1360 /*1361 * The APs use this path later in boot1362 */1363 base = kmalloc_node(sizeof(*base), GFP_KERNEL,1364 cpu_to_node(cpu);1365 if (!base)1366 return -ENOMEM;1367 memset(base, 0, sizeof(*b

21、ase);1368 per_cpu(tvec_bases, cpu) = base;1369 else 1370 /*1371 * This is for the boot CPU - we use compile-time1372 * static initialisation because per-cpu memory isnt1373 * ready yet and because the memory allocators are not1374 * initialised either.1375 */1376 boot_done = 1;1377 base = &boot_tvec

22、_bases;1378 1379 tvec_base_donecpu = 1;1380 else 1381 base = per_cpu(tvec_bases, cpu);1382 13831384 spin_lock_init(&base-lock);1385 lockdep_set_class(&base-lock, base_lock_keys + cpu);13861387 for (j = 0; j 1388 INIT_LIST_HEAD(base-tv5.vec + j);1389 INIT_LIST_HEAD(base-tv4.vec + j);1390 INIT_LIST_HE

23、AD(base-tv3.vec + j);1391 INIT_LIST_HEAD(base-tv2.vec + j);1392 1393 for (j = 0; j 1394 INIT_LIST_HEAD(base-tv1.vec + j);13951396 base-timer_jiffies = jiffies;1397 return 0;13984.2 将一个定时器插入到链表中 函数internal_add_timer()用于将一个不处于任何定时器向量中的定时器插入到它应该所处的定时器向量中去(根据定时器的expires 值来决定)。如下所示(kernel/timer.c): stati

24、c void internal_add_timer(tvec_base_t *base, struct timer_list *timer)9293 unsigned long expires = timer-expires;94 unsigned long idx = expires - base-timer_jiffies;95 struct list_head *vec;9697 if (idx 98 int i = expires & TVR_MASK;99 vec = base-tv1.vec + i;100 else if (idx 101 int i = (expires TVR

25、_BITS) & TVN_MASK;102 vec = base-tv2.vec + i;103 else if (idx 104 int i = (expires (TVR_BITS + TVN_BITS) & TVN_MASK;105 vec = base-tv3.vec + i;106 else if (idx 107 int i = (expires (TVR_BITS + 2 * TVN_BITS) & TVN_MASK;108 vec = base-tv4.vec + i;109 else if (signed long) idx 110 /*111 * Can happen if

26、 you add a timer with expires = jiffies,112 * or you set a timer to go off in the past,then return current timer list113 */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-bit118 * architectures then we use the maximum

27、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_BITS 及TVN_BITS的值依次求商(通

28、过移位实现),来获得对应分组的list首地址,然后将定时器添加到对应list的尾部。详细流程 如下: 首先,计算定时器的expires值与timer_jiffies的差值(注意!这里应该使用动态定时器自己的时间基准),这个差值就表示这个定时器相 对于上一次运行定时器机制的那个时刻还需要多长时间间隔才到期。局部变量idx保存这个差值。 根据idx的值确定这个定时器应被插入到哪一个定时器分组中。而由expires确定的i值决定了对应定时器分组的哪个向量中。定时器向量的头部指针 vec表示这个定时器应该所处的定时器向量链表头部,其指针域指向有效的定时器。 最后,调用list_add()函数将定时器插

29、入到vec指针所指向的定时器队列的尾部。4.3 定时器迁移 由于一个定时器的interval值会随着时间的不断流逝(即jiffies值的不断增大)而不断变小,因此那些原本到期紧迫程度较低的定时器会随着 jiffies值的不断增大而成为即将马上到期的定时器。比如定时器向量tv2.vec0中的定时器在经过256个时钟滴答后会成为未来256个时钟 滴答内会到期的定时器。因此,定时器在内核动态定时器链表中的位置也应相应地随着改变。改变的规则是: 当timer_jiffiesTVR_SIZE重新变为0时,则意味着tv1中的256个定时器向量都已被内核扫描一遍了,从而使tv1中的256个 定时器向量变为空

30、(此处不考虑中途添加的定时器),此时需要用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定时器向量中的定时器去填充

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1