linux 下 alarm(), setitimer 定時器與 POSIX 定時器 timer_settime()對比總結 (下)

上一篇 linux 下 alarm(), setitimer 定時器與 POSIX 定時器 timer_settime()對比總結 (上)總結了 alarm 和setitimer定時器的用法和注意事項。alarm不適用於精度要求比較高的場景,而setitimer也有一個缺點:因爲setitimer 是配合SIGALRM中斷信號使用的,而SIGALRM的中斷信號會終止sleep,因爲sleep就是用SIGALRM信號量實現的,因此也就有了POSIX 定時器出場的機會:

POSIX 定時器 主要分創建,初始化以及刪除三個操作,對應的函數分別爲:

timer_create()(創建定時器)
timer_settime()(初始化定時器)
timer_delete(銷燬定時器)

首先需要創建一個定時器對象,設置通知類型,一般包括信號、脈衝或線程創建,並創建通知結構(結構sigevent),設置定時類型 (相對與絕對,一次與週期),最後啓動它。

1. 創建定時器

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. 標準POSIX定義的時鐘,如果它處於省電模式,基於這個時鐘的定時器可以喚醒處理器

CLOCK_SOFTTIME: 這個時鐘只在處理器不處於節電模式時才激活。例如,使用CLOCK_SOFTTIME計時器休眠的應用程序不會在應用程序應該喚醒時喚醒處理器。這將允許處理器進入省電模式。

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.

第二個參數:sigevent數據結構的指針。這個數據結構用於通知內核,在“觸發”時,計時器應該傳遞什麼類型的事件。

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作爲爲一個參數。

 

2.  設置定時器

timer_create()所創建的定時器並未啓動。要將它關聯到一個到期時間以及啓動時鐘週期。使用timer_settime()的參數組合完成,timer_settime()函數用於實際啓動計時器:

int timer_settime (timer_t timerid,       int flags,    struct itimerspec *value,      struct itimerspec *oldvalue);

       timerid參數是從timer_create()函數調用中得到的值,創建一組計時器,然後在它們上分別調用timer_settime()來設置和啓動它們。
        flags參數就是指定絕對與相對標誌。如果傳遞常量TIMER_ABSTIME,則value所指定的時間值會被解讀成絕對值(此值的默認的解讀方式爲相對於當前的時間)。這個經修改的行爲可避免取得當前時間、計算“該時間”與“所期望的未來時間”的相對差額以及啓動定時器期間造成競爭條件。在希望計時器關閉的時候傳遞實際的日期和時間。如果傳遞一個0,那麼計時器將被視爲相對於當前時間。
       value 參數指定時間。以下是兩個數據結構的關鍵部分():

struct timespec
{    long    tv_sec,
             tv_nsec;
};
struct itimerspec 
{
     struct timespec it_value,//單次啓動時間
                        it_interval;//定時時間
};

如同settimer(),it_value用於指定當前的定時器到期時間。當定時器到期,it_value的值會被更新成it_interval 的值。如果it_interval的值爲0,則定時器不是一個時間間隔定時器,一旦it_value到期就會回到未啓動狀態。timespec的結構提供了納秒級分辨率:

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指定了無效的定時器

3. 刪除定時器

int timer_delete (timer_t timerid);

   一次成功的timer_delete()調用會銷燬關聯到timerid的定時器並且返回0。執行失敗時,此調用會返回-1並將errno設定會 EINVAL,這個唯一的錯誤情況代表timerid不是一個有效的定時器。

4. 示例

4.1 採用通知方式爲信號(signal)的處理方式

4.1.1:使用SIGALRM信號量定時

上一篇講到,如果使用信號量SIGALRM,(對 CLOCK_REALTIMER來說,默認信號就是SIGALRM)sleep()函數使用的就是實時時鐘CLOCK_REALTIMER
所以使用信號值SIGALRM會中斷sleep(int second)函數的休眠;

下面給出一個例子:

//timercreate_demo.cpp
#include <unistd.h> 
#include <stdio.h>
#include <signal.h>
#include <time.h>

void SignHandler(int iSignNo);
void testTimerSign();
void printTime();

int main() {
    testTimerSign();
    while(true){
         int left = sleep(5);
         printTime();
         printf("sleep(5)(left=%d)\n", left);
    }
    return 0; 
}

void SignHandler(int iSignNo){
    //printTime();
    if(iSignNo == SIGUSR1){
        printf("Capture sign no : SIGUSR1\n"); 
    }else if(SIGALRM == iSignNo){
        //printf("Capture sign no : SIGALRM\n"); 
    }else{
        printf("Capture sign no:%d\n",iSignNo); 
    }
}

