Epoll+Timerfd实现定时器

EpollfdTimer

基于linux timerfd实现的定时器模块. 使用Epoll来监听调度timerfd.

timerfd

timerfd是Linux为用户程序提供的一个定时器接口。这个接口基于文件描述符,通过文件描述符的可读事件进行超时通知,一般是被用于select/poll/epoll的应用场景。

我们的timerfd_create()把时间变成了一个文件描述符,该文件描述符会在超时时变得可读,这种特性可以使我们在写服务器程序时,很方便的便把定时事件变成和其他I/O事件一样的处理方式,并且此定时接口的精度也足够的高,所以我们只要以后在写I/O框架时用到了定时器就该首选

timerfd 与 select timeout 区别

  • timerfd_create 把时间变成了一个文件描述符,该“文件”在定时器超时的那一刻变得可读,这样就能很方便地融入到 select/poll 框架中,用统一的方式来处理 IO 事件和超时事件。
  • 利用select, poll的timeout实现定时功能,它们的缺点是定时精度只有毫秒,远低于 timerfd_settime 的定时精度。

timerfd C API 使用方法

主要是三个函数接口

1
2
3
4
5
6
#include <sys/timerfd.h>
int timerfd_create(int clockid, int flags);
int timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value);
int timerfd_gettime(int fd, struct itimerspec *curr_value);

timerfd_create

1
int timerfd_create(int clockid, int flags);

它是用来创建一个定时器描述符timerfd

clockid

指定时间类型,有两个值:

CLOCK_REALTIME :Systemwide realtime clock. 系统范围内的实时时钟

CLOCK_MONOTONIC:以固定的速率运行,从不进行调整和复位 ,它不受任何系统time-of-day时钟修改的影响

flags

可以是0或者O_CLOEXEC/O_NONBLOCK。

return

timerfd(文件描述符)

timerfd_settime

1
int timerfd_settime(int ufd, int flags, const struct itimerspec * utmr, struct itimerspec * otmr);

用于启动和停止定时器,fd为timerfd_create获得的定时器文件描述符,flags为0表示是相对定时器,为TFD_TIMER_ABSTIME表示是绝对定时器。const struct itimerspec *new_value表示设置超时的时间。

此函数用于设置新的超时时间,并开始计时。

ufd

timerfd_create返回的文件句柄。

flags

为1代表设置的是绝对时间;为0代表相对时间。

struct itimerspec * utmr

1
2
3
4
struct timespec {
time_t tv_sec; /* Seconds */
long tv_nsec; /* Nanoseconds */
};

为需要设置的时间。

struct itimerspec * otmr

1
2
3
4
struct itimerspec {
struct timespec it_interval; /* Interval for periodic timer */
struct timespec it_value; /* Initial expiration */
};

  • it_interval是后续周期性超时时间,是多少时间就填写多少。

  • it_interval不为0则表示是周期性定时器,大于0,是周期性的时间。

  • it_value 是首次超时时间,需要填写从clock_gettime获取的时间,并加上要超时的时间。

  • it_value和it_interval都为0表示停止定时器。

为定时器这次设置之前的超时时间。

return

一般来说函数返回0代表设置成功。

timerfd_gettime

1
int timerfd_gettime(int fd, struct itimerspec *curr_value);

此函数用于获得定时器距离下次超时还剩下的时间。如果调用时定时器已经到期,并且该定时器处于循环模式(设置超时时间时struct itimerspec::it_interval不为0),那么调用此函数之后定时器重新开始计时

timerfd 的例子

linux timerfd api中给出的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#include <sys/timerfd.h>
#include <time.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h> /* Definition of uint64_t */
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (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)
handle_error("clock_gettime");
}
if (clock_gettime(CLOCK_MONOTONIC, &curr) == -1)
handle_error("clock_gettime");
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);
}
int
main(int argc, char *argv[])
{
struct itimerspec new_value;
int max_exp, fd;
struct timespec now;
uint64_t exp, tot_exp;
ssize_t s;
if ((argc != 2) && (argc != 4)) {
fprintf(stderr, "%s init-secs [interval-secs max-exp]\n",
argv[0]);
exit(EXIT_FAILURE);
}
if (clock_gettime(CLOCK_REALTIME, &now) == -1)
handle_error("clock_gettime");
/* Create a CLOCK_REALTIME absolute timer with initial
expiration and interval as specified in command line */
new_value.it_value.tv_sec = now.tv_sec + atoi(argv[1]);
new_value.it_value.tv_nsec = now.tv_nsec;
if (argc == 2) {
new_value.it_interval.tv_sec = 0;
max_exp = 1;
} else {
new_value.it_interval.tv_sec = atoi(argv[2]);
max_exp = atoi(argv[3]);
}
new_value.it_interval.tv_nsec = 0;
fd = timerfd_create(CLOCK_REALTIME, 0);
if (fd == -1)
handle_error("timerfd_create");
if (timerfd_settime(fd, TFD_TIMER_ABSTIME, &new_value, NULL) == -1)
handle_error("timerfd_settime");
print_elapsed_time();
printf("timer started\n");
for (tot_exp = 0; tot_exp < max_exp;) {
s = read(fd, &exp, sizeof(uint64_t));
if (s != sizeof(uint64_t))
handle_error("read");
tot_exp += exp;
print_elapsed_time();
printf("read: %llu; total=%llu\n",
(unsigned long long) exp,
(unsigned long long) tot_exp);
}
exit(EXIT_SUCCESS);
}

