使用定時器的目的無非是爲了週期性的執行某一任務,或者是到了一個指定時間去執行某一個任務。要達到這一目的,通常有兩個常見的比較有效的方法。一個是用 Linux 內部的三個定時器;另外一個是用 sleep 或 usleep 函數讓進程睡眠一段時間;其實,還有一個方法,那就是用 gettimeofday、difftime 等本身來計算時間間隔,而後時間到了就執行某一任務,可是這種方法效率低,因此不經常使用。linux
若是不要求很精確的話,用 alarm() 和 signal() 就夠了異步
unsigned int alarm(unsigned int seconds)
專門爲SIGALRM信號而設,在指定的時間seconds秒後,將向進程自己發送SIGALRM信號,又稱爲鬧鐘時間。進程調用alarm後,任何之前的alarm()調用都將無效。若是參數seconds爲零,那麼進程內將再也不包含任何鬧鐘時間。若是調用alarm()前,進程中已經設置了鬧鐘時間,則返回上一個鬧鐘時間的剩餘時間,不然返回0。 ide
示例:函數
#include <stdio.h> #include <unistd.h> #include <signal.h> void sigalrm_fn(int sig) { printf("alarm!\n"); alarm(2); return; } int main(void) { signal(SIGALRM, sigalrm_fn); alarm(2); while(1) pause(); }
int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue)); int getitimer(int which, struct itimerval *value); strcut timeval { long tv_sec; /*秒*/ long tv_usec; /*微秒*/ }; struct itimerval { struct timeval it_interval; /*時間間隔*/ struct timeval it_value; /*當前時間計數*/ };
setitimer() 比 alarm() 功能強大,支持3種類型的定時器:測試
setitimer() 第一個參數 which 指定定時器類型(上面三種之一);第二個參數是結構 itimerval 的一個實例;第三個參數可不作處理。 線程
下面是關於setitimer調用的一個簡單示範,在該例子中,每隔一秒發出一個SIGALRM,每隔0.5秒發出一個SIGVTALRM信號::rest
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <time.h> #include <sys/time.h> int sec; void sigroutine(int signo){ switch (signo){ case SIGALRM: printf("Catch a signal -- SIGALRM \n"); signal(SIGALRM, sigroutine); break; case SIGVTALRM: printf("Catch a signal -- SIGVTALRM \n"); signal(SIGVTALRM, sigroutine); break; } return; } int main() { struct itimerval value, ovalue, value2; sec = 5; printf("process id is %d ", getpid()); signal(SIGALRM, sigroutine); signal(SIGVTALRM, sigroutine); value.it_value.tv_sec = 1; value.it_value.tv_usec = 0; value.it_interval.tv_sec = 1; value.it_interval.tv_usec = 0; setitimer(ITIMER_REAL, &value, &ovalue); value2.it_value.tv_sec = 0; value2.it_value.tv_usec = 500000; value2.it_interval.tv_sec = 0; value2.it_interval.tv_usec = 500000; setitimer(ITIMER_VIRTUAL, &value2, &ovalue); for(;;); }
該例子的執行結果以下:code
localhost:~$ ./timer_test process id is 579 Catch a signal – SIGVTALRM Catch a signal – SIGALRM Catch a signal – SIGVTALRM Catch a signal – SIGVTALRM Catch a signal – SIGALRM Catch a signal –GVTALRM
注意:Linux信號機制基本上是從Unix系統中繼承過來的。早期Unix系統中的信號機制比較簡單和原始,後來在實踐中暴露出一些問題,所以,把那些創建在早期機制上的信號叫作」不可靠信號」,信號值小於SIGRTMIN(Red hat 7.2中,SIGRTMIN=32,SIGRTMAX=63)的信號都是不可靠信號。這就是」不可靠信號」的來源。它的主要問題是:進程每次處理信號後,就將對信號的響應設置爲默認動做。在某些狀況下,將致使對信號的錯誤處理;所以,用戶若是不但願這樣的操做,那麼就要在信號處理函數結尾再一次調用 signal(),從新安裝該信號。繼承
#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; }
看到了吧,這個要比上面的簡單多了,並且你用秒錶測一下,時間很準,指定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,這個能夠精確到微妙。
int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
n 指監視的文件描述符範圍,一般設爲所要select的fd+1;readfds,writefds 和 exceptfds分別是讀,寫和異常文件描述符集;timeout 爲超時時間。
可能用到的關於文件描述符集操做的宏有:
FD_CLR(int fd, fd_set *set); // 清除fd FD_ISSET(int fd, fd_set *set); // 測試fd是否設置 FD_SET(int fd, fd_set *set); //設置fd FD_ZERO(fd_set *set); //清空描述符集
咱們此時用不到這些宏,由於咱們並不關心文件描述符的狀態,咱們關心的是select超時。因此咱們須要把 readfds,writefds 和 exceptfds 都設爲 NULL,只指定 timeout 時間就好了。至於 n 咱們能夠不關心,因此你能夠把它設爲任何非負值。實現代碼以下:
int msSleep(long ms) { struct timeval tv; tv.tv_sec = 0; tv.tv_usec = ms; return select(0, NULL, NULL, NULL, &tv); }
怎麼樣,是否是很簡單? setitimer 和 select 都能實現進程的精確休眠,這裏給出了一個簡單的基於 select 的實現。我不推薦使用 setitimer,由於 Linux 系統提供的 timer 有限(每一個進程至多能設3個不一樣類型的 timer),並且 setitimer 實現起來沒有 select 簡單。
須要在 kernel 中打開 「high resolution Timer support」,驅動程序中 hrtimer 的初始化以下:
hrtimer_init(&m_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_PINNED); m_timer.function = vibrator_timer_func; hrtimer_start(&m_timer, ktime_set(0, 62500), HRTIMER_MODE_REL_PINNED);
定時函數 vibrator_timer_func 以下:
static enum hrtimer_restart vibrator_timer_func(struct hrtimer *timer) { gpio_set_value(gpio_test, 1); gpio_set_value(gpio_test, 0); hrtimer_forward_now(&m_timer,ktime_set(0, 62500)); return HRTIMER_RESTART; }
其中 gpio_test 爲輸出引腳,爲了方便輸出查看。可是用示波器查看引腳波形時,發現雖然設定的週期爲62.5us,可是輸出老是爲72us左右,並且偶爾會有兩個波形靠的很近(也就是說週期忽然變爲10us如下)。我將週期設到40us的話,就會出現72us和10us常常交替出現,沒法實現精確的40us的波形,若是設置到100us時,則波形就是100us了,並且貌似沒有看到有10us如下的週期出現。
最強大的定時器接口來自POSIX時鐘系列,其建立、初始化以及刪除一個定時器的行動被分爲三個不一樣的函數:timer_create()(建立定時器)、timer_settime()(初始化定時器)以及 timer_delete()(銷燬它)。
int timer_create(clockid_t clock_id, struct sigevent *evp, timer_t *timerid)
進程能夠經過調用 timer_create() 建立特定的定時器,定時器是每一個進程本身的,不是在 fork 時繼承的。clock_id 說明定時器是基於哪一個時鐘的,*timerid 裝載的是被建立的定時器的 ID。該函數建立了定時器,並將他的 ID 放入timerid指向的位置中。參數evp指定了定時器到期要產生的異步通知。若是evp爲 NULL,那麼定時器到期會產生默認的信號,對 CLOCK_REALTIMER來講,默認信號就是SIGALRM。若是要產生除默認信號以外的其它信號,程序必須將 evp->sigev_signo設置爲指望的信號碼。struct sigevent 結構中的成員 evp->sigev_notify說明了定時器到期時應該採起的行動。一般,這個成員的值爲SIGEV_SIGNAL,這個值說明在定時器到期時,會產生一個信號。程序能夠將成員 evp->sigev_notify設爲SIGEV_NONE來防止定時器到期時產生信號。
若是幾個定時器產生了同一個信號,處理程序能夠用 evp->sigev_value來區分是哪一個定時器產生了信號。要實現這種功能,程序必須在爲信號安裝處理程序時,使用struct sigaction的成員sa_flags中的標誌符SA_SIGINFO。
clock_id取值爲如下:
CLOCK_REALTIME :Systemwide realtime clock. CLOCK_MONOTONIC:Represents monotonic time. Cannot be set. CLOCK_PROCESS_CPUTIME_ID :High resolution per-process timer. CLOCK_THREAD_CPUTIME_ID :Thread-specific timer. CLOCK_REALTIME_HR :High resolution version of CLOCK_REALTIME. CLOCK_MONOTONIC_HR :High resolution version of CLOCK_MONOTONIC.
struct sigevent { int sigev_notify; //notification type int sigev_signo; //signal number union sigval sigev_value; //signal value void (*sigev_notify_function)(union sigval); pthread_attr_t *sigev_notify_attributes; } union sigval { int sival_int; //integer value void *sival_ptr; //pointer value }
經過將evp->sigev_notify設定爲以下值來定製定時器到期後的行爲:
timer_create()所建立的定時器並未啓動。要將它關聯到一個到期時間以及啓動時鐘週期,可使用timer_settime()。
int timer_settime(timer_t timerid, int flags, const struct itimerspec *value, struct itimerspect *ovalue); struct itimespec{ struct timespec it_interval; struct timespec it_value; };
如同settimer(),it_value用於指定當前的定時器到期時間。當定時器到期,it_value的值會被更新成it_interval 的值。若是it_interval的值爲0,則定時器不是一個時間間隔定時器,一旦it_value到期就會回到未啓動狀態。timespec的結構提供了納秒級分辨率:
struct timespec{ time_t tv_sec; long tv_nsec; };
若是flags的值爲TIMER_ABSTIME,則value所指定的時間值會被解讀成絕對值(此值的默認的解讀方式爲相對於當前的時間)。這個經修改的行爲可避免取得當前時間、計算「該時間」與「所指望的將來時間」的相對差額以及啓動定時器期間形成競爭條件。
若是ovalue的值不是NULL,則以前的定時器到期時間會被存入其所提供的itimerspec。若是定時器以前處在未啓動狀態,則此結構的成員全都會被設定成0。
int timer_gettime(timer_t timerid,struct itimerspec *value);
有可能一個定時器到期了,而同必定時器上一次到期時產生的信號還處於掛起狀態。在這種狀況下,其中的一個信號可能會丟失。這就是定時器超限。程序能夠經過調用timer_getoverrun來肯定一個特定的定時器出現這種超限的次數。定時器超限只能發生在同一個定時器產生的信號上。由多個定時器,甚至是那些使用相同的時鐘和信號的定時器,所產生的信號都會排隊而不會丟失。
int timer_getoverrun(timer_t timerid);
執行成功時,timer_getoverrun()會返回定時器初次到期與通知進程(例如經過信號)定時器已到期之間額外發生的定時器到期次數。舉例來講,在咱們以前的例子中,一個1ms的定時器運行了10ms,則此調用會返回9。若是超限運行的次數等於或大於DELAYTIMER_MAX,則此調用會返回DELAYTIMER_MAX。
執行失敗時,此函數會返回-1並將errno設定會EINVAL,這個惟一的錯誤狀況表明timerid指定了無效的定時器。
int timer_delete (timer_t timerid);
一次成功的timer_delete()調用會銷燬關聯到timerid的定時器而且返回0。執行失敗時,此調用會返回-1並將errno設定會 EINVAL,這個惟一的錯誤狀況表明timerid不是一個有效的定時器。
例1:
void handle() { time_t t; char p[32]; time(&t); strftime(p, sizeof(p), "%T", localtime(&t)); printf("time is %s\n", p); } int main() { struct sigevent evp; struct itimerspec ts; timer_t timer; int ret; evp.sigev_value.sival_ptr = &timer; evp.sigev_notify = SIGEV_SIGNAL; evp.sigev_signo = SIGUSR1; signal(SIGUSR1, handle); ret = timer_create(CLOCK_REALTIME, &evp, &timer); if( ret ) perror("timer_create"); ts.it_interval.tv_sec = 1; ts.it_interval.tv_nsec = 0; ts.it_value.tv_sec = 3; ts.it_value.tv_nsec = 0; ret = timer_settime(timer, 0, &ts, NULL); if( ret ) perror("timer_settime"); while(1); }
例2:
void handle(union sigval v) { time_t t; char p[32]; time(&t); strftime(p, sizeof(p), "%T", localtime(&t)); printf("%s thread %lu, val = %d, signal captured.\n", p, pthread_self(), v.sival_int); return; } int main() { struct sigevent evp; struct itimerspec ts; timer_t timer; int ret; memset (&evp, 0, sizeof (evp)); evp.sigev_value.sival_ptr = &timer; evp.sigev_notify = SIGEV_THREAD; evp.sigev_notify_function = handle; evp.sigev_value.sival_int = 3; //做爲handle()的參數 ret = timer_create(CLOCK_REALTIME, &evp, &timer); if( ret) perror("timer_create"); ts.it_interval.tv_sec = 1; ts.it_interval.tv_nsec = 0; ts.it_value.tv_sec = 3; ts.it_value.tv_nsec = 0; ret = timer_settime(timer, TIMER_ABSTIME, &ts, NULL); if( ret ) perror("timer_settime"); while(1); }