對於每一個linux編程愛好者來說,他們都有一個共同的心願,就是了解linux的內核。但是linux內(nei)核(he)的(de)龐(pang)大(da)與(yu)複(fu)雜(za)讓(rang)人(ren)望(wang)而(er)生(sheng)畏(wei)。往(wang)往(wang)是(shi)鼓(gu)足(zu)勇(yong)氣(qi)一(yi)頭(tou)紮(zha)進(jin)去(qu),學(xue)得(de)昏(hun)天(tian)黑(hei)地(di)的(de),卻(que)沒(mei)有(you)學(xue)到(dao)什(shen)麼(me)。這(zhe)裏(li)我(wo)想(xiang)說(shuo),初(chu)學(xue)者(zhe)不(bu)妨(fang)先(xian)學(xue)習(xi)學(xue)習(xi)內(nei)核(he)中(zhong)一(yi)些(xie)簡(jian)單(dan)的(de)函(han)數(shu),從(cong)中(zhong)既(ji)可(ke)以(yi)得(de)到(dao)樂(le)趣(qu),又(you)能(neng)了(le)解(jie)到(dao)內(nei)核(he)的(de)一(yi)些(xie)編(bian)程(cheng)風(feng)格(ge)。然(ran)後(hou),再(zai)將(jiang)linux劃分成幾個部分,如進程調度、內(nei)存(cun)管(guan)理(li)等(deng),對(dui)每(mei)個(ge)部(bu)分(fen)從(cong)原(yuan)理(li)上(shang)去(qu)把(ba)握(wo)了(le)解(jie)。接(jie)著(zhe),在(zai)詳(xiang)細(xi)分(fen)析(xi)各(ge)個(ge)部(bu)分(fen)的(de)具(ju)體(ti)實(shi)現(xian)。最(zui)後(hou),各(ge)部(bu)分(fen)串(chuan)在(zai)一(yi)起(qi),把(ba)過(guo)去(qu)單(dan)獨(du)分(fen)析(xi)時(shi),不(bu)懂(dong)的(de)地(di)方(fang)加(jia)以(yi)重(zhong)新(xin)了(le)解(jie)。這(zhe)樣(yang)循(xun)環(huan)監(jian)禁(jin),可(ke)以(yi)讓(rang)我(wo)們(men)更(geng)快(kuai)更(geng)係(xi)統(tong)的(de)學(xue)習(xi)linux的內核。
這是我對內核學習的一些理解,歡迎各位提寶貴意見。我今天向大家介紹的是linux內核中一個有趣的函數calibrate_delay()。
calibrate_delay()函數可以計算出cpu在一秒鍾內執行了多少次一個極短的循環,計算出來的值經過處理後得到BogoMIPS值,Bogo是Bogus(偽)的意思,MIPS是millions of instructions per second(百萬條指令每秒)的縮寫。這樣我們就知道了其實這個函數是linux內核中一個cpu性xing能neng測ce試shi函han數shu。由you於yu內nei核he對dui這zhe個ge數shu值zhi的de要yao求qiu不bu高gao,所suo以yi內nei核he使shi用yong了le一yi個ge十shi分fen簡jian單dan而er有you效xiao的de算suan法fa用yong於yu得de到dao這zhe個ge值zhi。這zhe個ge值zhi雖sui然ran不bu準zhun確que,但dan也ye足zu以yi令ling我wo們men心xin動dong。如ru果guo你ni想xiang了le解jie自zi己ji機ji器qi的deBogoMIPS,你可以察看/proc/cpuinfo文件中的最後一行。在你知道了自己cpu的BogoMIPS之後,如果你覺得不過癮,那麼讓我們一起來看看calibrate_delay函數是怎麼完成工作的。
下麵是calibrate_delay的源代碼,我在每行之前都加上了行號,以便講解。
1 #define LPS_PREC 8
2 void __init calibrate_delay(void)
3 {
4 unsigned long ticks,loopbit;
5 int lps_precision=LPS_PREC
6
7 loops_per_sec=(1<<12);
8
9 printk(“Calibrating delay loop…”);
10 while(loops_per_sec<<=1) {
11 /* wait for “start of” clock tick */
12 ticks=jiffies;
13 while(ticks==jiffies)
14 /* nothing */;
15 /* Go… */
16 ticks=jiffies;
17 __delay(loops_per_sec);
18 ticks=jiffies-ticks;
19 if(ticks)
20 break;
21 }
22
23 /* Do a binary approximation to get loops_per_second set
24 * to equal one clock (up to lps_precision bits) */
25 loops_per_sec >>=1;
26 loopbit=loop_per_sec;
27 while(lps_precision-- && (loopbit >>=1) ) {
28 loops_per_sec |= loopbit;
29 ticks=jiffies;
30 while(ticks==jiffies);
31 ticks=jiffies;
32 __delay(loops_per_sec);
33 if(jiffies!=ticks) /* longer than 1 tick */
34 loops_per_sec &=~loopbit;
35 }
36 /* finally,adjust loops per second in terms of seconds
37 * instead of clocks */
38 loops_per_sec *= HZ;
39 /* Round the value and print it */
40 printk(“%lu.%02lu BogoMIPSn”,
41 (loops_per_sec+2500)/500000,
42 ((loops_per_sec+2500)/5000) % 100);
43 }
對calibrate_delay()函數分析如下:
1 定義計算BogoMIPS的精度,這個值越大,則計算出的BogoMIPS越精確。
7 loops_per_sec為每秒鍾執行一個極短的循環的次數。
9 printk()是內核消息日誌打印函數,用法同printf()函數。
10 第10至21行,是第一次計算loops_per_sec的值,這次計算隻是一個粗略的計算,為下麵的計算打好基礎。
11 第11 至16行,是用於等待一個新的定時器滴答(它大概是百萬分之一秒)的開始。可以想象我們要計算loops_per_secdezhi,keyizaiyigedidadekaishishi,lijizhongfuzhixingyigejiduandexunhuan,dangyigedidajieshushi,zhegexunhuanzhixingleduoshaocijiushiwomenyaoqiudechubudezhi,zaiyongtachengyiyimiaozhongneidedidashujiushiloops_per_sec的值。
12 係統用jiffies全局變量記錄了從係統開始工作到現在為止,所經過的滴答數。它會被內核自動更新。這行語句用於記錄當前滴答數到tick變量中。
13 注意這是一個沒有循環體得空循環,第14行僅有一個“;”號。這條循環語句是通過判斷tick的值與jiffies的值是否不同,來判斷jiffies是否變化,即是否一個新的滴答開始了
16 記錄下新的滴答數以備後用。
17 根據loops_per_sec值進行延時(及執行loop_per_sec次極短循環)。
18 以下三行用於判斷執行的延時是否超過一個滴答。一般loops_per_sec的初始值並不大,所以循環會逐步加大loops_per_sec的值,直到延時超過一個滴答。我們可以看出,前一次loops_per_sec的(de)值(zhi)還(hai)因(yin)太(tai)小(xiao)不(bu)合(he)適(shi)時(shi),經(jing)過(guo)一(yi)次(ci)增(zeng)大(da),它(ta)提(ti)高(gao)了(le)兩(liang)倍(bei),滿(man)足(zu)了(le)循(xun)環(huan)條(tiao)件(jian),跳(tiao)出(chu)循(xun)環(huan),而(er)這(zhe)個(ge)值(zhi)實(shi)在(zai)是(shi)誤(wu)差(cha)太(tai)大(da),所(suo)以(yi)我(wo)們(men)還(hai)要(yao)經(jing)過(guo)第(di)二(er)次(ci)計(ji)算(suan)。這(zhe)裏(li)還(hai)要(yao)注(zhu)意(yi)的(de)是(shi)通(tong)過(guo)上(shang)麵(mian)的(de)分(fen)析(xi),我們可以知道更加精確的loops_per_sec的值應該在現在的值與它的一半之間。
23 這裏開始就是第二次計算了。它用折半查找法在我們上麵所說的範圍內計算出了更精確的loops_per_sec的值。
25 義查找範圍的最小值,我把它稱為起點。
26 定義查找範圍,這樣我們就可以看到loop_per_sec的值在“起點”與“起點加範圍(終點)”之間。
27 進入循環,將查找範圍減半。
28 重新定義起點,起點在“原起點加27行減半範圍”處,即新起點在原先起點與終點的中間。這時我們可以看出loops_per_sec在“新起點”與“新起點加減半範圍(新終點)”之間。
29 第29至32行與第12至17行一致,都是等待新的滴答,執行延時。
33 如果延時過短,說明loops_per_sec的值小了,將會跳過這部分,再次進入循環。它將是通過不斷的折半方式來增大。如果延時過長,說明loops_per_sec的值大了,將起點重新返回原起點,當再次進入循環,由於範圍減半,故可以達到減小的效果。
38 計算出每秒執行極短循環的次數。從這裏我們可以看出它好像是個死循環,所以加入了lps_precision變量,來控製循環,即LPS_PREC越大,循環次數越多,越精確。可能這些不太好懂,總的說來,它首先將loop_per_sec的值定為原估算值的1/2,作為起點值(我這樣稱呼它),以估算值為終點值.然後找出起點值到終點值的中間值.用上麵相同的方法執行一段時間的延時循環.如果延時超過了一個tick,說明loop_per_sec值偏大,則仍以原起點值為起點值,以原中間值為終點值,以起點值和終點值的中間為中間值繼續進行查找,如果沒有超過一個tick,說明loop_per_sec偏小,則以原中間值為起點值,以原終點值為終點值繼續查找。
40 出BogoMIPS,並打印。
至此,我們就分析完了calibrate_delay()函數。你從中學到了什麼沒有?如果你還有什麼不明白的地方,可以給我發Email,如果你認為有什麼更好的方法,歡迎來信我們一同探討,我的Email是: