淺析 Linux 中的時間編程和實現原理一—— Linux 應用層的時間編程

淺析 Linux 中的時間編程和實現原理一—— Linux 應用層的時間編程

 

本篇文章主要介紹了"淺析 Linux 中的時間編程和實現原理一—— Linux 應用層的時間編程",主要涉及到淺析 Linux 中的時間編程和實現原理一—— Linux 應用層的時間編程方面的內容,對於淺析 Linux 中的時間編程和實現原理一—— Linux 應用層的時間編程感興趣的同窗能夠參考一下。
 

簡介: 本文試圖完整地描述 Linux 系統中 C 語言編程中的時間問題。主要內容包括應用程序中的時間編程方法;時鐘硬件簡介;Glibc 時間函數的實現以及 Linux 內核對時間的支持和實現原理。這是第 1 部分,探討應用開發中的時間編程問題。linux

 

引子程序員

咱們都生活在時間中,但卻沒法去思考它。什麼是時間呢?彷佛這是一個永遠也不能被回答的問題。然而做爲一個程序員,在工做中,總有那麼幾回我必須思考什麼是時間。好比,須要知道一段代碼運行了多久;要在 log 文件中記錄事件發生時的時間戳;再好比須要一個定時器以便可以按期作某些計算機操做。我發現,在計算機世界中,時間在不一樣場合也每每有不一樣的含義,讓試圖思考它的人感到迷茫。但值得慶幸的是,Linux 中的時間終究是能夠理解的。所以我打算討論一下有關時間的話題,嘗試着深刻理解 Linux 系統中 C 語言編程中的時間問題。主要內容以下:算法

  • 第 1 部分是應用程序中的時間問題。有三個方面:程序計時須要;獲取當前時間;定時器。
  • 第 2 部分包括時間硬件簡介和 GlibC 實現時間函數的原理。
  • 第 3 和第 4 部分是 Linux 內核對時間的支持和實現原理。

如今開始第 1 部分,探討應用開發中的時間編程問題。在這一部分中,全部的例子代碼都在 GlibC 2.14,內核 2.6.33 的 Linux 系統下編譯並驗證執行過。讀者若是使用低版本的 GlibC 和 Linux 內核有可能沒法正確執行。編程


獲取當前時間bash

時間的獲取數據結構

在程序當中, 咱們常常要輸出系統當前的時間,好比日誌文件中的每個事件都要記錄其產生時間。在 C 語言中獲取當前時間的方法有如下幾種,它們所得到的時間精度從秒級到納秒,各有所不一樣。ide


表 1. C 時間函數
函數

function 定義 含義 返回值 精度
time() time 函數得到從 1970 年 1 月 1 日 0 點到當前的秒數,存儲在time_t結構之中。 time_t
gettimeofday() gettimeofday 函數返回從 1970 年 1 月 1 日 0 點以來,到如今的時間。用 timeval 數據結構表示。 struct timeval
{
time_t tv_sec;
long int tv_usec;
};
微秒
clock_gettime() clock_gettime 函數返回從 1970 年 1 月 1 日 0 點以來,到如今的時間。用 timespec 數據結構表示。
支持不普遍。屬於實時擴展。
struct timespec
{
time_t tv_sec;
long int tv_nsec;
};
納秒
ftime() 函數返回從 1970 年 1 月 1 日 0 點以來,到如今的時間。用timeb數據結構表示。
已通過時, 被 time() 替代。儘可能不使用。
struct timeb {
time_t time;
unsigned short
millitm;
short timezone;
short dstflag;
};
毫秒

 

GUN/Linux 提供了三個標準的 API 用來獲取當前時間,time()/gettimeofday()/clock_gettime(),它們的區別僅在於獲取的時間精度不一樣,您能夠根據須要選取合適的調用。ftime() 是老的一些系統中的時間調用,不少 Linux 版本雖然支持它,但僅僅是爲了向前兼容性,新開發的軟件不建議使用 ftime() 來得到當前時間。性能

時間顯示和轉換學習

目前咱們獲得的時間是一個數字,不管精度如何,它表明的僅是一個差值。好比精度爲秒的 time() 函數,返回一個 time_t 類型的整數。假設當前時間爲 2011 年 12 月 7 日下午 20 點 29 分 51 秒,那麼 time_t 的值爲:1323318591。即距離 1970 年 1 月 1 日零點,咱們已通過去了 1323318591 秒。(這裏的 1970 年 1 月 1 日零點是格林威治時間,而不是北京時間。)咱們下面討論的時間若是不特別說明都是格林威治時間,也叫 GMT 時間,或者 UTC 時間。

