timerfd_create, timerfd_gettime, timerfd_settime系统调用及示例

好的,我们来深入学习 timerfd 相关的系统调用 (timerfd_create, timerfd_gettime, timerfd_settime)

1. 函数介绍

在 Linux 系统编程中,处理定时事件是一个常见需求。传统的定时方法包括:

data-ad-format="fluid" data-ad-layout-key="-7k+ex-4a-9w+4a">

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. 函数原型

1
2
3
4
5
6
7
8
9
10
11
#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 结构体

这个结构体用于定义定时器的时间设置:

1
2
3
4
5
6
7
8
9
10
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 系统调用中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
#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&#91;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&#91;0].fd = tfd_one_shot;
fds&#91;0].events = POLLIN;
fds&#91;1].fd = tfd_periodic;
fds&#91;1].events = POLLIN;
fds&#91;2].fd = STDIN_FILENO;
fds&#91;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&#91;0].revents & POLLIN) {
s = read(tfd_one_shot, &expirations, sizeof(expirations));
if (s == sizeof(expirations)) {
printf("\n&#91;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&#91;1].revents & POLLIN) {
s = read(tfd_periodic, &expirations, sizeof(expirations));
if (s == sizeof(expirations)) {
printf("\n&#91;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&#91;2].revents & POLLIN) {
char buf&#91;16];
ssize_t nread = read(STDIN_FILENO, buf, sizeof(buf) - 1);
if (nread > 0) {
buf&#91;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. 编译和运行

1
2
3
4
5
6
7
8
9
10
# 假设代码保存在 timerfd_example.c 中
gcc -o timerfd_example timerfd_example.c

# 运行程序
./timerfd_example
# 程序会启动定时器,并在终端输出信息。
# 等待 3 秒后一次性定时器触发。
# 每 2 秒周期性定时器会触发。
# 输入 'quit' 并回车可以退出。

10. 预期输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
--- 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)...

&#91;PERIODIC TIMER] Expired! Expirations counted: 1

&#91;PERIODIC TIMER] Expired! Expirations counted: 1

&#91;ONE-SHOT TIMER] Expired! Expirations counted: 1
One-shot timer final state: Timer is STOPPED

&#91;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 应用程序(尤其是服务器)非常有帮助。

1. 函数介绍

2. 函数原型

3. 功能

4. 参数详解

timerfd_create(int clockid, int flags)

timerfd_gettime(int fd, struct itimerspec *curr_value)

timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value)

struct itimerspec 结构体

5. 如何使用定时器文件描述符

6. 错误码 (errno)

7. 相似函数或关联函数

8. 示例代码

9. 编译和运行

10. 预期输出

11. 总结

data-ad-format="auto" data-full-width-responsive="true">