定时器timerfd的总结

定时器timerfd的总结 
以前总结过timerfd的用法,但还是总结的不详细,有些代码和理解仍然是有偏差,今天在看区块链的一些东西时发现了这个的用法,所以重新再整理一下,也算是对这个定时器的一个阶段性的收尾。
这里首先对原来的一个问题进行一下详细的说明,即设置超时时间是需要调用clock_gettime获取当前时间,如果是绝对定时器,那么需要获取CLOCK_REALTIME,在加上要超时的时间。如果是相对定时器,要获取CLOCK_MONOTONIC时间。
注意一个容易犯错的地方:tv_nsec加上去后一定要判断是否超出1000000000(如果超过要秒加一),否则会设置失败
即得到的时间再加一个你自己设定的时间超过1000000000,举一个例子,得到的时间是990000000,你自己又设定加了10000001,二者相加比1000000000多1,所以就得秒上加1,一定注意。


原来在http://fpcfjf.blog.163.com/blog/static/554697932014538227699/
“muduo库的一些心得之一定时器之自定义测试  ”也总结过,但语焉不详,这次从新说明一下。
定时器的同步的用法前面已经总结过了,这里就不再说明,这里分别总结一下POLL,SELECT和EPOLL的用法。
一、POLL中注意两点:
首先是监控的数量不能大于实际的句柄数量,否则会超时返回,而且还不好找原因。
其次是要用READ来解决连续触发。特别是READ在同步时,如果没有信号触发,他会阻塞在那里,这个和异步只是用来清除信号还是有些不同的,外表可能看起来没有什么不同,但是一定要注意。
特别注意的是POLL仍然是使用的轮询的方式,所以如果没有事件的话仍然会阻塞在当前进程或者线程。其实无论是poll,epoll还是select其实都是配合文件IO如FILE,SOCKET等来实现阻塞的,并不代表他们自己本身会阻塞或者非阻塞。
比如POLL,底层通过POLL_WAIT,监听中断事件来扫描IO队列。如果没有事件,则休眠或者超时。Epoll,select与之形式类似。
看下面的代码:
#include <cstdio>
#include <sys/timerfd.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <poll.h>
#include <stdint.h>        /* Definition of uint64_t */
#include <string.h>