字符串「1323318591 秒」對於多數人都沒有太大的意義,咱們更願意看到「2011 年 12 月 7 日」這樣的顯示。所以當咱們獲得秒,毫秒,甚至納秒錶示的當前時間以後,每每須要將這些數字轉換爲人們所熟悉的時間表示方法。

因爲國家,習慣和時區的不一樣,時間的表示方法並無一個統一的格式。爲了知足各類時間顯示的需求,標準 C 庫提供了許多時間格式轉換的函數。這些函數的數量衆多,容易讓人迷惑,記住它們的用法十分不易。在這裏我借用 Michael Kerrisk 在《Linux Programming Interface》一書中的插圖,來對這些標準 C 函數進行一個整體的概覽。


圖 1. 各類時間顯示格式轉換函數關係圖

從上圖能夠看到,time()/gettimeofday() 從內核獲得當前時間以後,該當前時間值能夠被兩大類函數轉換爲更加容易閱讀的顯示格式:

  • 固定格式轉換
  • 用戶指定格式轉換函數。

固定格式轉換

用 ctime() 函數轉換出來的時間格式是系統固定的,調用者沒法改動,所以被稱爲固定格式轉換。若是您對日期格式沒有特殊的要求,那麼用它基本上就能夠了,簡單,不用記憶不少的參數。

用戶指定格式轉換

典型的 ctime() 格式以下:

Wed Dec 7 20:45:43 PST 2011

有些人以爲這個格式太長,相似 Wed,星期三這樣的信息不少狀況下都沒有啥用途。人們可能更喜歡其餘格式:好比2011-12-07 20:45。在這種狀況下,就須要進行時間顯示格式轉換。作法爲:先把從內核獲得的時間值轉換爲 struct tm 類型的值,而後調用 strftime() 等函數來輸出自定義的時間格式字符串。

下面我列舉一些實例,以便讀者更清晰地理解衆多的時間轉換函數的用法。

各標準 C 時間轉換函數的解釋和舉例

 

char *ctime(const time_t *clock); 

 

使用函數 ctime 將秒數轉化爲字符串. 這個函數的返回類型是固定的:一個可能值爲」Thu Dec 7 14:58:59 2000」。這個字符串的長度和顯示格式是固定的。


清單 1,time 的使用

#include <time.h>
int main ()
{
 time_t time_raw_format;
 time ( &time_raw_format ); //獲取當前時間
 printf (" time is [%d]\n", time_raw_format);
 //用 ctime 將時間轉換爲字符串輸出
 printf ( "The current local time: %s", ctime(&time_raw_format));
 return 0;
}

 

自定義格式轉換

爲了更靈活的顯示,須要把類型 time_t 轉換爲 tm 數據結構。tm 數據結構將時間分別保存到表明年,月,日,時,分,秒等不一樣的變量中。再也不是一個使人費解的 64 位整數了。這種數據結構是各類自定義格式轉換函數所須要的輸入形式。


清單 2,數據結構 tm

struct tm {
int tm_sec; /* Seconds (0-60) */
int tm_min; /* Minutes (0-59) */
int tm_hour; /* Hours (0-23) */
int tm_mday; /* Day of the month (1-31) */
int tm_mon; /* Month (0-11) */
int tm_year; /* Year since 1900 */
int tm_wday; /* Day of the week (Sunday = 0)*/
int tm_yday; /* Day in the year (0-365; 1 Jan = 0)*/
int tm_isdst; /* Daylight saving time flag
 > 0: DST is in effect;
 = 0: DST is not effect;
 < 0: DST information not available */
};

 

可使用 gmtime() 和 localtime() 把 time_t 轉換爲 tm 數據格式,其中 gmtime() 把時間轉換爲格林威治時間;localtime 則轉換爲當地時間。


清單 3,時間轉換函數定義

#include <time.h>
struct tm *gmtime(const time_t *timep);
struct tm *localtime(const time_t *timep);

 

使用 tm 來表示時間,您就能夠調用 asctime() 和 strftime() 將時間轉換爲字符串了。asctime() 的輸出格式固定,和 ctime() 相同。strftime() 則相似咱們最熟悉的 printf() 函數,您能夠經過輸入參數自定義時間的輸出格式。

 

