timefd

timefd 到底是什么

你可以把timefd理解成“把定时器变成文件描述符(fd)的工具”—— Linux 系统把定时器功能包装成了一个和 “文件、网络套接字” 一样的 fd,你可以像操作文件一样操作定时器。

为什么要用它?新手可能用过alarm()这类简单定时器,但它们有坑:比如依赖信号处理(容易和其他信号冲突)、多线程下同步麻烦。而timefd把定时器变成 fd 后,能和epoll/poll(Linux 处理多 IO 事件的工具)结合,既不用处理复杂的信号,又能把 “定时器事件” 和 “网络 IO、文件 IO 事件” 放在一起管理,特别适合写服务程序。

核心特点:

  1. 定时器超时后,这个 fd 会变成 “可读” 状态,用read()就能读到超时信息;
  2. 支持两种定时器:一次性(比如 3 秒后响一次)、周期性(比如每 2 秒响一次)。

timefd 核心函数

使用timefd需要先包含头文件<sys/timerfd.h>,编译时部分 Linux 版本需要加-lrt(新版一般不用)。下面逐个讲核心函数,参数、返回值都用大白话解释,不用记复杂表格。

1. 创建定时器 fd:timerfd_create
int timerfd_create(int clockid, int flags);

作用:创建一个和定时器绑定的 fd,后续所有定时器操作都靠这个 fd 完成。

  • 参数 1:clockid(时钟类型)新手只需要选CLOCK_MONOTONIC!它代表 “系统开机后流逝的时间”,只会一直增加,哪怕手动改系统时间(比如把时间调回 1 小时前),这个时间也不会变,定时器不会乱。另一个值CLOCK_REALTIME是 “系统当前时间”,改系统时间会导致定时器出错,新手别用。

  • 参数 2:flags(标志位)新手推荐写TFD_NONBLOCK | TFD_CLOEXEC

    • TFD_NONBLOCK:把 fd 设为 “非阻塞”(读不到数据时不会卡主程序);
    • TFD_CLOEXEC:程序启动新进程时自动关闭这个 fd,避免 fd 泄漏。如果你想先简单理解,也可以设为 0(阻塞模式)。
  • 返回值:

    • 成功:返回一个整数(就是这个定时器的 fd 编号,比如 3、4);
    • 失败:返回 -1,系统会设置errno(可以用strerror(errno)看具体错在哪)。
2. 设置定时器规则:timerfd_settime
int timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value);

