Linux高级--3.2.4.1 Linux timer的系统调用方案
timerfd_create
是 Linux 系统中用于创建定时器的系统调用,它为每个定时器提供一个文件描述符,通过 epoll
或者 select
等机制来监听定时器的超时事件。该系统调用为定时器提供了方便的管理方式,特别适用于需要精确控制定时事件的场景。
1. timerfd_create
函数
timerfd_create
用于创建一个定时器文件描述符,该文件描述符可用于等待定时器超时。定时器的超时时间和行为通过 timerfd_settime
来配置。
函数原型:
#include <sys/timerfd.h>
int timerfd_create(int clockid, int flags);
参数说明:
-
clockid
:- 该参数指定所使用的时钟源,可以是以下几种类型之一:
CLOCK_REALTIME
:系统的实时时钟(墙钟时间)。CLOCK_MONOTONIC
:单调时钟(时间不断增加,不会跳跃)。CLOCK_PROCESS_CPUTIME_ID
:进程的 CPU 时间。CLOCK_THREAD_CPUTIME_ID
:线程的 CPU 时间。
通常使用
CLOCK_REALTIME
或CLOCK_MONOTONIC
。 - 该参数指定所使用的时钟源,可以是以下几种类型之一:
-
flags
:- 用于指定定时器的一些特性,通常为 0,表示使用默认行为。可以使用以下标志:
TFD_CLOEXEC
:设置文件描述符在执行exec()
时自动关闭。TFD_NONBLOCK
:设置非阻塞模式。如果设置该标志,则read()
时即使定时器未到期也不会阻塞。
- 用于指定定时器的一些特性,通常为 0,表示使用默认行为。可以使用以下标志:
返回值:
- 成功时返回一个定时器文件描述符(非负整数)。
- 失败时返回
-1
,并设置errno
以表示错误原因。
2. timerfd_settime
函数
timerfd_settime
用于设置或修改定时器的超时值。通过该函数,可以启动、重新设置或取消定时器。
函数原型:
#include <sys/timerfd.h>
int timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value);
参数说明:
-
fd
:- 定时器文件描述符,通常是通过
timerfd_create
创建的。
- 定时器文件描述符,通常是通过
-
flags
:- 该参数用于指定设置定时器的方式,常用的标志是:
TIMER_ABSTIME
:如果设置该标志,定时器的绝对时间是new_value
中的时间,而不是相对时间(即从当前时间开始)。否则,new_value
表示相对时间。
- 该参数用于指定设置定时器的方式,常用的标志是:
-
new_value
:- 定时器的新超时时间,类型为
struct itimerspec
。该结构包含两个字段:it_value
:定时器的初始超时值,即定时器首次超时的时间。it_interval
:定时器的周期。如果为零,定时器将只触发一次;如果不为零,则定时器会每隔it_interval
触发一次。
struct itimerspec
的定义如下:struct itimerspec { struct timespec it_value; // 定时器的初始超时值 struct timespec it_interval; // 定时器的周期 };
struct timespec
定义如下:struct timespec { time_t tv_sec; // 秒 long tv_nsec; // 纳秒 };
- 定时器的新超时时间,类型为
-
old_value
:- 可选参数,用于保存定时器设置之前的值。如果不关心旧值,可以传入
NULL
。
- 可选参数,用于保存定时器设置之前的值。如果不关心旧值,可以传入
返回值:
- 成功时返回
0
。 - 失败时返回
-1
,并设置errno
。
3. read
函数(读取定时器超时事件)
当定时器超时时,文件描述符变为可读,调用 read()
函数来读取超时事件并执行相应的操作。
函数原型:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
参数说明:
-
fd
:- 定时器文件描述符。
-
buf
:- 用于存储读取数据的缓冲区。对于
timerfd
,读取时会返回一个 8 字节的整数,表示定时器被触发的次数。
- 用于存储读取数据的缓冲区。对于
-
count
:- 要读取的字节数,通常为 8。
返回值:
- 返回实际读取的字节数。如果定时器没有超时,则读取操作会阻塞。
- 返回
-1
表示失败,并设置errno
。
4. 代码示例
下面是一个简单的示例,演示如何使用 timerfd_create
、timerfd_settime
和 read
创建一个定时器并等待其超时。
代码示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/timerfd.h>
#include <sys/epoll.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
// 宏控制使用绝对时间还是相对时间
// #define USE_ABSTIME // 如果需要使用绝对时间,取消注释此行
int main() {
// 创建定时器文件描述符
int tfd = timerfd_create(CLOCK_REALTIME, 0);
if (tfd == -1) {
perror("timerfd_create");
exit(EXIT_FAILURE);
}
// 设置定时器超时时间为 5 秒后触发
struct itimerspec new_value;
new_value.it_value.tv_sec = 5; // 初始超时时间 5 秒
new_value.it_value.tv_nsec = 0;
new_value.it_interval.tv_sec = 0; // 不重复
new_value.it_interval.tv_nsec = 0;
// 使用宏控制设置 timerfd 的时间类型
#ifdef USE_ABSTIME
int flags = TIMER_ABSTIME; // 使用绝对时间
#else
int flags = 0; // 使用相对时间
#endif
if (timerfd_settime(tfd, flags, &new_value, NULL) == -1) {
perror("timerfd_settime");
exit(EXIT_FAILURE);
}
// 创建 epoll 实例
int epfd = epoll_create1(0);
if (epfd == -1) {
perror("epoll_create1");
exit(EXIT_FAILURE);
}
// 将定时器文件描述符添加到 epoll 中
struct epoll_event ev;
ev.events = EPOLLIN; // 定时器可读(超时)
ev.data.fd = tfd;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, tfd, &ev) == -1) {
perror("epoll_ctl");
exit(EXIT_FAILURE);
}
// 等待定时器事件触发
printf("Waiting for timer to expire...\n");
while (1) {
struct epoll_event events[1];
int n = epoll_wait(epfd, events, 1, -1);
if (n == -1) {
perror("epoll_wait");
exit(EXIT_FAILURE);
}
if (events[0].data.fd == tfd) {
uint64_t expirations;
ssize_t s = read(tfd, &expirations, sizeof(expirations));
if (s != sizeof(expirations)) {
perror("read");
exit(EXIT_FAILURE);
}
printf("Timer expired %llu times\n", expirations);
break; // 退出循环
}
}
close(tfd);
close(epfd);
return 0;
}
代码解释
-
获取当前时间:
- 通过
clock_gettime(CLOCK_REALTIME, ¤t_time)
获取当前的系统时间,返回值是struct timespec
类型,包含秒数 (tv_sec
) 和纳秒数 (tv_nsec
)。
- 通过
-
计算绝对时间:
- 在 绝对时间 模式下,我们要将当前时间与定时器的延迟时间相加。假设定时器设置为 5 秒后超时,
new_value.it_value.tv_sec
就是当前时间的秒数加上 5 秒。 new_value.it_value.tv_nsec
保持与当前的纳秒数一致。
- 在 绝对时间 模式下,我们要将当前时间与定时器的延迟时间相加。假设定时器设置为 5 秒后超时,
-
设置定时器:
flags
根据宏USE_ABSTIME
的定义来决定是否使用TIMER_ABSTIME
标志。这样,如果启用绝对时间模式,定时器的超时将是一个绝对时间戳。- 如果没有启用
USE_ABSTIME
,则使用相对时间,new_value.it_value
就是定时器的延迟时间(5 秒)。
-
等待定时器超时并触发:
- 使用
epoll_wait
等待定时器事件触发,并通过read
函数获取定时器到期的次数。
- 使用
总结
- 对于绝对时间模式,必须基于当前时间计算出定时器的绝对时间戳。
- 如果使用相对时间,直接设置延迟时间即可。
- 通过宏
USE_ABSTIME
来方便地选择使用绝对时间还是相对时间。https://github.com/0voice