我有一个应用程序,它定期(通过计时器)检查一些数据存储
像这样:
#include <iostream>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <sys/fcntl.h>
#include <unistd.h>
// EPOLL & TIMER
#include <sys/epoll.h>
#include <sys/timerfd.h>
int main(int argc, char **argv)
{
/* epoll instance */
int efd = epoll_create1(EPOLL_CLOEXEC);
if (efd < 0)
{
std::cerr << "epoll_create error: " << strerror(errno) << std::endl;
return EXIT_FAILURE;
}
struct epoll_event ev;
struct epoll_event events[128];
/* timer instance */
int tfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
struct timespec ts;
// first expiration in 3. seconds after program start
ts.tv_sec = 3;
ts.tv_nsec = 0;
struct itimerspec new_timeout;
struct itimerspec old_timeout;
bzero(&new_timeout, sizeof(new_timeout));
bzero(&old_timeout, sizeof(old_timeout));
// value
new_timeout.it_value = ts;
// no interval;
// timer will be armed in epoll_wait event trigger
new_timeout.it_interval.tv_sec =
new_timeout.it_interval.tv_nsec = 0;
// Add the timer descriptor to epoll.
if (tfd != -1)
{
ev.events = EPOLLIN | EPOLLERR /*| EPOLLET*/;
ev.data.ptr = &tfd;
epoll_ctl(efd, EPOLL_CTL_ADD, tfd, &ev);
}
int flags = 0;
if (timerfd_settime(tfd, flags, &new_timeout, &old_timeout) < 0)
{
std::cerr << "timerfd_settime error: " << strerror(errno) << std::endl;
}
int numEvents = 0;
int timeout = 0;
bool checkTimer = false;
while (1)
{
checkTimer = false;
numEvents = epoll_wait(efd, events, 128, timeout);
if (numEvents > 0)
{
for (int i = 0; i < numEvents; ++i)
{
if (events[i].data.ptr == &tfd)
{
std::cout << "timeout" << std::endl;
checkTimer = true;
}
}
}
else if(numEvents == 0)
{
continue;
}
else
{
std::cerr << "An error occured: " << strerror(errno) << std::endl;
}
if (checkTimer)
{
/* Check data storage */
uint64_t value;
ssize_t readBytes;
//while ( (readBytes = read(tfd, &value, 8)) > 0)
//{
// std::cout << "tread: '" << value << "'" << std::endl;
//}
itimerspec new_timeout;
itimerspec old_timeout;
new_timeout.it_value.tv_sec = rand() % 3 + 1;
new_timeout.it_value.tv_nsec = 0;
new_timeout.it_interval.tv_sec =
new_timeout.it_interval.tv_nsec = 0;
timerfd_settime(tfd, flags, &new_timeout, &old_timeout);
}
}
return EXIT_SUCCESS;
}
这是对我的应用程序的简单描述。在每次超时之后,计时器需要在每次超时中重新武装一些不同的值。
问题是:
- 是否需要将timerfd添加到带有EPOLLET标志的epoll(epoll_ctl)
- 是否有必要在每次超时后读取timerfd
- 是否需要无限地epoll_wait(超时=-1)
您可以在两种模式中的一种中执行此操作,即边缘触发或级别触发。如果您选择边缘触发路由,那么您必须通过EPOLLET
,并且不需要在每次唤醒后读取timerfd
。您从epoll
接收到一个事件,这意味着已经触发了一个或多个超时。您可以选择读取timerfd
,它将返回自上次读取以来触发的超时次数。
如果选择级别触发路由,则不需要通过EPOLLET
,但每次唤醒后必须读取timerfd
。如果你不这样做,那么你会立即被再次唤醒,直到你消耗掉这段时间。
您应该将-1
传递给epoll
作为超时或一些正值。如果你通过了0
,就像你在这个例子中所做的那样,那么你永远不会入睡,你只会旋转,等待时间结束。这几乎可以肯定是不可取的行为。
问题答案:
- 是否需要将timerfd添加到带有EPOLLET标志的epoll(epoll_ctl)
否。添加EPOLLET
(边缘触发器)确实会改变接收事件的行为。如果没有EPOLLET
,您将连续接收来自epoll_wait
的与timerfd
相关的事件,直到您从timerfd
获得read()
。使用EPOLLET
,即使出现新的过期,您也不会收到第一个事件之外的其他事件,直到您从timerfd
中获得read()
并出现新的到期为止。
- 每次超时后是否需要读取timerfd
是,以便在发生新的过期时(仅)继续并接收事件(请参见上文)。不使用周期性计时器时(仅限单次过期),并且关闭timerfd
而不读取时,则为否。
- 是否需要无限地epoll_wait(超时=-1)
否。您可以使用epoll_wait
的超时而不是timerfd
。我个人认为使用timerfd
比不断计算EPOLL
的下一个超时更容易,尤其是当您期望多个超时间隔时;当超时与唤醒的特定事件相关联时,在发生超时时监视下一个任务要容易得多。