size_t strftime(char *outstr, size_t maxsize, const char *format,
 const struct tm *timeptr);



清單 4,時間顯示轉換

int main ()
{
	time_t time_raw_format;
	struct tm * time_struct;
	char buf [100];
	time ( &time_raw_format );
	time_struct = localtime ( &time_raw_format );
	strftime (buf,100,"It is now: %I:%M%p.",time_struct);
	puts (buf);
	return 0;
}

 

該例子程序的輸出結果以下:

 

It is now: 02:45PM.

 

從以上的例子能夠看到,利用從 time() 獲得的時間值,能夠調用各類轉換函數將其轉換成更方便人們閱讀的形式。

此外從前面的總結中咱們也瞭解到,還有兩個 C 函數能夠得到當前時間,gettimeofday() 以及 clock_gettime(),它們分別返回 struct timeval 或者 timespec 表明的高精度的時間值。在目前的 GLibC 中,尚未直接把 struct timeval/timespec 轉換爲 struct tm 的函數。通常的作法是將 timeval 中的 tv_sec 轉換爲 tm,使用上面所述的方法轉換爲字符串,最後在顯示的時候追加上 tv_usec,好比下面的例子代碼:


清單 5,更多時間顯示轉換

struct timeval tv;
time_t nowtime; 
struct tm *nowtm; 
char tmbuf[64], buf[64]; 
gettimeofday(&tv, NULL); //獲取當前時間到 tv
nowtime = tv.tv_sec; //nowtime 存儲了秒級的時間值
nowtm = localtime(&nowtime); //轉換爲 tm 數據結構
//用 strftime 函數將 tv 轉換爲字符串,但 strftime 函數只能達到秒級精度
strftime(tmbuf, sizeof tmbuf, "%Y-%m-%d %H:%M:%S", nowtm);
//將毫秒值追加到 strftime 轉換的字符串末尾 
snprintf(buf, sizeof buf, "%s.%06d", tmbuf, tv.tv_usec);

 


時間的測量

有時候咱們要計算某段程序執行的時間,好比須要對算法進行時間分析。基本的實現思路爲在被測試代碼的開始和結束的地方獲取當時時間,相減後獲得相對值,即所須要的統計時間。爲了實現高精度的時間測量,必須使用高精度的時間獲取方式,通常有兩種方法:

  • 系統調用 gettimeofday
  • 彙編指令 RDTSC。

gettimeofday

可使用 gettimeofday() 函數進行時間測量,其精度在 us 級別,能夠用來作通常的時間分析。

gettimeofday() 將時間保存在結構 tv 之中。gettimeofday() 的第二個參數表明時區,在 Linux 中已經廢棄不用,只能用 NULL 傳入。一個典型的例子程序以下:


清單 6,gettimeofday 例子程序

void function() 
{ 
 unsigned int i,j; 
 double y; 
 for(i=0;i<1000;i++) 
 for(j=0;j<1000;j++) 
 y=sin((double)i); //耗時操做
} 

main() 
{ 
 struct timeval tpstart,tpend; 
 float timeuse; 

 gettimeofday(&tpstart,NULL); //記錄開始時間戳
 function(); 
 gettimeofday(&tpend,NULL); //記錄結束時間戳
 timeuse = 1000000*(tpend.tv_sec-tpstart.tv_sec)+ 
tpend.tv_usec-tpstart.tv_usec; //計算差值
 timeuse /= 1000000; 
 printf("Used Time:%f\n",timeuse); 
 exit(0); 
} 

 

這個程序輸出函數的執行時間,咱們可使用這個來進行系統性能的測試,或者是函數算法的效率分析。在我我的機器上的輸出結果是:Used Time:0.556070

RDTSC

gettimeofday() 是一個系統調用,在某些場合下頻繁調用它是不合適的。好比性能要求很高的代碼段內。由於 gettimeofday() 須要用戶態/內核態切換,開銷較大。Intel X86 處理器提供了 TSC 硬件,而且能夠用非特權指令 rdtsc 來讀取該硬件的時間值,這就避免了過分的內核用戶態切換。

如何使用 RDTSC

參考下面的例子代碼,採用 GCC 的彙編擴展,定義 rdtsc 的函數,它返回當前時間戳。

 

