目录
一 timerfd 定时器类
timerfd
是 Linux 为用户程序提供的一个定时器接口。这个接口基于文件描述符,通过文件描述符的可读事件进行超时通知,因此可以配合 select/poll/epoll
等使用。
timerfd
的特点是将时间变成一个文件描述符,定时器超时时,文件可读。这样就能很容易融入 select(2)/poll(2)/epoll(7)
的框架中,用统一的方式来处理IO事件、超时事件。这也是 Reactor
模式的特点。
定时器是每个进程自己的,不是在 fork
时继承的,不会被传递给子进程。编译时加编译选项 -lrt
。
1 timerfd定时器与传统Reactor模式定时器
传统 Reactor
模式使用 select/poll/epoll
的 timeout
参数实现定时功能,但其精度只有毫秒(注意区分表示精度和实际精度,表示精度可能为微妙和纳秒)。
另外,select/poll/epoll
的定时器也有一个缺陷,那就是只能针对的是所有监听的文件描述符 fd
,而非绑定某个 fd
。
timerfd
可以解决这个问题,单独为某个 fd
指定定时器。
2 timerfd_create 创建定时器对象函数
头文件:
#include <sys/timerfd.h>
函数原型:
int timerfd_create(int clockid, int flags);
作用:
创建一个定时器对象,同时返回一个与之关联的文件描述符
参数含义:
clockid
:标识指定的时钟计数器,可选值CLOCK_REALTIME
、CLOCK_MONOTONIC
CLOCK_REALTIME
:系统实时时间,随系统实时时间改变而改变,即从 UTC1970-1-1 0:0:0 开始计时,中间时刻如果系统时间被用户改成其他,则对应的时间相应改变。
CLOCK_MONOTONIC
:从系统启动这一刻起开始计时,不受系统时间被用户改变的影响。
flags
:可以为 0
或 TFD_NONBLOCK
,TFD_CLOEXEC
。
0 表示无属性,TFD_NONBLOCK
表示设置为非阻塞模式,TFD_CLOEXEC
表示当程序执行 exec
函数时本 fd 将被系统自动关闭,表示不传递。在 2.6.26 之前的 Linux 版本中,标志必须指定为零。
返回值:
如果成功返回一个定时器的文件描述符,失败返回-1
3 timerfd_settime 设置新的超时时间函数。
头文件:
#include <sys/timerfd.h>
函数原型:
int timerfd_settime(int fd, int flags,const struct itimerspec *new_value,struct itimerspec *old_value);
作用:
此函数用于设置新的超时时间,并开始计时,能够启动和停止定时器 ;
参数含义:
fd
:timerfd_create
函数返回的文件句柄;
flags
:设置为 1 代表设置的是绝对时间;为 0 代表相对时间;
new_value
:指定定时器的超时时间以及超时间隔时间,new_value.it_value
非零则启动定时器,否则关闭定时器,如果 new_value.it_interval
为0,则定时器只定时一次,即初始那次,否则之后每隔设定时间超时一次;
old_value
:不为 null,则返回定时器这次设置之前的超时时间;
struct itimerspec
{
struct timespec it_interval; //定时间隔周期
struct timespec it_value; //第一次超时时间
};
struct timespec
{
__time_t tv_sec; /* 秒. */
__syscall_slong_t tv_nsec; /* 纳秒. */
};
返回值:
成功返回 0,失败返回-1
4 timerfd_gettime 获取距离下次超时的剩余时间
头文件:
#include <sys/timerfd.h>
函数原型:
int timerfd_gettime(int fd, struct itimerspec *curr_value);
作用:
获取距离下次超时剩余的时间。
参数含义:
fd
:timerfd_create
函数返回的文件句柄。
curr_value
:curr_value.it_value
字段表示距离下次超时的时间,如果该值为 0,表示计时器已经解除,改字段表示的值永远是一个相对值,无论 TFD_TIMER_ABSTIME
是否被设置 curr_value.it_interval
定时器间隔时间。
返回值:
成功返回 0,失败返回-1。
5 操作定时器fd
read
读取 timefd
超时事件通知。当定时器超时,read
读事件发生即可读,返回超时次数(从上次调用timerfd_settime()
启动开始或上次 read
成功读取开始),它是一个 8 字节的 unit64_t
类型整数,如果定时器没有发生超时事件,则 read
将阻塞若 timerfd
为阻塞模式,否则返回 EAGAIN
错误( O_NONBLOCK
模式),如果 read
时提供的缓冲区小于 8 字节将以 EINVAL
错误返回。
poll(2)/select(2)/epoll (7)
监听定时器 fd
,fd
可读时,会收到就绪通知。
close(2)
关闭 fd
对应定时器。如果不需要该定时器,可调用 close
关闭之。
6 优点
- 与 epoll、select 和 poll 等事件驱动机制兼容。
- 可以设置为周期性定时器。
- 提供了高精度和可靠的定时器功能。
7 用途
- 实现定时事件的通知机制。
- 用于需要精确定时功能的应用程序,如网络服务器定时任务。
- 与事件循环机制(如 epoll)配合使用,处理异步任务。
- timerfd_create 提供了一种高效、便捷的定时器创建和使用方法,适用于各种需要定时通知的 Linux 应用程序。
8 配合定时器使用
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>
#include <sys/epoll.h>
#include <sys/timerfd.h>
#include <unistd.h>
#define EPOLL_LISTEN_CNT 256
#define EPOLL_LISTEN_TIMEOUT 500
static int g_epollfd = -1;
static int g_timerfd = -1;
uint64_t tot_exp = 0;
static void print_elapsed_time(void)
{
static struct timespec start;
struct timespec curr;
static int first_call = 1;
int secs, nsecs;
if (first_call)
{
first_call = 0;
if (clock_gettime(CLOCK_MONOTONIC, &start) == -1)
printf("clock_gettime\n");
}
if (clock_gettime(CLOCK_MONOTONIC, &curr) == -1)
printf("clock_gettime\n");
secs = curr.tv_sec - start.tv_sec;
nsecs = curr.tv_nsec - start.tv_nsec;
if (nsecs < 0)
{
secs--;
nsecs += 1000000000;
}
printf("%d.%03d: ", secs, (nsecs + 500000) / 1000000);
}
void timerfd_handler(int fd)
{
uint64_t exp = 0;
read(fd, &exp, sizeof(uint64_t));
tot_exp += exp;
print_elapsed_time();
printf("read: %llu, total: %llu\n", (unsigned long long)exp, (unsigned long long)tot_exp);
return;
}
void epoll_event_handle(void)
{
int i = 0;
int fd_cnt = 0;
int sfd;
struct epoll_event events[EPOLL_LISTEN_CNT];
memset(events, 0, sizeof(events));
while(1)
{
/* wait epoll event */
fd_cnt = epoll_wait(g_epollfd, events, EPOLL_LISTEN_CNT,EPOLL_LISTEN_TIMEOUT);
for(i = 0; i < fd_cnt; i++)
{
sfd = events[i].data.fd;
if(events[i].events & EPOLLIN)
{
if (sfd == g_timerfd)
{
timerfd_handler(sfd);
}
}
}
}
}
int epoll_add_fd(int fd)
{
int ret;
struct epoll_event event;
memset(&event, 0, sizeof(event));
event.data.fd = fd;
event.events = EPOLLIN | EPOLLET;
ret = epoll_ctl(g_epollfd, EPOLL_CTL_ADD, fd, &event);
if(ret < 0)
{
printf("epoll_ctl Add fd:%d error, Error:[%d:%s]\n", fd, errno,strerror(errno));
return -1;
}
printf("epoll add fd:%d--->%d success\n", fd, g_epollfd);
return 0;
}
int epollfd_init()
{
int epfd;
/* create epoll fd */
epfd = epoll_create(EPOLL_LISTEN_CNT);
if (epfd < 0)
{
printf("epoll_create error, Error:[%d:%s]\n", errno,
strerror(errno));
return -1;
}
g_epollfd = epfd;
printf("epoll fd:%d create success\n", epfd);
return epfd;
}
int timerfd_init()
{
int tmfd;
int ret;
struct itimerspec new_value;
new_value.it_value.tv_sec = 2;
new_value.it_value.tv_nsec = 0;
new_value.it_interval.tv_sec = 1;
new_value.it_interval.tv_nsec = 0;
tmfd = timerfd_create(CLOCK_MONOTONIC, 0);
if (tmfd < 0)
{
printf("timerfd_create error, Error:[%d:%s]\n", errno,
strerror(errno));
return -1;
}
ret = timerfd_settime(tmfd, 0, &new_value, NULL);
if (ret < 0)
{
printf("timerfd_settime error, Error:[%d:%s]\n", errno,
strerror(errno));
close(tmfd);
return -1;
}
if (epoll_add_fd(tmfd))
{
close(tmfd);
return -1;
}
g_timerfd = tmfd;
return 0;
}
int main(int argc, char **argv)
{
if (epollfd_init() < 0)
{
return -1;
}
if (timerfd_init())
{
return -1;
}
/* event handle */
epoll_event_handle();
return 0;
}
9 示例代码
下面的示例演示了如何使用 timerfd_create
来创建一个定时器,并在定时器到期时读取事件。
#include <iostream>
#include <sys/timerfd.h>
#include <unistd.h>
#include <cstring>
#include <ctime>
void set_timer(int timer_fd, uint64_t initial_expire, uint64_t interval)
{
struct itimerspec new_value;
memset(&new_value, 0, sizeof(new_value));
// Initial expiration
new_value.it_value.tv_sec = initial_expire;
new_value.it_value.tv_nsec = 0;
// Interval for periodic timer
new_value.it_interval.tv_sec = interval;
new_value.it_interval.tv_nsec = 0;
timerfd_settime(timer_fd, 0, &new_value, nullptr);
}
int main()
{
// Create a timer that uses the monotonic clock and is non-blocking
int timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
if (timer_fd == -1) {
perror("timerfd_create");
return 1;
}
// Set the timer to expire after 5 seconds and then every 1 second
set_timer(timer_fd, 5, 1);
while (true)
{
uint64_t expirations;
ssize_t s = read(timer_fd, &expirations, sizeof(expirations));
if (s != sizeof(expirations)) {
if (s == -1) {
if (errno == EAGAIN) {
// Non-blocking mode, no data to read
continue;
} else {
perror("read");
close(timer_fd);
return 1;
}
} else {
std::cerr << "read error, expected " << sizeof(expirations) << " bytes, got " << s << " bytes.\n";
close(timer_fd);
return 1;
}
}
std::cout << "Timer expired " << expirations << " times\n";
}
close(timer_fd);
return 0;
}
10 解释
创建定时器:
int timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
设置定时器:
void set_timer(int timer_fd, uint64_t initial_expire, uint64_t interval)
{
struct itimerspec new_value;
memset(&new_value, 0, sizeof(new_value));
new_value.it_value.tv_sec = initial_expire;
new_value.it_value.tv_nsec = 0;
new_value.it_interval.tv_sec = interval;
new_value.it_interval.tv_nsec = 0;
timerfd_settime(timer_fd, 0, &new_value, nullptr);
}
读取定时器事件:
uint64_t expirations;
ssize_t s = read(timer_fd, &expirations, sizeof(expirations));
read
调用会在定时器到期时返回,读取到的值表示定时器到期的次数。
二 timer_create系列定时器函数
进程可以通过调用 timer_create()
创建特定的定时器,定时器是每个进程自己的,不是在 fork
时继承的,不会被传递给子进程。编译时加编译选项 -lrt。
linux
下 timer_t
定时器的使用,总共有3个函数。
timer_create()
timer_settime()
timer_gettime()
头文件:
#include <signal.h>
#include <time.h>
函数声明:
int timer_create(clockid_t clockid, struct sigevent* sevp, timer_t* timerid)
功能:创建一个POSIX标准的进程定时器
参数:
@clockid
可选系统系统的宏,比如 CLOCK_REALTIME
@sevp
环境值,结构体 struct sigevent
变量的地址
@timerid
定时器标识符,结构体 timer_t
变量的地址
返回值:0-成功;-1-失败,errno被设置。
头文件:
#include <time.h>
函数声明:
int timer_settime(timer_t timerid, int flags, const struct itimerspec *new_value, struct itimerspec *old_value);
int timer_gettime(timer_t timerid, struct itimerspec *curr_value);
功能:设置或者获得定时器时间值参数:
参数:
@timerid 定时器标识
@flags 0标识相对时间,1标识绝对时间
@new_value 定时器的新初始值和间隔,如下面的it
@old_value 取值通常为0或NULL,若不为NULL,则返回定时器前一个值。
举例1:采用新线程派驻的通知方式
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
void timer_thread(union sigval v)
{
printf("timer_thread function! %d\n", v.sival_int);
}
int main()
{
timer_t timerid;
struct sigevent evp;
memset(&evp, 0, sizeof(struct sigevent)); //清零初始化
evp.sigev_value.sival_int = 111; //也是标识定时器的,回调函数可以获得
evp.sigev_notify = SIGEV_THREAD; //线程通知的方式,派驻新线程
evp.sigev_notify_function = timer_thread; //线程函数地址
if (timer_create(CLOCK_REALTIME, &evp, &timerid) == -1)
{
perror("fail to timer_create");
exit(-1);
}
/* 第一次间隔it.it_value这么长,以后每次都是it.it_interval这么长,就是说it.it_value变0的时候会>装载it.it_interval的值 */
struct itimerspec it;
it.it_interval.tv_sec = 1; // 回调函数执行频率为1s运行1次
it.it_interval.tv_nsec = 0;
it.it_value.tv_sec = 3; // 倒计时3秒开始调用回调函数
it.it_value.tv_nsec = 0;
if (timer_settime(timerid, 0, &it, NULL) == -1)
{
perror("fail to timer_settime");
exit(-1);
}
//pause();
while (1);
return 0;
}
/*
* int timer_gettime(timer_t timerid, struct itimerspec *curr_value);
* 获取timerid指定的定时器的值,填入curr_value
*/
举例2:通知方式为信号的处理方式
#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;
}
跨平台的定时器
https://github.com/grijjy/DelphiTimerQueue/tree/master