timerfd + epoll 的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
#include <sys/epoll.h>
#include <sys/timerfd.h>
#include <time.h>
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#define MX_EVNTS 10
#define EPL_TOUT 3000
#define MX_CNT 5
struct param{
struct itimerspec its;
int tfd;
};
void *strt_eplth(void *arg)
{
struct epoll_event evnts[MX_EVNTS];
int *eplfd = (int *)arg;
int n = -1;
size_t i,cnt = 0;
while(1){
n = epoll_wait(*eplfd,evnts,MX_EVNTS,EPL_TOUT);
if(n == -1){
perror("epoll_wait() error");
break;
}else if(n == 0){
printf("time out %d sec expired\n",EPL_TOUT / 1000);
break;
}
for(i = 0; i < n;i++){
struct param *pm = (struct param *)(evnts[i].data.ptr);
printf("tfd: %d\ninitial expiration: %ld\ninterval: %ld\n\n",
pm->tfd,
(long)(pm->its.it_value.tv_sec),
(long)(pm->its.it_interval.tv_sec));
if(epoll_ctl(*eplfd,EPOLL_CTL_DEL,pm->tfd,NULL) != 0){
perror("epoll_ctl(DEL) error in thread");
break;
}
struct epoll_event ev;
ev.events = EPOLLIN | EPOLLET;
pm->its.it_value.tv_sec =
pm->its.it_value.tv_sec +
pm->its.it_interval.tv_sec;
ev.data.ptr = pm;
if(timerfd_settime(pm->tfd,TFD_TIMER_ABSTIME,&(pm->its),NULL) != 0){
perror("timerfd_settime() error in thread");
break;
}
if(epoll_ctl(*eplfd,EPOLL_CTL_ADD,pm->tfd,&ev) != 0){
perror("epoll_ctl(ADD) error in thread");
break;
}
}
if(++cnt == MX_CNT){
printf("cnt reached MX_CNT, %d\n",MX_CNT);
break;
}
}
close(*eplfd);
pthread_exit(NULL);
}
int create_timerfd(struct itimerspec *its,time_t interval)
{
int tfd = timerfd_create(CLOCK_MONOTONIC,TFD_NONBLOCK);
if(tfd < 0){
perror("timerfd_create() error");
return -2;
}
struct timespec nw;
if(clock_gettime(CLOCK_MONOTONIC,&nw) != 0){
perror("clock_gettime() error");
return -1;
}
its->it_value.tv_sec = nw.tv_sec + interval;
its->it_value.tv_nsec = 0;
its->it_interval.tv_sec = interval;
its->it_interval.tv_nsec = 0;
return tfd;
}
int main()
{
time_t INTERVAL = 2;
struct itimerspec its;
int tfd = create_timerfd(&its,INTERVAL);
if(tfd < 0)
return -1;
int eplfd = epoll_create1(0);
if(eplfd < 0){
perror("epoll_create1() error");
return -1;
}
struct param pm;
pm.its = its;
pm.tfd = tfd;
if(timerfd_settime(pm.tfd,TFD_TIMER_ABSTIME,&(pm.its),NULL) != 0){
perror("timerfd_settime() error");
return -1;
}
struct epoll_event ev;
ev.events = EPOLLIN | EPOLLET;
ev.data.ptr = &pm;
if(epoll_ctl(eplfd,EPOLL_CTL_ADD,pm.tfd,&ev) != 0){
perror("epoll_ctl() error");
return -1;
}
pthread_t pid;
if(pthread_create(&pid,NULL,strt_eplth,(void *)&eplfd) != 0){
perror("pthread_create() error");
return -1;
}
//// add another timerfd.
INTERVAL = 1;
struct itimerspec its2;
int tfd2 = create_timerfd(&its2,INTERVAL);
if(tfd2 < 0)
return -1;
struct param pm2;
pm2.its = its2;
pm2.tfd = tfd2;
if(timerfd_settime(pm2.tfd,TFD_TIMER_ABSTIME,&(pm2.its),NULL) != 0){
perror("timerfd_settime() error");
return -1;
}
struct epoll_event ev2;
ev2.events = EPOLLIN | EPOLLET;
ev2.data.ptr = &pm2;
if(epoll_ctl(eplfd,EPOLL_CTL_ADD,pm2.tfd,&ev2) != 0){
perror("epoll_ctl() error");
return -1;
}
if(pthread_join(pid,NULL) != 0){
perror("pthread_join() error");
return -1;
}
close(tfd);
close(tfd2);
return 0;
}

python 实现

参照linux timerfd api,用PyObject封装了timerfd的c api, 因为python标准库里不存在timerfd定时接口



参考

峰云就她了的BLOG