Linuxc - 關於Linux的應用層定時器

linuxc-關於Linux的應用層定時器

使用定時器的目的無非是爲了週期性的執行某一任務,或者是到了一個指定時間去執行某一個任務。要達到這一目的,通常有兩個常見的比較有效的方法。一個是用 Linux 內部的三個定時器;另外一個是用 sleep 或 usleep 函數讓進程睡眠一段時間;其實,還有一個方法,那就是用 gettimeofday、difftime 等本身來計算時間間隔,而後時間到了就執行某一任務,可是這種方法效率低,因此不經常使用。linux

一、alarm

若是不要求很精確的話,用 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();

}

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

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

  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(),從新安裝該信號。繼承

三、用 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秒到了就給你輸出一個字符串。因此,若是你只作通常的定時,到了時間去執行一個任務,這種方法是最簡單的。接口

四、經過本身計算時間差的方法來定時

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

五、使用 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 簡單。

六、高精度硬件中判定時器 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如下的週期出現。

七、高精度定時器 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 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設定爲以下值來定製定時器到期後的行爲:

  • 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);

}
相關文章
相關標籤/搜索