目录
- [一、timerfd 简介](#一、timerfd 简介)
- [二、核心 API 介绍](#二、核心 API 介绍)
-
- [2.1 timerfd_create](#2.1 timerfd_create)
- [2.2 timerfd_settime](#2.2 timerfd_settime)
- [2.3 timerfd_gettime](#2.3 timerfd_gettime)
- [2.4 读取超时事件(read ())](#2.4 读取超时事件(read ()))
- 三、基本使用示例
-
- [3.1 示例 1](#3.1 示例 1)
- [3.2 示例 2](#3.2 示例 2)
- [3.3 示例 3](#3.3 示例 3)
- 四、参考资料
|-------------------------|
| Linux timerfd 的基本使用 |
一、timerfd 简介
timerfd 是 Linux 内核 2.6.25 版本引入的基于文件描述符的定时器接口,它将定时器功能与文件描述符绑定,核心特点是可以把定时器事件融入到 select/poll/epoll 等 IO 多路复用框架中,实现「IO 事件 + 定时器事件」的统一处理。
- 核心优势(对比传统定时器
alarm/setitimer/signal)
- 无信号困扰:传统定时器依赖信号(如 SIGALRM)实现回调,存在竞态、重入、信号屏蔽等问题,timerfd 基于文件描述符,通过「可读事件」触发超时,无需处理信号。
- 支持 IO 多路复用:可直接将 timerfd 的文件描述符加入 epoll 监听集合,与网络套接字等 IO 描述符统一管理,简化高并发程序的事件循环逻辑。
- 高精度与灵活配置:支持纳秒级定时,支持「一次性定时」和「周期性定时」,还支持相对定时 / 绝对定时两种模式。
- 易于管理:多个定时器可通过不同文件描述符区分,管理便捷,无传统定时器的 ID 冲突问题。
- 核心原理
- 调用
timerfd_create()创建定时器,内核返回一个唯一的文件描述符( timerfd)。 - 调用
timerfd_settime()配置超时时间(首次超时 + 周期间隔),启动定时器。 - 定时器超时后,对应的 timerfd 会变为「可读」状态。
- 通过
read()读取该文件描述符,可获取超时次数(一个 64 位无符号整数),从而触发后续业务逻辑。 - 使用完毕后,调用
close()关闭 timerfd,释放资源。
二、核心 API 介绍
timerfd 相关接口定义在 <sys/timerfd.h> 头文件中,核心有 3 个函数,下面重点讲解常用参数和用法。
2.1 timerfd_create
c
timerfd_create (): 创建定时器文件描述符
用于创建一个定时器对象,返回对应的文件描述符。
c
#include <sys/timerfd.h>
int timerfd_create(int clockid, int flags);
参数说明:
- clockid:时钟类型,决定定时器的计时基准,常用 2 种:
- CLOCK_REALTIME:系统实时时钟(墙上时钟),对应系统当前时间,会受系统时间修改(手动改时间、NTP 同步)的影响。
- CLOCK_MONOTONIC:系统单调时钟,从系统启动后开始计时,不受系统时间修改影响,稳定性更高,推荐用于业务定时任务。
- flags:创建标志,常用组合:
- TFD_NONBLOCK:创建非阻塞模式的文件描述符,read() 时不会阻塞等待超时。
- TFD_CLOEXEC:进程执行 exec() 时自动关闭该文件描述符,避免资源泄露。
返回值:成功返回非负文件描述符(timerfd),失败返回 -1 并设置 errno
2.2 timerfd_settime
c
timerfd_settime ():配置并启动 / 停止定时器
用于设置定时器的超时参数,启动、修改或停止定时器。
c
int timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value);
参数说明:
- fd :
timerfd_create()返回的定时器文件描述符。 - flags:定时模式,可选 2 种:
- 0:相对定时模式(默认),定时器从调用函数开始,经过 new_value->it_value 时间后首次超时。
TFD_TIMER_ABSTIME:绝对定时模式,定时器在 new_value->it_value 对应的系统时钟时间点超时(需与 clockid 对应)。
- new_value:核心配置参数,结构体类型,定义定时器的「首次超时时间」和「周期性间隔时间」:
c
struct itimerspec {
// 周期性定时的间隔时间:若为 0,则为「一次性定时器」;非 0 则为「周期性定时器」
struct timespec it_interval;
// 首次超时时间:若为 0,则停止当前定时器
struct timespec it_value;
};
struct timespec {
time_t tv_sec; // 秒(>= 0)
long tv_nsec; // 纳秒(0 ~ 999999999,超出范围会自动转换为秒)
};
- old_value:用于保存定时器的上一次配置,若不需要获取历史配置,可传 NULL。
返回值:成功返回 0,失败返回 -1 并设置 errno。
2.3 timerfd_gettime
timerfd_gettime() 是 Linux 下专用于 timerfd 系列定时器的查询接口,核心功能是获取指定 timerfd 定时器的当前状态,包括:
- 距离下一次超时还剩余的时间(若定时器处于活跃状态)。
- 定时器的周期性间隔时间(无论是否活跃)。
c
int timerfd_gettime(int fd, struct itimerspec *curr_value);
参数说明
该函数仅有 2 个参数,一个输入参数,一个输出参数
- 输入参数:fd 待查询的定时器文件描述符,由 timerfd_create() 调用成功后返回。
要求:必须是有效的、已关联定时器对象的文件描述符,否则会返回错误。 - 输出参数:curr_value
类型:struct itimerspec *(指向itimerspec结构体的指针)
含义:输出参数,用于存储函数查询到的定时器当前状态数据,调用者需提前分配该结构体的内存空间(不可传 NULL)。 - 核心结构体(与 timerfd_settime() 中一致):
c
struct itimerspec {
// 定时器的周期性间隔时间(与 timerfd_settime() 配置的 it_interval 一致)
// 无论定时器是否活跃,该字段都会返回配置的周期值(0 表示一次性定时器)
struct timespec it_interval;
// 核心:距离下一次超时的剩余时间(关键查询结果)
// 结构体成员 tv_sec(秒)、tv_nsec(纳秒,0~999999999)
struct timespec it_value;
};
struct timespec {
time_t tv_sec; // 秒(>= 0)
long tv_nsec; // 纳秒(0 ~ 999999999)
};
- 关于
it_value的特殊说明(判断定时器是否活跃):
- 若 it_value.tv_sec == 0 且 it_value.tv_nsec == 0:表示该定时器当前处于非活跃状态(未启动,或一次性定时器已超时,或已通过 timerfd_settime() 停止)。
- 若 it_value 两个成员不全为 0:表示定时器处于活跃状态,该值即为距离下一次超时的剩余时间(基于定时器创建时指定的 clockid 时钟基准)。
- 返回值
成功:返回 0,查询结果已存入 curr_value 指向的结构体中。
失败:返回 -1,同时设置全局变量 errno 标识错误类型,常见错误如下:
错误码及其含义
- EBADF 输入参数 fd 不是有效的文件描述符
- EINVAL fd 不是 timerfd 类型的文件描述符,或 curr_value 是无效指针
- EFAULT curr_value 指向的内存空间不可访问(如空指针、无写权限)
2.4 读取超时事件(read ())
定时器超时后, timerfd 会变为「可读」状态,此时调用 read() 可读取超时次数,缓冲区必须至少为 8 字节(对应 uint64_t 类型)。
这个 read 并非读取普通文件的实际数据,而是专用于从 timerfd 文件描述符中读取内核维护的「定时器超时次数计数器」,和普通文件的 read 调用有本质区别,
c
#include <stdint.h>
uint64_t timeout_count;
// 阻塞读取(若 timerfd 为阻塞模式),读取到超时次数后返回
ssize_t ret = read(timer_fd, &timeout_count, sizeof(timeout_count));
若程序在定时器多次超时后才调用 read(),timeout_count 会返回累积的超时总次数。
读取成功后, timerfd 会恢复为「不可读」状态,直到下一次超时。
- 参数说明
| 参数 | 类型 | 含义 | 注意点 |
|---|---|---|---|
| fd | int | 待读取的文件描述符 | 必须是已打开、且有读权限的有效 fd(如 timerfd、普通文件、套接字等) |
| buf | void * | 内存缓冲区首地址 | 用于存储读取到的数据,需提前分配内存,且保证可写(通常传变量地址) |
| count | size_t | 期望读取的最大字节数 | 不能超过缓冲区的实际大小,否则会导致内存越界(缓冲区溢出) |
- 返回值说明
| 返回值 | 含义 | 常见场景 |
|---|---|---|
| > 0 | 成功读取的实际字节数 | 正常读取(普通文件:可能等于 / 小于 count;timerfd:成功则必等于 8) |
| 0 | 到达文件末尾(EOF) | 仅适用于普通文件 / 管道,timerfd 永远不会返回 0 |
| -1 | 读取失败 | 错误原因存在全局变量 errno 中,可通过 perror() 打印 |
三、基本使用示例
下面提供两个可直接编译运行的示例,分别演示「一次性定时器」和「周期性定时器」,编译要求:Linux 内核 ≥ 2.6.25,使用 gcc 编译。
3.1 示例 1
一次性定时器(3 秒后超时,触发一次)
c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/timerfd.h>
#include <time.h>
#include <stdint.h> // 用于 uint64_t 类型
int main() {
// 1. 创建 timerfd:使用单调时钟,自动关闭(TFD_CLOEXEC)
int timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
if (timer_fd == -1) {
perror("timerfd_create failed"); // 打印错误信息
exit(EXIT_FAILURE);
}
printf("timerfd 创建成功,文件描述符:%d\n", timer_fd);
// 2. 配置定时器:一次性定时,3 秒后首次超时,无周期
struct itimerspec new_timer_config;
// 首次超时时间:3 秒,0 纳秒
new_timer_config.it_value.tv_sec = 3;
new_timer_config.it_value.tv_nsec = 0;
// 周期性间隔:0(一次性定时器,无后续超时)
new_timer_config.it_interval.tv_sec = 0;
new_timer_config.it_interval.tv_nsec = 0;
// 3. 启动定时器(相对定时模式,flags=0)
if (timerfd_settime(timer_fd, 0, &new_timer_config, NULL) == -1) {
perror("timerfd_settime failed");
close(timer_fd); // 失败时关闭文件描述符,释放资源
exit(EXIT_FAILURE);
}
printf("一次性定时器已启动,3 秒后超时...\n");
// 4. 阻塞读取超时事件(等待 timerfd 可读)
uint64_t timeout_count; // 存储超时次数
ssize_t read_bytes = read(timer_fd, &timeout_count, sizeof(timeout_count));
if (read_bytes != sizeof(timeout_count)) {
perror("read failed");
close(timer_fd);
exit(EXIT_FAILURE);
}
printf("定时器超时!本次超时次数:%lu\n", (unsigned long)timeout_count);
// 5. 关闭 timerfd,释放系统资源
close(timer_fd);
return 0;
}
示例 1 运行结果
c
timerfd 创建成功,文件描述符:3
一次性定时器已启动,3 秒后超时...
(等待 3 秒后)
定时器超时!本次超时次数:1
3.2 示例 2
周期性定时器(首次 2 秒超时,之后每 1 秒超时一次)
c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/timerfd.h>
#include <time.h>
#include <stdint.h>
int main() {
// 1. 创建 timerfd
int timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
if (timer_fd == -1) {
perror("timerfd_create failed");
exit(EXIT_FAILURE);
}
printf("timerfd 创建成功,文件描述符:%d\n", timer_fd);
// 2. 配置定时器:首次 2 秒超时,之后每 1 秒超时一次
struct itimerspec new_timer_config;
// 周期性间隔:1 秒(每次超时后,再过 1 秒再次超时)
new_timer_config.it_interval.tv_sec = 1;
new_timer_config.it_interval.tv_nsec = 0;
// 首次超时时间:2 秒
new_timer_config.it_value.tv_sec = 2;
new_timer_config.it_value.tv_nsec = 0;
// 3. 启动定时器
if (timerfd_settime(timer_fd, 0, &new_timer_config, NULL) == -1) {
perror("timerfd_settime failed");
close(timer_fd);
exit(EXIT_FAILURE);
}
printf("周期性定时器已启动:2 秒后首次超时,之后每 1 秒超时一次\n");
printf("读取 5 次超时后程序退出...\n");
// 4. 循环读取超时事件,读取 5 次后退出
uint64_t timeout_count;
int read_times = 0; // 记录读取超时的次数
while (read_times < 5) {
ssize_t read_bytes = read(timer_fd, &timeout_count, sizeof(timeout_count));
if (read_bytes != sizeof(timeout_count)) {
perror("read failed");
close(timer_fd);
exit(EXIT_FAILURE);
}
read_times++;
printf("第 %d 次超时,累计超时次数:%lu\n", read_times, (unsigned long)timeout_count);
}
// 5. 关闭 timerfd
close(timer_fd);
printf("定时器已关闭,程序退出\n");
return 0;
}
示例 2 运行结果
c
timerfd 创建成功,文件描述符:3
周期性定时器已启动:2 秒后首次超时,之后每 1 秒超时一次
读取 5 次超时后程序退出...
(等待 2 秒后)
第 1 次超时,累计超时次数:1
(每 1 秒后)
第 2 次超时,累计超时次数:1
第 3 次超时,累计超时次数:1
第 4 次超时,累计超时次数:1
第 5 次超时,累计超时次数:1
定时器已关闭,程序退出
3.3 示例 3
timerfd 常与 epoll 系统调用结合使用:
c
// 创建一个 timerfd 句柄
int fdTimer = timerfd_create(CLOCK_MONOTONIC, 0 /*flags*/);
itimerspec timespec {
// 设置超时间隔
.it_interval = {
.tv_sec = 5,
.tv_nsec = 0,
},
//第一次超时时间
.it_value = {
.tv_sec = 5,
.tv_nsec = 0,
},
};
//启动定时器
int timeRes = timerfd_settime(fdTimer, 0 /*flags*/, ×pec, nullptr);
//初始化 fd
mEpollFd = epoll_create(1024);
if (epollfd == -1) {
perror("epoll_create");
exit(EXIT_FAILURE);
}
// epoll 监听 timerfd
epoll_ctl(mEpollFd, EPOLL_CTL_Add, fdTimer, &eventItem);
while (true)
{
// 进入休眠状态
int count = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
if (count < 0)
{
perror("epoll failed");
break;
}
for (int i=0;i < count;i++)
{
//处理计时器到达事件
read(fdTimer ......);
}
}