【转】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;
}
输出结果:


————————————————
版权声明:本文为CSDN博主「ppipp1109」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/p942005405/article/details/100883568

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值