作用:告诉系统 “这个定时器什么时候超时、要不要重复”。

  • 参数 1:fd就是timerfd_create返回的定时器 fd,指定要设置哪个定时器。

  • 参数 2:flags新手先设为 0!代表 “超时时间是相对时间”(比如 “3 秒后超时”);如果设为TFD_TIMER_ABSTIME,就是 “绝对时间”(比如 “开机后 100 秒超时”),新手暂时用不上。

  • 参数 3:new_value(核心!设置超时规则)这是一个结构体,新手不用记复杂定义,只需要知道它管两个事:

    struct itimerspec { struct timespec it_interval; // 定时器的重复间隔(多久重复一次) struct timespec it_value; // 定时器第一次超时的时间 }; // 其中 timespec 结构体只需要关注这两个值: struct timespec { time_t tv_sec; // 秒(比如 3 就是 3 秒) long tv_nsec; // 纳秒(1秒=10亿纳秒,新手直接设 0 就行) };
    • 一次性定时器:it_interval.tv_sec = 0(不重复),it_value.tv_sec = 3(3 秒后第一次超时);
    • 周期性定时器:it_interval.tv_sec = 2(每 2 秒重复),it_value.tv_sec = 1(1 秒后第一次超时)。
  • 参数 4:old_value新手直接设为 NULL!它的作用是 “返回之前的定时器规则”,暂时用不上。

  • 返回值:

    • 成功:返回 0;
    • 失败:返回 -1,用strerror(errno)能看到错误原因(比如 fd 无效)。
3. 查看定时器剩余时间:timerfd_gettime
int timerfd_gettime(int fd, struct itimerspec *curr_value);

作用:查一下这个定时器还剩多久才会超时。

  • 参数 1:fd目标定时器的 fd;
  • 参数 2:curr_value传一个空的itimerspec结构体就行,调用后系统会把 “剩余时间” 填到这个结构体里(比如curr_value->it_value.tv_sec就是剩余的秒数);
  • 返回值:
    • 成功:返回 0;
    • 失败:返回 -1。
4. 读取定时器超时事件:read(普通的 read 函数)

新手重点:定时器超时后,它的 fd 会变成 “可读” 状态,必须用read()读取,否则 fd 会一直处于 “可读” 状态,反复触发事件!

函数原型(就是普通的 read,不用记新函数):

ssize_t read(int fd, void *buf, size_t count);
  • 参数 1:fd:定时器的 fd;
  • 参数 2:buf:要存一个uint64_t类型的变量(8 字节),这个变量会记录 “累计超时次数”(比如定时器该超时 2 次但你没读,这里就会是 2);
  • 参数 3:count:必须填 8(因为uint64_t是 8 字节);
  • 返回值:
    • 成功:返回 8(表示读到了 8 字节的超时次数);
    • 失败:返回 -1(比如没超时就读,非阻塞模式下会返回 -1,且errno=EAGAIN)。

样例

样例 1:最简单的一次性定时器(阻塞版)

新手先跑这个,理解核心流程,代码注释写满,复制就能编译运行。

#include <iostream> // timefd 必需的头文件 #include <sys/timerfd.h> #include <unistd.h> #include <cstdint> // 用于 uint64_t(存超时次数) #include <cerrno> // 用于 errno(错误码) #include <cstring> // 用于 strerror(转错误码为文字) #include <cstdlib> // 用于 exit(退出程序) // 新手友好的错误处理:出错时打印信息并退出 void error_exit(const char *msg) { std::cerr << "错误:" << msg << " - " << strerror(errno) << std::endl; exit(1); } int main() { // 步骤1:创建定时器fd(阻塞模式,新手先不用非阻塞) int tfd = timerfd_create(CLOCK_MONOTONIC, 0); if (tfd == -1) { error_exit("创建定时器失败"); } std::cout << "步骤1:定时器fd创建成功,fd号:" << tfd << std::endl; // 步骤2:设置定时器规则:3秒后超时,一次性(不重复) struct itimerspec timer_rule; // 第一次超时时间:3秒,0纳秒 timer_rule.it_value.tv_sec = 3; timer_rule.it_value.tv_nsec = 0; // 重复间隔:0秒(一次性,不重复) timer_rule.it_interval.tv_sec = 0; timer_rule.it_interval.tv_nsec = 0; // 调用 timerfd_settime 设置规则 if (timerfd_settime(tfd, 0, &timer_rule, NULL) == -1) { error_exit("设置定时器规则失败"); } std::cout << "步骤2:定时器设置完成,3秒后超时..." << std::endl; // 步骤3:等待定时器超时并读取事件(阻塞模式下,read会等直到超时) uint64_t timeout_count = 0; // 存超时次数 ssize_t read_ret = read(tfd, &timeout_count, 8); // 第三个参数必须是8 if (read_ret == -1) { error_exit("读取定时器超时事件失败"); } std::cout << "步骤3:定时器超时!累计超时次数:" << timeout_count << std::endl; // 步骤4:查看定时器剩余时间(超时后剩余时间应该是0) struct itimerspec remain_time; if (timerfd_gettime(tfd, &remain_time) == -1) { error_exit("获取剩余时间失败"); } std::cout << "步骤4:定时器剩余超时时间:" << remain_time.it_value.tv_sec << "秒" << std::endl; // 步骤5:关闭fd(新手别忘!用完必须关) close(tfd); std::cout << "步骤5:定时器fd已关闭" << std::endl; return 0; }

编译运行命令

g++ -std=c++11 timerfd_demo1.cpp -o timerfd_demo1 -lrt ./timerfd_demo1

运行效果:

步骤1:定时器fd创建成功,fd号:3 步骤2:定时器设置完成,3秒后超时... (等待3秒) 步骤3:定时器超时!累计超时次数:1 步骤4:定时器剩余超时时间:0秒 步骤5:定时器fd已关闭
样例 2:实用的周期性定时器(非阻塞 + poll)

实际开发中不会用阻塞 read,而是用 poll 监听,主线程还能做其他事。

#include <iostream> #include <sys/timerfd.h> #include <sys/poll.h> // poll 头文件(监听fd事件) #include <unistd.h> #include <cstdint> #include <cerrno> #include <cstring> #include <cstdlib> void error_exit(const char *msg) { std::cerr << "错误:" << msg << " - " << strerror(errno) << std::endl; exit(1); } // 新手友好:打印当前时间(毫秒),方便看效果 uint64_t get_ms() { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return ts.tv_sec * 1000 + ts.tv_nsec / 1000000; } int main() { // 步骤1:创建非阻塞的定时器fd int tfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); if (tfd == -1) error_exit("创建定时器失败"); std::cout << "[" << get_ms() << "] 定时器fd创建成功" << std::endl; // 步骤2:设置周期性定时器:1秒后首次超时,每2秒重复一次 struct itimerspec timer_rule; timer_rule.it_value.tv_sec = 1; // 1秒后第一次超时 timer_rule.it_value.tv_nsec = 0; timer_rule.it_interval.tv_sec = 2; // 每2秒重复一次 timer_rule.it_interval.tv_nsec = 0; if (timerfd_settime(tfd, 0, &timer_rule, NULL) == -1) { error_exit("设置定时器规则失败"); } std::cout << "[" << get_ms() << "] 周期性定时器设置完成:1秒后首次超时,每2秒重复" << std::endl; // 步骤3:初始化poll,监听定时器fd的“可读”事件 struct pollfd pfd; pfd.fd = tfd; // 要监听的fd是定时器fd pfd.events = POLLIN; // 监听“可读”事件(定时器超时后fd可读) pfd.revents = 0; // 初始化,不用改 // 步骤4:非阻塞事件循环(主线程可以做其他事) int count = 0; // 记录超时次数,到3次就退出 while (true) { // poll监听:每100毫秒检查一次(非阻塞) int poll_ret = poll(&pfd, 1, 100); if (poll_ret == -1) { if (errno == EINTR) continue; // 信号中断,继续循环 error_exit("poll监听失败"); } // 情况1:poll超时(100ms内没事件),主线程做其他事 if (poll_ret == 0) { std::cout << "[" << get_ms() << "] 主线程空闲中...(定时器未超时)" << std::endl; continue; } // 情况2:定时器fd可读(超时了) if (pfd.revents & POLLIN) { uint64_t timeout_count = 0; ssize_t read_ret = read(tfd, &timeout_count, 8); if (read_ret != 8) error_exit("读取超时事件失败"); std::cout << "[" << get_ms() << "] 定时器超时!累计超时次数:" << timeout_count << std::endl; count++; if (count >= 3) { // 超时3次后退出 std::cout << "[" << get_ms() << "] 超时3次,退出程序" << std::endl; break; } } } // 步骤5:关闭fd close(tfd); return 0; }

编译运行命令:

g++ -std=c++11 timerfd_demo2.cpp -o timerfd_demo2 -lrt ./timerfd_demo2

运行效果:

[123456] 定时器fd创建成功 [123457] 周期性定时器设置完成:1秒后首次超时,每2秒重复 [123557] 主线程空闲中...(定时器未超时) [123657] 主线程空闲中...(定时器未超时) [123757] 定时器超时!累计超时次数:1 [123857] 主线程空闲中...(定时器未超时) [123957] 主线程空闲中...(定时器未超时) [124057] 定时器超时!累计超时次数:1 [124157] 主线程空闲中...(定时器未超时) [124257] 主线程空闲中...(定时器未超时) [124357] 定时器超时!累计超时次数:1 [124357] 超时3次,退出程序

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1215377.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《穷查理宝典查理芒格的智慧箴言录》-20万字 最完整版

自用备份,有人要也可以自取 链接:https://pan.quark.cn/s/55e9e8be82c0

贾子普世智慧公理(Kucius Axioms of Universal Wisdom)的深度研究与系统论述

智能扩张的伦理边界&#xff1a;贾子普世智慧公理及其对AI与文明的裁决摘要&#xff1a; 贾子普世智慧公理是一个旨在界定智慧本质、为技术进步划定伦理边界的文明级规范体系。它提出“思想主权、普世中道、本源探究、悟空跃迁”四大核心公理&#xff0c;强调智慧是品格与价值的…

Linux写sh开机启动脚本-bash报错的两种克服方法

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

基于Spring Boot的长春美食推荐管理系统的设计与实现(源码+论文+部署+安装)

感兴趣的可以先收藏起来&#xff0c;还有在毕设选题&#xff0c;项目以及论文编写等相关问题都可以给我留言咨询&#xff0c;我会一一回复&#xff0c;希望可以帮到大家。一、程序背景在信息技术飞速发展与人们对美食体验需求升级的双重驱动下&#xff0c;长春作为美食文化底蕴…

本章节我们将讨论如何在 React 中使用表单DFS。

React 表单与事件本章节我们将讨论如何在 React 中使用表单。HTML 表单元素与 React 中的其他 DOM 元素有所不同,因为表单元素生来就保留一些内部状态。在 HTML 当中&#xff0c;像 <input>, <textarea>, 和 <select> 这类表单元素会维持自身状态&#xff0c…

看一遍就懂-大模型架构及encoder-decoder详细训练和推理计算过程

看一遍就懂-大模型架构及encoder-decoder详细训练和推理计算过程看一遍就懂-大模型架构及encoder-decoder详细训练和推理计算过程一、特殊Token的意思 不同模型架构的特殊token体系 BERT(Encoder-only,用于理解任务)…

完整教程:Android内核进阶之获取DMA地址snd_pcm_sgbuf_get_addr:用法实例(九十一)

完整教程:Android内核进阶之获取DMA地址snd_pcm_sgbuf_get_addr:用法实例(九十一)2026-01-25 18:21 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; ove…

CAD二次开发中关于非模态对话框的使用

1.添加 Dialog 资源&#xff08;普通 DLL 中创建对话框&#xff0c;可视化操作&#xff09; 普通 DLL 项目添加 Dialog 资源和 BRX 模板项目操作一致&#xff0c;全程可视化&#xff0c;无需手动写复杂脚本&#xff1a; 1.1 添加资源脚本文件&#xff08;.rc&#xff09; 右…

机器学习——线性回归、代价(损失)函数、L1L2正则化、梯度下降算法、正态分布和标准正态分布

一、线性回归与损失函数 举个例子&#xff08;图片来自于b站耿直哥丶&#xff09;&#xff1a; 比如父亲和儿子的身高存在某种关系&#xff0c;这些父亲和儿子身高的数据&#xff0c;画在二维坐标系上就是一堆点&#xff0c;那么我们可不可以从这堆数据中找到一个准确的数学模…

微积分:世界是用“微分”写成的,我们是用“积分”读懂的

——试着不用符号理解微积分 &#x1f343; 01. 世界是连续变化的 温度不是“突然 5℃”&#xff0c;而是慢慢升的 汽车不是“瞬间到 60 km/h”&#xff0c;而是一点点加速 树不是“咻”一下长高&#xff0c;而是毫米级地生长 河水不是“啪”地冲过去&#xff0c;而是持续…

宝塔面板一键部署 Emlog 教程:从服务器准备到站点上线全攻略

文章目录宝塔面板一键部署 Emlog 教程&#xff1a;从服务器准备到站点上线全攻略一、宝塔面板简介二、部署前准备三、宝塔面板安装1. 下载并执行安装脚本2. 访问宝塔面板四、宝塔面板一键部署 Emlog1. 搜索并选择 Emlog2. 填写部署信息3. 部署完成与访问4. 设置管理员账号五、部…

爬虫项目:利用 Playwright 和 Asyncio 高效收集酒店信息

更多内容请见: 《爬虫和逆向教程》 - 专栏介绍和目录 文章目录 一、为什么选择 Playwright + Asyncio? 1.1 Playwright 的核心优势 1.2 Asyncio 的并发优势 1.3 实践建议 二、环境准备与基础配置 2.1 安装依赖 2.2 基础目录结构 三、核心架构设计 3.1 异步爬虫工作流 3.2 关键…

华为MetaERP锂电池行业数字化转型总体蓝图架构设计解决方案

华为MetaERP锂电池行业数字化转型总体蓝图架构设计解决方案 核心理念&#xff1a; 以华为MetaERP为数字核心基座&#xff0c;构建覆盖锂电池“研、产、供、销、服、管”全价值链的一体化、智能化、韧性化运营平台&#xff0c;驱动业务模式创新与效率跃升。 一、 业务架构蓝图…

将“100小时精通Oracle ERP,华为MetaERP和SAP”称为“不得不把握的世纪机会”

在当今数字化转型的浪潮中&#xff0c;能同时掌握这三大巨头&#xff08;两大国际巨头一个中国自研翘楚&#xff09;的知识&#xff0c;无疑会为您的职业生涯带来巨大的竞争优势。 然而&#xff0c;我们必须现实地看待“100小时”和“精通”这两个词。 “精通” 在ERP领域通常…

华为MetaERP作为华为自主研发的新一代企业资源计划系统,其成功部署和高效运行依赖于多个核心技术组件和生态合作伙伴的协同支持

华为MetaERP作为华为自主研发的新一代企业资源计划系统&#xff0c;其成功部署和高效运行依赖于多个核心技术组件和生态合作伙伴的协同支持。尽管搜索结果中未直接提及“ERP伴侣”这一术语&#xff0c;但结合上下文分析&#xff0c;可以理解为MetaERP的核心技术支撑、关键模块及…

232. 用栈实现队列

题目:232. 用栈实现队列 题目链接:https://leetcode.cn/problems/implement-queue-using-stacks/description/ 思路:定义两个栈,一个作为出栈,一个作为入栈。 代码:点击查看代码 class MyQueue {Deque<Intege…

吐血推荐8个AI论文工具,本科生搞定毕业论文!

吐血推荐8个AI论文工具&#xff0c;本科生搞定毕业论文&#xff01; AI 工具如何帮你轻松应对论文难题 对于大多数本科生来说&#xff0c;毕业论文是大学生活中最令人头疼的任务之一。从选题、写大纲到撰写初稿、反复修改&#xff0c;再到查重降重&#xff0c;每一个环节都可能…

SAP ERP的成本中心与Oracle ERP会计科目弹性域中部门段的比较

SAP ERP的成本中心与Oracle ERP会计科目弹性域中部门段的比较 这两个概念在ERP系统中属于不同维度的管理工具&#xff0c;虽然都涉及部门维度的财务数据追踪&#xff0c;但存在本质区别。 核心概念对比 维度SAP成本中心Oracle会计科目弹性域中的部门段系统定位管理会计&#…