好的,我们来深入学习 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. 函数原型
1 2 3 4 5 6 7 8 9 10 11
| #include <sys/timerfd.h>
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:
指定定时器基于哪个时钟源。常用的有:
flags:
用于修改定时器行为的标志位。常用的有:
返回值:
timerfd_gettime(int fd, struct itimerspec *curr_value)
fd:
curr_value:
返回值:
成功: 返回 0。
失败: 返回 -1,并设置 errno。
timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value)
fd:
flags:
控制 new_value 中时间的解释方式。主要标志:
new_value:
old_value:
返回值:
成功: 返回 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: 指定首次超时的时间。
it_interval: 指定定时器到期后,重复的间隔时间。
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 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/timerfd.h> #include <poll.h> #include <string.h> #include <errno.h> #include <inttypes.h>
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]; uint64_t expirations; ssize_t s;
printf("--- Demonstrating timerfd ---\n"); printf("PID: %d\n", getpid());
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);
one_shot_time.it_value.tv_sec = 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");
periodic_time.it_value.tv_sec = 0; periodic_time.it_value.tv_nsec = 1; periodic_time.it_interval.tv_sec = 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");
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); }
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");
int one_shot_done = 0; while (!one_shot_done) { int poll_num = poll(fds, 3, -1); if (poll_num == -1) { if (errno == EINTR) { continue; } 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"); } } }
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; } } } }
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)...
&
&
& One-shot timer final state: Timer is STOPPED
& ...
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. 总结
相关阅读