我們在程序中會頻繁地取當前時間,例如處理一個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]
- 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上,www.linuxidc.com 然後調用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嗎?繼續往下。