Unix/Linux程式設計:POSIX時鐘
POSIX時鐘所提供的時鐘訪問API可以支援納秒級的時間精度,其中標識納秒級時間值的timespec結構同樣也用於nanosleep()呼叫
Linux 中,呼叫此 API 的程式必須以-lrt 選項進行編譯,從而與 librt(realtime,實時)函式庫相連結
獲取以及設定時鐘
POSIX 時鐘 API 的主要系統呼叫包括獲取時鐘當前值的 clock_gettime()、返回時鐘解析度的 clock_getres(),以及更新時鐘的 clock_settime()
NAME
clock_getres, clock_gettime, clock_settime - clock and time functions
SYNOPSIS
#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);
DESCRIPTION
函式clock_getres()查詢指定時鐘clk_id的解析度(精度),如果res為非空,則將其儲存在res指向的
struct timespec中。時鐘的解析度取決於實現,不能由特定程序配置。如果clock_settime()的引數
tp指向的時間值不是res的倍數,則它將被截斷為res的倍數。
clock_gettime() 、clock_settime() 檢索並設定指定時鐘clk_id的時間。
res和tp引數是timespec結構:
struct timespec {
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
系統呼叫 clock_gettime()針對引數 clockid 所指定的時鐘返回時間。 返回的時間值置於tp指標所指向的timespec結構中。雖然 timespec 結構提供了納秒級精度,但 clock_gettime()返回的時間值粒度可能還是要更大一點。系統呼叫 clock_getres()在引數 res
中返回指向 timespec 結構的指標,其中包含了由 clockid 所指定時鐘的解析度。
clockid_t 是一種由 SUSv3 定義的資料型別,用於表示時鐘識別符號,其值如下:
clockid_t | 描述 |
---|---|
CLOCK_REALTIME | 可設定的系統級實時時鐘 |
CLOCK_MONOTONIC | 不可設定的恆定態時鐘 |
CLOCK_PROCESS_CPUTIME_ID | 每程序 CPU 時間的時鐘(自 Linux 2.6.12) |
CLOCK_THREAD_CPUTIME_ID | 每執行緒 CPU 時間的時鐘(自 Linux 2.6.12) |
- CLOCK_REALTIME 時鐘是一種系統級時鐘,用於度量真實時間。與CLOCK_MONOTONIC 時鐘不同,它的設定是可以變更的。
- SUSv3 規定,CLOCK_MONOTONIC時鐘對時間的度量始於"未予規範的過去某一時點",系統啟動後就不會發生改變。該時鐘適用於那些無法容忍系統時鐘發生跳躍性變化(例如:手工改變了系統時間)的應用程式。Linux 上,這種時鐘對時間的測量始於系統啟動
- CLOCK_PROCESS_CPUTIME_ID 時鐘測量呼叫程序所消耗的使用者和系統 CPU 時間。
- CLOCK_THREAD_CPUTIME_ID 時鐘的功用與之相類似,不過測量物件是程序中的單條執行緒
- Linux 2.6.28 增加了一種新的時鐘型別:CLOCK_MONOTONIC_RAW。類似於 CLOCK_ MONOTONIC,這也是一種無法設定的時鐘,但是提供了對純基於硬體時間的訪問,且不受 NTP 時間調整的影響。這種非標準時鐘適用於專業時鐘同步應用程式。
- Linux 2.6.35 又提供了兩種新時鐘:CLOCK_REALTIME_COARSE 和 CLOCK_MONTIC_ COARSE。這些時鐘類似於 CLOCK_REALTIME 和 CLOCK_MONTONIC,適用於那些希望以最小代價獲取較低解析度時間戳的程式。這些非標準時鐘不會引發對硬體時鐘的任何訪問(訪問某些硬體時鐘源的代價高昂),其返回值的解析度為 jiffy(軟體時鐘週期)
系統呼叫 clock_settime()利用引數 tp 所指向緩衝區中的時間來設定由 clockid 指定的時鐘。
- 如果由tp指定的時間並非由clock_getres()所返回時鐘解析度的整數倍,時間會向下取整。
- 特權(CAP_SYS_TIME)程序可以設定 CLOCK_REALTIME 時鐘。該時鐘的初始值通常是自 Epoch(1970 年 1 月 1 日 0 點 0 分 0 秒)以來的時間。
獲取特定程序或執行緒的時鐘ID
要測量特定程序或者執行緒所消耗的CPU時間,首先可以藉助下面函式來獲取其時鐘ID,接著再以此返回 id 去呼叫 clock_gettime(),從而獲得程序或執行緒耗費的 CPU 時間
函式 clock_getcpuclockid()會將隸屬於 pid 程序的 CPU 時間時鐘的識別符號置於 clockid 指標所指向的緩衝區中。
NAME
clock_getcpuclockid - 獲取程序CPU時鐘的ID
SYNOPSIS
#include <time.h>
int clock_getcpuclockid(pid_t pid, clockid_t *clock_id);
DESCRIPTION
獲取ID為pid的程序的CPU時鐘的ID,並將其返回到clock_id所指向的位置
如果pid為零,則返回呼叫程序的CPU時鐘的時鐘ID。
RETURN VALUE
成功時,clock\u getcpuclockid()返回0;出錯時,它返回錯誤中列出的一個正錯誤號。
函式 pthread_getcpuclockid()是 clock_getcpuclockid()的 POSIX 執行緒版,返回的識別符號所標
識的時鐘用於度量呼叫程序中指定執行緒消耗的 CPU 時間。
NAME
pthread_getcpuclockid - 檢索執行緒的CPU時鐘ID
SYNOPSIS
#include <pthread.h>
#include <time.h>
int pthread_getcpuclockid(pthread_t thread, clockid_t *clock_id);
編譯並使用-pthread連結。
DESCRIPTION
pthread_getcpuclockid() 函式返回執行緒的CPU時鐘的時鐘ID。
RETURN VALUE
成功時,此函式返回0;出錯時,它返回一個非零的錯誤號。
高解析度休眠的改進版:clock_nanosleep()
類似於nanosleep(),Linux特有的clock_nanosleep()系統呼叫也可以暫停呼叫程序,直到歷經一段指定的時間間隔後,或者是收到訊號才恢復執行。
NAME
clock_nanosleep - high-resolution sleep with specifiable clock
SYNOPSIS
#include <time.h>
int clock_nanosleep(clockid_t clock_id, int flags,
const struct timespec *request,
struct timespec *remain);
Link with -lrt (only for glibc versions before 2.17).
引數 request 及 remain 同 nanosleep()中的對應引數目的相似。
- 預設情況下(即flags為0),由request指定的休眠間隔時間是相對時間
- 如果在flags中設定了TIMER_ABSTIME,request則表示clockid時鐘所測量的絕對時間。
這一特性對於那些需要精確休眠一段指定時間的應用程式序至關重要。如果只是先獲取當前時間,計算與目標時間的差距,再以相對時間進行休眠,程序可能執行到一半就被佔先了(優先順序更高的任務搶佔了),結果休眠時間比預期的要久
對於那些被訊號處理函式中斷並使用迴圈重啟休眠的程序來說,“嗜睡(oversleeping)”問題尤其明顯。如果以高頻率接收訊號,那麼按相對時間休眠(nanosleep()所執行的型別)的程序在休眠時間上會有較大誤差。但可以通過如下方式來避免嗜睡問題:先呼叫 clock_gettime()獲取時間,加上期望休眠的時間量,再以 TIMER_ABSTIME 標誌呼叫clock_nanosleep()函式(並且,如果被訊號處理器中斷,則會重啟系統呼叫)。
指定TIMER_ABSTIME時,不再(也不需要)使用引數remain。如果訊號處理器程式中斷了clock_nanosleep()呼叫,再次呼叫該函式來重啟休眠時,request引數不變。
clock_nanosleep()與 nanosleep()區分開來的另一特性在於,可以選擇不同的時鐘來測量休眠間隔時間。可在 clockid 中指定所期望的時鐘 CLOCK_REALTIME、CLOCK_ MONOTONIC或 CLOCK_PROCESS_CPUTIME_ID。
下面使用了clock_nanosleep來避免過度睡眠
struct timespec request;
/* Retrieve current value of CLOCK_REALTIME clock */
if (clock_gettime(CLOCK_REALTIME, &request) == -1)
errExit("clock_gettime");
request.tv_sec += 20; /* Sleep for 20 seconds from now */
s = clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &request, NULL);
if (s != 0) {
if (s == EINTR)
printf("Interrupted by signal handler\n");
else
errExitEN(s, "clock_nanosleep");
}
例程
clock_gettime
#include <time.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv)
{
struct timespec time1 = {0, 0};
struct timespec time2 = {0, 0};
float temp;
clock_gettime(CLOCK_REALTIME, &time1);
usleep(1000);
clock_gettime(CLOCK_REALTIME, &time2);
temp = (time2.tv_nsec - time1.tv_nsec) / 1000000;
printf("time = %f ms\n", temp);
return 0;
}