版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)
文檔簡介
內(nèi)核定時器,分級結(jié)構(gòu),定時器遷移刷新,DEFINE_TIMER,init_timer,setup_timer,add_timer,mod_timer,del_timer
1內(nèi)核定時器概述
Linux內(nèi)核2.4版中去掉了老版本內(nèi)核中的靜態(tài)定時器機制,而只留下動態(tài)定時器。動態(tài)定時器與靜態(tài)定時器這二個概念是相對于Linux內(nèi)核定時器機制的可擴展功能而言的,動態(tài)定時器是指內(nèi)核的定時器隊列是可以動態(tài)變化的,然而就定時器本身而言,二者并無本質(zhì)的區(qū)別??紤]到靜態(tài)定時器機制的能力有限,因此Linux內(nèi)核2.4版中完全去掉了以前的靜態(tài)定時器機制。2.6內(nèi)核為了支持SMP及CPU熱插拔,對定時器相關(guān)結(jié)構(gòu)又做了改動。本文所有代碼基于2.6.19內(nèi)核(摘自)
Linux
11structlist_headentry;
12unsignedlongexpires;
13
14void(*function)(unsignedlong);
15unsignedlongdata;
16
17structtvec_t_base_s*base;
18};各數(shù)據(jù)成員的含義如下:
雙向鏈表元素entry:用來將多個定時器連接成一條雙向循環(huán)隊列。
expires:指定定時器到期的時間,這個時間被表示成自系統(tǒng)啟動以來的時鐘滴答計數(shù)(也即時鐘節(jié)拍數(shù))。當一個定時器的expires值小于或等于jiffies變量時,我們就說這個定時器已經(jīng)超時或到期了。在初始化一個定時器后,通常把它的expires域設(shè)置成當前expires變量的當前值加上某個時間間隔值(以時鐘滴答次數(shù)計)。
函數(shù)指針function:指向一個可執(zhí)行函數(shù)。當定時器到期時,內(nèi)核就執(zhí)行function所指定的函數(shù)。
data域:被內(nèi)核用作function函數(shù)的調(diào)用參數(shù)。
base:當前timer所屬的base。由于考慮了SMP的情況,每個CPU都含有一個base。
2動態(tài)內(nèi)核定時器的組織結(jié)構(gòu)
Linux是怎樣為其內(nèi)核定時器機制提供動態(tài)擴展能力的呢?其關(guān)鍵就在于“定時器向量”的概念。所謂“定時器向量”就是指這樣一條雙向循環(huán)定時器隊列(隊列中的每一個元素都是一個timer_list結(jié)構(gòu)):對列中的所有定時器都在同一個時刻到期,也即對列中的每一個timer_list結(jié)構(gòu)都具有相同的expires值。顯然,可以用一個timer_list結(jié)構(gòu)類型的指針來表示一個定時器向量。
顯然,定時器expires成員的值與jiffies變量的差值決定了一個定時器將在多長時間后到期。在32位系統(tǒng)中,這個時間差值的最大值應(yīng)該是0xffffffff。因此如果是基于“定時器向量”基本定義,內(nèi)核將至少要維護0xffffffff個timer_list結(jié)構(gòu)類型的指針,這顯然是不現(xiàn)實的。
另一方面,從內(nèi)核本身這個角度看,它所關(guān)心的定時器顯然不是那些已經(jīng)過期而被執(zhí)行過的定時器(這些定時器完全可以被丟棄),也不是那些要經(jīng)過很長時間才會到期的定時器,而是那些當前已經(jīng)到期或者馬上就要到期的定時器(注意!時間間隔是以滴答次數(shù)為計數(shù)單位的)。
基于上述考慮,并假定一個定時器要經(jīng)過interval個時鐘滴答后才到期(interval=expires-jiffies),則Linux采用了下列思想來實現(xiàn)其動態(tài)內(nèi)核定時器機制:對于那些0≤interval≤255的定時器,Linux嚴格按照定時器向量的基本語義來組織這些定時器,也即Linux內(nèi)核最關(guān)心那些在接下來的255個時鐘節(jié)拍內(nèi)就要到期的定時器,因此將它們按照各自不同的expires值組織成256個定時器向量。而對于那些256≤interval≤0xffffffff的定時器,由于他們離到期還有一段時間,因此內(nèi)核并不關(guān)心他們,而是將它們以一種擴展的定時器向量語義(或稱為“松散的定時器向量語義”)進行組織。所謂“松散的定時器向量語義”就是指:各定時器的expires值可以互不相同的一個定時器隊列。
各定時器向量數(shù)據(jù)結(jié)構(gòu)定義在kernel/timer.c文件中,如下述代碼段所示:
///////////////////////////////////////////////2.4.19內(nèi)核///////////////////////////////////////////////
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內(nèi)核///////////////////////////////////////////////
///////////////////////////////////////////////2.6.19內(nèi)核///////////////////////////////////////////////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:由于內(nèi)核動態(tài)定時器鏈表是一種系統(tǒng)全局共享資源,為了實現(xiàn)對它的互斥訪問,Linux定義了專門的自旋鎖lock成員來保護。任何想要訪問動態(tài)定時器鏈表的代碼段都首先必須先持有該自旋鎖,并且在訪問結(jié)束后釋放該自旋鎖。
running_timer:用于SMPtimer_jiffies:定時器是在軟中斷中執(zhí)行的,從觸發(fā)到真正執(zhí)行這段時間內(nèi)可能會有幾次時鐘中斷發(fā)生。因此內(nèi)核必須記住上一次運行定時器機制是什么時候,也即內(nèi)核必須保存上一次運行定時器機制時的jiffies值。
2tv1:0-255第一級定時器隊列
2tv2:。。。。。
///////////////////////////////////////////////2.6.19
與2.4內(nèi)核的區(qū)別:
無index域。利用timer_jiffies求余后即可自動獲得每個tvi當前的index;
將零散的tvi變量組織到了一起,將數(shù)組tvecs更改為了新的結(jié)構(gòu)體變量;
將lock、running_timer、timer_jiffies等變量封裝在結(jié)構(gòu)內(nèi)部,體現(xiàn)了更好的面向?qū)ο蟮奶匦裕?/p>
22.6內(nèi)核支持CPU熱插拔,此時定時器可以在各個CPU間轉(zhuǎn)換,因此需要多組定時器結(jié)構(gòu)變量。原有的單個變量形式無法滿足需求。
3定時器的組織原則
具體的組織方案可以分為兩大部分:(1)對于內(nèi)核最關(guān)心的、interval值在[0,255]之間的前256個定時器向量,內(nèi)核是這樣組織它們的:這256個定時器向量被組織在一起組成一個定時器向量數(shù)組,并作為數(shù)據(jù)結(jié)構(gòu)timer_vec_root的一部分?;跀?shù)據(jù)結(jié)構(gòu)timer_vec_root,Linux定義了一個成員tv1,以表示內(nèi)核所關(guān)心的前256個定時器向量。這樣內(nèi)核在處理是否有到期定時器時,它就只從定時器向量數(shù)組tv1.vec[256]中的某個定時器向量內(nèi)進行掃描。而利用timer_jiffies對TVR_SIZE求余后即可自動獲得每個tv1當前處理的向量,也即tv1.vec[]數(shù)組的索引index,其初值為0,最大值為255(以256為模)。每個時鐘節(jié)拍時timer_jiffies字段都會加1。顯然,index字段所指定的定時器向量tv1.vec[index]中包含了當前時鐘節(jié)拍內(nèi)已經(jīng)到期的所有動態(tài)定時器。而定時器向量tv1.vec[index+k]則包含了接下來第k個時鐘節(jié)拍時刻將到期的所有動態(tài)定時器。當timer_jiffies求余后又重新變?yōu)?時,就意味著內(nèi)核已經(jīng)掃描了tv1變量中的所有256個定時器向量。在這種情況下就必須將那些以松散定時器向量語義來組織的定時器向量補充到tv1中來。(2)而對于內(nèi)核不關(guān)心的、interval值在[0xff,0xffffffff]之間的定時器,它們的到期緊迫程度也隨其interval值的不同而不同。顯然interval值越小,定時器緊迫程度也越高。因此在將它們以松散定時器向量進行組織時也應(yīng)該區(qū)別對待。通常,定時器的interval值越小,它所處的定時器向量的松散度也就越低(也即向量中的各定時器的expires值相差越?。欢鴌nterval值越大,它所處的定時器向量的松散度也就越大(也即向量中的各定時器的expires值相差越大)。
內(nèi)核規(guī)定,對于那些滿足條件:0x100≤interval≤0x3fff的定時器,只要表達式(interval>>8)具有相同值的定時器都將被組織在同一個松散定時器向量中,即以1》8=256為一個基本單位。因此,為組織所有滿足條件0x100≤interval≤0x3fff的定時器,就需要2^6=64個松散定時器向量。同樣地,為方便起見,這64個松散定時器向量也放在一起形成數(shù)組,并作為數(shù)據(jù)結(jié)構(gòu)timer_vec的一部分?;跀?shù)據(jù)結(jié)構(gòu)timer_vec,Linux定義了成員tv2,來表示這64條松散定時器向量。如上述代碼段所示。
對于那些滿足條件0x4000≤interval≤0xfffff的定時器,只要表達式(interval>>8+6)的值相同的定時器都將被放在同一個松散定時器向量中。同樣,要組織所有滿足條件0x4000≤interval≤0xfffff的定時器,也需要2^6=64個松散定時器向量。類似地,這64個松散定時器向量也可以用一個timer_vec結(jié)構(gòu)來描述,相應(yīng)地Linux定義了成員tv3來表示這64個松散定時器向量。
對于那些滿足條件0x100000≤interval≤0x3ffffff的定時器,只要表達式(interval>>8+6+6)的值相同的定時器都將被放在同一個松散定時器向量中。同樣,要組織所有滿足條件0x100000≤interval≤0x3ffffff的定時器,也需要2^6=64個松散定時器向量。類似地,這64個松散定時器向量也可以用一個timer_vec結(jié)構(gòu)來描述,相應(yīng)地Linux定義了tv4成員來表示這64個松散定時器向量。
對于那些滿足條件0x4000000≤interval≤0xffffffff的定時器,只要表達式(interval>>8+6+6+6)的值相同的定時器都將被放在同一個松散定時器向量中。同樣,要組織所有滿足條件0x4000000≤interval≤0xffffffff的定時器,也需要2^6=64個松散定時器向量。類似地,這64個松散定時器向量也可以用一個timer_vec結(jié)構(gòu)來描述,相應(yīng)地Linux定義了tv5成員來表示這64個松散定時器向量。
最后,為了引用方便,Linux定義了一個整體的數(shù)據(jù)結(jié)構(gòu)tvec_base_t,以此統(tǒng)一處理各個定時器向量。
4動態(tài)定時器的內(nèi)部實現(xiàn)機制
在內(nèi)核動態(tài)定時器機制的實現(xiàn)中,有三個操作時非常重要的:
2將一個定時器插入到它應(yīng)該所處的定時器向量中。
2定時器的遷移,也即將一個定時器從它原來所處的定時器向量遷移到另一個定時器向量中。
2掃描并執(zhí)行當前已經(jīng)到期的定時器。
4.1動態(tài)定時器機制的初始化
函數(shù)init_timers_cpu()實現(xiàn)對動態(tài)定時器機制的初始化。該函數(shù)被sched_init()初始化例程所調(diào)用。動態(tài)定時器機制初始化過程的主要任務(wù)就是將tv1、tv2、…、tv5這5個成員變量中的定時器向量指針數(shù)組vec[]初始化為NULL。對于SMP,bootCPU使用靜態(tài)定義的boot_tvec_bases,而其他CPU都是動態(tài)申請的。如下所示(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將一個定時器插入到鏈表中
函數(shù)internal_add_timer()用于將一個不處于任何定時器向量中的定時器插入到它應(yīng)該所處的定時器向量中去(根據(jù)定時器的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}
從最小值開始,根據(jù)TVR_BITS及TVN_BITS的值依次求商(通過移位實現(xiàn)),來獲得對應(yīng)分組的list首地址,然后將定時器添加到對應(yīng)list的尾部。詳細流程如下:
2首先,計算定時器的expires值與timer_jiffies的差值(注意!這里應(yīng)該使用動態(tài)定時器自己的時間基準),這個差值就表示這個定時器相對于上一次運行定時器機制的那個時刻還需要多長時間間隔才到期。局部變量idx保存這個差值。
2根據(jù)idx的值確定這個定時器應(yīng)被插入到哪一個定時器分組中。而由expires確定的i值決定了對應(yīng)定時器分組的哪個向量中。定時器向量的頭部指針vec表示這個定時器應(yīng)該所處的定時器向量鏈表頭部,其指針域指向有效的定時器。
2最后,調(diào)用list_add()函數(shù)將定時器插入到vec指針所指向的定時器隊列的尾部。
4.3定時器遷移
由于一個定時器的interval值會隨著時間的不斷流逝(即jiffies值的不斷增大)而不斷變小,因此那些原本到期緊迫程度較低的定時器會隨著jiffies值的不斷增大而成為即將馬上到期的定時器。比如定時器向量tv2.vec[0]中的定時器在經(jīng)過256個時鐘滴答后會成為未來256個時鐘滴答內(nèi)會到期的定時器。因此,定時器在內(nèi)核動態(tài)定時器鏈表中的位置也應(yīng)相應(yīng)地隨著改變。改變的規(guī)則是:
2當timer_jiffies%TVR_SIZE重新變?yōu)?時,則意味著tv1中的256個定時器向量都已被內(nèi)核掃描一遍了,從而使tv1中的256個定時器向量變?yōu)榭眨ù颂幉豢紤]中途添加的定時器),此時需要用tv2.vec[(timer_jiffies>>TVR_BITS)&TVN_MASK]定時器向量中的定時器去填充tv1。
2隨著timer_jiffies增加TVR_SIZE后,timer_jiffies>>TVR_BITS)&TVN_MASK自動加1,當timer_jiffies>>TVR_BITS)&TVN_MASK==TVN_MASK時,意味著tv2中的64個定時器向量都已經(jīng)被全部填充到tv1中去了,從而使得tv2變?yōu)榭?,則用tv3.vec[(timer_jiffies>>(TVR_BITS+TVN_BITS)&TVN_MASK]定時器向量中的定時器去填充tv2,tv1。
2如此一直類推下去,直到tv5。
函數(shù)cascade_timers()完成定時器從tv(i+1)到tvi-tv1層的遷移操作,三個參數(shù):
2base,定時器所處的基隊列;
2tv,待遷移的tv(i+1)層;
2index,tv(i+1)層中對應(yīng)待遷移的向量。
因此函數(shù)實現(xiàn)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}
相應(yīng)流程如下:
2首先,list_replace_init將原有的tv->vec+index向量的頭賦給tv_list,然后將其頭部初始化為NULL,即從原始隊列中刪除了所有的定時器。
2然后,將tv_list所鏈接的所有定時器用list_for_each_entry_safe宏獲得,然后調(diào)用internal_add_timer()將其添加到隊列中。由于定時器到點時刻jiffiesx未變,而timer_jiffies增大,二者差值縮小,故其在隊列中的新位置發(fā)送變化。
4.4掃描更新并執(zhí)行當前已經(jīng)到期的定時器
函數(shù)__run_timers完成這個功能。和2.4內(nèi)核的時鐘中斷的BottomHalf不同,2.6內(nèi)核采用了定時器軟中斷,該函數(shù)是被run_timer_softirq函數(shù)所調(diào)用的,其由TIMER_SOFTIRQ定時器軟中斷觸發(fā)。
timer_jiffies表示了內(nèi)核上一次執(zhí)行run_timer_list()函數(shù)的時間,因此jiffies與timer_jiffies的差值就表示了自從上一次處理定時器以來,期間一共發(fā)生了多少次時鐘中斷,顯然run_timer_list()函數(shù)必須為期間所發(fā)生的每一次時鐘中斷補上定時器服務(wù)。但通常每次時鐘中斷后的某一時刻就會執(zhí)行run_timer_softirq。該函數(shù)的源碼如下(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}
函數(shù)run_timer_list()的執(zhí)行過程主要就是用一個大while{}循環(huán)來為時鐘中斷執(zhí)行定時器服務(wù),每一次循環(huán)服務(wù)一次時鐘中斷。因此一共要執(zhí)行(jiffies-timer_jiffies+1)次循環(huán)。循環(huán)體所執(zhí)行的服務(wù)步驟如下:
2刷新定時器隊列。首先,判斷index是否為0,如果為0則需要從tv2中補充定時器到tv1中來。若tv2已經(jīng)處理完最后一列向量,即(base->timer_jiffies>>(TVR_BITS+(N)*TVN_BITS))&TVN_MASK)==0,則需要從tv3補充,依次類推。若tv2中有未處理完的,則將部分補充到tv1中,此時tv3、tv4、tv5都不需要更新。注意&&的執(zhí)行關(guān)系,前面為假后后面就不再執(zhí)行。
2執(zhí)行到期的timer。list_replace_init獲取當前待處理的向量列。將頭保存到head中,刪除所有定時器。while(!list_empty(head))循環(huán)若有到期的timer則獲得待執(zhí)行的函數(shù)地址及其參數(shù),調(diào)用detach_timer從head中刪除當前timer。依次執(zhí)行head鏈中所有的timer。
5內(nèi)核定時器的API
5.1初始化
5.1.1靜態(tài)初始化
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構(gòu)造一個內(nèi)核timer元素,當其他結(jié)構(gòu)體中包含一個內(nèi)核timer時,此宏可以直接內(nèi)嵌在結(jié)構(gòu)體中。Linux內(nèi)核中常見的數(shù)據(jù)結(jié)構(gòu)都采用了此方法,如自旋鎖,等待隊列等。
29#defineDEFINE_TIMER(_name,_function,_expires,_data)\
30structtimer_list_name=\
31TIMER_INITIALIZER(_function,_expires,_data)
靜態(tài)定義一個內(nèi)核timer變量,同時對各個元素進行初始化。好處在于用戶無需知道定時器的實現(xiàn)細節(jié),同時可以防止用戶忘記初始化導致的問題。
5.1.2動態(tài)初始化init_timer
33voidfastcallinit_timer(structtimer_list*timer);
//////////////////////////////
#definefastcall__attribute__((regparm(3)))//通過寄存器傳遞參數(shù)可以提高性能,內(nèi)核中的大部分函數(shù)都有此修飾符
#defineasmlinkage__attribute__((regparm(0)))函數(shù)定義前加宏asmlinkage,表示這些函數(shù)通過堆棧而不是通過寄存器傳遞參數(shù)。
gcc編譯器在匯編過程中調(diào)用c語言函數(shù)時傳遞參數(shù)有兩種方法:一種是通過堆棧,另一種是通過寄存器。缺省時采用寄存器,假如你要在你的匯編過程中調(diào)用c語言函數(shù),并且想通過堆棧傳遞參數(shù),你定義的c函數(shù)時要在函數(shù)前加上宏asmlinkage
//////////////////////////////
內(nèi)核函數(shù)init_timer()用來初始化一個定時器。實際上,這個初始化函數(shù)僅僅將結(jié)構(gòu)中的list成員初始化為空并獲得對應(yīng)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}
動態(tài)初始化一個內(nèi)核定時器,經(jīng)過setup_timer的調(diào)用后,內(nèi)核timer的各個域都有初始值了;該函數(shù)也可以對已經(jīng)初始化的定時器重新初始化。
2.4的內(nèi)核代碼中還沒有setup_timer函數(shù),通常動態(tài)初始化時先用init_timer,接著對function域及data域賦值。這樣用戶直接操作結(jié)構(gòu)體成員變量,若內(nèi)核timer結(jié)構(gòu)改變,則可移植性就降低了。因此2.6內(nèi)核通過DEFINE_TIMER及setup_timer對內(nèi)核定時器的初始化進行了全面封裝,用戶無需知道內(nèi)核的實現(xiàn)細節(jié),只需要按照內(nèi)核timer的API編程即可,接口是固定的,內(nèi)部實現(xiàn)細節(jié)的變化對用戶程序影響最小。
5.2內(nèi)部實現(xiàn)細節(jié)
5.2.1時間比較
在定時器應(yīng)用中經(jīng)常需要比較兩個時間值,以確定timer是否超時,所以Linux內(nèi)核在timer.h頭文件中定義了4個時間關(guān)系比較操作宏。這里我們說時刻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掛起判斷
由于定時器通常被連接在一個雙向循環(huán)隊列中等待執(zhí)行,此時我們說定時器處于pending狀態(tài)。因此函數(shù)time_pending()就可以用entry成員是否為空來判斷一個定時器是否處于pending狀態(tài)。如下所示(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內(nèi)部刪除
函數(shù)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}
函數(shù)detach_timer()用來將一個定時器從相應(yīng)的內(nèi)核定時器隊列中刪除。前提條件,該定時器處于定時器隊列中。首先將起從鏈表中刪除,根據(jù)clear_pending標志是否清除entry->next。
5.2.5內(nèi)部修部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);
該函數(shù)首先判斷回調(diào)函數(shù)是否設(shè)置否則返回。然后獲得timer所在的根base,判斷是否已經(jīng)是否已經(jīng)添加,若已經(jīng)添加則調(diào)用detach_timer()函數(shù)將該定時器從它原來所屬的鏈表中刪除。接著獲得更新的timer所在的根base,最后調(diào)用internal_add_timer()函數(shù)將該定時器根據(jù)它新的expires值重新插入到相應(yīng)的鏈表中。
5.3添加定時器到內(nèi)核中add_timer
函數(shù)add_timer()用來將參數(shù)timer指針所指向的定時器插入到一個合適的定時器鏈表中。它首先調(diào)用timer_pending()函數(shù)判斷所指定的定時器是否已經(jīng)位于在某個定時器向量中等待執(zhí)行。如果是,則不進行任何操作,只是打印一條內(nèi)核告警信息就返回了;如果不是,則調(diào)用__mod_timer函數(shù)完成實際的插入操作。其源碼如下(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實現(xiàn)了所有timer的更改。add_timer只是m
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
- 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 《密封件基礎(chǔ)知識》課件
- 2024年貴州建設(shè)職業(yè)技術(shù)學院單招職業(yè)技能測試題庫標準卷
- 單位管理制度集合大全人事管理十篇
- 單位管理制度匯編大全人事管理
- 單位管理制度合并匯編【人員管理】
- 單位管理制度呈現(xiàn)匯編職工管理篇十篇
- 單位管理制度呈現(xiàn)大全人員管理
- 《礦山勞動衛(wèi)生》課件
- 《生活中的問題》課件
- 《安全防護欄標準》課件
- 工藝豎井開挖支護施工技術(shù)方案(清楚明了)
- 水利五大員施工員教材講義
- 醫(yī)療機構(gòu)資產(chǎn)負債表(通用模板)
- 廢舊鋰離子電池高值資源化回收利用項目環(huán)評報告書
- 審計英語詞匯大全講課教案
- JIS G3507-1-2021 冷鐓用碳素鋼.第1部分:線材
- 初二家長會ppt通用PPT課件
- 小學生家庭作業(yè)布置存在的誤區(qū)及改進策略論文1
- 生物醫(yī)學研究的統(tǒng)計學方法課后習題答案 2014 主編 方積乾
- 牛仔面料成本核算
- 加拿大礦業(yè)政策
評論
0/150
提交評論