Notes:用戶空間時間相關接口函數:
類型 | API | 精度 | 說明 |
時間 | time stime |
time_t 精度為秒級 |
逐漸要被淘汰。需要定義__ARCH_WANT_SYS_TIME才能支持。 設定時間的進程需具備CAP_SYS_TIME權限。 |
gettimerofday settimeofday |
timeval 精度為微秒級別 |
設定時間的進程需具備CAP_SYS_TIME權限。 |
|
tims clock |
tims進程創建后使用的CPU時間數量。 clock進程使用的總的CPU時間(用戶和系統)。 |
||
clock_gettime clock_settime clock_getres clock_getcpuclockid pthread_getcpuclockid |
timespec 精度為納秒級別 |
提供多種類型的是種類型(參照下面描述)。 設定時間的進程需具備CAP_SYS_TIME權限。 |
|
adjtime adjtimex clock_adjtime |
settimeofday()調用造成的系統時間突然變化,可能會對依賴於系統時鍾單調遞增用用造成有害影響。推薦使用adjtime()。 | ||
睡眠 | sleep | 秒 | 基於nanosleep實現,基於hrtimer。 這兩個函數需要,查看libc/unistd/sleep.c和usleep.c的實現。有的是基於nanosleep實現,有的是基於alarm(sleep)或者select(usleep)實現的。 |
usleep | 微秒 | ||
nanosleep | 納秒 | hrtimer.c中調用hrtimer_nanosleep。 | |
clock_nanosleep | timespec 納秒 |
posix-timers.c調用k_clock->nsleep()。 | |
定時器 | alarm | 秒 | 精度低,容易被覆蓋。 |
setitimer getitimer |
itimerval 微妙 |
同樣是基於信號。最多三個。 | |
timer_create timer_settime timer_gettime timer_delete timer_getoverrun |
納秒 | 多timer共存,多種時鍾類型。 | |
timerfd_create timerfd_settime timerfd_gettime |
納秒 | 基於select或poll等待。 | |
一、前言
從應用程序的角度看,內核需要提供的和時間相關的服務有三種:
1、和系統時間相關的服務。例如,在向數據庫寫入一條記錄的時候,需要記錄操作時間(何年何月何日何時)。
2、讓進程睡眠一段時間
3、和timer相關的服務。在一段指定的時間過去后,kernel要alert用戶進程
本文主要描述和時間子系統相關的用戶空間接口函數知識。
二、和系統時間相關的服務
1、秒級別的時間函數:time和stime
time和stime函數的定義如下:
#include <time.h>
time_t time(time_t *t);
int stime(time_t *t);
time函數返回了當前時間點到linux epoch的秒數(內核中timekeeper模塊保存了這個值,timekeeper->xtime_sec)。stime是設定當前時間點到linux epoch的秒數。對於linux kernel,設定時間的進程必須擁有CAP_SYS_TIME的權利,否則會失敗。
linux kernel用系統調用sys_time和sys_stime來支持這兩個函數。實際上,在引入更高精度的時間相關的系統調用之后(例如:sys_gettimeofday),上面這兩個系統調用可以用新的系統調在用戶空間實現time和stime函數。在kernel中,只有定義了__ARCH_WANT_SYS_TIME這個宏,系統才會提供上面這兩個系統調用。當然,提供這樣的系統調用多半是為了兼容舊的應用軟件。
配合上面的接口函數還有一系列將當前時間點到linux epoch的秒數轉換成適合人類閱讀的接口函數,例如asctime, ctime, gmtime, localtime, mktime, asctime_r, ctime_r, gmtime_r, localtime_r ,這些函數主要用來將time_t類型的時間轉換成break-down time或者字符形式。
2、微秒級別的時間函數:gettimeofday和settimeofday
#include <sys/time.h>
int gettimeofday(struct timeval *tv, struct timezone *tz);
int settimeofday(const struct timeval *tv, const struct timezone *tz);
這兩個函數和上一小節秒數的函數類似,只不過時間精度可以達到微秒級別。gettimeofday函數可以獲取從linux epoch到當前時間點的秒數以及微秒數(在內核態,這個時間值仍然是通過timekeeper模塊獲得的,具體接口是getnstimeofday64,該接口的時間精度是納秒級別的,不過沒有關系,除以1000就獲得微秒級別的精度了),settimeofday則是設定從linux epoch到當前時間點的秒數以及微秒數。同樣的,設定時間的進程必須擁有CAP_SYS_TIME的權利,否則會失敗。tz參數是由於歷史原因而存在,實際上內核並沒有對timezone進行支持。
顯然,sys_gettimeofday和sys_settimeofday這兩個系統調用是用來支持上面兩個函數功能的,值得一提的是:這些系統調用在新的POSIX標准中 gettimeofday和settimeofday接口函數被標注為obsolescent,取而代之的是clock_gettime和clock_settime接口函數
3、納秒級別的時間函數:clock_gettime和clock_settime
#include <time.h>
int clock_getres(clockid_t clk_id, struct timespec *res);
int clock_gettime(clockid_t clk_id, struct timespec *tp);
int clock_settime(clockid_t clk_id, const struct timespec *tp);
如果不是clk_id這個參數,clock_gettime和clock_settime基本上是不用解釋的,其概念和gettimeofday和settimeofday接口函數是完全類似的,除了精度是納秒。clock就是時鍾的意思,它記錄了時間的流逝。clock ID當然就是識別system clock(系統時鍾)的ID了,定義如下:
CLOCK_REALTIME
CLOCK_MONOTONIC
CLOCK_MONOTONIC_RAW
CLOCK_PROCESS_CPUTIME_ID
CLOCK_THREAD_CPUTIME_ID
根據應用的需求,內核維護了幾個不同系統時鍾。大家最熟悉的當然就是CLOCK_REALTIME這個系統時鍾,因為它表示了真實世界的牆上時鍾(前面兩節的接口函數沒有指定CLOCK ID,實際上獲取的就是CLOCK_REALTIME的時間值)。CLOCK_REALTIME這個系統時鍾允許用戶對其進行設定(當然要有CAP_SYS_TIME權限),這也就表示在用戶空間可以對該系統時鍾進行修改,產生不連續的時間間斷點。除此之外,也可以通過NTP對該時鍾進行調整(不會有間斷點,NTP調整的是local oscillator和上游服務器頻率誤差而已)。
僅僅從名字上就可以看出CLOCK_MONOTONIC的系統時鍾應該是單調遞增的,此外,該時鍾也是真實世界的牆上時鍾,只不過其基准點不一定是linux epoch(當然也可以是),一般會把系統啟動的時間點設定為其基准點。隨后該時鍾會不斷的遞增。除了可以通過NTP對該時鍾進行調整之外,其他任何程序不允許設定該時鍾,這樣也就保證了該時鍾的單調性。
CLOCK_MONOTONIC_RAW具備CLOCK_MONOTONIC的特性,除了NTP調整。也就是說,clock id是CLOCK_MONOTONIC_RAW的系統時鍾是一個完全基於本地晶振的時鍾。不能設定,也不能對對晶振頻率進行調整。
在調用clock_gettime和clock_settime接口函數時,如果傳遞clock id參數是CLOCK_REALTIME的話,那么這兩個函數的行為和前兩個小節描述的一致,除了是ns精度。讀到這里,我詳細廣大人民群眾不免要問:為何要有其他類型的系統時鍾呢?MONOTONIC類型的時鍾相對比較簡單,如果你設定事件A之后5秒進行動作B,那么用MONOTONIC類型的時鍾是一個比較好的選擇,如果使用REALTIME的時鍾,當用戶在事件A和動作B之間插入時間設定的操作,那么你設定事件A之后5秒進行動作B將不能觸發。此外,用戶需要了解系統啟動時間,這個需求需要使用MONOTONIC類型的時鍾的時鍾。需要指出的是MONOTONIC類型的時鍾不是絕對時間的概念,多半是計算兩個采樣點之間的時間,並且保證采樣點之間時間的單調性。MONOTONIC_RAW是一個底層工具,一般而言程序員不會操作它,使用MONOTONIC類型的時鍾就夠用了,當然,一些高級的應用場合,例如你想使用另外的方法(不是NTP)來調整時間,那么就可以使用MONOTONIC_RAW了。
有些應用場景使用real time的時鍾(牆上時鍾)是不合適的,例如當我們進行系統中各個應用程序的性能分析和統計的時候。正因為如此,kernel提供了基於進程或者線程的系統時鍾,也就是CLOCK_PROCESS_CPUTIME_ID和CLOCK_THREAD_CPUTIME_ID了。當我們打算使用基於進程或者線程的系統時鍾的時候,需要首先獲取clock id:
#include <time.h>
int clock_getcpuclockid(pid_t pid, clockid_t *clock_id);
如果是線程的話,需要調用pthread_getcpuclockid接口函數:
#include <pthread.h>
#include <time.h>int pthread_getcpuclockid(pthread_t thread, clockid_t *clock_id);
雖然這組函數接口的精度可以達到ns級別,但是實際的系統可以達到什么樣的精度是實現相關的,因此,clock_getres用來獲取系統時鍾的精度。
4、系統時鍾的調整
設定系統時間是一個比較粗暴的做法,一旦修改了系統時間,系統中的很多依賴絕對時間的進程會有各種奇奇怪怪的行為。正因為如此,系統提供了時間同步的接口函數,可以讓外部的精准的計時服務器來不斷的修正系統時鍾。
(1)adjtime接口函數
int adjtime(const struct timeval *delta, struct timeval *olddelta);
該函數可以根據delta參數緩慢的修正系統時鍾(CLOCK_REALTIME那個)。olddelta返回上一次調整中尚未完整的delta。
(2)adjtimex
#include <sys/timex.h>
int adjtimex(struct timex *buf);
RFC 1305定義了更復雜,更強大的時間調整算法,因此linux kernel通過sys_adjtimex支持這個算法,其用戶空間的接口函數就是adjtimex。由於這個算法過去強大,這里就不再贅述,等有時間、有興趣之后再填補這里的空白吧。
Linux內核提供了sys_adjtimex系統調用來支持上面兩個接口函數。此外,還提供了sys_clock_adjtime的系統調用來支持POSIX clock tunning。
三、進程睡眠
1、秒級別的sleep函數:sleep
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
調用該函數會導致當前進程sleep,seconds之后(基於CLOCK_REALTIME)會返回繼續執行程序。該函數的返回值說明了進程沒有進入睡眠的時間。例如如果我們想要睡眠8秒,但是由於siganl中斷了睡眠,只是sleep了5秒,那么返回值就是3,表示有3秒還沒有睡。
2、微秒級別的sleep函數:usleep
#include <unistd.h>
int usleep(useconds_t usec);
概念上和sleep一樣,不過返回值的定義不同。usleep返回0表示執行成功,返回-1說明執行失敗,錯誤碼在errno中獲取。
3、納秒級別的sleep函數:nanosleep
#include <time.h>
int nanosleep(const struct timespec *req, struct timespec *rem);
usleep函數已經是過去式,不建議使用,取而代之的是nanosleep函數。req中設定你要sleep的秒以及納秒值,然后調用該函數讓當前進程sleep。返回0表示執行成功,返回-1說明執行失敗,錯誤碼在errno中獲取。EINTR表示該函數被signal打斷。rem參數是remaining time的意思,也就是說還有多少時間沒有睡完。
linux kernel並沒有提供sleep和usleep對應的系統調用,sleep和usleep的實現位於c lib。在有些系統中,這些實現是依賴信號的,也有的系統使用timer來實現的,對於GNU系統,sleep和usleep和nanosleep函數一樣,都是通過kernel的sys_nanosleep的系統調用實現的(底層是基於hrtimer)。
4、更高級的sleep函數:clock_nanosleep
#include <time.h>
int clock_nanosleep(clockid_t clock_id, int flags,
const struct timespec *request,
struct timespec *remain);
clock_nanosleep接口函數需要傳遞更多的參數,當然也就是意味着它功能更強大。clock_id說明該接口函數不僅能基於real time clock睡眠,還可以基於其他的系統時鍾睡眠。flag等於0或者1,分別指明request參數設定的時間值是相對時間還是絕對時間。
四、和timer相關的服務
1、alarm函數
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
alarm函數是使用timer最簡單的接口。在指定秒數(基於CLOCK_REALTIME)的時間過去后,向該進程發送SIGALRM信號。當然,調用該接口的程序需要設定signal handler。
2、Interval timer函數
#include <sys/time.h>
int getitimer(int which, struct itimerval *curr_value);
int setitimer(int which, const struct itimerval *new_value,
struct itimerval *old_value);
Interval timer函數的行為和alarm函數類似,不過功能更強大。每個進程支持3種timer,不同的timer定義了如何計時以及發送什么樣的信號給進程,which參數指明使用哪個timer:
(1)ITIMER_REAL。基於CLOCK_REALTIME計時,超時后發送SIGALRM信號,和alarm函數一樣。
(2)ITIMER_VIRTUAL。只有當該進程的用戶空間代碼執行的時候才計時,超時后發送SIGVTALRM信號。
(3)ITIMER_PROF。只有該進程執行的時候才計時,不論是執行用戶空間代碼還是陷入內核執行(例如系統調用),超時后發送SIGPROF信號。
struct itimerval定義如下:
struct itimerval {
struct timeval it_interval; /* next value */
struct timeval it_value; /* current value */
};
兩個成員分別指明了本次和下次(超期后如何設定)的時間值。通過這樣的定義,interval timer可以實現one shot類型的timer和periodic的timer。例如current value設定為5秒,next value設定為3秒,設定這樣的timer后,it_value值會不斷遞減,直到5秒后觸發,而隨后it_value的值會被重新加載(使用it_interval的值),也就是等於3秒,之后會按照3為周期不斷的觸發。
old_value返回上次setitimer函數的設定值。getitimer函數獲取當前的Interval timer的狀態,其中的it_value成員可以得到當前時刻到下一次觸發點的世時間信息,it_interval成員保持不變,除非你重新調用setitimer重新設定。
雖然interval timer函數也是POSIX標准的一部分,不過在新的POSIX標准中,interval timer接口函數被標注為obsolescent,取而代之的是POSIX timer接口函數。
3、更高級,更靈活的timer函數
上一節介紹的Interval timer函數還是有功能不足之處:例如一個進程只能有ITIMER_REAL、ITIMER_VIRTUAL和ITIMER_PROF三個timer,如果連續設定其中一種timer(例如ITIMER_REAL),這會導致第一個設定被第二次設定覆蓋。此外,超時處理永遠是用信號的方式,而且發送的signal不能修改。當mask信號處理的時候,雖然timer多次超期,但是signal handler只會調用一次,無法獲取更詳細的信息。最后一點,Interval timer函數精度是微秒級別,精度有進一步提升的空間。正因為傳統的Interval timer函數的不足之處,POSIX標准定義了更高級,更靈活的timer函數,我們稱之POSIX (interval)Timer。
(1)創建timer
#include <signal.h>
#include <time.h>int timer_create(clockid_t clockid, struct sigevent *sevp, timer_t *timerid);
在這個接口函數中,clock id相信大家都很熟悉了, timerid一看就是返回的timer ID的句柄,就像open函數返回的文件描述符一樣。因此,要理解這個接口函數重點是了解struct sigevent這個數據結構:
union sigval { /* Data passed with notification */
int sival_int; /* Integer value */
void *sival_ptr; /* Pointer value */
};typedef struct sigevent {
sigval_t sigev_value;
int sigev_signo;
int sigev_notify;
union {
int _pad[SIGEV_PAD_SIZE];
int _tid;struct {
void (*_function)(sigval_t);
void *_attribute; /* really pthread_attr_t */
} _sigev_thread;
} _sigev_un;
} sigevent_t;
sigev_notify定義了當timer超期后如何通知該進程,可以設定:
(a)SIGEV_NONE。不需要異步通知,程序自己調用timer_gettime來輪詢timer的當前狀態
(b)SIGEV_SIGNAL。使用sinal這樣的異步通知方式。發送的信號由sigev_signo定義。如果發送的是realtime signal,該信號的附加數據由sigev_value定義。
(c)SIGEV_THREAD。創建一個線程執行timer超期callback函數,_attribute定義了該線程的屬性。
(d)SIGEV_THREAD_ID。行為和SIGEV_SIGNAL類似,不過發送的信號被送達進程內的一個指定的thread,這個thread由_tid標識。
Notes:b/c/d都是通過發送信號這種異步通知方式;只不過c將callback函數放在一個單獨線程中進行處理;d是通過創建的線程來接收信號通知。
(2)設定timer
#include <time.h>
int timer_settime(timer_t timerid, int flags, const struct itimerspec *new_value,
struct itimerspec * old_value);
int timer_gettime(timer_t timerid, struct itimerspec *curr_value);
timerid就是上一節中通過timer_create創建的timer。new_value和old_value這兩個參數類似setitimer函數,這里就不再細述了。flag等於0或者1,分別指明new_value參數設定的時間值是相對時間還是絕對時間。如果new_value.it_value是一個非0值,那么調用timer_settime可以啟動該timer。如果new_value.it_value是一個0值,那么調用timer_settime可以stop該timer。
timer_gettime函數和getitimer類似,可以參考上面的描述。
(3)刪除timer
#include <time.h>
int timer_delete(timer_t timerid);
有創建就有刪除,timer_delete用來刪除指定的timer,釋放資源。
原創文章,轉發請注明出處。蝸窩科技
http://www.wowotech.net/timer_subsystem/timer_subsystem_userspace.html