#define rdtsc(low,high) __asm__ \
 __volatile__("rdtsc" : "=a" (low), "=d" (high))

 

在 C 代碼中使用 rdtsc 十分簡單。好比:


清單 7,RDTSC 例子程序

unsigned long long get_cycles()
{
	unsigned low, high;
	unsigned long long val;
	rdtsc(low,high);
	val = high;
val = (val << 32) | low; //將 low 和 high 合成一個 64 位值
	return val;
}

double get_cpu_mhz(void)
{
	FILE* f;
	char buf[256];
	double mhz = 0.0;

f = fopen("/proc/cpuinfo","r"); //打開 proc/cpuinfo 文件
	if (!f)
		return 0.0;
	while(fgets(buf, sizeof(buf), f)) {
		double m;
		int rc;
rc = sscanf(buf, "cpu MHz : %lf", &m); //讀取 cpu MHz
		if (mhz == 0.0) {
			mhz = m;
			break;
		}
	}
	fclose(f);
return mhz; //返回 HZ 值
}

int main()
{
	double mhz;
	mhz = get_cpu_mhz();
	cycles_t c1, c2;

	for(;;)
	{
		c1 = get_cycles(); 
		sleep(1);
		c2 = get_cycles();
 //c2 和 c1 的差值應該爲 1000000us,即 1 秒
		printf("1 sec = %g usec\n", (c2 - c1) / mhz); 
	}
}

 

函數 get_cycles 將返回 64 位整數,表明當前時間,單位是 CPU 的 cycle 數。函數 get_cpu_mhz 得到當前 CPU 的工做頻率。用兩個 CPU cycle 的差值除以 CPU 頻率,就是微妙。

但 RDTSC 只能在 IA 系列處理器上使用。並且因爲處理器的亂序執行,RDTSC 有些狀況下並不許確,在 SMP 下使用 RDTSC 也有必定的問題。但這些問題只有在須要極高時間精度的狀況下才會出現,對於通常的時間測量要求,採用 RDTSC 是一個能夠考慮的選擇。


計時器的使用

有時咱們須要定時完成一些任務。簡單的方法是使用 while 循環加 sleep。好比每隔 1 分鐘檢查連接狀況的 heartbeat 任務等。


清單 8,sleep 加循環

while(condtion)
{
 //do something
 sleep(interval);
}

 

這能夠知足不少程序的定時須要,但假如您不但願程序「偷懶」,即上例中 sleep 的時候您仍是但願程序作些有用的工做,那麼使用定時器是一般的選擇。Linux 系統上最經常使用的定時器是 setitmer 計時器。

setitimer

Linux 爲每個進程提供了 3 個 setitimer 間隔計時器:

  • ITIMER_REAL:減小實際時間,到期的時候發出 SIGALRM 信號。
  • ITIMER_VIRTUAL:減小有效時間 (進程執行的時間),產生 SIGVTALRM 信號。
  • ITIMER_PROF:減小進程的有效時間和系統時間 (爲進程調度用的時間)。這個常常和上面一個使用用來計算系統內核時間和用戶時間。產生 SIGPROF 信號。

所謂 REAL 時間,即咱們人類天然感覺的時間,英文計算機文檔中也常用 wall-clock 這個術語。說白了就是咱們一般所說的時間,好比如今是下午 5 點 10 分,那麼一分鐘的 REAL 時間以後就是下午 5 點 11 分。

VIRTUAL 時間是進程執行的時間,Linux 是一個多用戶多任務系統,在過去的 1 分鐘內,指定進程實際在 CPU 上的執行時間每每並無 1 分鐘,由於其餘進程會被 Linux 調度執行,在那些時間內,雖然天然時間在流逝,但指定進程並無真正的運行。VIRTUAL 時間就是指定進程真正的有效執行時間。好比 5 點 10 分開始的 1 分鐘內,進程 P1 被 Linux 調度並佔用 CPU 的執行時間爲 30 秒,那麼 VIRTUAL 時間對於進程 P1 來說就是 30 秒。此時天然時間已經到了 5 點 11 分,但從進程 P1 的眼中看來,時間只過了 30 秒。

