linux時間函式gettimeofday解析

2021-08-02 05:54:43 字數 3546 閱讀 6895

我們在程式中會頻繁地取當前時間,例如處理乙個http請求時,兩次呼叫gettimeofday取差值計算出處理該請求消耗了多少秒。這樣的呼叫無處不在,所以我們有必要詳細了解下,gettimeofday這個函式做了些什麼?核心1ms一次的時鐘中斷處理真的可以支援tv_usec欄位達到微秒精度嗎?它的呼叫成本在i386/x86_64體系架構上代價一樣嗎?如果在系統繁忙時,頻繁的呼叫它有問題嗎?

gettimeofday是c庫提供的函式(不是系統呼叫),它封裝了核心裡的sys_gettimeofday系統呼叫,就是說,歸根到底是系統呼叫。

但是,核心對於x86_64體系結構下,除了普通的系統呼叫外,還提供了sysenter和vsyscall方式來獲取核心態的資料。目前我們使用的作業系統大都是x86_64體系的,如果我們用strace命令跟蹤,就會發現gettimeofday命令實際上沒有執行系統呼叫(i386體系會有),這是因為:x86_64體系上,使用vsyscall實現了gettimeofday這個系統呼叫。具體就是,建立了乙個共享的記憶體頁面,它是在核心態的,它的資料由核心來維護,但是,使用者態也有許可權訪問這個核心頁面,由此,不通過中斷gettimeofday也就拿到了系統時間。

接下來,我來詳細回答以上4個問題。

一、gettimeofday做了些什麼?

它把核心儲存的牆上時間和jiffies綜合處理後返回給使用者。解釋下牆上時間和jiffies是什麼:1、牆上時間就是實際時間(1970/1/1號以來的時間),它是由我們主機板電池供電的(裝過pc機的同學都了解)rtc單元儲存的,這樣即使機器斷電了時間也不用重設。當作業系統啟動時,會用這個rtc來初始化牆上時間,接著,核心會在一定精度內根據jiffies維護這個牆上時間。2、jiffies就是作業系統啟動後經過的時間,它的單位是節拍數。有些體系架構,1個節拍數是10ms,但我們常用的x86體系下,1個節拍數是1ms。也就是說,jiffies這個全域性變數儲存了作業系統啟動以來共經歷了多少毫秒。我們來看看gettimeofday是如何做的。首先它呼叫了sys_gettimeofday系統呼叫。

[cpp]

asmlinkagelongsys_gettimeofday(structtimeval __user *tv,structtimezone __user *tz)  

if(unlikely(tz != null))   

return0;  

}  

大家看到,它呼叫do_gettimeofday函式取到當前時間儲存到區域性變數ktv上,www.linuxidc.com 然後呼叫copy_to_user把結果複製到使用者空間。每個體系都有自己的實現,我這裡就簡單列下x86_64體系下do_gettimeofday的實現:

[cpp]

voiddo_gettimeofday(structtimeval *tv)  

while(read_seqretry(&xtime_lock, seq));  

tv->tv_sec = sec + usec / 1000000;  

tv->tv_usec = usec % 1000000;  

}  

大家看到,只是把xtime加以jiffies修正後返回給使用者而已。而xtime變數和jiffies的維護更新頻率,就決定了時間精度,上面說了,每10或者1ms才處理一次時鐘中斷,難道精度只到1ms嗎?繼續往下看。

二、核心1ms一次的時鐘中斷真的可以支援tv_usec欄位達到微秒精度嗎?

可以,因為這個時間還會由high precision event timer來維護,這個模組會處理微秒級的中斷,並更新xtime和jiffies變數。我們看下x86_64體系結構下的維護**:

[cpp]

static

structirqaction irq0 = ;  

這個timer_interrupt函式會處理hpet時間中斷,來更新xtime變數。

三、它的呼叫成本在所有的作業系統上代價一樣嗎?如果在系統繁忙時,1毫秒內呼叫多次有問題嗎?

最上面已經說了,對於x86_64系統來說,這是個虛擬系統呼叫vsyscall!所以,這裡它不用傳送中斷!速度很快,成本低,呼叫一次的成本大概不到一微秒!

對於i386體系來說,這就是系統呼叫了!最簡單的系統呼叫都有無法避免的成本:陷入核心態。當我們呼叫gettimeofday時,將會向核心傳送軟中斷,然後將陷入核心態,這時核心至少要做下列事:處理軟中斷、儲存所有暫存器值、從使用者態複製函式引數到核心態、執行、將結果複製到使用者態。這些成本至少在1微秒以上!

四、關於jiffies值得一提的兩點

先看看它的定義:

[cpp]

volatileunsignedlong__jiffies;  

只談兩點。

1、它用了乙個c語言裡比較罕見的關鍵字volatile,這個關鍵字用於解決併發問題。c語言編譯器很喜歡做優化的,它不清楚某個變數可能會被併發的修改,例如上面的jiffies變數首先是0,如果首先乙個cpu修改了它的值為1,緊接著另乙個cpu在讀它的值,例如 __jiffies = 0; while (__jiffies == 1),那麼在核心的c**中,如果不加volatile欄位,那麼第二個cpu裡的迴圈體可能不會被執行到,因為c編譯器在對**做優化時,生成的彙編**不一定每次都會去讀記憶體!它會根據**把變數__jiffies設為0,並一直使用下去!www.linuxidc.com 而加了volatile欄位後,就會要求編譯器,每次使用到__jiffies時,都要到記憶體裡真實的讀取這個值。

2、它的型別是unsigned long,在32位系統中,最大值也只有43億不到,從系統啟動後49天就到達最大值了,之後就會清0重新開始。那麼jiffies達到最大值時的迴轉問題是怎麼解決的呢?或者換句話說,我們需要保證當jiffies迴轉為乙個小的正數時,例如1,要比幾十秒毫秒前的大正數大,例如4294967290,要達到jiffies(1)>jiffies(4294967290)這種效果。

核心是通過定義了兩個巨集來解決的:

[cpp]

#define time_after(a,b)     \

(typecheck(unsignedlong, a) && \  

typecheck(unsignedlong, b) && \  

((long)(b) - (long)(a) 

#define time_before(a,b)    time_after(b,a)  

很巧妙的設計!僅僅把unsigned long轉為long型別後相減比較,就達到了jiffies(1)>jiffies(4294967290)效果,簡單的解決了jiffies的迴轉問題。

linux時間函式

1 時間型別。linux下常用的時間型別 time t,struct timeval,struct tm 1 time t是乙個長整型,一般用來表示用1970年以來的秒數。2 struct timeval有兩個成員,乙個是秒,乙個是微妙。struct timeval long tv sec long...

linux時間函式

1 時間型別。linux下常用的時間型別 time t,struct timeval,struct tm 1 time t是乙個長整型,一般用來表示用1970年以來的秒數。2 struct timeval有兩個成員,乙個是秒,乙個是微妙。struct timeval long tv sec long...

Linux時間函式

一。時間程式設計 1.核心理論 1 時間型別 2.函式學習 1 獲取日曆時間 函式名 time 函式原型 time t time time t t 函式功能 獲取當前日曆時間 所屬標頭檔案 返回值 成功時 返回日曆時間 失敗時 返回 1 引數說明 t 不為空的儲存返回值 2 獲取格林威治時間 函式名...