好的,我们来深入学习 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:
指定定时器基于哪个时钟源。常用的有:
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 // 启用 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. 编译和运行 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)... [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 应用程序(尤其是服务器)非常有帮助。
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. 总结