int main(int argc, char *argv[])
{
struct itimerspec howlog;
bzero(&howlog, sizeof howlog);


howlog.it_value.tv_sec = 3;
howlog.it_value.tv_nsec = 0;


howlog.it_interval.tv_sec =3;
howlog.it_interval.tv_nsec = 0;


//此处用异步和同步对最后的结果处理影响不大,如果不直接操作read等IO操作。
int fd = timerfd_create(/*CLOCK_REALTIME*/CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
printf("###########:-----------flag is:%d-,%d,%d---------------\n", CLOCK_MONOTONIC, TFD_NONBLOCK, TFD_CLOEXEC);


if (fd == -1)
printf("timer is error----------------\n");


if (timerfd_settime(fd, /*TFD_TIMER_ABSTIME*/0, &howlog, NULL) == -1)
printf("timer set is error----------------\n");


printf("timer started----------------\n");


//此处直接使用原来的单个实例操作指针与下面的一个数组操作异曲同工。
struct pollfd myp[1];
myp[0].fd = fd;
myp[0].events = POLLIN /*| POLLPRI*/;
myp[0].revents = 0;


int /*int*/  cur = 0;
uint64_t tot_exp = 0;
uint64_t exp=0;


for (;;)
{
printf("listen is value:%d\n",sizeof(myp));
//注意下面的监控数量,如果超出监控的实际大小,就会引起定时器异常,无法正常工作。
int num = ::poll(myp, 5, -1);//这里实际只有一个,写了5(原代码sizeof(myp)),就会异常
printf("cur value is:%d==%d==%d\n", myp[0].revents, POLLIN, POLLPRI);

if (myp[0].revents & POLLIN /*| POLLPRI*/)
{
////printf("poll data read is ok\n");
//如果不read,程序会连续触发,定时器无法使用。
ssize_t s = read(fd, &exp, sizeof(uint64_t));
//printf("s value is :%d\n", s);
//此处如果没有这个处理,在前面异常的情况下,仍然可以通过对S的判断保证定时器的运行。
//前面博客中提到的定时器的句柄变化,其实就是读到了超过指定数量后的定时器的变化,而
//正常的定时器的触发就淹没在了这些非正常触发的海洋里。
//if (s < 0)continue;            
}
printf("front is value:%d==========================================================%d\n", fd,num);


cur++;
//sleep(1);
printf("=-=-=-=-=-= cur is :%d\n", cur);
if (cur > 180000000)
{
printf("quit program cur is :%d\n",cur);
break;
}

}


exit(EXIT_SUCCESS);
}
二、Epoll如果使用正常的LT触发,则必须同poll一样用read来处理事件,否则也会连续触发。如果使用ET,也必须使用READ来读尽数据,才会再次触发事件。仍然如上面一样,外表看来一样,但其实处理的底层机制其实是不同的。
如果像共识里的使用EPOLLONESHOT,则需要每次添加这个时间句柄到监控队列 。但是这样就有一个好处,不用再用READ处理事件了。
下面是logcabin中的对定时器的处理的一个例子, 使用EPOLLONESHOT,勿须采用read处理信号:
monitor.setEvents(EPOLLOUT|EPOLLONESHOT);
void  File::Monitor::setEvents(uint32_t fileEvents)
{
    std::lock_guard<std::mutex> mutexGuard(mutex);
    if (file == NULL)
        return;
    struct epoll_event event;
    memset(&event, 0, sizeof(event));
    event.events = fileEvents;
    event.data.ptr = file;
    int r = epoll_ctl(eventLoop.epollfd, EPOLL_CTL_MOD, file->fd, &event);
    if (r != 0) {
        PANIC("Modifying file %d event with epoll_ctl failed: %s",
              file->fd, strerror(errno));
    }
}


Int  createTimerFd()
{
    int fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC);
    if (fd < 0) {
        PANIC("Could not create timerfd: %s", strerror(errno));
    }
    return fd;
}
再看一个需要用Read来处理的:(采用ET 或者LT)
此段代码转自:
http://www.cnblogs.com/wenqiang/p/6698371.html
并进行了适当修改和测试
#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>


#if 0
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 */
};
#endif


#define EPOLL_LISTEN_CNT        256
#define EPOLL_LISTEN_TIMEOUT    500


#define LOG_DEBUG_ON 1


