注:本文並非原創,我只是在原來的基礎上稍微修改了點東西,原網址:http://decimal.blog.51cto.com/1484476/410673,但是想讓更多的朋友們看到這篇文章,所以用了原創,很不錯的!
轉載:http://blog.csdn.net/jk110333/article/details/8177285
1. 概述
在Linux內核中,TCP/IP協議棧在很多用到時間比較的地方都使用了jiffies?本文介紹了什么是jiffies,jiffies溢出可能造成的問題,使用time_after等宏來正確地比較時間及其背后的原理。
2. jiffies簡介
2.1 時鍾中斷
在Linux內核中,TCP/IP協議棧在很多用到時間比較的地方都使用了jiffies。
那么jiffies是什么呢?我們知道,操作系統應該能夠在將來某個時刻准時調度某個任務,所以需要一種能保證任務准時調度運行的機制。希望支持每種操作系統的微處理器必須包含一個可周期性中斷它的可編程間隔定時器。這個周期性中斷被稱為系統時鍾滴答(或system timer),它象節拍器一樣來組織系統任務,也稱為時鍾中斷。
時鍾中斷的發生頻率設定為HZ,HZ是一個與體系結構無關的常數,在文件<linux/param.h>中定義。至少從2.0版到 2.1.43版,Alpha平台上Linux定義HZ的值為1024,而其他平台上定義為100。時鍾中斷對操作系統是非常重要的,當時鍾中斷發生時,將周期性地執行一些功能,例如:
. 更新系統的uptime
. 更新time of day
. 檢查當前任務的時間片是否用光,如果是則需要重新調度任務
. 執行到期的dynamic timer
. 更新系統資源使用統計和任務所用的時間統計
2.2 jiffies及其溢出
全局變量jiffies取值為自操作系統啟動以來的時鍾滴答的數目,在頭文件<linux/sched.h>中定義,數據類型為 unsigned long volatile (32位無符號長整型)。關於jiffies為什么要采用volatile來限定,可參考《關於volatile和jiffies.txt》。
jiffies轉換為秒可采用公式:(jiffies/HZ)計算,將秒轉換為jiffies可采用公式:(seconds*HZ)計算。
當時鍾中斷發生時,jiffies值就加1。因此連續累加一年又四個多月后就會溢出(假定HZ=100,1個jiffies等於1/100 秒,jiffies可記錄的最大秒數為(2^32 -1)/100=42949672.95秒,約合497天或1.38年),即當取值到達最大值時繼續加1,就變為了0。
在Vxworks操作系統中,定義HZ的值為60,因此連續累加兩年又三個多月后也將溢出(jiffies可記錄的最大秒數為約合2.27 年)。如果在Vxworks操作系統上的應用程序對jiffies的溢出沒有加以充分考慮,那么在連續運行兩年又三個多月后,這些應用程序還能夠穩定運行嗎?
下面我們來考慮jiffies的溢出,我們將從以下幾個方面來闡述:
. 無符號整型溢出的具體過程
. jiffies溢出造成程序邏輯出錯
. Linux內核如何來防止jiffies溢出
. time_after等比較時間先/后的宏背后的原理
. 代碼中使用time_after等比較時間先/后的宏
3. 無符號整型溢出的具體過程
我們首先來看看無符號長整型(unsigned long)溢出的具體過程。實際上,無符號整型的溢出過程都很類似。為了更具體地描述無符號長整型溢出的過程,我們以8位無符號整型為例來加以說明。
8位無符號整型從0開始持續增長,當增長到最大值255時,繼續加1將變為0,然后該過程周而復始:
0, 1, 2, ..., 253, 254, 255,
0, 1, 2, ..., 253, 254, 255,
...
4. jiffies溢出造成程序邏輯出錯
下面,通過一個例子來看jiffies的溢出。
例4-1:jiffies溢出造成程序邏輯出錯
unsigned long timeout = jiffies + HZ/2; /* timeout in 0.5s */ /* do some work ... */ do_somework(); /* then see whether we took too long */ if (timeout > jiffies) { /* we did not time out, call no_timeout_handler() ... */ no_timeout_handler(); } else { /* we timed out, call timeout_handler() ... */ timeout_handler(); }
本例的目的是從當前時間起,如果在0.5秒內執行完do_somework(),則調用no_timeout_handler()。如果在0.5秒后執行完do_somework(),則調用timeout_handler()。
我們來看看本例中一種可能的溢出情況,即在設置timeout並執行do_somework()后,jiffies值溢出,取值為0。設在設置 timeout后,timeout的值臨近無符號長整型的最大值,即小於2^32-1。設執行do_somework()花費了1秒,那么代碼應當調用 timeout_handler()。但是當jiffies值溢出取值為0后,條件timeout > jiffies成立,jiffies值(等於0)小於timeout(臨近但小於2^32-1),盡管從邏輯上講jiffies值要比timeout大。但最終代碼調用的是no_timeout_handler(),而不是timeout_handler()。
5. Linux內核如何來防止jiffies溢出
#define time_after(a,b) \ (typecheck(unsigned long, a) && \ typecheck(unsigned long, b) && \ ((long)(b) - (long)(a) < 0)) #define time_before(a,b) time_after(b,a) #define time_after_eq(a,b) \ (typecheck(unsigned long, a) && \ typecheck(unsigned long, b) && \ ((long)(a) - (long)(b) >= 0)) #define time_before_eq(a,b) time_after_eq(b,a) #define time_in_range(a,b,c) \ (time_after_eq(a,b) && \ time_before_eq(a,c)) /* Same as above, but does so with platform independent 64bit types. * These must be used when utilizing jiffies_64 (i.e. return value of * get_jiffies_64() */ #define time_after64(a,b) \ (typecheck(__u64, a) && \ typecheck(__u64, b) && \ ((__s64)(b) - (__s64)(a) < 0)) #define time_before64(a,b) time_after64(b,a) #define time_after_eq64(a,b) \ (typecheck(__u64, a) && \ typecheck(__u64, b) && \ ((__s64)(a) - (__s64)(b) >= 0)) #define time_before_eq64(a,b) time_after_eq64(b,a)
在宏time_after中,首先確保兩個輸入參數a和b的數據類型為unsigned long,然后才執行實際的比較。這是以后編碼中應當注意的地方。
6. time_after等比較時間先后的宏背后的原理
那么,上述time_after等比較時間先/后的宏為什么能夠解決jiffies溢出造成的錯誤情況呢?
我們仍然以8位無符號整型(unsigned char)為例來加以說明。仿照上面的time_after宏,我們可以給出簡化的8位無符號整型對應的after宏:
#define uc_after(a, b) ((char)(b) - (char)(a) < 0)
設a和b的數據類型為unsigned char,b為臨近8位無符號整型最大值附近的一個固定值254,下面給出隨着a(設其初始值為254)變化而得到的計算值:
a b (char)(b) - (char)(a)
254 254 0
255 - 1
0 - 2
1 - 3
...
124 -126
125 -127
126 -128
127 127
128 126
...
252 2
253 1
從上面的計算可以看出,設定b不變,隨着a(設其初始值為254)不斷增長1,a的取值變化為:
254, 255, (一次產生溢出)
0, 1, ..., 124, 125, 126, 127, 126, ..., 253, 254, 255, (二次產生溢出)
0, 1, ...
...
而(char)(b) - (char)(a)的變化為:
0, -1,
-2, -3, ..., -126, -127, -128, 127, 126, ..., 1, 0, -1,
-2, -3, ...
...
從上面的詳細過程可以看出,當a取值為254,255, 接着在(一次產生溢出)之后變為0,然后增長到127之前,uc_after(a,b)的結果都顯示a是在b之后,這也與我們的預期相符。但在a取值為 127之后,uc_after(a,b)的結果卻顯示a是在b之前。
從上面的運算過程可以得出以下結論:
使用uc_after(a,b)宏來計算兩個8位無符號整型a和b之間的大小(或先/后,before/after),那么a和b的取值應當滿足以下限定條件:
. 兩個值之間相差從邏輯值來講應小於有符號整型的最大值。
. 對於8位無符號整型,兩個值之間相差從邏輯值來講應小於128。
從上面可以類推出以下結論:
對於time_after等比較jiffies先/后的宏,兩個值的取值應當滿足以下限定條件:
兩個值之間相差從邏輯值來講應小於有符號整型的最大值。
對於32位無符號整型,兩個值之間相差從邏輯值來講應小於2147483647。
對於HZ=100,那么兩個時間值之間相差不應當超過2147483647/100秒 = 0.69年 = 248.5天。對於HZ=60,那么兩個時間值之間相差不應當超過2147483647/60秒 = 1.135年。在實際代碼應用中,需要比較先/后的兩個時間值之間一般都相差很小,范圍大致在1秒~1天左右,所以以上time_after等比較時間先 /后的宏完全可以放心地用於實際的代碼中。
7. 代碼中使用time_after等比較時間先/后的宏
下面代碼是針對例4-1修改后的正確代碼:
例7-1:在例4-1基礎上使用time_before宏后的正確代碼
unsigned long timeout = jiffies + HZ/2; /* timeout in 0.5s */ /* do some work ... */ do_somework(); /* then see whether we took too long */ if (time_before(jiffies, timeout)) { /* we did not time out, call no_timeout_handler() ... */ no_timeout_handler(); } else { /* we timed out, call timeout_handler() ... */ timeout_handler(); }
8. 結論
系統中采用jiffies來計算時間,但由於jiffies溢出可能造成時間比較的錯誤,因而強烈建議在編碼中使用time_after等宏來比較時間先后關系,這些宏可以放心使用。