void testTimerSign(){
    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 = SIGALRM;
    signal(evp.sigev_signo, SignHandler); 
    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 = 1;
    ts.it_value.tv_nsec = 0;  
    printTime();
    printf("start\n");
    ret = timer_settime(timer, 0, &ts, NULL);  
    if(ret) {
        perror("timer_settime"); 
    } 
}

void printTime(){
    struct tm *cursystem;
    time_t tm_t;
    time(&tm_t);
    cursystem = localtime(&tm_t);
    char tszInfo[2048] ;
    sprintf(tszInfo, "%02d:%02d:%02d", 
        cursystem->tm_hour, 
        cursystem->tm_min, 
        cursystem->tm_sec);
        printf("[%s]",tszInfo);
}

è¿éåå¾çæè¿°

因爲timer_settime()中定時器間隔時間爲1秒
於是sleep(5)每次都被打斷不能按時休眠,剩餘4秒未能執行;

4.1.2 示例2 SIGALRM信號量不同線程的影響

因爲休眠sleep(unsigned int)爲線程內操作
所以如果不同線程,信號量SIGALRM是不能中斷sleep();

下面用一個例子說明:

//timercreate_demo.cpp
#include <unistd.h> 
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <pthread.h>  

void SignHandler(int iSignNo);
void testTimerSign();
void printTime();
void *function(void *arg);

int main() {
    pthread_t thread1;  
    pthread_create(&thread1,NULL,function,(char*)"111"); 
    testTimerSign();
    while(true);
    return 0; 
}

void SignHandler(int iSignNo){
    if(iSignNo == SIGUSR1){
        printf("Capture sign no : SIGUSR1\n"); 
    }else if(SIGALRM == iSignNo){
        //printf("Capture sign no : SIGALRM\n"); 
    }else{
        printf("Capture sign no:%d\n",iSignNo); 
    }
}

void testTimerSign(){
    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 = SIGALRM;
    signal(evp.sigev_signo, SignHandler); 
    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 = 1;
    ts.it_value.tv_nsec = 0;  
    printTime();
    printf("start\n");
    ret = timer_settime(timer, 0, &ts, NULL);  
    if(ret) {
        perror("timer_settime"); 
    } 
}

void printTime(){
    struct tm *cursystem;
    time_t tm_t;
    time(&tm_t);
    cursystem = localtime(&tm_t);
    char tszInfo[2048] ;
    sprintf(tszInfo, "%02d:%02d:%02d", 
        cursystem->tm_hour, 
        cursystem->tm_min, 
        cursystem->tm_sec);
        printf("[%s]",tszInfo);
}

void *function(void *arg){  
    char *m;  
    m = (char *)arg;  
    while(true) {
        while(true){
            int left = sleep(3);
            printTime();
            printf("sleep(3)(left=%d)\n", left);
            }
    }
}

注意編譯的時候添加 -lpthread

輸出結果: 

可以看到主線程的定時器中的信號量SIGALRM是無法中斷子線程thread1的休眠;

4.1.3 示例3 使用SIGUSR1信號量定時

//signalDemo.cpp
//compile : gcc signalDemo.cpp -o testSignal -lrt
#include <unistd.h>
#include <stdio.h> 
#include <signal.h>
#include <time.h>

void testTimerSign();
void SignHandler(int iSignNo);
void printTime();

int main() {
    testTimerSign();
    while(true) {
        sleep(1); 
    }
    return 0; 
}

void SignHandler(int iSignNo){
    printTime();
    if(iSignNo == SIGUSR1){
        printf("Capture sign No.=SIGUSR1\n"); 
    }else{
        printf("Capture sign No.=%d\n",iSignNo); 
    }
}

void testTimerSign(){
    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(evp.sigev_signo, SignHandler); 
    ret = timer_create(CLOCK_REALTIME, &evp, &timer);  
    if(ret) {
        perror("timer_create");
    } 
    ts.it_interval.tv_sec = 1; // the spacing time  
    ts.it_interval.tv_nsec = 0;  
    ts.it_value.tv_sec = 2;  // the delay time start
    ts.it_value.tv_nsec = 0;  
    printTime();
    printf("start\n");
    ret = timer_settime(timer, 0, &ts, NULL);  
    if(ret) {
        perror("timer_settime"); 
    } 
}

