Epoll+Timerfd实现定时器


原文链接: Epoll+Timerfd实现定时器

timerfd的使用方法 - 简书

linux新API---timerfd的使用方法

timerfd是Linux为用户程序提供的一个定时器接口。这个接口基于文件描述符,通过文件描述符的可读事件进行超时通知,所以能够被用于select/poll的应用场景。timerfd是linux内核2.6.25版本中加入的借口。
timerfd、eventfd、signalfd配合epoll使用,可以构造出一个零轮询的程序,但程序没有处理的事件时,程序是被阻塞的。这样的话在某些移动设备上程序更省电。

clock_gettime 函数可以获取系统时钟,精确到纳秒。需要在编译时指定库:-lrt。可以获取两种类型事件:
CLOCK_REALTIME:相对时间,从1970.1.1到目前的时间。更改系统时间会更改获取的值。也就是,它以系统时间为坐标。
CLOCK_MONOTONIC:与CLOCK_REALTIME相反,它是以绝对时间为准,获取的时间为系统重启到现在的时间,更改系统时间对齐没有影响。

timerfd_create:
生成一个定时器对象,返回与之关联的文件描述符。接收两个入参,一个是clockid,填写CLOCK_REALTIME或者CLOCK_MONOTONIC,参数意义同上。第二个可以传递控制标志:TFD_NONBLOCK(非阻塞),TFD_CLOEXEC(同O_CLOEXEC)

注:timerfd的进度要比usleep要高。

timerfd_settime:能够启动和停止定时器;可以设置第二个参数:flags,0表示是相对定时器,TFD_TIMER_ABSTIME表示是绝对定时器。
第三个参数设置超时时间,如果为0则表示停止定时器。定时器设置超时方法:
1、设置超时时间是需要调用clock_gettime获取当前时间,如果是绝对定时器,那么需要获取CLOCK_REALTIME,在加上要超时的时间。如果是相对定时器,要获取CLOCK_MONOTONIC时间。
2、数据结构: 

struct timespec {
    time_t tv_sec;                /* Seconds */
    long   tv_nsec;               /* Nanoseconds */
};

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

     1. it_value和it_interval都为0表示停止定时器。
     2. it_value是首次超时时间,需要填写从clock_gettime获取的时间,并加上要超时的时间。 it_interval是后续周期性超时时间,是多少时间就填写多少。
     注意一个容易犯错的地方:tv_nsec加上去后一定要判断是否超出1000000000(如果超过要秒加一),否则会设置失败。
     
     3. it_interval不为0则表示是周期性定时器。

注:timerfd_create第一个参数和clock_gettime的第一个参数都是CLOCK_REALTIME或者CLOCK_MONOTONIC,timerfd_settime的第二个参数为0(相对定时器)或者TFD_TIMER_ABSTIME,三者的关系:
1、如果timerfd_settime设置为TFD_TIMER_ABSTIME(决定时间),则后面的时间必须用clock_gettime来获取,获取时设置CLOCK_REALTIME还是CLOCK_MONOTONIC取决于timerfd_create设置的值。
2、如果timerfd_settime设置为0(相对定时器),则后面的时间必须用相对时间,就是:
    new_value.it_value.tv_nsec = 500000000;
    new_value.it_value.tv_sec = 3;
    new_value.it_interval.tv_sec = 0;
    new_value.it_interval.tv_nsec = 10000000;

read函数可以读timerfd,读的内容为uint_64,表示超时次数。
看一段代码例子:

#include <sys/timerfd.h>
#include <sys/time.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)
 
void printTime()
{  
	struct timeval tv;  
	gettimeofday(&tv, NULL);  
	printf("printTime:  current time:%ld.%ld ", tv.tv_sec, tv.tv_usec);
}
 
int main(int argc, char *argv[])
{
   struct timespec now;
   if (clock_gettime(CLOCK_REALTIME, &now) == -1)
	   handle_error("clock_gettime");
 
   struct itimerspec new_value;
   new_value.it_value.tv_sec = now.tv_sec + atoi(argv[1]);
   new_value.it_value.tv_nsec = now.tv_nsec;
   new_value.it_interval.tv_sec = atoi(argv[2]);
   new_value.it_interval.tv_nsec = 0;
 
   int 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");
 
   printTime();
   printf("timer started\n");
   
   for (uint64_t tot_exp = 0; tot_exp < atoi(argv[3]);) 
   {
       uint64_t exp;
	   ssize_t s = read(fd, &exp, sizeof(uint64_t));
	   if (s != sizeof(uint64_t))
		   handle_error("read");
 
	   tot_exp += exp;
	   printTime();
	   printf("read: %llu; total=%llu\n",exp, tot_exp);
   }
 
   exit(EXIT_SUCCESS);
}



#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/timerfd.h>

