版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
内核定时器,分级结构,定时器迁移刷新,DEFINE_TIMER,init_timer,setup_timer,add_timer,mod_timer,del_timer
1内核定时器概述
Linux内核2.4版中去掉了老版本内核中的静态定时器机制,而只留下动态定时器。动态定时器与静态定时器这二个概念是相对于Linux内核定时器机制的可扩展功能而言的,动态定时器是指内核的定时器队列是可以动态变化的,然而就定时器本身而言,二者并无本质的区别。考虑到静态定时器机制的能力有限,因此Linux内核2.4版中完全去掉了以前的静态定时器机制。2.6内核为了支持SMP及CPU热插拔,对定时器相关结构又做了改动。本文所有代码基于2.6.19内核(摘自)
Linux
11structlist_headentry;
12unsignedlongexpires;
13
14void(*function)(unsignedlong);
15unsignedlongdata;
16
17structtvec_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内核///////////////////////////////////////////////
structtimer_vec{
intindex;
structlist_headvec[TVN_SIZE];
};
structtimer_vec_root{
intindex;
structlist_headvec[TVR_SIZE];
};
staticstructtimer_vectv5;
staticstructtimer_vectv4;
staticstructtimer_vectv3;
staticstructtimer_vectv2;
staticstructtimer_vec_roottv1;
staticstructtimer_vec*consttvecs[]={
(structtimer_vec*)&tv1,&tv2,&tv3,&tv4,&tv5
};
staticstructlist_head*run_timer_list_running;
staticunsignedlongtimer_jiffies;
/*Initializebothexplicitly-let'strytohavetheminthesamecacheline*/
spinlock_ttimerlist_lock=SPIN_LOCK_UNLOCKED;
volatilestructtimer_list*volatilerunning_timer;
///////////////////////////////////////////////2.4.19内核///////////////////////////////////////////////
///////////////////////////////////////////////2.6.19内核///////////////////////////////////////////////51#defineTVN_BITS(CONFIG_BASE_SMALL?4:6)
52#defineTVR_BITS(CONFIG_BASE_SMALL?6:8)
53#defineTVN_SIZE(1
54#defineTVR_SIZE(1
55#defineTVN_MASK(TVN_SIZE-1)
56#defineTVR_MASK(TVR_SIZE-1)
58typedefstructtvec_s{
59structlist_headvec[TVN_SIZE];
60}tvec_t;
61
62typedefstructtvec_root_s{
63structlist_headvec[TVR_SIZE];
64}tvec_root_t;
65
66structtvec_t_base_s{
67spinlock_tlock;
68structtimer_list*running_timer;
69unsignedlongtimer_jiffies;
70tvec_root_ttv1;
71tvec_ttv2;
72tvec_ttv3;
73tvec_ttv4;
74tvec_ttv5;
75}____cacheline_aligned_in_smp;
76
77typedefstructtvec_t_base_stvec_base_t;
78
79tvec_base_tboot_tvec_bases;
80EXPORT_SYMBOL(boot_tvec_bases);
lock:由于内核动态定时器链表是一种系统全局共享资源,为了实现对它的互斥访问,Linux定义了专门的自旋锁lock成员来保护。任何想要访问动态定时器链表的代码段都首先必须先持有该自旋锁,并且在访问结束后释放该自旋锁。
running_timer:用于SMPtimer_jiffies:定时器是在软中断中执行的,从触发到真正执行这段时间内可能会有几次时钟中断发生。因此内核必须记住上一次运行定时器机制是什么时候,也即内核必须保存上一次运行定时器机制时的jiffies值。
²tv1:0-255第一级定时器队列
²tv2:。。。。。
///////////////////////////////////////////////2.6.19
与2.4内核的区别:
无index域。利用timer_jiffies求余后即可自动获得每个tvi当前的index;
将零散的tvi变量组织到了一起,将数组tvecs更改为了新的结构体变量;
将lock、running_timer、timer_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_jiffies对TVR_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,bootCPU使用静态定义的boot_tvec_bases,而其他CPU都是动态申请的。如下所示(kernel/timer.c):
staticint__devinitinit_timers_cpu(intcpu)
1351{
1352intj;
1353tvec_base_t*base;
1354staticchar__devinitdatatvec_base_done[NR_CPUS];
1355
1356if(!tvec_base_done[cpu]){
1357staticcharboot_done;
1358
1359if(boot_done){
1360/*
1361*TheAPsusethispathlaterinboot
1362*/
1363base=kmalloc_node(sizeof(*base),GFP_KERNEL,
1364cpu_to_node(cpu));
1365if(!base)
1366return-ENOMEM;
1367memset(base,0,sizeof(*base));
1368per_cpu(tvec_bases,cpu)=base;
1369}else{
1370/*
1371*ThisisforthebootCPU-weusecompile-time
1372*staticinitialisationbecauseper-cpumemoryisn't
1373*readyyetandbecausethememoryallocatorsarenot
1374*initialisedeither.
1375*/
1376boot_done=1;
1377base=&boot_tvec_bases;
1378}
1379tvec_base_done[cpu]=1;
1380}else{
1381base=per_cpu(tvec_bases,cpu);
1382}
1383
1384spin_lock_init(&base->lock);
1385lockdep_set_class(&base->lock,base_lock_keys+cpu);
1386
1387for(j=0;j
1388INIT_LIST_HEAD(base->tv5.vec+j);
1389INIT_LIST_HEAD(base->tv4.vec+j);
1390INIT_LIST_HEAD(base->tv3.vec+j);
1391INIT_LIST_HEAD(base->tv2.vec+j);
1392}
1393for(j=0;j
1394INIT_LIST_HEAD(base->tv1.vec+j);
1395
1396base->timer_jiffies=jiffies;
1397return0;
1398}
4.2将一个定时器插入到链表中
函数internal_add_timer()用于将一个不处于任何定时器向量中的定时器插入到它应该所处的定时器向量中去(根据定时器的expires值来决定)。如下所示(kernel/timer.c):
staticvoidinternal_add_timer(tvec_base_t*base,structtimer_list*timer)
92{
93unsignedlongexpires=timer->expires;
94unsignedlongidx=expires-base->timer_jiffies;
95structlist_head*vec;
96
97if(idx
98inti=expires&TVR_MASK;
99vec=base->tv1.vec+i;
100}elseif(idx
101inti=(expires>>TVR_BITS)&TVN_MASK;
102vec=base->tv2.vec+i;
103}elseif(idx
104inti=(expires>>(TVR_BITS+TVN_BITS))&TVN_MASK;
105vec=base->tv3.vec+i;
106}elseif(idx
107inti=(expires>>(TVR_BITS+2*TVN_BITS))&TVN_MASK;
108vec=base->tv4.vec+i;
109}elseif((signedlong)idx
110/*
111*Canhappenifyouaddatimerwithexpires==jiffies,
112*oryousetatimertogooffinthepast,thenreturncurrenttimerlist
113*/
114vec=base->tv1.vec+(base->timer_jiffies&TVR_MASK);
115}else{
116inti;
117/*Ifthetimeoutislargerthan0xffffffffon64-bit
118*architecturesthenweusethemaximumtimeout:
119*/
120if(idx>0xffffffffUL){
121idx=0xffffffffUL;
122expires=idx+base->timer_jiffies;
123}
124i=(expires>>(TVR_BITS+3*TVN_BITS))&TVN_MASK;
125vec=base->tv5.vec+i;
126}
127/*
128*TimersareFIFO:
129*/
130list_add_tail(&timer->entry,vec);
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_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):
383staticintcascade(tvec_base_t*base,tvec_t*tv,intindex)
384{
385/*cascadeallthetimersfromtvuponelevel*/
386structtimer_list*timer,*tmp;
387structlist_headtv_list;
388
389list_replace_init(tv->vec+index,&tv_list);
390
391/*
392*Weareremoving_all_timersfromthelist,sowe
393*don'thavetodetachthemindividually.
394*/
395list_for_each_entry_safe(timer,tmp,&tv_list,entry){
396BUG_ON(timer->base!=base);
397internal_add_timer(base,timer);
398}
399
400returnindex;
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内核的时钟中断的BottomHalf不同,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#defineINDEX(N)((base->timer_jiffies>>(TVR_BITS+(N)*TVN_BITS))&TVN_MASK)当前待处理的定时器向量
404
405/**
406*__run_timers-runallexpiredtimers(ifany)onthisCPU.
407*@base:thetimervectortobeprocessed.
408*
409*Thisfunctioncascadesallvectorsandexecutesallexpiredtimervectors.
411*/
412staticinlinevoid__run_timers(tvec_base_t*base)
413{
414structtimer_list*timer;
415
416spin_lock_irq(&base->lock);
417while(time_after_eq(jiffies,base->timer_jiffies)){
418structlist_headwork_list;
419structlist_head*head=&work_list;
420intindex=base->timer_jiffies&TVR_MASK;
421
422/*
423*Cascadetimers:
424*/
425if(!index&&
426(!cascade(base,&base->tv2,INDEX(0)))&&
427(!cascade(base,&base->tv3,INDEX(1)))&&
428!cascade(base,&base->tv4,INDEX(2)))
429cascade(base,&base->tv5,INDEX(3));
430++base->timer_jiffies;
431list_replace_init(base->tv1.vec+index,&work_list);
432while(!list_empty(head)){
433void(*fn)(unsignedlong);
434unsignedlongdata;
435
436timer=list_entry(head->next,structtimer_list,entry);
437fn=timer->function;
438data=timer->data;
439
440set_running_timer(base,timer);
441detach_timer(timer,1);
442spin_unlock_irq(&base->lock);
443{
444intpreempt_count=preempt_count();
445fn(data);
446if(preempt_count!=preempt_count()){
447printk(KERN_WARNING"huh,entered%p"
448"withpreempt_count%08x,exited"
449"with%08x?\n",
450fn,preempt_count,
451preempt_count());
452BUG();
453}
454}
455spin_lock_irq(&base->lock);
456}
457}
458set_running_timer(base,NULL);
459spin_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静态初始化
20externstructtvec_t_base_sboot_tvec_bases;
21
22#defineTIMER_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#defineDEFINE_TIMER(_name,_function,_expires,_data)\
30structtimer_list_name=\
31TIMER_INITIALIZER(_function,_expires,_data)
静态定义一个内核timer变量,同时对各个元素进行初始化。好处在于用户无需知道定时器的实现细节,同时可以防止用户忘记初始化导致的问题。
5.1.2动态初始化init_timer
33voidfastcallinit_timer(structtimer_list*timer);
//////////////////////////////
#definefastcall__attribute__((regparm(3)))//通过寄存器传递参数可以提高性能,内核中的大部分函数都有此修饰符
#defineasmlinkage__attribute__((regparm(0)))函数定义前加宏asmlinkage,表示这些函数通过堆栈而不是通过寄存器传递参数。
gcc编译器在汇编过程中调用c语言函数时传递参数有两种方法:一种是通过堆栈,另一种是通过寄存器。缺省时采用寄存器,假如你要在你的汇编过程中调用c语言函数,并且想通过堆栈传递参数,你定义的c函数时要在函数前加上宏asmlinkage
//////////////////////////////
内核函数init_timer()用来初始化一个定时器。实际上,这个初始化函数仅仅将结构中的list成员初始化为空并获得对应CPU上的base。如下所示(/kernel/timer.c):140voidfastcallinit_timer(structtimer_list*timer)
141{
142timer->entry.next=NULL;
143timer->base=__raw_get_cpu_var(tvec_bases);
144}
145EXPORT_SYMBOL(init_timer);
5.1.3完全初始化setup_timer
34
35staticinlinevoidsetup_timer(structtimer_list*timer,
36void(*function)(unsignedlong),
37unsignedlongdata)
38{
39timer->function=function;
40timer->data=data;
41init_timer(timer);
42}
动态初始化一个内核定时器,经过setup_timer的调用后,内核timer的各个域都有初始值了;该函数也可以对已经初始化的定时器重新初始化。
2.4的内核代码中还没有setup_timer函数,通常动态初始化时先用init_timer,接着对function域及data域赋值。这样用户直接操作结构体成员变量,若内核timer结构改变,则可移植性就降低了。因此2.6内核通过DEFINE_TIMER及setup_timer对内核定时器的初始化进行了全面封装,用户无需知道内核的实现细节,只需要按照内核timer的API编程即可,接口是固定的,内部实现细节的变化对用户程序影响最小。
5.2内部实现细节
5.2.1时间比较
在定时器应用中经常需要比较两个时间值,以确定timer是否超时,所以Linux内核在timer.h头文件中定义了4个时间关系比较操作宏。这里我们说时刻a在时刻b之后,就意味着时间值a≥b。Linux强烈推荐用户使用它所定义的下列4个时间比较操作宏(include/linux/jiffies.h),还是一句话,封装可以改善移植性:
106#definetime_after(a,b)\
107(typecheck(unsignedlong,a)&&\
108typecheck(unsignedlong,b)&&\
109((long)(b)-(long)(a)
110#definetime_before(a,b)time_after(b,a)
111
112#definetime_after_eq(a,b)\
113(typecheck(unsignedlong,a)&&\
114typecheck(unsignedlong,b)&&\
115((long)(a)-(long)(b)>=0))
116#definetime_before_eq(a,b)time_after_eq(b,a)
5.2.2挂起判断
由于定时器通常被连接在一个双向循环队列中等待执行,此时我们说定时器处于pending状态。因此函数time_pending()就可以用entry成员是否为空来判断一个定时器是否处于pending状态。如下所示(include/linux/timer.h):44/***
Callersmustensureserializationwrt.otheroperationsdonetothistimer,erruptcontexts,orotherCPUsonSMP.
returnvalue:1ifthetimerispending,0ifnot.
53*/
54staticinlineinttimer_pending(conststructtimer_list*timer)
55{
56returntimer->entry.next!=NULL;
57}
5.2.3锁定定时器base
158/*
159*Weareusinghashedlocking:holdingper_cpu(tvec_bases).lock
160*meansthatalltimerswhicharetiedtothisbaseviatimer->baseare
161*locked,andthebaseitselfislockedtoo.
166*Whenthetimer'sbaseislocked,andthetimerremovedfromlist,itis
167*possibletosettimer->base=NULLanddropthelock:thetimerremains
168*locked.
169*/
170statictvec_base_t*lock_timer_base(structtimer_list*timer,
171unsignedlong*flags)
172__acquires(timer->base->lock)
173{
174tvec_base_t*base;
175
176for(;;){
177base=timer->base;
178if(likely(base!=NULL)){
179spin_lock_irqsave(&base->lock,*flags);
180if(likely(base==timer->base))
181returnbase;
182/*ThetimerhasmigratedtoanotherCPU*/
183spin_unlock_irqrestore(&base->lock,*flags);
184}
185cpu_relax();
186}
187}
5.2.4内部删除
函数detach_timer()如下所示(kernel/timer.c):
147staticinlinevoiddetach_timer(structtimer_list*timer,
148intclear_pending)
149{
150structlist_head*entry=&timer->entry;
151
152__list_del(entry->prev,entry->next);
153if(clear_pending)
154entry->next=NULL;
155entry->prev=LIST_POISON2;//非0的防范值,操作时将导致页表异常
156}
函数detach_timer()用来将一个定时器从相应的内核定时器队列中删除。前提条件,该定时器处于定时器队列中。首先将起从链表中删除,根据clear_pending标志是否清除entry->next。
5.2.5内部修部timer值
189int__mod_timer(structtimer_list*timer,unsignedlongexpires)
190{
191tvec_base_t*base,*new_base;
192unsignedlongflags;
193intret=0;
194
195BUG_ON(!timer->function);
////
#defineBUG_ON(condition)do{if(condition);}while(0)
/////
196
197base=lock_timer_base(timer,&flags);//获得timer所在的根base,同时锁定
198
199if(timer_pending(timer)){
200detach_timer(timer,0);
201ret=1;
202}
203
204new_base=__get_cpu_var(tvec_bases);
205
206if(base!=new_base){
207/*
208*WearetryingtoschedulethetimeronthelocalCPU.
213*/
214if(likely(base->running_timer!=timer)){
215/*Seethecommentinlock_timer_base()*/
216timer->base=NULL;
217spin_unlock(&base->lock);
218base=new_base;
219spin_lock(&base->lock);
220timer->base=base;
221}
222}
223
224timer->expires=expires;
225internal_add_timer(base,timer);
226spin_unlock_irqrestore(&base->lock,flags);
227
228returnret;
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):
staticinlinevoidadd_timer(structtimer_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只是m
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 二零二五年度建筑工程施工围挡租赁协议3篇
- 二零二五年度智慧医疗软件开发框架委托开发合同
- 年度大孔烧结空心砖战略市场规划报告
- 2024年项目技术开发合作协议范本版B版
- 二零二五年度全国重点建设项目借款垫资合作协议书3篇
- 二零二五年度房产经纪加盟店合同规范范本3篇
- 2025年度ktv消防安全评估与整改专项服务合同2篇
- 二零二五年度智慧城市基础设施建设可行性研究合同3篇
- 2024年度债务明确离婚协议模板3篇
- 水路运输行业标准编制
- 领导沟通的艺术
- 纯视觉方案算法
- 道士述职报告
- 绿色贷款培训课件
- 2024年七年级语文上学期期末作文题目及范文汇编
- 云南省昆明市五华区2023-2024学年九年级上学期期末英语试卷+
- 2023年生产运营副总经理年度总结及下一年计划
- 2023年中考语文标点符号(顿号)练习(含答案)
- 施工图审查招标文件范文
- 新课标人教版数学三年级上册第八单元《分数的初步认识》教材解读
- 布袋式除尘器制造工序检验规定
评论
0/150
提交评论