關於Linux的應用程式層定時器__Linux

來源:互聯網
上載者:User

  使用定時器的目的無非是為了周期性的執行某一任務,或者是到了一個指定時間去執行某一個任務。要達到這一目的,一般有兩個常見的比較有效方法。一個是用 Linux 內部的三個定時器;另一個是用 sleep 或 usleep 函數讓進程睡眠一段時間;其實,還有一個方法,那就是用 gettimeofday、difftime 等自己來計算時間間隔,然後時間到了就執行某一任務,但是這種方法效率低,所以不常用。

1、alarm
  如果不要求很精確的話,用 alarm() 和 signal() 就夠了

unsigned int alarm(unsigned int seconds)

  專門為SIGALRM訊號而設,在指定的時間seconds秒後,將向進程本身發送SIGALRM訊號,又稱為鬧鐘時間。進程調用alarm後,任何以前的alarm()調用都將無效。如果參數seconds為零,那麼進程內將不再包含任何鬧鐘時間。如果調用alarm()前,進程中已經設定了鬧鐘時間,則返回上一個鬧鐘時間的剩餘時間,否則返回0。
樣本:

#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();}

2、setitimer

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種類型的定時器:

ITIMER_REAL:給一個指定的時間間隔,按照實際的時間來減少這個計數,當時間間隔為0的時候發出SIGALRM訊號。
ITIMER_VIRTUAL:給定一個時間間隔,當進程執行的時候才減少計數,時間間隔為0的時候發出SIGVTALRM訊號。
ITIMER_PROF:給定一個時間間隔,當進程執行或者是系統為進程調度的時候,減少計數,時間到了,發出SIGPROF訊號。

  setitimer() 第一個參數 which 指定定時器類型(上面三種之一);第二個參數是結構 itimerval 的一個執行個體;第三個參數可不做處理。
  下面是關於setitimer調用的一個簡單示範,在該例子中,每隔一秒發出一個SIGALRM,每隔0.5秒發出一個SIGVTALRM訊號::

#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(;;);}

  該例子的執行結果如下:

localhost:~$ ./timer_testprocess id is 579Catch a signal – SIGVTALRMCatch a signal – SIGALRMCatch a signal – SIGVTALRMCatch a signal – SIGVTALRMCatch a signal – SIGALRMCatch a signal –GVTALRM

  注意:Linux訊號機制基本上是從Unix系統中繼承過來的。早期Unix系統中的訊號機制比較簡單和原始,後來在實踐中暴露出一些問題,因此,把那些建立在早期機制上的訊號叫做”不可靠訊號”,訊號值小於SIGRTMIN(Red hat 7.2中,SIGRTMIN=32,SIGRTMAX=63)的訊號都是不可靠訊號。這就是”不可靠訊號”的來源。它的主要問題是:進程每次處理訊號後,就將對訊號的響應設定為預設動作。在某些情況下,將導致對訊號的錯誤處理;因此,使用者如果不希望這樣的操作,那麼就要在訊號處理函數結尾再一次調用 signal(),重新安裝該訊號。

3、用 sleep 以及 usleep 實現定時執行任務

#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秒到了就給你輸出一個字串。所以,如果你只做一般的定時,到了時間去執行一個任務,這種方法是最簡單的。

4、通過自己計算時間差的方法來定時

#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,這個可以精確到微妙。

5、使用 select 來提供精確定時和休眠

 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 簡單。

6、高精度硬體中斷定時器 hrtimer

  需要在 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以下的周期出現。

7、高精度定時器 posix_timer

  最強大的定時器介面來自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 typeint sigev_signo; //signal numberunion sigval   sigev_value; //signal valuevoid (*sigev_notify_function)(union sigval);pthread_attr_t *sigev_notify_attributes;}union sigval{int sival_int; //integer valuevoid *sival_ptr; //pointer value}

通過將evp->sigev_notify設定為如下值來定製定時器到期後的行為:
SIGEV_NONE:什麼都不做,只提供通過timer_gettime和timer_getoverrun查詢逾時資訊。
SIGEV_SIGNAL: 當定時器到期,核心會將sigev_signo所指定的訊號傳送給進程。在訊號處理常式中,si_value會被設定會sigev_value。
SIGEV_THREAD: 當定時器到期,核心會(在此進程內)以sigev_notification_attributes為線程屬性建立一個線程,並且讓它執行sigev_notify_function,傳入sigev_value作為為一個參數。

啟動一個定時器:
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);}

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.