void printTime(){
    struct tm *cursystem;
    time_t tm_t;
    time(&tm_t);
    cursystem = localtime(&tm_t);
    char tszInfo[2048] ;
    sprintf(tszInfo, "%02d:%02d:%02d", 
        cursystem->tm_hour, cursystem->tm_min, 
        cursystem->tm_sec);
    printf("[%s]",tszInfo);
}

輸出結果:

4.1.4 示例4 信號量爲signaction的處理函數

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
 
#define CLOCKID CLOCK_REALTIME
 
void sig_handler(int signo)
{
	printf("timer_signal function! %d\n", signo);
}
 
int main()
{
	//XXX int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
	// signum--指定的信號編號,可以指定SIGKILL和SIGSTOP以外的所有信號編號
	// act結構體--設置信號編號爲signum的處理方式
	// oldact結構體--保存上次的處理方式
	//
	// struct sigaction 
	// {
	// void (*sa_handler)(int);			//信號響應函數地址
	// void (*sa_sigaction)(int, siginfo_t *, void *);   //但sa_flags爲SA——SIGINFO時才使用
	// sigset_t sa_mask;         //說明一個信號集在調用捕捉函數之前,會加入進程的屏蔽中,當捕捉函數返回時,還原
	// int sa_flags;
	// void (*sa_restorer)(void);	//未用
	// };
	//
	timer_t timerid;
	struct sigevent evp;
 
	struct sigaction act;
	memset(&act, 0, sizeof(act));
	act.sa_handler = sig_handler;
	act.sa_flags = 0;
 
	// XXX int sigaddset(sigset_t *set, int signum);  //將signum指定的信號加入set信號集
	// XXX int sigemptyset(sigset_t *set);			//初始化信號集
	
	sigemptyset(&act.sa_mask);
 
	if (sigaction(SIGUSR1, &act, NULL) == -1)
	{
		perror("fail to sigaction");
		exit(-1);
	}
 
	memset(&evp, 0, sizeof(struct sigevent));
	evp.sigev_signo = SIGUSR1;
	evp.sigev_notify = SIGEV_SIGNAL;
	if (timer_create(CLOCK_REALTIME, &evp, &timerid) == -1)
	{
		perror("fail to timer_create");
		exit(-1);
	}
 
	struct itimerspec it;
	it.it_interval.tv_sec = 2;
	it.it_interval.tv_nsec = 0;
	it.it_value.tv_sec = 1;
	it.it_value.tv_nsec = 0;
	if (timer_settime(timerid, 0, &it, 0) == -1)
	{
		perror("fail to timer_settime");
		exit(-1);
	}
 
	pause();
 
	return 0;
}

輸出結果:

4.2 採用新線程派駐的通知方式

4.2.1 示例5 簡單函數線程的處理方式

#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
 
#define CLOCKID CLOCK_REALTIME
 
void timer_thread(union sigval v)
{
	printf("timer_thread function! %d\n", v.sival_int);
}
 
int main()
{
	// XXX int timer_create(clockid_t clockid, struct sigevent *evp, timer_t *timerid);
	// clockid--值:CLOCK_REALTIME,CLOCK_MONOTONIC,CLOCK_PROCESS_CPUTIME_ID,CLOCK_THREAD_CPUTIME_ID
	// evp--存放環境值的地址,結構成員說明了定時器到期的通知方式和處理方式等
	// timerid--定時器標識符
	timer_t timerid;
	struct sigevent evp;
	memset(&evp, 0, sizeof(struct sigevent));	//清零初始化
 
	evp.sigev_value.sival_int = 111;		//也是標識定時器的,這和timerid有什麼區別?回調函數可以獲得
	evp.sigev_notify = SIGEV_THREAD;		//線程通知的方式,派駐新線程
	evp.sigev_notify_function = timer_thread;	//線程函數地址
 
	if (timer_create(CLOCKID, &evp, &timerid) == -1)
	{
		perror("fail to timer_create");
		exit(-1);
	}
 
	// XXX int timer_settime(timer_t timerid, int flags, const struct itimerspec *new_value,struct itimerspec *old_value);
	// timerid--定時器標識
	// flags--0表示相對時間,1表示絕對時間,通常使用相對時間
	// new_value--定時器的新初始值和間隔,如下面的it
	// old_value--取值通常爲0,即第四個參數常爲NULL,若不爲NULL,則返回定時器的前一個值
	
	//第一次間隔it.it_value這麼長,以後每次都是it.it_interval這麼長,就是說it.it_value變0的時候會裝載it.it_interval的值
	//it.it_interval可以理解爲週期
	struct itimerspec it;
	it.it_interval.tv_sec = 1;	//間隔1s
	it.it_interval.tv_nsec = 0;
	it.it_value.tv_sec = 1;		
	it.it_value.tv_nsec = 0;
 
	if (timer_settime(timerid, 0, &it, NULL) == -1)
	{
		perror("fail to timer_settime");
		exit(-1);
	}
 
	pause();
 
	return 0;
}
/*
 * int timer_gettime(timer_t timerid, struct itimerspec *curr_value);
 * 獲取timerid指定的定時器的值,填入curr_value
 *
 */

