轉載自:http://www.cnblogs.com/feisky/archive/2010/03/20/1690561.html
使用定時器的目的無非是為了周期性的執行某一任務,或者是到了一個指定時間去執行某一個任務。要達到這一目的,一般有兩個常見的比較有效方法。
一個是用linux內部的三個定時器:ITIME_REAL,ITIMER_VIRTUAL,ITIME_PROF;
另一個是用sleep, usleep函數讓進程睡眠一段時間;
其實,還有一個方法,那就是用gettimeofday(), difftime()等自己來計算時間間隔,然後時間到了就執行某一任務,但是這種方法效率低,所以不常用。
1. 系統提供的定時器
linux作業系統為每一個進程提供的3個內部計時器:
ITIMER_REAL: 給一個指定的時間間隔,按照實際的時間來減少這個計數,當時間間隔為0的時候發出SIGALRM訊號
ITIMER_VIRTUAL: 給定一個時間間隔,當進程執行的時候才減少計數,時間間隔為0的時候發出SIGVTALRM訊號
ITIMER_PROF: 給定一個時間間隔,當進程執行或者是系統為進程調度的時候,減少計數,時間到了,發出SIGPROF訊號,這個和ITIMER_VIRTUAL聯合,常用來計算系統核心程式的時間和使用者時間。
用到的函數有:
#include<sys/time.h>
int getitimer(intwhich,structitimerval
*value);
int setitimer(intwhich,structitimerval*newvalue,structitimerval*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秒,再重新計數。
struct sigaction
{
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
其中,sa_handler是一個指標,指向訊號處理函數;
與後面兩種實現方式的主要區別:
由於定時器時系統自身提供的,所以時間到了之後,訊息也是由系統自動傳給訊息佇列的;
而後兩種由於是使用者自己實現,所以必須將進程ID和訊號ID傳送到訊息佇列(通過sigqueue()函數來實現)。
使用步驟:
1. 初始化定時器的類型和時間間隔;
2. 初始化訊號的類型和回呼函數;
3. 設定時間到了後,回呼函數執行;
樣本:
#include <stdio.h>#include <signal.h>#include <sys/time.h>#include <string.h>#include <unistd.h>//訊號回呼函數void sign_proc(int signo){ printf("time is running out\n");}//建立訊號處理機制void init_sigaction(){ struct sigaction signact; //指定有訊號之後的回呼函數 signact.sa_handler=sign_proc; signact.sa_flags=0; //初始化訊號集 sigemptyset(&signact.sa_mask); //指定訊號類型,注意與定時器類型保持一致 sigaction(SIGALRM, &signact, NULL);}//設定定時器void init_time(){ struct itimerval interval; //初始值 interval.it_value.tv_sec=5; interval.it_value.tv_usec=0; //間隔時間 interval.it_interval=interval.it_value; //設定定時器類型為:ITIMER_REAL setitimer(ITIMER_REAL, &interval, NULL);}int main(){ init_time(); init_sigaction(); while(1); return 0;}
說明:程式設定定時器類型為即時定時器:ITMER_REAL,每隔5秒鐘都會發送一個SIGALRM訊號,當主函數接收到了這個訊號之後,調用訊號處理函數sign_proc處理。
對於ITIMER_VIRTUAL和ITIMER_PROF的使用方法類似,當你在setitimer裡面設定的定時器為ITIMER_VIRTUAL的時候,你把sigaction裡面的SIGALRM改為SIGVTALARM, 同理,ITIMER_PROF對應SIGPROF。
2. 通過sleep以及usleep實現定時
#include <signal.h>#include <unistd.h>#include <string.h>#include <stdio.h>void show_msg(int signo){ printf("time is using up\n");}int main(){ struct sigaction signact; union sigval tsval; //設定訊號的回呼函數 signact.sa_handler=show_msg; signact.sa_flags=0; //初始化訊號集 sigemptyset(&signact.sa_mask); sigaction(50, &signact, NULL); while(1) { sleep(2); sigqueue(getpid(), 50, tsval); } return 0;}
看到了吧,這個要比上面的簡單多了,而且你用秒錶測一下,時間很准,指定2秒到了就給你輸出一個字串。所以,如果你只做一般的定時,到了時間去執行一個任務,這種方法是最簡單的。
3. 通過自己計算時間差的方法來定時
#include <signal.h>#include <unistd.h>#include <string.h>#include <stdio.h>#include <time.h>void sign_proc(int signo){ printf("time is running out\n");}int main(){ struct sigaction signact; union sigval tsval; //設定訊號變數 signact.sa_handler=sign_proc; signact.sa_flags=0; sigemptyset(&signact.sa_mask); sigaction(50, &signact, NULL); time_t oldtime; time(&oldtime); while(1) { time_t nowtime; time(&nowtime); if(nowtime-oldtime>=2) { //將進程ID,訊號ID放到訊息機制中 sigqueue(getpid(), 50, tsval); oldtime=nowtime; } }}
這種做法效率比較低,一般很少使用。
這個和上面不同之處在於,是自己手工計算時間差的,如果你想更精確的計算時間差,你可以把 time 函數換成gettimeofday,這個可以精確到微妙。
上面介紹的幾種定時方法各有千秋,在計時效率上、方法上和時間的精確度上也各有不同,採用哪種方法,就看你程式的需要。