Linux timerfd 的基本使用

目录

  • [一、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
  1. 无信号困扰:传统定时器依赖信号(如 SIGALRM)实现回调,存在竞态、重入、信号屏蔽等问题,timerfd 基于文件描述符,通过「可读事件」触发超时,无需处理信号。
  2. 支持 IO 多路复用:可直接将 timerfd 的文件描述符加入 epoll 监听集合,与网络套接字等 IO 描述符统一管理,简化高并发程序的事件循环逻辑。
  3. 高精度与灵活配置:支持纳秒级定时,支持「一次性定时」和「周期性定时」,还支持相对定时 / 绝对定时两种模式。
  4. 易于管理:多个定时器可通过不同文件描述符区分,管理便捷,无传统定时器的 ID 冲突问题。
  • 核心原理
  1. 调用 timerfd_create() 创建定时器,内核返回一个唯一的文件描述符( timerfd)。
  2. 调用 timerfd_settime() 配置超时时间(首次超时 + 周期间隔),启动定时器。
  3. 定时器超时后,对应的 timerfd 会变为「可读」状态。
  4. 通过 read() 读取该文件描述符,可获取超时次数(一个 64 位无符号整数),从而触发后续业务逻辑。
  5. 使用完毕后,调用 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);

参数说明:

  • fdtimerfd_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*/, &timespec, 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 ......);
    }
}

四、参考资料

Linux fd 系列 --- 定时器 timerfd 是什么?

相关推荐
寄存器漫游者2 小时前
Linux 软件编程 命令、内核与 Shell
linux·运维·服务器
三月微暖寻春笋2 小时前
【和春笋一起学C++】(五十八)类继承
c++·派生类·类继承·基类构造函数·派生类构造函数
热爱编程的小刘2 小时前
Lesson05&6 --- C&C++内存管理&模板初阶
开发语言·c++
qinyia2 小时前
通过本地构建解决Cartographer编译中absl依赖缺失问题
linux·运维·服务器·mysql·ubuntu
郝亚军2 小时前
ubuntu启一个udp server,由一个client访问
linux·ubuntu·udp
czy87874752 小时前
深入了解 C++ 中的 Lambda 表达式(匿名函数)
c++
苦逼IT运维2 小时前
从 0 到 1 理解 Kubernetes:一次“破坏式”学习实践(一)
linux·学习·docker·容器·kubernetes
CSDN_RTKLIB3 小时前
include_directories和target_include_directories说明
c++
李小白202002023 小时前
EMMC写入/烧录逻辑
linux·运维·服务器