POSIX定时器--timer_create等系列

时间:2022-04-09 02:22:49

POSIX定时器

最强大的定时器接口来自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:采用新线程派驻的通知方式

  1. #include <stdio.h>  
  2. #include <signal.h>  
  3. #include <time.h>  
  4. #include <string.h>  
  5. #include <stdlib.h>  
  6. #include <unistd.h>  
  7.   
  8. #define CLOCKID CLOCK_REALTIME  
  9.   
  10. void timer_thread(union sigval v)  
  11. {  
  12.     printf("timer_thread function! %d\n", v.sival_int);  
  13. }  
  14.   
  15. int main()  
  16. {  
  17.     // XXX int timer_create(clockid_t clockid, struct sigevent *evp, timer_t *timerid);  
  18.     // clockid--值:CLOCK_REALTIME,CLOCK_MONOTONIC,CLOCK_PROCESS_CPUTIME_ID,CLOCK_THREAD_CPUTIME_ID  
  19.     // evp--存放环境值的地址,结构成员说明了定时器到期的通知方式和处理方式等  
  20.     // timerid--定时器标识符  
  21.     timer_t timerid;  
  22.     struct sigevent evp;  
  23.     memset(&evp, 0, sizeof(struct sigevent));       //清零初始化  
  24.   
  25.     evp.sigev_value.sival_int = 111;            //也是标识定时器的,这和timerid有什么区别?回调函数可以获得  
  26.     evp.sigev_notify = SIGEV_THREAD;            //线程通知的方式,派驻新线程  
  27.     evp.sigev_notify_function = timer_thread;       //线程函数地址  
  28.   
  29.     if (timer_create(CLOCKID, &evp, &timerid) == -1)  
  30.     {  
  31.         perror("fail to timer_create");  
  32.         exit(-1);  
  33.     }  
  34.   
  35.     // XXX int timer_settime(timer_t timerid, int flags, const struct itimerspec *new_value,struct itimerspec *old_value);  
  36.     // timerid--定时器标识  
  37.     // flags--0表示相对时间,1表示绝对时间  
  38.     // new_value--定时器的新初始值和间隔,如下面的it  
  39.     // old_value--取值通常为0,即第四个参数常为NULL,若不为NULL,则返回定时器的前一个值  
  40.       
  41.     //第一次间隔it.it_value这么长,以后每次都是it.it_interval这么长,就是说it.it_value变0的时候会装载it.it_interval的值  
  42.     struct itimerspec it;  
  43.     it.it_interval.tv_sec = 1;  
  44.     it.it_interval.tv_nsec = 0;  
  45.     it.it_value.tv_sec = 1;  
  46.     it.it_value.tv_nsec = 0;  
  47.   
  48.     if (timer_settime(timerid, 0, &it, NULL) == -1)  
  49.     {  
  50.         perror("fail to timer_settime");  
  51.         exit(-1);  
  52.     }  
  53.   
  54.     pause();  
  55.   
  56.     return 0;  
  57. }  
  58. /* 
  59.  * int timer_gettime(timer_t timerid, struct itimerspec *curr_value); 
  60.  * 获取timerid指定的定时器的值,填入curr_value 
  61.  * 
  62.  */  

编译需要librt库支持:

       gcc -o main main.c -lrt



程序2:通知方式为信号的处理方式

  1. #include <stdio.h>  
  2. #include <time.h>  
  3. #include <stdlib.h>  
  4. #include <signal.h>  
  5. #include <string.h>  
  6. #include <unistd.h>  
  7.   
  8. #define CLOCKID CLOCK_REALTIME  
  9.   
  10. void sig_handler(int signo)  
  11. {  
  12.     printf("timer_signal function! %d\n", signo);  
  13. }  
  14.   
  15. int main()  
  16. {  
  17.     // XXX int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);  
  18.     // signum--指定的信号编号,可以指定SIGKILL和SIGSTOP以外的所有信号编号  
  19.     // act结构体--设置信号编号为signum的处理方式  
  20.     // oldact结构体--保存上次的处理方式  
  21.     //  
  22.     // struct sigaction   
  23.     // {  
  24.     // void (*sa_handler)(int);         //信号响应函数地址  
  25.     // void (*sa_sigaction)(int, siginfo_t *, void *);   //但sa_flags为SA——SIGINFO时才使用  
  26.     // sigset_t sa_mask;         //说明一个信号集在调用捕捉函数之前,会加入进程的屏蔽中,当捕捉函数返回时,还原  
  27.     // int sa_flags;  
  28.     // void (*sa_restorer)(void);   //未用  
  29.     // };  
  30.     //  
  31.     timer_t timerid;  
  32.     struct sigevent evp;  
  33.   
  34.     struct sigaction act;  
  35.     memset(&act, 0, sizeof(act));  
  36.     act.sa_handler = sig_handler;  
  37.     act.sa_flags = 0;  
  38.   
  39.     // XXX int sigaddset(sigset_t *set, int signum);  //将signum指定的信号加入set信号集  
  40.     // XXX int sigemptyset(sigset_t *set);          //初始化信号集  
  41.       
  42.     sigemptyset(&act.sa_mask);  
  43.   
  44.     if (sigaction(SIGUSR1, &act, NULL) == -1)  
  45.     {  
  46.         perror("fail to sigaction");  
  47.         exit(-1);  
  48.     }  
  49.   
  50.     memset(&evp, 0, sizeof(struct sigevent));  
  51.     evp.sigev_signo = SIGUSR1;  
  52.     evp.sigev_notify = SIGEV_SIGNAL;  
  53.     if (timer_create(CLOCK_REALTIME, &evp, &timerid) == -1)  
  54.     {  
  55.         perror("fail to timer_create");  
  56.         exit(-1);  
  57.     }  
  58.   
  59.     struct itimerspec it;  
  60.     it.it_interval.tv_sec = 2;  
  61.     it.it_interval.tv_nsec = 0;  
  62.     it.it_value.tv_sec = 1;  
  63.     it.it_value.tv_nsec = 0;  
  64.     if (timer_settime(timerid, 0, &it, 0) == -1)  
  65.     {  
  66.         perror("fail to timer_settime");  
  67.         exit(-1);  
  68.     }  
  69.   
  70.     pause();  
  71.   
  72.     return 0;  
  73. }

编译需要librt库支持:

         gcc -o main main.c -lrt