使用定時器的目的無非是為了周期性的執行某一任務,或者是到了一個指定時間去執行某一個任務。要達到這一目的,一般有兩個常見的比較有效方法。一個是用linux內部的三個定時器,另一個是用sleep, usleep函數讓進程睡眠一段時間,其實,還有一個方法,那就是用gettimeofday, difftime等自己來計算時間間隔,然後時間到了就執行某一任務,但是這種方法效率低,所以不常用。
首先來看看linux作業系統為每一個進程提供的3個內部計時器。
ITIMER_REAL: 給一個指定的時間間隔,按照實際的時間來減少這個計數,當時間間隔為0的時候發出SIGALRM訊號
ITIMER_VIRTUAL: 給定一個時間間隔,當進程執行的時候才減少計數,時間間隔為0的時候發出SIGVTALRM訊號
ITIMER_PROF: 給定一個時間間隔,當進程執行或者是系統為進程調度的時候,減少計數,時間到了,發出SIGPROF訊號,這個和ITIMER_VIRTUAL聯合,常用來計算系統核心程式的時間和使用者時間。
用到的函數有:
#include <sys/time.h> int getitimer(int which, struct itimerval *value); int setitimer(int which, struct itimerval*newvalue, struct itimerval* oldvalue); strcut timeval { long tv_sec; /*秒*/ long tv_usec; /*微秒*/ }; struct itimerval { struct timeval it_interval; /*時間間隔*/ struct timeval it_value; /*目前時間計數*/ };
it_interval用來指定每隔多長時間執行任務, it_value用來儲存目前時間離執行任務還有多長時間。比如說, 你指定it_interval為2秒(微秒為0),開始的時候我們把it_value的時間也設定為2秒(微秒為0),當過了一秒, it_value就減少一個為1, 再過1秒,則it_value又減少1,變為0,這個時候發出訊號(告訴使用者時間到了,可以執行任務了),並且系統自動把it_value的時間重設為it_interval的值,即2秒,再重新計數。
為了協助你理解這個問題,我們來看一個例子:
#include <sys/time.h> #include <stdio.h> #include <unistd.h> #include <signal.h> #include <string.h> static char msg[] = "time is running out/n"; static int len; // 向標準錯誤輸出資訊,告訴使用者時間到了 void prompt_info(int signo) { write(STDERR_FILENO, msg, len); } // 建立訊號處理機制 void init_sigaction(void) { struct sigaction tact; /*訊號到了要執行的任務處理函數為prompt_info*/ tact.sa_handler = prompt_info; tact.sa_flags = 0; /*初始化訊號集*/ sigemptyset(&tact.sa_mask); /*建立訊號處理機制*/ sigaction(SIGALRM, &tact, NULL); } void init_time() { struct itimerval value; /*設定執行任務的時間間隔為2秒0微秒*/ value.it_value.tv_sec = 2; value.it_value.tv_usec = 0; /*設定初始時間計數也為2秒0微秒*/ value.it_interval = value.it_value; /*設定計時器ITIMER_REAL*/ setitimer(ITIMER_REAL, &value, NULL); } int main() { len = strlen(msg); init_sigaction(); init_time(); while ( 1 ); exit(0); }
該程式的ITMER_REAL定時器,每隔2秒鐘都會發送一個SIGALRM訊號,當主函數接收到了這個訊號之後,調用訊號處理函數prompt_info在標準錯誤上輸出time is running out這個字串。
對於ITIMER_VIRTUAL和ITIMER_PROF的使用方法類似,當你在setitimer裡面設定的定時器為ITIMER_VIRTUAL的時候,你把sigaction裡面的SIGALRM改為SIGVTALarm, 同理,ITIMER_PROF對應SIGPROF。
不過,你可能會注意到,當你用ITIMER_VIRTUAL和ITIMER_PROF的時候,你拿一個秒錶,你會發現程式輸出字串的時間間隔會不止2秒,甚至5-6秒才會輸出一個,至於為什麼,自己好好琢磨一下^_^
#include <signal.h> #include <unistd.h> #include <string.h> #include <stdio.h> static char msg[] = "I received a msg./n"; int len; void show_msg(int signo) { write(STDERR_FILENO, msg, len); } int main() { struct sigaction act; union sigval tsval; act.sa_handler = show_msg; act.sa_flags = 0; sigemptyset(&act.sa_mask); sigaction(50, &act, NULL); len = strlen(msg); while ( 1 ) { sleep(2); /*睡眠2秒*/ /*向主進程發送訊號,實際上是自己給自己發訊號*/ sigqueue(getpid(), 50, tsval); } return 0; }
下面我們來看看用sleep以及usleep怎麼實現定時執行任務。
看到了吧,這個要比上面的簡單多了,而且你用秒錶測一下,時間很准,指定2秒到了就給你輸出一個字串。所以,如果你只做一般的定時,到了時間去執行一個任務,這種方法是最簡單的。
下面我們來看看,通過自己計算時間差的方法來定時:
#include <signal.h> #include <unistd.h> #include <string.h> #include <stdio.h> #include <time.h> static char msg[] = "I received a msg./n"; int len; static time_t lasttime; void show_msg(int signo) { write(STDERR_FILENO, msg, len); } int main() { struct sigaction act; union sigval tsval; act.sa_handler = show_msg; act.sa_flags = 0; sigemptyset(&act.sa_mask); sigaction(50, &act, NULL); len = strlen(msg); time(&lasttime); while ( 1 ) { time_t nowtime; /*擷取目前時間*/ time(&nowtime); /*和上一次的時間做比較,如果大於等於2秒,則立刻發送訊號*/ if (nowtime - lasttime >= 2) { /*向主進程發送訊號,實際上是自己給自己發訊號*/ sigqueue(getpid(), 50, tsval); lasttime = nowtime; } } return 0; }
這個和上面不同之處在於,是自己手工計算時間差的,如果你想更精確的計算時間差,你可以把 time 函數換成gettimeofday,這個可以精確到微妙。
上面介紹的幾種定時方法各有千秋,在計時效率上、方法上和時間的精確度上也各有不同,採用哪種方法,就看你程式的需要了。