輸出結果:

4.2.2 示例6  使用pthread 創建線程的處理方法

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <time.h>
#include <error.h>
#include <errno.h>
#include <string.h>
#include <pthread.h> 
#include <sys/wait.h>
  
 
void timer_thread(union sigval v)  
{  
    printf("timer_thread function! %d\n", v.sival_int);  
}  
 
int init_timer(timer_t *timerid, struct sigevent *evp, struct itimerspec *it)
{
    if ( !evp || !it )
        return -1;
 
    memset(evp, 0, sizeof(struct sigevent));   //清零初始化  
  
    evp->sigev_value.sival_int = 111;        //也是標識定時器的,這和timerid有什麼區別?回調函數可以獲得  
    evp->sigev_notify = SIGEV_THREAD;        //線程通知的方式,派駐新線程  
    evp->sigev_notify_function = timer_thread;   //線程函數地址  
 
    if (timer_create(CLOCK_REALTIME, evp, timerid) == -1)  
    {  
        perror("fail to timer_create");  
        return -1;;  
    }  
 
    printf("timer_create timerid = %d\n", *timerid);
    it->it_interval.tv_sec = 1;  // 後續按照該時間間隔 
    it->it_interval.tv_nsec = 0;  
    it->it_value.tv_sec = 3;     // 最初開始時間間隔 
    it->it_value.tv_nsec = 0;  
 
    return 0;
}
 
int start_timer(timer_t *timerid, struct itimerspec *it)
{
    if (it == NULL){
        return -1;
    }
    if (timer_settime(*timerid, 0, it, NULL) == -1)  
    {  
        perror("fail to timer_settime");  
        return -1;
    }  
    
    return 0;
}
 
void* thread_func(void *param)
{
    int a = *(int *)param;
    while(1){
        sleep(1);
        printf("This is thread..\n");
    }
 
    a = 100;
    printf("param = %d\n", a);
}
 
int main(int argc, const char *argv[])
{
    pid_t pid = 0;
    pthread_t thread;
    timer_t timerid = 0;
    int ret;
    struct sigevent evp;  
    struct itimerspec it;  
 
    int a = 10;  
#if 0
 
    int ret = init_timer(&timerid, &evp, &it);
    if (ret < 0){
        printf("init_timer failed\n");
        return -1;
    }
#endif
  
    if ((pid = fork()) < 0)
    {
        printf("fork failed.\n");
        return -1;
    }
    else if ( pid == 0){
        printf("child proc..\n");
 
        ret = pthread_create(&thread, NULL, thread_func, &a);//最後一個參數爲void*類型,傳遞變量的地址,其實其他的類型也遵循這個原則
 
        int ret = init_timer(&timerid, &evp, &it);
        if (ret < 0){
            printf("init_timer failed\n");
            return -1;
        }
        sleep(2);
        printf("child timer_Id addr = %d\n", timerid);
        start_timer(&timerid, &it);
 
        sleep(10);
        exit(0);
    }
    else{
        printf("I'm parent proc..\n");
        printf("parent timer_Id addr = %d\n", timerid);
        printf("pthread_id = %d\n", thread);
        do {
            ret = waitpid(pid, NULL, WNOHANG);
            if (ret == 0){
                printf("No child exit\n");
                sleep(2);
            }
            else if (ret == pid){
                printf("Successly get child %d\n", pid);
            }
            else
                printf("something error\n");
        }while(ret == 0);
    
        /*ret = waitpid(pid, NULL, 0);*/
        /*if (ret == pid)*/
            /*printf("successly get child %d\n", pid);*/
 
    }
    pause();
    return 0;
}

輸出結果:

 

5.  參考鏈接:

QNX-----定時器的使用

timer_create()建立定時器剖析

POSIX定時器:timer_settime()

linux下定時器timer_create()的使用

linux下定時器的使用--timer_create等系列

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章