PROF 時間比較獨特,對進程 P1 來講從 5 點 10 分開始的 1 分鐘內,雖然本身的執行時間爲 30 秒,但實際上還有 10 秒鐘內核是在執行 P1 發起的系統調用,那麼這 10 秒鐘也被加入到 PROF 時間。這種時間定義主要用於全面衡量進程的性能,由於在統計程序性能的時候,10 秒的系統調用時間也應該算到 P1 的頭上。這也許就是 PROF 這個名字的來歷吧。

使用 setitimer Timer 須要瞭解下面這些接口 API:

 

int getitimer(int which,struct itimerval *value); 
int setitimer(int which,struct itimerval *newval, 
struct itimerval *oldval); 

 

itimerval 的定義以下:

 

struct itimerval { 
struct timeval it_interval; 
struct timeval it_value; 
} 

 

getitimer 函數獲得間隔計時器的時間值,保存在 value 中。

setitimer 函數設置間隔計時器的時間值爲 newval. 並將舊值保存在 oldval 中;which 表示使用三個計時器中的哪個。

itimerval 結構中的 it_value 是第一次調用後觸發定時器的時間,當這個值遞減爲 0 時,系統會向進程發出相應的信號。此後將以 it_internval 爲週期定時觸發定時器。

給出一個具體的例子:


清單 9,setitmer 例子

void print_info(int signo) 
{ 
 printf(「timer fired\n」); //簡單的打印,表示 timer 到期
} 

void init_sigaction(void) 
{ 
 struct sigaction act; 
 act.sa_handler= print_info; 
 act.sa_flags=0; 
 sigemptyset(&act.sa_mask); 
 sigaction(SIGPROF,&act,NULL); //設置信號 SIGPROF 的處理函數爲 print_info
} 

void init_time() 
{ 
 struct itimerval value; 
 value.it_value.tv_sec=2; 
 value.it_value.tv_usec=0; 
 value.it_interval=value.it_value; 
 setitimer(ITIMER_PROF,&value,NULL); //初始化 timer,到期發送 SIGPROF 信號
} 

int main() 
{ 
 len=strlen(prompt); 
 init_sigaction(); 
 init_time(); 
 while(1); 
 exit(0); 
} 

 

這個程序使用 PROF 時間,每通過兩秒 PROF 時間以後就會打印一下 timer fired 字符串。

須要指出:setitimer 計時器的精度爲 ms,即 1000 分之 1 秒,足以知足絕大多數應用程序的須要。但多媒體等應用可能須要更高精度的定時,那麼就須要考慮使用下一類定時器:POSIX Timer。

POSIX Timer

間隔定時器 setitimer 有一些重要的缺點,POSIX Timer 對 setitimer 進行了加強,克服了 setitimer 的諸多問題:

首先,一個進程同一時刻只能有一個 timer。假如應用須要同時維護多個 Interval 不一樣的計時器,必須本身寫代碼來維護。這很是不方便。使用 POSIX Timer,一個進程能夠建立任意多個 Timer。

setitmer 計時器時間到達時,只能使用信號方式通知使用 timer 的進程,而 POSIX timer 能夠有多種通知方式,好比信號,或者啓動線程。

使用 setitimer 時,通知信號的類別不能改變:SIGALARM,SIGPROF 等,而這些都是傳統信號,而不是實時信號,所以有 timer overrun 的問題;而 POSIX Timer 則可使用實時信號。

setimer 的精度是 ms,POSIX Timer 是針對有實時要求的應用所設計的,接口支持 ns 級別的時鐘精度。


表 2. POSIX Timer 函數

函數名 功能描述
timer_create 建立一個新的 Timer;而且指定定時器到時通知機制
timer_delete 刪除一個 Timer
timer_gettime Get the time remaining on a POSIX.1b interval timer
timer_settime 開始或者中止某個定時器。
timer_getoverrun 獲取丟失的定時通知個數。

 

使用 Posix Timer 的基本流程很簡單,首先建立一個 Timer。建立的時候能夠指定該 Timer 的一些特性,好比 clock ID。

clock ID 即 Timer 的種類,能夠爲下表中的任意一種:


表 3. POSIX Timer clock ID

Clock ID 描述
CLOCK_REALTIME Settable system-wide real-time clock;
CLOCK_MONOTONIC Nonsettable monotonic clock
CLOCK_PROCESS_CPUTIME_ID Per-process CPU-time clock
CLOCK_THREAD_CPUTIME_ID Per-thread CPU-time clock

 

