settime计时器和POSIX timer函数

Linux 系统上最常用的定时器是 setitmer 计时器

settimmmer原型:

int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue);
which为定时器类型,setitimer支持3种类型的定时器:

ITIMER_REAL: 以系统真实的时间来计算,它送出SIGALRM信号。
ITIMER_VIRTUAL: -以该进程在用户态下花费的时间来计算,它送出SIGVTALRM信号。
ITIMER_PROF: 以该进程在用户态下和内核态下所费的时间来计算,它送出SIGPROF信号。
setitimer()调用成功返回0,否则返回-1。

setitimer()第一个参数which指定定时器类型(上面三种之一);第二个参数是结构itimerval的一个实例;第三个参数可不做处理。

计时器值由以下结构定义:

struct itimerval {
    struct timeval it_interval; /* next value */
    struct timeval it_value;    /* current value */
};

struct timeval {
    time_t      tv_sec;         /* seconds */
    suseconds_t tv_usec;        /* microseconds */
};

it_interval指定间隔时间,it_value指定初始定时时间。如果只指定it_value,就是实现一次定时;如果同时指定 it_interval,则超时后,系统会重新初始化it_value为it_interval,实现重复定时;两者都清零,则会清除定时器。

tv_sec提供秒级精度,tv_usec提供微秒级精度,以值大的为先

示例代码:

#include <stdio.h>
#include <sys/time.h>
#include <string.h>
#include <signal.h>
#include <time.h>


//简单打印信息,定时器触发函数
void print_info(int signo){
    printf("timer fired\n");
}

void init_sigaction(){
    struct sigaction act;
    act.sa_handler = print_info;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    sigaction(SIGPROF,&act,NULL); //设置信号 SIGPROF 的处理函数为 print_info

}


void init_time() {
    struct itimerval value;
    value.it_value.tv_sec=2; //定时器启动后,每隔2秒将执行相应的函数
    value.it_value.tv_usec=0;
    value.it_interval=value.it_value;
    setitimer(ITIMER_PROF,&value,NULL); //初始化 timer,到期发送 SIGPROF 信号
}

int main(int argc ,char *argv[]){

    init_sigaction();
    init_time();
    while(1);

    return 0;
}

编译输出:
settime计时器和POSIX timer函数
这个程序使用 PROF 时间,每经过两秒 PROF 时间之后就会打印一下 timer fired 字符串。

需要指出:setitimer 计时器的精度为 ms,即 1000 分之 1 秒,足以满足绝大多数应用程序的需要。但多媒体等应用可能需要更高精度的定时,那么就需要考虑使用下一类定时器:POSIX Timer。

posix timer函数

最强大的定时器接口来自POSIX时钟系列,其创建、初始化以及删除一个定时器的行动被分为三个不同的函数:timer_create()(创建定时器)、timer_settime()(初始化定时器)以及 timer_delete()(销毁它)。POSIX Timer 对 setitimer 进行了增强,克服了 setitimer 的诸多问题:

  • 1、一个进程同一时刻只能有一个 timer。假如应用需要同时维护多个 Interval 不同的计时器,必须自己写代码来维护。这非常不方便。使用 POSIX Timer,一个进程可以创建任意多个 Timer。
  • 2、setitmer 计时器时间到达时,只能使用信号方式通知使用 timer 的进程,而 POSIX timer 可以有多种通知方式,比如信号,或者启动线程。
  • 3、使用 setitimer 时,通知信号的类别不能改变:SIGALARM,SIGPROF 等,而这些都是传统信号,而不是实时信号,因此有 timer overrun 的问题;而 POSIX Timer 则可以使用实时信号。
  • 4、 setimer 的精度是 ms,POSIX Timer 是针对有实时要求的应用所设计的,接口支持 ns 级别的时钟精度。

time_create[创建一个定时器]

time_create函数原型:

 int timer_create(clockid_t clock_id, struct sigevent *evp, timer_t *timerid)

参数说明:

clock_id 说明定时器是基于哪个时钟的,*timerid 装载的是被创建的定时器的 ID。该函数创建了定时器,并将他的 ID 放入timerid指向的位置中。参数evp指定了定时器到期要产生的异步通知。clock_id取值为以下:

CLOCK_REALTIME :Systemwide realtime clock. //时间是系统保存的时间,即可以由 date 命令显示的时间,该时间可以重新设置。
CLOCK_MONOTONIC:Represents monotonic time. Cannot be set.
CLOCK_PROCESS_CPUTIME_ID :High resolution per-process timer.  //CLOCK_PROCESS_CPUTIME_ID 的含义与 setitimer 的 ITIMER_VIRTUAL 类似。
CLOCK_THREAD_CPUTIME_ID :Thread-specific timer.// CLOCK_THREAD_CPUTIME_ID 以线程为计时实体,当前进程中的某个线程真正地运行了一定时间才触发 Timer。
CLOCK_REALTIME_HR :High resolution version of CLOCK_REALTIME.
CLOCK_MONOTONIC_HR :High resolution version of CLOCK_MONOTONIC.

如果evp为 NULL,那么定时器到期会产生默认的信号,对 CLOCK_REALTIMER来说,默认信号就是SIGALRM。如果要产生除默认信号之外的其它信号,程序必须将 evp->sigev_signo设置为期望的信号码。

struct sigevent 结构中的成员 evp->sigev_notify说明了定时器到期时应该采取的行动。通常,这个成员的值为SIGEV_SIGNAL,这个值说明在定时器到期时,会产生一个信号。程序可以将成员 evp->sigev_notify设为SIGEV_NONE来防止定时器到期时产生信号。

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;  
};
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);
	
//取得一个定时器的超限运行次数
int timer_getoverrun(timer_t timerid);

有可能一个定时器到期了,而同一定时器上一次到期时产生的信号还处于挂起状态。在这种情况下,其中的一个信号可能会丢失。这就是定时器超限。程序可以通过调用timer_getoverrun来确定一个特定的定时器出现这种超限的次数。定时器超限只能发生在同一个定时器产生的信号上。由多个定时器,甚至是那些使用相同的时钟和信号的定时器,所产生的信号都会排队而不会丢失。

执行成功时,timer_getoverrun()会返回定时器初次到期与通知进程(例如通过信号)定时器已到期之间额外发生的定时器到期次数。

//删除一个定时器
int timer_delete (timer_t timerid);

一次成功的timer_delete()调用会销毁关联到timerid的定时器并且返回0。执行失败时,此调用会返回-1并将errno设定会 EINVAL,这个唯一的错误情况代表timerid不是一个有效的定时器。

POSIX timer到期通知方式

SIGEV_NONE  定时器到期时不产生通知。。。
SIGEV_SIGNAL    定时器到期时将给进程投递一个信号,sigev_signo 可以用来指定使用什么信号。
SIGEV_THREAD    定时器到期时将启动新的线程进行需要的处理
SIGEV_THREAD_ID(仅针对 Linux)  定时器到期时将向指定线程发送信号。

设置通知方式:

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

这里将通知方式设为 SIGEV_THREAD,handle为入口函数。然后设置定时器间隔,并启动 Timer:

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

示例代码

#include <stdio.h>
#include <sys/time.h>
#include <string.h>
#include <signal.h>
#include <time.h>


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(int argc, char *argv[]){

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

}

编译输出:
settime计时器和POSIX timer函数

上一篇:OpenSSL实现3DES算法


下一篇:使用OpenSSL_EVP接口完成SHA3和国密SM3散列生成