static char *itimerspec_dump(struct itimerspec *ts);

int main()
{
	int tfd, epfd, ret;
	struct epoll_event ev;
	int msec = 10; // timer fires after 10msec
	uint64_t res;

	printf("testcase start\n");

	tfd = timerfd_create(CLOCK_MONOTONIC, 0);
	if (tfd == -1) {
		printf("timerfd_create() failed: errno=%d\n", errno);
		return EXIT_FAILURE;
	}
	printf("created timerfd %d\n", tfd);
	
    struct itimerspec ts;
	ts.it_interval.tv_sec = 0;
	ts.it_interval.tv_nsec = 0;
	ts.it_value.tv_sec = msec / 1000;
	ts.it_value.tv_nsec = (msec % 1000) * 1000000;

	if (timerfd_settime(tfd, 0, &ts, NULL) < 0) {
		printf("timerfd_settime() failed: errno=%d\n", errno);
		close(tfd);
		return EXIT_FAILURE;
	}
	printf("set timerfd time=%s\n", itimerspec_dump(&ts));

	epfd = epoll_create(1);
	if (epfd == -1) {
		printf("epoll_create() failed: errno=%d\n", errno);
		close(tfd);
		return EXIT_FAILURE;
	}
	printf("created epollfd %d\n", epfd);

	ev.events = EPOLLIN;
	if (epoll_ctl(epfd, EPOLL_CTL_ADD, tfd, &ev) == -1) {
		printf("epoll_ctl(ADD) failed: errno=%d\n", errno);
		close(epfd);
		close(tfd);
		return EXIT_FAILURE;
	}
	printf("added timerfd to epoll set\n");

	sleep(1);

	memset(&ev, 0, sizeof(ev));
	ret = epoll_wait(epfd, &ev, 1, 500); // wait up to 500ms for timer
	if (ret < 0) {
		printf("epoll_wait() failed: errno=%d\n", errno);
		close(epfd);
		close(tfd);
		return EXIT_FAILURE;
	}
	printf("waited on epoll, ret=%d\n", ret);

	ret = read(tfd, &res, sizeof(res));
	printf("read() returned %d, res=%" PRIu64 "\n", ret, res);

	if (close(epfd) == -1) {
		printf("failed to close epollfd: errno=%d\n", errno);
		return EXIT_FAILURE;
	}

	if (close(tfd) == -1) {
		printf("failed to close timerfd: errno=%d\n", errno);
		return EXIT_FAILURE;
	}

	return EXIT_SUCCESS;
}

static char *
itimerspec_dump(struct itimerspec *ts)
{
    static char buf[1024];

    snprintf(buf, sizeof(buf),
            "itimer: [ interval=%lu s %lu ns, next expire=%lu s %lu ns ]",
            ts->it_interval.tv_sec,
            ts->it_interval.tv_nsec,
            ts->it_value.tv_sec,
            ts->it_value.tv_nsec
           );

    return (buf);
}

root@node1:/home/c_test/unix_test# ./timerfd 20 3 4
printTime: current time:1396594376.746760 timer started
printTime: current time:1396594396.747705 read: 1; total=1
printTime: current time:1396594399.747667 read: 1; total=2
printTime: current time:1396594402.747728 read: 1; total=3
printTime: current time:1396594405.746874 read: 1; total=4

第一个参数为第一次定时器到期间隔,第二个参数为定时器的间隔,第三个参数为定时器多少次则退出。
timerfd简单的性能测试:
申请1000个定时器,超时间定位1s,每秒超时一次,发现cpu占用率在3.0G的cpu上大概为1%,10000个定时器的话再7%左右,而且不会出现同时超时两个的情况,如果有printf到前台,则一般会出现定时器超时多次(3-5)才回调。

PS:linux内核新添加的API timerfd、signalfd、eventfd都有异曲同工之妙,都可以将本来复杂的处理转化思维变得简单
————————————————
版权声明:本文为CSDN博主「鱼思故渊」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/yusiguyuan/article/details/22936707

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 s的timeout实现定时功能,它们的缺点是定时精度只有毫秒,远低于 timerfd_settime 的定时精度。
timerfd C API 使用方法
主要是三个函数接口

#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
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
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
struct timespec {
time_t tv_sec; /* Seconds /
long tv_nsec; /
Nanoseconds */
};
为需要设置的时间。

struct itimerspec * otmr
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
int timerfd_gettime(int fd, struct itimerspec *curr_value);
此函数用于获得定时器距离下次超时还剩下的时间。如果调用时定时器已经到期,并且该定时器处于循环模式(设置超时时间时struct itimerspec::it_interval不为0),那么调用此函数之后定时器重新开始计时

timerfd 的例子
linux timerfd api中给出的例子:

#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 的例子
#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;
}
`