CLOCK_REALTIME 時間是系統保存的時間,便可以由 date 命令顯示的時間,該時間能夠從新設置。好比當前時間爲上午 10 點 10 分,Timer 打算在 10 分鐘後到時。假如 5 分鐘後,我用 date 命令修改當前時間爲 10 點 10 分,那麼 Timer 還會再等十分鐘到期,所以實際上 Timer 等待了 15 分鐘。假如您但願不管任何人如何修改系統時間,Timer 都嚴格按照 10 分鐘的週期進行觸發,那麼就可使用 CLOCK_MONOTONIC。

CLOCK_PROCESS_CPUTIME_ID 的含義與 setitimer 的 ITIMER_VIRTUAL 相似。計時器只記錄當前進程所實際花費的時間;好比仍是上面的例子,假設系統很是繁忙,當前進程只能得到 50%的 CPU 時間,爲了讓進程真正地運行 10 分鐘,應該到 10 點 30 分才容許 Timer 到期。

CLOCK_THREAD_CPUTIME_ID 以線程爲計時實體,當前進程中的某個線程真正地運行了必定時間才觸發 Timer。

設置到期通知方式

timer_create 的第二個參數 struct sigevent 用來設置定時器到時時的通知方式。該數據結構以下:


清單 10,結構 sigevent

 struct sigevent {
 int sigev_notify; /* Notification method */
 int sigev_signo; /* Notification signal */
 union sigval sigev_value; /* Data passed with
 notification */
 void (*sigev_notify_function) (union sigval);
 /* Function used for thread
 notification (SIGEV_THREAD) */
 void *sigev_notify_attributes;
 /* Attributes for notification thread
 (SIGEV_THREAD) */
 pid_t sigev_notify_thread_id;
 /* ID of thread to signal (SIGEV_THREAD_ID) */
 };

 

其中 sigev_notify 表示通知方式,有以下幾種:


表 3. POSIX Timer 到期通知方式

通知方式 描述
SIGEV_NONE 定時器到期時不產生通知。。。
SIGEV_SIGNAL 定時器到期時將給進程投遞一個信號,sigev_signo 能夠用來指定使用什麼信號。
SIGEV_THREAD 定時器到期時將啓動新的線程進行須要的處理
SIGEV_THREAD_ID(僅針對 Linux) 定時器到期時將向指定線程發送信號。

 

若是採用 SIGEV_NONE 方式,使用者必須調用timer_gettime 函數主動讀取定時器已經走過的時間。相似輪詢。

若是採用 SIGEV_SIGNAL 方式,使用者能夠選擇使用什麼信號,用 sigev_signo 表示信號值,好比 SIG_ALARM。

若是使用 SIGEV_THREAD 方式,則須要設置 sigev_notify_function,當 Timer 到期時,將使用該函數做爲入口啓動一個線程來處理信號;sigev_value 保存了傳入 sigev_notify_function 的參數。sigev_notify_attributes 若是非空,則應該是一個指向 pthread_attr_t 的指針,用來設置線程的屬性(好比 stack 大小,detach 狀態等)。

SIGEV_THREAD_ID 一般和 SIGEV_SIGNAL 聯合使用,這樣當 Timer 到期時,系統會向由 sigev_notify_thread_id 指定的線程發送信號,不然可能進程中的任意線程均可能收到該信號。這個選項是 Linux 對 POSIX 標準的擴展,目前主要是 GLibc 在實現 SIGEV_THREAD 的時候使用到,應用程序不多會須要用到這種模式。

啓動定時器

建立 Timer 以後,即可以調用 timer_settime() 函數指定定時器的時間間隔,並啓動該定時器了。

 

 int timer_settime(timer_t timerid, int flags,
 const struct itimerspec *new_value,
 struct itimerspec * old_value);

 

第一次看到 timer_settime 的參數列表或許會使人以爲費解。先來看看 new_value 和 old_value,它們都是 struct itimerspec 數據結構。

 

struct itimerspec
{
 struct timespec it_interval; //定時器週期值
 struct timespec it_value; //定時器到期值
};

 

啓動和中止 Timer 均可以經過設置 new_value 來實現:

new_value->it_interval 爲定時器的週期值,好比 1 秒,表示定時器每隔 1 秒到期;

new_value->it_value 若是大於 0,表示啓動定時器,Timer 將在 it_value 這麼長的時間過去後到期,此後每隔 it_interval 便到期一次。若是 it_value 爲 0,表示中止該 Timer。

