Linux应用编程:定时器

定时任务sleep

嵌入式Linux应用编程中,通常会需要定时完成一些任务,最平常可见的方法就是使用sleep函数。

while(condition)
{
   
    // timed_task
    sleep(20); // 每隔20s就执行一次任务timed_task
}

但是sleep()函数是一个阻塞式函数,会在指定的时间内暂停整个线程。此时就会浪费cpu执行。此时希望程序做一些其他事情。于是就可以使用定时器。

定时器setitimer

setitimer

#include <sys/time.h> // 头文件
int getitimer(int which, struct itimerval *curr_value);
int setitimer(int which, const struct itimerval *new_value,
                     struct itimerval *old_value);
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 1/1000000 seconds */
};

  • which:
    ITIMER_REAL:以系统真实的时间来计算,它送出SIGALRM信号。
    ITIMER_VIRTUAL:以该进程在用户态下花费的时间来计算,它送出SIGVTALRM信号。
    ITIMER_PROF:以该进程在用户态下和内核态下所费的时间来计算,它送出SIGPROF信号

  • new_value:
    参数用来对计时器进行设置,it_interval为计时间隔,it_value为延时时长
    在setitimer方法调用成功后,延时it_value后触发一次SIGALRM信号,
    以后每隔it_interval触发一次SIGALRM信号。

settimer工作机制是,先对it_value倒计时,当it_value为零时触发信号,然后重置为it_interval,继续对it_value倒计时,一直这样循环下去。基于此机制,setitimer既可以用来延时执行,也可定时执行。假如it_value为0是不会触发信号的,所以要能触发信号,it_value得大于0;如果it_interval为零,只会延时,不会定时(也就是说只会触发一次信号)。

  • old_value

参数,通常用不上,设置为NULL,它是用来存储上一次setitimer调用时设置的new_value值。

sample

每隔2s执行一次timer_handler函数

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

void timer_handler(int signum) {
   
    // 执行定时器到期时需要做的操作
    // 注意:定时器处理函数应该尽量保持简短,避免执行耗时操作
    printf("timed\n");
}

void set_timer(int seconds) {
   
    struct itimerval timer;
    timer.it_value.tv_sec = seconds;  // 第一次定时器到期的秒数
    timer.it_value.tv_usec = 0;       // 第一次定时器到期的微秒数
    // timer.it_interval = timer.it_value;
    timer.it_interval.tv_sec = seconds;  // 定时器周期的秒数(如果为0,则只执行一次)
    timer.it_interval.tv_usec = 0;       // 定时器周期的微秒数

    // 设置定时器信号处理函数
    // signal(SIGALRM, timer_handler);
    struct sigaction act;
    act.sa_handler = timer_handler;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask); 
    sigaction(SIGALRM,&act,NULL); //设置信号 SIGALRM 的处理函数为 timer_handler

    // 启动定时器
    setitimer(ITIMER_REAL, &timer, NULL);
}

/*停止setitimer定时器*/
void delete_setitimer() 
{
   
    struct itimerval value; 
    value.it_value.tv_sec = 0; 
    value.it_value.tv_usec = 0; 
    value.it_interval = value.it_value; 
    setitimer(ITIMER_REAL, &value, NULL); 
}

int main()
{
   
    set_timer(2);

    while(1);
    return 0;
}

但是这个定时器有一个缺点,就是一个进程只能使用一个定时器,假如应用需要同时维护多个 Interval 不同的计时器,必须自己写代码来维护,而且该定时器的精度是ms。如果需要多个定时器和更高精度的定时,可以使用POSIX Timer

POSIX Timer

  • 几个函数
// 创建一个定时器
int timer_create(clockid_t clock_id, struct sigevent *evp, timer_t *timerid)
// 启动一个定时器
int timer_settime(timer_t timerid, int flags, const struct itimerspec *value, 
                  struct itimerspect *ovalue);
// 获取定时器剩余时间
int timer_gettime(timer_t timerid,struct itimerspec *value);
// 取得一个定时器的超限运行次数
int timer_getoverrun(timer_t timerid);
// 删除定时器
int timer_delete (timer_t timerid);
  • 和setitimer比较

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

创建定时器timer_create

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

该定时器属于进程级别的资源,在fork的时候,子进程不能继承该定时器。

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.

evp:指定了定时器到期要产生的异步通知。

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为NULL,那么定时器到期会产生默认的信号,对 CLOCK_REALTIMER来说,默认信号就是SIGALRM。

evp->sigev_signo:设置定时器产生后发出的信号类型。

如果要产生除默认信号之外的其它信号,程序必须将evp->sigev_signo设置为期望的信号码

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作为为一个参数。

evp->sigev_value:来区分是哪个定时器引起的信号或者线程。

如果几个定时器产生了同一个信号,处理程序可以用 evp->sigev_value来区分是哪个定时器产生了信号。要实现这种功能,程序必须在为信号安装处理程序时,使用struct sigaction的成员sa_flags中的标志符SA_SIGINFO。

timerid:创建的定时器id。

启动定时器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;   
}; 

timer_create()所创建的定时器并未启动。要将它关联到一个到期时间以及启动时钟周期,可以使用timer_settime()

flag:如果flags的值为TIMER_ABSTIME,则value所指定的时间值会被解读成绝对值(此值的默认的解读方式为相对于当前的时间)。

工作机制和setitimer类似,如果ovalue的值不是NULL,则之前的定时器到期时间会被存入其所提供的itimerspec。如果定时器之前处在未启动状态,则此结构的成员全都会被设定成0。

剩余时间timer_gettime

//获得一个活动定时器的剩余时间
int timer_gettime(timer_t timerid,struct itimerspec *value);

定时器超限timer_getoverrun

//取得一个定时器的超限运行次数
int timer_getoverrun(timer_t timerid);

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

执行成功时,timer_getoverrun()会返回定时器初次到期与通知进程(例如通过信号)定时器已到期之间额外发生的定时器到期次数。举例来说,一个1ms的定时器运行了10ms,则此调用会返回9。如果超限运行的次数等于或大于DELAYTIMER_MAX,则此调用会返回DELAYTIMER_MAX

执行失败时,此函数会返回-1并将errno设定会EINVAL,这个唯一的错误情况代表timerid指定了无效的定时器。

删除定时器timer_delete

int timer_delete (timer_t timerid);

执行成功时:销毁timerid的定时器,并返回0;

执行失败时:此调用会返回-1并将errno设定会 EINVAL,这个唯一的错误情况代表timerid不是一个有效的定时器。

sample1:信号方式

编译:gcc timer_sample1.c -o timer_sample1 -lrt

rt库:librt 库

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

/*定时器到期产生信号的handle*/
void handle(int signum)
{
   
    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; // 定时器id
    int ret;

    evp.sigev_value.sival_ptr = &timer; // 传递定时器id
    evp.sigev_notify = SIGEV_SIGNAL; // 设置为信号触发方式
    evp.sigev_signo = SIGUSR1; // 定时器到时产生SIGUSR1信号
    signal(SIGUSR1, handle); // 绑定信号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; // 间隔1s
    ts.it_value.tv_sec = 3;
    ts.it_value.tv_nsec = 0; // 第一次定时器为3s
    // ret = timer_settime(timer, TIMER_ABSTIME, &ts, NULL);
    ret = timer_settime(timer, 0, &ts, NULL);
    if( ret )
        perror("timer_settime");
    
    while(1);
}

sample2:线程形式

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

void* handle(void* sigval_v)
{
   
    union sigval v = (sigval)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; // 定时器id
    int ret;

    memset(&evp, 0, sizeof(evp));  // 必须要有这个,不然会出现段错误
    evp.sigev_value.sival_ptr = &timer; // 传递定时器id
    // evp.sigev_notify = SIGEV_SIGNAL; // 设置为信号触发方式
    evp.sigev_notify = SIGEV_THREAD; // 设置为线程模式
    evp.sigev_notify_function = handle; // 设置线程的函数
    evp.sigev_value.sival_int = 3;   //和sival_ptr作为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; // 间隔1s
    ts.it_value.tv_sec = 3;
    ts.it_value.tv_nsec = 0; // 第一次定时器为3s
    ret = timer_settime(timer, TIMER_ABSTIME, &ts, NULL);
    // ret = timer_settime(timer, 0, &ts, NULL);
    if( ret )
        perror("timer_settime");
    
    while(1);
}

/**** 执行结果
05:41:04 thread 2230478592, val = 3, signal captured.
05:41:05 thread 2230478592, val = 3, signal captured.
05:41:05 thread 2230478592, val = 3, signal captured
****/

相关推荐

  1. Linux应用编程定时器

    2024-01-13 13:32:01       31 阅读
  2. Linux PWM 应用编程

    2024-01-13 13:32:01       39 阅读
  3. Linux GPIO 应用编程

    2024-01-13 13:32:01       34 阅读
  4. Linux/Uinx 系统编程定时器以及时钟同步

    2024-01-13 13:32:01       19 阅读
  5. Linux定时器

    2024-01-13 13:32:01       31 阅读
  6. Linux定时器

    2024-01-13 13:32:01       33 阅读
  7. Linux应用编程笔记】GPIO

    2024-01-13 13:32:01       33 阅读
  8. CAN Linux C应用编程

    2024-01-13 13:32:01       18 阅读
  9. 1. Linux应用编程概念

    2024-01-13 13:32:01       19 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-01-13 13:32:01       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-01-13 13:32:01       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-01-13 13:32:01       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-01-13 13:32:01       18 阅读

热门阅读

  1. 边缘计算:挑战与机遇并存

    2024-01-13 13:32:01       32 阅读
  2. 边缘计算的挑战和机遇

    2024-01-13 13:32:01       32 阅读
  3. centos 编译升级内核

    2024-01-13 13:32:01       36 阅读
  4. 2023年末整理后端开发用软件合集

    2024-01-13 13:32:01       43 阅读
  5. Python个人学习笔记目录

    2024-01-13 13:32:01       35 阅读
  6. 代码随想录算法训练营29期Day17|LeetCode 110,257,404

    2024-01-13 13:32:01       32 阅读
  7. 编程笔记 html5&css&js 034 HTML MathML

    2024-01-13 13:32:01       23 阅读
  8. 深入理解Golang中的接口与实例展示

    2024-01-13 13:32:01       38 阅读