quejing
发布于 2024-02-22 / 786 阅读
0

Linux下定时器使用

定时器的使用主要涉及到以下3个函数:

#include <time.h>
timer_create();
timer_settime();
timer_delete();

1. 创建定时器

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

参数说明:

  • clock_id: 指定定时器的时钟类型,可以是CLOCK_REALTIME或CLOCK_MONOTONIC
  • evp: 指向sigevent结构体的指针,用来指定定时器到期时的通知方式
  • timerid: 指向用来保存定时器指针,可以理解为就是创建的定时器对象
  • 返回值:0表示创建成功,其他表示创建失败

1.1 clock_id取值

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.

1.2 struct sigevent结构体说明

typedef struct sigevent {
    __sigval_t sigev_value;
    int sigev_signo;
    int sigev_notify;
    union {
	    int _pad[__SIGEV_PAD_SIZE];
	    __pid_t _tid;
	    struct {
	        void (*_function) (__sigval_t);	/* Function to start.  */
	        pthread_attr_t *_attribute;		/* Thread attributes.  */
        } _sigev_thread;
    } _sigev_un;
  } sigevent_t;
#define sigev_notify_function   _sigev_un._sigev_thread._function
#define sigev_notify_attributes _sigev_un._sigev_thread._attribute

......
union sigval {
  int sival_int;
  void *sival_ptr;
};
typedef union sigval __sigval_t;

对其中的参数说明如下:

  • sigev_value 表示该信号传递的值,可以是一个整数或者一个指针
  • sigev_signo 指定信号的值
  • sigev_notify表示定时器到期后信号的通知方式,可以是以下几种:
    • SIGEV_NONE:什么都不做,只提供通过timer_gettime和timer_getoverrun查询超时信息。
    • SIGEV_SIGNAL: 当定时器到期,内核会将sigev_signo所指定的信号传送给进程。
    • SIGEV_THREAD: 当定时器到期,内核会(在此进程内)以sigev_notification_attributes为线程属性创建一个线程,并且让它执行sigev_notify_function,传入sigev_value作为为一个参数。
    • SIGEV_THREAD_ID:将信号传递给指定的线程(不通用,Linux平台特有的)
  • _tid 指定接收信号的线程id
  • sigev_notify_function 定时器到期后的回调函数(配合SIGEV_THREAD通知使用)
  • sigev_notify_attributes 定时器到期后创建线程的属性(配合SIGEV_THREAD通知使用)

1.3 创建定时器举例

static void timer_callback_function(union sigval sigval)
{
  // @TODO
}
void demo_create_timer()
{
    timer_t timer_id;
    struct sigevent s_event;
    s_event.sigev_notify = SIGEV_THREAD;
    s_event.sigev_notify_function = timer_callback_function; // 设置到期后的回调函数
    s_event.sigev_value.sival_int = index; // 设置需要传递的值,如果无需传递值也可以不写
    s_event.sigev_notify_attributes = NULL;
    int result = timer_create(CLOCK_REALTIME, &s_event, &timer_id);
    if (result) {
        LOG_ERROR("timer create error is %s", strerror(errno));
        return;
    }
}

以上创建了一个定时器,该定时器到期后,会创建一个线程运行timer_callback_function回调函数。

2. 启动定时器

当定时器创建后是没有运行的,需要使用timer_settime指定运行参数后启动。

timer_settime的原型和使用的的时间相关结构体如下:

int timer_settime(timer_t timerid, int flags, const struct itimerspec *value, struct itimerspect *ovalue);

struct itimespec{
    struct timespec it_interval;  // 定时器重复时间,如果这个结构体的值设置为0表示只执行1次,否则会按照这个设定的时间不断执行
    struct timespec it_value;  // 定时器的超时时间,当这个结构体设置为0时,表示关闭这个定时器
};

struct timespec{
    time_t tv_sec;  // 秒
    long tv_nsec;  // 纳秒
};

函数的一些参数说明:

  • timerid:定时器 ID,可以使用 timer_create() 函数创建。
  • flags:控制定时器行为的标志。目前只有一个选项 0,表示不需要特殊行为。
  • new_value:一个指向 struct itimerspec 结构的指针,用于设置定时器的新值。
  • old_value:一个指向 struct itimerspec 结构的指针,用于存储设置之前定时器的旧值。可以为 NULL,表示不需要保存旧值。
  • 返回0表示成功,返回其他表示失败。

接上面的demo程序,当需要启动一个定时器是:

void demo_create_timer()
{
    timer_t timer_id;
    struct itimerspec ts;
    ......
    // 设定运行时间
    ts.it_value.tv_sec = 1;
    ts.it_value.tv_nsec = 0;
    ts.it_interval.tv_sec = 1;
    ts.it_interval.tv_nsec = 0;
    timer_settime(timerid, 0, &timer_value, NULL);
}

以上代码会让定时器按1秒的周期调用一次回调函数。

3. 删除定时器

删除定时器的函数原型如下:

int timer_delete (timer_t timerid);
  • timerid 待删除的定时器
  • 返回0表示删除成功,否则表示删除失败

4. 其他定时器函数

int timer_gettime(timer_t timerid, struct itimerspec *curr_value);  // 获得定时器的到期时间和间隔
int timer_getoverrun(timer_t timerid); // 获得定时器超限的次数

5. SIGEV_THREAD_ID 通知方式的使用

示例代码:

timer_t *timer_id;

/** 线程函数 */
static void *timer_thread_function(void *data)
{
    struct sigevent s_event;
    s_event.sigev_notify = SIGEV_THREAD_ID;
    s_event._sigev_un._tid = gettid(); // 获取当前线程的 TID
    s_event.sigev_signo = 50; // 指定信号值,如果无特殊需求也可以不指定,默认是14 (SIGALRM)
    timer_create(CLOCK_REALTIME, &s_event, &timer_id);
  
    sigset_t set; // 指定当前线程能够接收的信号
    // 以下设置根据需要进行选择
    sigfillset(&set); // 接收所有的信号
    // sigemptyset(&set); // 清空,不接收所有的信号
    // sigaddset(&set, 50); // 接收指定信号50
    pthread_sigmask(SIG_SETMASK, &set, NULL); // 让上面设定接收的信号生效
    while (true) {
        sigwait(&set, &sig); // 等待定时器超时后的信号
        // @TODO 
    }
}

void demo_create_timer()
{
  // 创建线程
  pthread_t timer_callback_thread
  pthread_create(&timer_callback_thread, NULL,timer_thread_function, NULL);
}

完成上面的定时器创建后,调用timer_settime启动定时器,当定时器超时后,即可回调到创建的线程中。