掃盲:1秒=1000毫秒=1000000微妙=1000000000納秒
首先:linux有一個很重要的概念——節拍,它的單位是(次/秒)。2.6內核這個值是1000,系統中用一個HZ的宏表征這個值。同時有全局的jiffies變量,表征從開機以來經過的節拍次數(這里面還有故事,后面說,先記住這個)。當然還有wall_jiffies的牆上jiffies來表示從 07-01-1970 到現在的節拍數。每個節拍里面執行一次時鍾中斷。就是說,它的精度是毫秒。
接着:內核中還有一個變量xtime表征系統的實際時間(牆上時間),定義如下。其中xtime.tv_sec以秒為單位,存放從Unix祖宗定的紀元時間(19700701)到現在的秒數。xtime.tv_nsec以納秒為單位,記錄從上一秒開始經過的納秒數。就是說,它的精度是納秒。
struct timespec xtime;
struct timespec{
time_t tv_sec; //秒
long tv_nsec; //納秒
};
struct timespec{
time_t tv_sec; //秒
long tv_nsec; //納秒
};
最后:linux提供一個gettimeofday的系統調用,它會返回一個timeval的結構體,定義如下。解釋同上,tv_sec代表牆上時間的秒,tv_usec表示從上一秒到現在經過的微秒數。就是說,它的精度是微妙。
struct timeval{
long tv_sec; //秒
long tv_usec; //微妙
};
long tv_sec; //秒
long tv_usec; //微妙
};
精彩的來了:
1. 內核中的xtime會在每個時鍾中斷的時候被更新一次,也就是每個節拍更新一次。你妹!!每毫秒更新一次怎么能冒出來納秒的精度??而且,內核還有可能丟失節拍。怎么能是納秒??
2. 各種書上說,gettimeofday系統調用是讀取的xtime的值。日,為啥讀出來之后精度丟了?變成微妙了?
尋尋覓覓終於理清了故事:
針對問題1:在linux啟動的時候,一個節拍的時間長度還會以納秒為單位初始化到tick_nsec中,初始化值為999848ns,坑爹啊!不到一毫秒!節拍大約為1000.15Hz。靠!實際的節拍竟然不是准確的1000!所以在每個時鍾中斷通過wall_jiffies去更新xtime的時候得到的就是一個以納秒為最小單位的的值。所以!xtime的粒度應該是不到1毫秒,也就是精度是不到1毫秒。
針對問題2:gettimeday系統調用的讀xtime代碼部分如下:
do{
unsigned long lost;
seq = read_seqbegin(&xtime_lock);
usec = timer->get_offset(); //在計時器中取從上一次時鍾中斷到現在的微秒數
lost = jiffies - wall_jiffies;
if(lost)
usec += lost*(1000000/HZ); //HZ是節拍宏,值1000
sec = xtime.tv_sec;
usec += (xtime.tv_nsec/1000); //由納秒轉為微妙
}while(read_seqretry(&xtime_lock, seq))
unsigned long lost;
seq = read_seqbegin(&xtime_lock);
usec = timer->get_offset(); //在計時器中取從上一次時鍾中斷到現在的微秒數
lost = jiffies - wall_jiffies;
if(lost)
usec += lost*(1000000/HZ); //HZ是節拍宏,值1000
sec = xtime.tv_sec;
usec += (xtime.tv_nsec/1000); //由納秒轉為微妙
}while(read_seqretry(&xtime_lock, seq))
while部分使用了seg鎖,只看中間的就好了。加了注釋之后就很清晰了。由於節拍可能會丟失,所以lost是丟失的節拍數(不會很多)。至於計時器就比較麻煩了,timer可能有下面四種情況。
a. 如果cur_timer指向timer_hpet對象,該方法使用HPET定時器——Inter與Microsoft開發的高精度定時器頻率至少10MHz,也就是說此時可提供真正的微妙級精度。
b. 如果cur_timer指向timer_pmtmr對象,該方法使用ACPI PMT計時器(電源管理定時器)平率大約3.58MHz,也就是說也可以提供真正的微妙級精度。
c. 如果cur_timer指向timer_tsc對象,該方法使用時間戳計數器,內置在所有8086處理器,每個CPU時鍾,計數器增加一次,頻率就是CPU頻率,所以timer精度最高。完全可以勝任微妙級的精度。
d. 如果cur_timer指向timer_pit對象,該方法使用PIT計數器,也即是最開始提到的節拍計數,頻率大概是1000Hz,此時顯然不能提供精度達到微妙的時間。所以只有這種情況是假毫秒精度!
綜上:如果使用gettimeofday系統調用,只要不要使用節拍計數器就可以保證達到微妙精度的時間(刨除進程上下文時間誤差)。至於網上說的可以拿到納秒精度的時間,看起來都是錯的。除非通過修改內核,使用時間戳計數器實現。Over!
最后最后說一個事情:jiffies的定義的是4字節,你可能猜想它初始值是0。實際上,事實並非如此!linux中jiffies被初始化為0xfffb6c20,它是一個32位有符號數,正好等於-300 000。因此,計數器會在系統啟動5分鍾內溢出。這是為了使對jiffies溢出處理有缺陷的內核代碼在開發階段被發現,避免此類問題出現在穩定版本中。
參考《深入理解linux內核》

