?? (ldd) ch06-時間流(轉載).txt
字號:
(LDD) Ch06-時間流(轉載)
第6章 時間流
至此,我們知道怎樣寫一個特性比較完全的字符模塊了。我們將在后面幾章陸續討論驅
動程序可以訪問的一些內核資源。本章,我們先來看看內核代碼是如何對時間問題進行
處理的。該問題包括(按復雜程度排列):
l 如何獲得當前時間
l 如何將操作延遲指定的一段時間
l 如何調度函數到指定的時間后異步執行
內核中的時間間隔
我們首先要涉及的是時鐘中斷,操作系統通過時鐘中斷來確定時間間隔。時鐘中斷的發
生頻率設定為HZ,HZ是一個與體系結構無關的常數,在文件<linux/param.h>中定義。至
少從2.0版到2.1.43版,Alpha平臺上Linux定義HZ的值為1024,而其他平臺上定義為100
。
當時鐘中斷發生時,jiffies值就加1。因此,jiffies值就是自操作系統啟動以來的時鐘
滴答的數目;jiffies在頭文件<linux/sched.h>中被定義為數據類型為unsigned long
volatile (32位無符號長整型)的變量,因此連續累加一年又四個多月后就會溢出(假定H
Z=100,1個jiffies等于1/100秒,jiffies可記錄的最大秒數為42949672.96秒,約合1.3
8年)。如果你打算連續運行Linux一年又四個多月以上,你最好買臺Alpha,那么,就是
跑五億年也不會溢出了-Alpha機器上jiffies有64位。我是無法準確地告訴你jiffies溢
出時會發生些什么的,我可沒有那么長的時間來等這件事發生。
如果你修改HZ值后重編譯內核,在用戶空間你不會注意到有什么不同。盡管jiffies值以
不同的步長增長,但一切似乎還正常。會產生更多的中斷,系統開銷更大了,但是因為
處理器調度得更頻繁了,系統會很不穩定。
我在我的PC上測試了一些jiffies值:在100Hz時,系統的響應很慢;100Hz是缺省值;在
1kHz時,系統跑的相當慢,但響應得很快;在10kHz時,系統極慢;在50kHz時,系統已
經令人無法忍受了。修改中斷頻率還有副作用,jiffies值溢出要花的時間不同了(10kHz
的時鐘頻率下,只要五天),BogoMips值的計算精度也不同了*。而且還有一些別處都沒
提及的硬件上的限制。例如,19是PC上時鐘頻率的能設的最小值,其他體系結構上也存
在著類似的限制。
此外,在使用模塊時還要小心。如果你改變了HZ的定義,你必須重新編譯和安裝你使用
的所有模塊。內核中一切都與HZ值有關,包括模塊。我是在增加了HZ值因而無法雙擊鼠
標后,發現到這一點的。
總而言之,時鐘中斷的最好方法是保留HZ的缺省值,因為我們可以完全相信內核的開發
者們,他們一定已經為我們挑選了最佳值。關于本專題的更多信息可參見頭文件<linux/
timex.h>。
獲取當前時間
內核一般通過jiffies值來獲取當前時間。盡管該數值表示的是自上次系統啟動
到當前的時間間隔,但因為驅動程序的生命期只限于系統的運行期(uptime),所以也是
可行的。驅動程序利用jiffies的當前值來計算不同事件間的時間間隔(我在kmouse模塊
中就用它來分辨鼠標的單雙擊)。簡而言之,利用jiffies值來測量時間間隔還是很有效
的。
驅動程序一般不需要知道墻上時間,通常只有象cron和at這樣用戶程序才需要墻
上時間。需要墻上時間的情形是使用設備驅動程序的特殊情況,此時可以通過用戶程序
來將墻上時間轉換成系統時鐘。
如果驅動程序真的需要獲取當前時間,可以使用do_gettimeofday函數。該函數
并不返回今天是本周的星期幾或類似的信息;它是用微秒值來填充一個指向struct
并不返回今天是本周的星期幾或類似的信息;它是用微秒值來填充一個指向struct
timeval的指針變量。相應的原型如下:
#include <linux/time.h>
void do_gettimeofday(struct timeval *tv);
源碼中聲明的do_gettimeofday在Alpha和Sparc之外的體系結構上有“接近微秒級的分辨
率” ,在Alpha和Sparc上和jiffies值的分辨率一樣。Sparc的移植版本在2.1.34版的內
核中升級了,可以支持更細粒度的時間度量。當前時間也可以通過xtime變量(類型為str
uct timeval)獲得(但精度差些);但是,并不鼓勵直接使用該變量,因為除非關閉中斷
,無法原子性地訪問timeval變量的兩個域tv_sec和tv_usec。使用do_gettimeofday填充
的timeval結構變量會更快些。
令人遺憾的是,1.2版的Linux并未開放do_gettimeofday函數。如果你要獲取當前時間,
又希望程序能夠向后兼容,你應該使用該函數下面的這個版本:
#if LINUX_VERSION_CODE < VERSION_CODE(1,3,46)
/*
* 內核頭文件已經該函數是非靜態的。
* 我們應先用其他名字來實現它,再 #define 它。
*/
extern inline void redo_gettimeofday(struct timeval *tv)
{
unsigned long flags;
save_flags(flags);
cli();
*tv=xtime;
restore_flags(flags);
}
#define do_gettimeofday(tv) redo_gettimeofday(tv)
#endif
這個版本比實際的版本精度要差些,因為它只使用xtime結構的當前值,這個值
并不會比jiffies值的粒度更細。但是,它卻能在不同的Linux平臺間移植。“實際的”
函數是通過與體系結構相關的代碼查詢硬件時鐘來獲得更高的分辨率。
獲取當前時間的代碼可見于jit("Just In Time")模塊,源文件可以從O'Reilly
公司的FTP站點獲得。jit模塊將創建/proc/currentime文件,它以ASCII碼的形式返回它
公司的FTP站點獲得。jit模塊將創建/proc/currentime文件,它以ASCII碼的形式返回它
讀該文件的時間。我選擇用動態的/proc文件,是因為這樣模塊代碼量會小些-不值得為
返回兩行文本而寫一整個設備驅動程序。
如果你用cat命令在一個時鐘滴答內多次讀該文件,就會發現xtime和do_gettime
ofday兩者的差異了:
morgana%cat /proc/currentime /proc/currentime /proc/currentime
gettime: 846157215.937221
xtime: 846157215.931188
jiffies: 1308094
gettime: 846157215.939950
xtime: 846157215.931188
jiffies: 1308094
gettime: 846157215.942465
xtime: 846157215.941188
jiffies: 1308095
延遲執行
使用定時器中斷和jiffies值,時鐘滴答的整數倍的時間間隔很容易獲得,但更小的時延
,程序員必須通過軟件循環來獲得,這將在本節稍后處介紹。
盡管我將會介紹一些很奇特的技術,但我認為最好先看些簡單的延遲實現代碼,盡管下
面要介紹的第一種實現并不是最好的。
長延遲
如果你想將執行延遲幾個時鐘滴答或者你對延遲的精度要求不高(比如,你想延遲整數數
目的秒數),最簡單的實現(最笨的)如下,也就是忙等待:
unsigned long j=jiffies+jit_delay*HZ;
while (jiffies<j)
/* 什么也不做 */
這種實現當然要避免*。我在這提到它只是因為有時你可以運行這段代碼來更好地理解其
他實現(在本章稍后處我會說明如何利用忙等待來做測試)。
還是先看看這段代碼是如何工作的。因為內核的頭文件中jiffies被聲明為volatile型變
量,每次C代碼訪問它時都會重新讀取它,因此該循環可以起到延遲的作用。盡管也是“
正確”的實現,但這個忙等待循環在延遲期間會鎖住整臺計算機;因為調度器不會中斷
運行在內核空間的進程。而且當前的內核實現為不可重入的,因此內核中的忙等待循環
將會鎖住一臺SMP機器的所有處理器。
更糟糕的是,如果在進入循環之前關閉了中斷,jiffies值就不會得到更新,那么while
循環的條件就永真。你將不得不按下大紅按鈕(指冷啟動)。
這種延遲和下面的幾種延遲方法都在jit模塊中實現了。由該模塊創建的所有/proc/jit*
文件每次被讀時都延遲整整1秒。如果你想測試忙等待代碼,可以讀/proc/jitbusy文件
,當該文件的read方法被調用時它將進入忙等待循環,延遲1秒;而象dd
if=/proc/jitbusy bs=1這樣的命令每次讀一個字符就要延遲1秒。
可以想見,讀/proc/jitbusy文件會大大影響系統性能,因為此時計算機要到1秒后才能
運行其他進程。
更好的延遲方法如下,它允許其他進程在延遲的時間間隔內運行,盡管這種方法不能用
于實時任務或者其他時間要求很嚴格的場合:
while (jiffies<j)
schedule();
這個例子和下面各例中的變量j應是延遲到達時的jiffies值,在忙等待時一般就
是象這樣使用的。
這種循環(可以通過讀/proc/jitsched文件來測試它)延遲方法還不是最優的。系
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -