【Linux】timerfd定时器
timerfd
是 Linux 内核提供的一种定时器机制,它通过文件描述符(file descriptor)来通知定时器超时事件,因此可以很方便地融入 select
、poll
、epoll
等 I/O 多路复用机制中,实现高效的定时事件处理。
核心 timerfd
API 简介
timerfd
主要涉及三个系统调用:
timerfd_create
:创建一个新的定时器对象,并返回对应的文件描述符。timerfd_settime
:启动、停止或设置定时器。timerfd_gettime
:获取定时器的当前设置(可选)。
使用 timerfd
的基本步骤
使用 timerfd
创建和运行一个周期性定时器,通常遵循以下步骤:
创建定时器 (
timerfd_create
):使用
timerfd_create
函数创建定时器文件描述符。你需要指定时钟源:CLOCK_REALTIME
:系统实时时间,可能会因系统时间调整(如 NTP 同步)而受到影响。CLOCK_MONOTONIC
:单调递增的时间,从系统启动开始计算,不受系统时间更改的影响,更适合间隔定时。flags
参数可以设置为TFD_NONBLOCK
(非阻塞)或TFD_CLOEXEC
,通常可设为 0。
设置定时器 (
timerfd_settime
):使用
timerfd_settime
函数设置定时器的首次超时时间和周期超时间隔。你需要填充一个itimerspec
结构体:struct itimerspec {struct timespec it_interval; /* 周期性的超时间隔 */struct timespec it_value; /* 首次超时时间 */ };
如果
it_interval
的tv_sec
和tv_nsec
都为 0,则定时器只触发一次。如果
it_value
为 0,则会停止定时器。flags
参数可以是 0(相对时间)或TFD_TIMER_ABSTIME
(绝对时间)。
等待并处理超时事件:
定时器超时后,其文件描述符会变得可读。你需要使用read
操作从该文件描述符中读取一个uint64_t
类型的值,该值表示自上次读取后累积的超时次数。
非常重要:务必在超时后读取该文件描述符,否则后续的超时事件可能无法正常触发(在水平触发模式下,会一直提示可读)。
你可以使用read
阻塞等待,或者更常见的,将timerfd
加入到select
、poll
、epoll
等 I/O 多路复用的监听集合中,与其他 I/O 事件一起处理。关闭定时器:
当不再需要定时器时,使用close
函数关闭文件描述符,释放资源。
普通例程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/timerfd.h>
#include <stdint.h> // 用于 uint64_t
#include <inttypes.h>int main() {// 1. 创建 timerfd,使用单调递增时钟int timer_fd = timerfd_create(CLOCK_MONOTONIC, 0);if (timer_fd == -1) {perror("timerfd_create");exit(EXIT_FAILURE);}// 2. 设置定时器:首次 1 秒后超时,之后每秒超时一次struct itimerspec new_value;new_value.it_value.tv_sec = 1; // 首次超时:1 秒new_value.it_value.tv_nsec = 0;new_value.it_interval.tv_sec = 1; // 超时间隔:1 秒new_value.it_interval.tv_nsec = 0;if (timerfd_settime(timer_fd, 0, &new_value, NULL) == -1) {perror("timerfd_settime");close(timer_fd);exit(EXIT_FAILURE);}printf("定时器已启动,每秒触发一次...\n");// 3. 循环读取超时事件uint64_t expirations;ssize_t s;while (1) {// read 会阻塞,直到定时器超时s = read(timer_fd, &expirations, sizeof(expirations));if (s != sizeof(expirations)) {perror("read");close(timer_fd);exit(EXIT_FAILURE);}printf("定时器超时次数: %" PRIu64 "\n", expirations);// 在这里执行你的定时任务...}// 4. 关闭 (通常不会执行到这里)close(timer_fd);return 0;
}
应用库
#include <iostream>
#include <functional>
#include <sys/timerfd.h>
#include <unistd.h>
#include <inttypes.h>
#include <thread>
#include <chrono>class TimerFdWrapper
{
public:TimerFdWrapper(int interval, const std::function<void()> &func): interval(interval), func(func), running(false) {}~TimerFdWrapper(){stop();}void start(){if (running)return;running = true;timerThread = std::thread(&TimerFdWrapper::timerLoop, this);}void stop(){if (!running)return;running = false;if (timerThread.joinable())timerThread.join();}
private:int interval;std::function<void()> func;std::thread timerThread;bool running;int initTimerFd(){int fd = timerfd_create(CLOCK_MONOTONIC, 0);if (fd == -1){perror("timerfd_create");return -1;}struct itimerspec its;its.it_value.tv_sec = 0;its.it_value.tv_nsec = interval * 1000000;its.it_interval.tv_sec = 0;its.it_interval.tv_nsec = interval * 1000000;if (timerfd_settime(fd, 0, &its, NULL) == -1){perror("timerfd_settime");close(fd);return -1;}return fd;}void timerLoop(){int fd = initTimerFd();if (fd == -1)return;// 尝试设置新的高优先级,值越大优先级越高(1-99)struct sched_param param;param.sched_priority = 10;pthread_setschedparam(pthread_self(), SCHED_FIFO, ¶m);uint64_t expirations;while (running){if (read(fd, &expirations, sizeof(expirations)) != sizeof(expirations)){perror("read");break;}// printf("expirations = %d\r\n", expirations);for (uint64_t i = 0; i < expirations; ++i)func();}close(fd);}
};