timerfd_create, timerfd_gettime, timerfd_settime系统调用及示例
好的,我们来深入学习 timerfd 相关的系统调用 (timerfd_create, timerfd_gettime, timerfd_settime)
1. 函数介绍
在 Linux 系统编程中,处理定时事件是一个常见需求。传统的定时方法包括:
alarm() 和 setitimer() + 信号 (SIGALRM):当定时器到期时,内核会发送一个信号给进程。这属于异步通知方式。信号处理函数有诸多限制(只能调用异步安全函数),并且容易引入竞态条件。
sleep() 系列函数:让进程休眠指定时间。这会阻塞进程,不够灵活。
timerfd 是 Linux 2.6.25 引入的一种基于文件描述符 (File Descriptor) 的定时器接口。它巧妙地将定时器“变成”了一个可以像文件一样操作的描述符。
-
你调用 timerfd_create() 创建一个定时器,它会返回一个文件描述符。
-
你调用 timerfd_settime() 来启动或修改这个定时器的超时时间和间隔。
-
当定时器到期时,这个文件描述符就会变为可读状态。
-
你可以在程序的主循环中使用 read()、poll()、select() 或 epoll_wait() 等 I/O 多路复用函数来监听这个文件描述符。当 read() 成功时,就意味着定时器超时了。
简单来说,timerfd 就是把定时器“变成”了可以像文件一样读取的数据流,让你可以用处理文件 I/O 或网络 I/O 的方式来处理定时事件。
典型应用场景:
-
事件驱动服务器:将定时器集成到 epoll/poll 的主循环中,统一处理网络 I/O 和定时事件。
-
避免信号处理的复杂性:用同步的 read() 替代异步的信号处理函数。
-
精确控制定时:可以创建一次性定时器或周期性定时器。
2. 函数原型
#include <sys/timerfd.h> // 包含 timerfd 相关函数和结构体
// 创建一个定时器并返回文件描述符
int timerfd_create(int clockid, int flags);
// 获取定时器的当前设置
int timerfd_gettime(int fd, struct itimerspec *curr_value);
// 启动或重新设置定时器
int timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value);
3. 功能
-
timerfd_create: 创建一个新的定时器对象,并返回一个与之关联的文件描述符。
-
timerfd_gettime: 查询指定定时器文件描述符的当前定时设置(下次超时时间和间隔)。
-
timerfd_settime: 启动、停止或修改指定定时器文件描述符的定时设置。
4. 参数详解
timerfd_create(int clockid, int flags)
clockid:
- int 类型。
指定定时器基于哪个时钟源。常用的有:
-
CLOCK_REALTIME: 系统实时时钟,表示从 Unix 纪元(1970-01-01 00:00:00 UTC)开始的时间。如果系统时间被手动修改,这个时钟会受到影响。
-
CLOCK_MONOTONIC: 单调时钟,表示从某个未指定起点开始的时间,不会受到系统时间调整的影响,是测量间隔的首选。
flags:
- int 类型。
用于修改定时器行为的标志位。常用的有:
-
TFD_CLOEXEC: 在执行 exec() 系列函数时自动关闭该文件描述符。
-
TFD_NONBLOCK: 使 read() 操作变为非阻塞模式。如果当前没有定时器到期事件可读,read() 会立即返回 -1 并设置 errno 为 EAGAIN 或 EWOULDBLOCK。
返回值:
-
成功: 返回一个有效的定时器文件描述符(非负整数)。
-
失败: 返回 -1,并设置 errno。
timerfd_gettime(int fd, struct itimerspec *curr_value)
fd:
-
int 类型。
-
一个有效的定时器文件描述符。
curr_value:
-
struct itimerspec * 类型。
-
一个指向 struct itimerspec 结构体的指针。函数调用成功后,会将定时器的当前设置填充到这个结构体中。
返回值:
-
成功: 返回 0。
-
失败: 返回 -1,并设置 errno。
timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value)
fd:
-
int 类型。
-
一个有效的定时器文件描述符。
flags:
- int 类型。
控制 new_value 中时间的解释方式。主要标志:
-
0 (默认): new_value->it_value 指定的是从当前时间点开始的相对超时时间。
-
TFD_TIMER_ABSTIME: new_value->it_value 指定的是一个绝对的超时时间点(基于创建定时器时指定的 clockid)。
new_value:
-
const struct itimerspec * 类型。
-
指向一个 struct itimerspec 结构体,定义了新的定时器设置。
old_value:
-
struct itimerspec * 类型。
-
如果不为 NULL,函数调用成功后,会将定时器在设置前的旧设置填充到这个结构体中。如果不需要旧值,可以传 NULL。
返回值:
-
成功: 返回 0。
-
失败: 返回 -1,并设置 errno。
struct itimerspec 结构体
这个结构体用于定义定时器的时间设置:
struct timespec {
time_t tv_sec; /* 秒 */
long tv_nsec; /* 纳秒 */
};
struct itimerspec {
struct timespec it_interval; /* 定时器的时间间隔 (周期性定时器) */
struct timespec it_value; /* 首次超时的相对/绝对时间 */
};
it_value: 指定首次超时的时间。
-
如果 tv_sec 和 tv_nsec 都为 0,则定时器被停止。
-
如果非 0,则指定定时器下次到期的时间。
it_interval: 指定定时器到期后,重复的间隔时间。
-
如果 tv_sec 和 tv_nsec 都为 0,则定时器是一次性的。
-
如果非 0,则定时器在首次到期后,会按此间隔周期性地重复到期。
5. 如何使用定时器文件描述符
创建: 使用 timerfd_create() 创建定时器,获得 fd。
设置: 使用 timerfd_settime() 配置定时器的超时和间隔。
等待: 在主循环中,使用 poll(), select(), epoll_wait() 或直接 read() (如果设置为阻塞) 来等待 fd 变为可读。
读取: 当 fd 可读时,调用 read(fd, &expirations, sizeof(expirations))。read() 会成功,并且读取到一个 uint64_t 类型的整数,表示自上次 read() 以来定时器到期的次数。
查询/修改: 可以随时使用 timerfd_gettime() 查询当前设置,或再次调用 timerfd_settime() 修改设置。
6. 错误码 (errno)
这些函数共享一些常见的错误码:
-
EINVAL: 参数无效(例如 clockid 无效,flags 无效,itimerspec 字段值无效)。
-
EMFILE: 进程已打开的文件描述符数量达到上限 (RLIMIT_NOFILE)。
-
ENFILE: 系统已打开的文件描述符数量达到上限。
-
ENOMEM: 内核内存不足。
-
EBADF: fd 不是有效的文件描述符。
-
EFAULT: new_value, old_value, 或 curr_value 指向无效内存。
7. 相似函数或关联函数
-
alarm / setitimer: 传统的基于信号的定时器。
-
poll, select, epoll_wait: I/O 多路复用函数,可以监听 timerfd 文件描述符的可读事件。
-
read: 用于从 timerfd 中读取到期次数。
-
close: 关闭 timerfd 文件描述符。
-
clock_gettime / clock_settime: 获取和设置不同类型的系统时钟。
-
POSIX 定时器 (timer_create, timer_settime, timer_gettime): 另一套定时器 API,也使用信号通知。
8. 示例代码
下面的示例演示了如何使用 timerfd 创建一次性定时器和周期性定时器,并将其集成到 poll 系统调用中。
#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/timerfd.h> // timerfd 相关
#include <poll.h> // poll 相关
#include <string.h>
#include <errno.h>
#include <inttypes.h> // 包含 PRIu64 宏,用于 printf uint64_t
// 辅助函数:打印 itimerspec 结构
void print_itimerspec(const char* prefix, const struct itimerspec *ts) {
printf("%s: ", prefix);
if (ts->it_value.tv_sec == 0 && ts->it_value.tv_nsec == 0) {
printf("Timer is STOPPED\n");
} else {
printf("Next expiration in %ld.%09ld seconds\n", ts->it_value.tv_sec, ts->it_value.tv_nsec);
}
printf(" Interval: %ld.%09ld seconds\n", ts->it_interval.tv_sec, ts->it_interval.tv_nsec);
}
int main() {
int tfd_one_shot, tfd_periodic;
struct itimerspec one_shot_time, periodic_time;
struct pollfd fds[3]; // 监听两个 timerfd 和 标准输入
uint64_t expirations;
ssize_t s;
printf("--- Demonstrating timerfd ---\n");
printf("PID: %d\n", getpid());
// 1. 创建两个 timerfd
// 一次性定时器,基于单调时钟
tfd_one_shot = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
if (tfd_one_shot == -1) {
perror("timerfd_create one-shot");
exit(EXIT_FAILURE);
}
printf("Created one-shot timerfd: %d\n", tfd_one_shot);
// 周期性定时器,基于单调时钟,非阻塞
tfd_periodic = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
if (tfd_periodic == -1) {
perror("timerfd_create periodic");
close(tfd_one_shot);
exit(EXIT_FAILURE);
}
printf("Created periodic timerfd: %d (non-blocking)\n", tfd_periodic);
// 2. 设置一次性定时器:3秒后到期
one_shot_time.it_value.tv_sec = 3; // 3 秒后
one_shot_time.it_value.tv_nsec = 0;
one_shot_time.it_interval.tv_sec = 0; // 一次性,不重复
one_shot_time.it_interval.tv_nsec = 0;
if (timerfd_settime(tfd_one_shot, 0, &one_shot_time, NULL) == -1) {
perror("timerfd_settime one-shot");
close(tfd_one_shot);
close(tfd_periodic);
exit(EXIT_FAILURE);
}
printf("Set one-shot timer to expire in 3 seconds.\n");
// 3. 设置周期性定时器:立即启动,每 2 秒重复一次
periodic_time.it_value.tv_sec = 0; // 立即启动
periodic_time.it_value.tv_nsec = 1; // 纳秒设为1,确保立即触发第一次
periodic_time.it_interval.tv_sec = 2; // 每 2 秒重复
periodic_time.it_interval.tv_nsec = 0;
if (timerfd_settime(tfd_periodic, 0, &periodic_time, NULL) == -1) {
perror("timerfd_settime periodic");
close(tfd_one_shot);
close(tfd_periodic);
exit(EXIT_FAILURE);
}
printf("Set periodic timer to start immediately and repeat every 2 seconds.\n");
// 4. 查询并打印初始定时器状态
struct itimerspec curr_time;
if (timerfd_gettime(tfd_one_shot, &curr_time) == 0) {
print_itimerspec("One-shot timer initial state", &curr_time);
}
if (timerfd_gettime(tfd_periodic, &curr_time) == 0) {
print_itimerspec("Periodic timer initial state", &curr_time);
}
// 5. 设置 poll 的文件描述符数组
fds[0].fd = tfd_one_shot;
fds[0].events = POLLIN;
fds[1].fd = tfd_periodic;
fds[1].events = POLLIN;
fds[2].fd = STDIN_FILENO;
fds[2].events = POLLIN;
printf("\nEntering main loop. Waiting for timers or input (type 'quit' to exit)...\n");
// 6. 主循环:使用 poll 等待事件
int one_shot_done = 0;
while (!one_shot_done) { // 循环直到一次性定时器完成
int poll_num = poll(fds, 3, -1); // -1 表示无限期等待
if (poll_num == -1) {
if (errno == EINTR) {
continue; // poll 被信号中断,继续等待
} else {
perror("poll");
break;
}
}
if (poll_num > 0) {
// 检查一次性定时器
if (fds[0].revents & POLLIN) {
s = read(tfd_one_shot, &expirations, sizeof(expirations));
if (s == sizeof(expirations)) {
printf("\n[ONE-SHOT TIMER] Expired! Expirations counted: %" PRIu64 "\n", expirations);
one_shot_done = 1; // 设置标志退出循环
// 再次查询状态,确认已停止
if (timerfd_gettime(tfd_one_shot, &curr_time) == 0) {
print_itimerspec("One-shot timer final state", &curr_time);
}
} else {
perror("read one-shot timerfd");
}
}
// 检查周期性定时器
if (fds[1].revents & POLLIN) {
s = read(tfd_periodic, &expirations, sizeof(expirations));
if (s == sizeof(expirations)) {
printf("\n[PERIODIC TIMER] Expired! Expirations counted: %" PRIu64 "\n", expirations);
// 可以在这里处理周期性任务
} else if (s == -1) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
perror("read periodic timerfd"); // 非 EAGAIN 的错误
} // EAGAIN/EWOULDBLOCK 是正常的,因为我们设置了 NONBLOCK
}
}
// 检查标准输入
if (fds[2].revents & POLLIN) {
char buf[16];
ssize_t nread = read(STDIN_FILENO, buf, sizeof(buf) - 1);
if (nread > 0) {
buf[nread] = '\0';
printf("Read from stdin: %s", buf);
if (strncmp(buf, "quit\n", 5) == 0) {
printf("Exiting due to 'quit' command.\n");
break;
}
} else if (nread == 0) {
printf("EOF on stdin (Ctrl+D). Exiting.\n");
break;
}
}
}
}
// 7. 清理资源
printf("\nClosing timerfds...\n");
close(tfd_one_shot);
close(tfd_periodic);
printf("Program finished.\n");
return 0;
}
9. 编译和运行
# 假设代码保存在 timerfd_example.c 中
gcc -o timerfd_example timerfd_example.c
# 运行程序
./timerfd_example
# 程序会启动定时器,并在终端输出信息。
# 等待 3 秒后一次性定时器触发。
# 每 2 秒周期性定时器会触发。
# 输入 'quit' 并回车可以退出。
10. 预期输出
--- Demonstrating timerfd ---
PID: 12345
Created one-shot timerfd: 3
Created periodic timerfd: 4 (non-blocking)
Set one-shot timer to expire in 3 seconds.
Set periodic timer to start immediately and repeat every 2 seconds.
One-shot timer initial state: Next expiration in 2.999999000 seconds
Interval: 0.000000000 seconds
Periodic timer initial state: Next expiration in 0.000000001 seconds
Interval: 2.000000000 seconds
Entering main loop. Waiting for timers or input (type 'quit' to exit)...
[PERIODIC TIMER] Expired! Expirations counted: 1
[PERIODIC TIMER] Expired! Expirations counted: 1
[ONE-SHOT TIMER] Expired! Expirations counted: 1
One-shot timer final state: Timer is STOPPED
[PERIODIC TIMER] Expired! Expirations counted: 1
...
# (继续每2秒打印,直到输入 'quit')
Read from stdin: quit
Exiting due to 'quit' command.
Closing timerfds...
Program finished.
11. 总结
timerfd 提供了一种现代化、同步化、且易于与事件驱动模型集成的定时器机制。
-
核心优势:将定时事件转换为文件描述符的可读事件,可以方便地融入 poll/select/epoll 等 I/O 多路复用框架。
-
避免信号:解决了传统信号定时器的异步处理复杂性和竞态条件问题。
-
灵活配置:通过 timerfd_settime 可以轻松创建一次性或周期性定时器,并支持相对和绝对时间。
-
信息丰富:read() 返回的到期次数 (uint64_t) 可以帮助处理定时器“堆积”(例如程序忙于处理其他任务导致多次到期)的情况。
掌握 timerfd 对于编写高性能、事件驱动的 Linux 应用程序(尤其是服务器)非常有帮助。