#ifdef LOG_DEBUG_ON 
#define LOG_DEBUG(fmt, args...) \
    do {  \
        printf("[DEBUG]:");\
        printf(fmt "\n", ##args); \
    } while(0);
#define LOG_INFO(fmt, args...) \
    do { \
        printf("[INFO]:");\
        printf(fmt "\n", ##args); \
    } while(0);
#define LOG_WARNING(fmt, args...) \
    do { \
        printf("[WARNING]:");\
        printf(fmt "\n", ##args); \
    } while(0);
#else
#define LOG_DEBUG(fmt, args...) 
#define LOG_INFO(fmt, args...) 
#define LOG_WARNING(fmt, args...) 
#endif
#define LOG_ERROR(fmt, args...) \
    do{ \
        printf("[ERROR]:");\
        printf(fmt "\n", ##args);\
    }while(0);


#define handle_error(msg) \
        do { perror(msg); exit(EXIT_FAILURE); } while (0)


static int g_epollfd = -1;
static int g_timerfd = -1;
uint64_t tot_exp = 0;


static void help(void)
{
exit(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);
}


void timerfd_handler(int fd)
{
uint64_t exp = 0;


read(fd, &exp, sizeof(uint64_t));//此处不处理read同样产生异常现象,同POLL类似
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*/;//解决开注释即为ET


ret = epoll_ctl(g_epollfd, EPOLL_CTL_ADD, fd, &event);
if (ret < 0) {
LOG_ERROR("epoll_ctl Add fd:%d error, Error:[%d:%s]", fd, errno, strerror(errno));
return -1;
}


LOG_DEBUG("epoll add fd:%d--->%d success", fd, g_epollfd);
return 0;
}


int epollfd_init()
{
int epfd;


/* create epoll fd */
epfd = epoll_create(EPOLL_LISTEN_CNT);
if (epfd < 0) {
LOG_ERROR("epoll_create error, Error:[%d:%s]", errno, strerror(errno));
return -1;
}
g_epollfd = epfd;
LOG_DEBUG("epoll fd:%d create success", epfd);


return epfd;
}


int timerfd_init()
{
int tmfd;
int ret;
struct itimerspec new_value;


new_value.it_value.tv_sec = 5;
new_value.it_value.tv_nsec = 0;
new_value.it_interval.tv_sec = 3;
new_value.it_interval.tv_nsec = 0;


tmfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);//0改成非阻塞
if (tmfd < 0) {
LOG_ERROR("timerfd_create error, Error:[%d:%s]", errno, strerror(errno));
return -1;
}


ret = timerfd_settime(tmfd, 0, &new_value, NULL);
if (ret < 0) {
LOG_ERROR("timerfd_settime error, Error:[%d:%s]", 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;
}
三、SELECT的用法我的网易博客写过,但是不知道为什么始终通不过不能访问。这里再重新总结 一下,上面说过,其实他和POLL基本致,没有什么太大的区别,区别可能就是它不如POLL复杂。看代码:
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/select.h>
#include <sys/timerfd.h>
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/un.h>
//#include "test-macros.h"
// test, that select () with timeout={0,0} exits immediately
static void
test_select_null_null(void)
{
// create timer to check whether a followixng call to select() blocks
int timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
//TEST_ASSERT_UNEQUAL (timerfd, -1);


// arm timer to fire in 5 seconds
struct itimerspec new_value;
new_value.it_value.tv_sec = 6;
new_value.it_value.tv_nsec = 0;
new_value.it_interval.tv_sec = 6;
new_value.it_interval.tv_nsec = 0;
int status = timerfd_settime(timerfd, 0, &new_value, 0);
//TEST_ASSERT_EQUAL (status, 0);
// prepare fd_set to select() on it
fd_set fds;
FD_ZERO(&fds);
FD_SET(timerfd, &fds);


// doing select() with timeout = {0, 0}


struct timeval timeout =
{
0, 0
};
printf("test-select end------start.\n ");
int nfds = select(timerfd + 1, &fds, NULL, NULL, 0);//&timeout
printf("test-select end------end.%d\n ",nfds);
// no fds must be ready and select() should complete without errors
//TEST_ASSERT_EQUAL (nfds, 0);
timeout.tv_sec = 1;
timeout.tv_usec = 0;


// select(2):
// Some  code  calls  select() with all three sets empty, nfds zero, and a
// non-NULL timeout as a fairly portable way to sleep with subsecond
// precision.
nfds = select(0, &fds, NULL, NULL, &timeout);


// no fds must be ready and select() should complete without errors
//TEST_ASSERT_EQUAL (nfds, 0);


close(timerfd);


}
// test, that select () returns correctly if there is two fds for reading and only one available


// solved with else mustWait=false
static void test_select_rfds(void)
{
// create timer to check whether a following call to select() blocks
int timerfd = timerfd_create(CLOCK_MONOTONIC, 0);
//TEST_ASSERT_UNEQUAL (timerfd, -1);
// arm timer to fire in 5 seconds
struct itimerspec new_value;
new_value.it_value.tv_sec = 2;
new_value.it_value.tv_nsec = 0;
new_value.it_interval.tv_sec = 2;
new_value.it_interval.tv_nsec = 0;
int status = timerfd_settime(timerfd, 0, &new_value, 0);


//TEST_ASSERT_EQUAL (status, 0);


int filefd = open("X1", O_CREAT | O_TRUNC | O_RDWR, S_IRWXU);
// prepare fd_set to select() on it
fd_set fds;
FD_ZERO(&fds);
FD_SET(timerfd, &fds);
FD_SET(filefd, &fds);


// doing select() with timeout = {0, 0}
struct timeval timeout =
{
6, 0
};
int nfds = select(timerfd > filefd ? timerfd + 1 : filefd + 1, &fds, NULL, NULL, &timeout);
close(timerfd);
close(filefd);


}


int main(int argc, char *argv[])
{
signal(SIGPIPE, SIG_IGN);


if (1 < 2)
{
test_select_null_null();
test_select_rfds();
}


printf("test-select end.\n ");
fflush(stdout);
fflush(stderr);
sleep(1);
return 0;
}
这里只看test_select_null_null(void)这个函数就可以了,下面的基本都类似。
自己也写了一个类似的:
int bQuit = 1;
int curTimerValue = 0;
int delayValue = 0;
 
void Close()
{
   bQuit = 0;
}
void SetValue()
{
   delayValue = curTimerValue ;
}
void CloseTimer(int timerfd)
{
  close(timerfd);
}
void *ThreadProc(void*param)
{
   //clear data pointer
   //delete *p
   //memset(0,p,len);
   while (bQuit)
   {
     curTimerValue++;
     int delay = curTimerValue - delayValue;
     if (delay > 10 )
     {
        //delete *p
     }
   }
}
void Init()
{
   pthread_t id;
   if (-1 == pthread_create(&id,NULL,ThreadProc,NULL))
   {
         //error
   }
}
void CreateTimer (struct itimerspec new_value)
{
  int timerfd = timerfd_create (CLOCK_MONOTONIC, 0);
  int status = timerfd_settime (timerfd, 0, &new_value, 0);
  fd_set fds;
  FD_ZERO (&fds);
  FD_SET (timerfd, &fds);
  struct timeval timeout =
  {
    0, 0
  };
  int nfds = select (timerfd + 1, &fds, NULL, NULL, &timeout);
  timeout.tv_sec = 1;
  timeout.tv_usec = 0;
  nfds = select (0, &fds, NULL, NULL, &timeout);
  CloseTimer (timerfd);
}
int main(int argc,char*argv[])
{
  struct itimerspec new_value;
  new_value.it_value.tv_sec = 5;
  new_value.it_value.tv_nsec = 0;
  new_value.it_interval.tv_sec = 0;
  new_value.it_interval.tv_nsec = 0;
  CreateTimer(new_value);
}
其实二者的代码差不多,特别是下面的代码,这是一个半成品,本来是想写好用在Tee的环境里,在Tee的环境里有一个时间戳,或者说叫生存时间,大约是十分钟。可是后来为了赶进度,这个就没有用上去。
其实从前面分析Muduo库开始学习这个新的定时器方法就一直在考虑如何写一个好的定时器甚至是一个定时器轮。不过没有压力就没有动力,所以一直就没有完全写成。只有这么一个雏形,但想了下还是要总结出来,不然以后就又忘记了。
通过几次的分析,其实可以发现,这个时间fd,确实如陈硕所说,解决了统一管理句柄的问题。大家可以用Poll当然也就可以用Select,毕竟定时器这个东西,应用范围还是非常广的,大的用大的方法,小的用小的方法。
原理是一致的,当文件句柄有事件时,就会触发,从而形成一个定时的触发机制。
  struct itimerspec new_value;
  new_value.it_value.tv_sec = 5;     //定时间隔5秒,下面重置
  new_value.it_value.tv_nsec = 0;
  new_value.it_interval.tv_sec = 5;     //5秒重新设置一次,和上面匹配
  new_value.it_interval.tv_nsec = 0;
int nfds = select (timerfd + 1, &fds, NULL, NULL, &timeout);
  int n = read(timerfd,buf,10);   //清除事件
//timerfd_settime (timerfd, 0, &new_value, 0)  ;//如果不设置上面红色部分,也可重新设置时间,但每次都得设置,太麻烦。
这个类似于C#和VC中定时器的调用回调函数的dueTime(启动延时) ,period(重复时间间隔),只不过这里叫it_value 、interval。
代码的形式就如上面一样,一个完整的定时器基本就出来了。然后根据这个就可以不断的循环一个定时器了。


这三种再加上原来的同步的MUDUO中的总结,其实就可以基本上把这个timerfd的使用框架描述出来了。





猜你喜欢

转载自blog.csdn.net/fpcc/article/details/76131073