應用層gettimeofday和系統層do_gettimeofday的關係

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

 

gettimeofdayC庫提供的函數(不是系統調用),它封裝了內核裏的sys_gettimeofday系統調用,就是說,歸根到底是系統調用。

 

但是,內核對於x86_64體系結構下,除了普通的系統調用外,還提供了sysentervsyscall方式來獲取內核態的數據。目前我們使用的操作系統大都是x86_64體系的,如果我們用strace命令跟蹤,就會發現gettimeofday命令實際上沒有執行系統調用(i386體系會有),這是因爲:x86_64體系上,使用vsyscall實現了gettimeofday這個系統調用。具體就是,創建了一個共享的內存頁面,它是在內核態的,它的數據由內核來維護,但是,用戶態也有權限訪問這個內核頁面,由此,不通過中斷gettimeofday也就拿到了系統時間。

 

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

 

一、gettimeofday做了些什麼?

它把內核保存的牆上時間和jiffies綜合處理後返回給用戶。解釋下牆上時間和jiffies是什麼:1牆上時間就是實際時間(1970/1/1號以來的時間),它是由我們主板電池供電的(裝過PC機的同學都瞭解)RTC單元存儲的,這樣即使機器斷電了時間也不用重設。當操作系統啓動時,會用這個RTC來初始化牆上時間,接着,內核會在一定精度內根據jiffies維護這個牆上時間

2jiffies就是操作系統啓動後經過的時間,它的單位是節拍數。有些體系架構,1個節拍數是10ms,但我們常用的x86體系下,1個節拍數是1ms。也就是說,jiffies這個全局變量存儲了操作系統啓動以來共經歷了多少毫秒。我們來看看gettimeofday是如何做的。 

首先它調用了sys_gettimeofday系統調用。

 

[cpp]

asmlinkage long sys_gettimeofday(struct timeval __user *tv, struct timezone __user *tz)  

{  

    if (likely(tv != NULL)) {  

        struct timeval ktv;  

        do_gettimeofday(&ktv);  

        if (copy_to_user(tv, &ktv, sizeof(ktv)))  

            return -EFAULT;  

    }  

    if (unlikely(tz != NULL)) {  

        if (copy_to_user(tz, &sys_tz, sizeof(sys_tz)))  

            return -EFAULT;  

    }  

    return 0;  

}  

大家看到,它調用do_gettimeofday函數取到當前時間存儲到局部變量ktv上,然後調用copy_to_user把結果複製到用戶空間。每個體系都有自己的實現,我這裏就簡單列下x86_64體系下do_gettimeofday的實現:

[cpp]

void do_gettimeofday(struct timeval *tv)  

{  

    unsigned long seq, t;  

    unsigned int sec, usec;  

  

    do {  

        seq = read_seqbegin(&xtime_lock);  

  

        sec = xtime.tv_sec;  

        usec = xtime.tv_nsec / 1000;  

  

        /* i386 does some correction here to keep the clock  

           monotonous even when ntpd is fixing drift.

           But they didn't work for me, there is a non monotonic

           clock anyways with ntp.

           I dropped all corrections now until a real solution can

           be found. Note when you fix it here you need to do the same

           in arch/x86_64/kernel/vsyscall.c and export all needed

           variables in vmlinux.lds. -AK */   

  

        t = (jiffies - wall_jiffies) * (1000000L / HZ) +  

            do_gettimeoffset();  

        usec += t;  

  

    } 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來維護,這個模塊會處理微秒級的中斷,並更新xtimejiffies變量。我們看下x86_64體系結構下的維護代碼:

 

[cpp]

static struct irqaction irq0 = {  

    timer_interrupt, SA_INTERRUPT, CPU_MASK_NONE, "timer", NULL, NULL  

};  

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

 

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

 

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

 

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

 

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

 

先看看它的定義:

 

[cpp]

volatile unsigned long __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(unsigned long, a) && \  

     typecheck(unsigned long, b) && \  

     ((long)(b) - (long)(a) < 0))  

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章