有些時候,應用程序會先啓動用一個時間間隔啓動定時器,隨後又修改該定時器的時間間隔,這均可以經過修改 new_value 來實現;假如應用程序在修改了時間間隔以後但願瞭解以前的時間間隔設置,則傳入一個非 NULL 的 old_value 指針,這樣在 timer_settime() 調用返回時,old_value 就保存了上一次 Timer 的時間間隔設置。多數狀況下咱們並不須要這樣,即可以簡單地將 old_value 設置爲 NULL,忽略它。

下面給出一個使用 posix timer 的例子程序。最傳統的例子就是建立通知方式爲 SIGEV_SIGNAL 的 Timer。這樣當定時器到期時,將產生信號通知,主程序須要定義本身的信號處理函數,來處理信號到期事件。這種例子比比皆是,我打算在這裏寫一個採用通知方式爲 SIGEV_THREAD 的例子。該例子程序從 main 函數開始主線程,在開始的時候打印出主線程的進程 ID 和線程 ID。


清單 11,打印 TID

 pid_t tid = (pid_t) syscall (SYS_gettid);
 printf("start program in PID:[%d]TID:[%d]\n",getpid(),tid);

 

得到 ThreadID 的系統調用還沒有被 GLibC 標準化,所以這裏直接調用 syscall。

而後,主線程初始化建立 Timer 所須要的數據結構:


清單 12,設置通知方式

 se.sigev_notify = SIGEV_THREAD;
 se.sigev_value.sival_ptr = &timer_id;
 se.sigev_notify_function = timer_thread;
 se.sigev_notify_attributes = NULL;
 status = timer_create(CLOCK_REALTIME, &se, &timer_id);

 

這裏將通知方式設爲 SIGEV_THREAD,timer_thread 爲線程入口函數。

而後主線程設置定時器間隔,並啓動 Timer:


清單 13,啓動 Timer

 ts.it_value.tv_sec = 5;
 ts.it_value.tv_nsec = 0;
 ts.it_interval.tv_sec = 5;
 ts.it_interval.tv_nsec = 0;
 status = timer_settime(timer_id, 0, &ts, 0);

 

此後主線程進入一個循環,在循環中等待線程條件變量:


清單 14,主程序中的循環

 while (counter < 5) {
 status = pthread_cond_wait (&cond, &mutex);
}

 

條件變量 cond 將在 timer_thread() 處理函數中觸發,這樣每 5 秒鐘,定時器將調用 timer_thread() 處理函數,並喚醒主線程等待的條件變量一次。5 次以後測試程序退出。

如今咱們看看 timer_thread() 函數:


清單 15,timer_thread 函數

void timer_thread (void *arg)
{
 status = pthread_mutex_lock (&mutex);
 if (++counter >= 5) {
 status = pthread_cond_signal (&cond);
 }
 status = pthread_mutex_unlock (&mutex);
 pid_t tid = (pid_t) syscall (SYS_gettid);
 printf ("Timer %d in PID:[%d]TID:[%d]\n", counter,getpid(),tid);
}

 

在整個程序中咱們都沒有使用信號,定時器到期時,將啓動新的線程運行 timer_thread。所以在該函數中,咱們還打印了當前的線程號以即可以看出它們確實在不一樣線程中運行。

這裏是運行該程序的一個輸出:

 

-bash-3.2$ gcc threadtimer.c -lrt -lpthread -o test
-bash-3.2$ ./test
start program in PID:[21483]TID:[21483]
Timer 1 in PID:[21483]TID:[21498]
Timer 2 in PID:[21483]TID:[21510]
Timer 3 in PID:[21483]TID:[21534]

 

能夠看到每次 Timer 都運行在不一樣的線程中。


小結

至此,但願我已經講述了 Linux 系統提供的大多數關於時間的編程方法。使用這些方法咱們能夠:

  • 得到當前時間,並轉換爲合適的顯示方式;
  • 衡量程序運行通過的時間;
  • 使用定時器完成周期性的任務;

另外不知道您是否和我同樣,對於 Linux 系統如何實現這些機制十分好奇。計算機畢竟是一個機器,底層硬件提供了怎樣的功能,操做系統和 C 庫如何協同工做才能夠提供這些一絲不苟的,優美的方法呢?我將在後續的部分試圖探討這個話題。


參考資料

學習

相關文章
相關標籤/搜索