始于版本2.6.25,Linux内核提供了另一种创建定时器的API。Linux特有的timefd API,可从文件描述符中读取其所创建定时器的到期通知。因为可以使用select()、poll()和epoll()将这种文件描述符会同其他描述符一同进行监控,所以非常实用。
int timerfd_create(int clockid, int flags);
int timerfd_settime(int fd, int flags,
const struct itimerspec *new_value,
struct itimerspec *old_value);
int timerfd_gettime(int fd, struct itimerspec *curr_value);
struct timespec {
time_t tv_sec; /* Seconds */
long tv_nsec; /* Nanoseconds */
};
struct itimerspec {
struct timespec it_interval; /* Interval for periodic timer */
struct timespec it_value; /* Initial expiration */
};
这组API中的3个新系统调用第一个系统调用是timerfd_create(),它会创建一个新的定时器对象,并返回一个指代对象的文件描述符。参数clockid可以设置为CLOCK_REALTIME或CLOCK_MONOTONIC。
CLOCK_REALTIME:可设定的系统级实时时钟。用于度量真实时间。
CLOCK_MONOTONIC:不可设定的恒定态时钟。对时间的度量始于“未予规范的古曲某一时间点”,系统启动后就不会发生改变。该时钟适用于那些无法忍受系统时钟发生跳跃性变化的应用程序。LInux上,这种时钟对时间的测量始于系统启动。
对上述的两个类型做个简单测试:
#include <stdio.h>
#include <time.h>
int main()
{
struct timespec realtime_time;
struct timespec monotonic_time;
clock_gettime(CLOCK_REALTIME, &realtime_time);
clock_gettime(CLOCK_MONOTONIC, &monotonic_time);
printf("%d\n", time(NULL));
printf("realtime s:%d ns:%ld\n", realtime_time.tv_sec, realtime_time.tv_nsec);
printf("monotonic s:%d ns:%ld\n", monotonic_time.tv_sec, monotonic_time.tv_nsec);
}
加-lrt编译执行,执行./clock ; cat /proc/uptime
1604536414
realtime s:1604536414 ns:127853247
monotonic s:6039260 ns:752312307
6039260.75 360242290.69
可以看到,使用CLOCK_REALTIME输出的时间为从Epoch以来的秒数;CLOCK_MONOTONIC输出的时间为系统启动时间。
timerfd_create()的flags参数从Linux内核2.6.27版本开始支持设置为两种flags:TFD_CLOEXEC和TFD_NONBLOCK。TFD_CLOEXEC就是设置close-on-exec,TFD_NONBLOCK设置为非阻塞的。close-on-exec可以参看之前的博客close-on-exec 。
timefd_create()创建的定时器使用完毕后,应调用close()关闭相应的文件描述符,以便于内核能够释放与定时器相关的资源。
系统调用timefd_settime()可以配备(启动)或解除(停止)由文件描述符fd所指代的定时器。参数new_value为定时器指定新设置。参数old_value可用来返回定时器的前一设置。如果不关心定时器的前一设置,可将old_value置为NULL。参数flags可以是0,此时将new_value.it_value的值视为相对于调用timerfd_settime()时间点的相对时间,也可以设为TFD_TIMER_ABSTIME,将其视为一个绝对时间。
itimerspec:it_value指定了定时器首次到期的时间。如果it_interval在任一子字段非0,那么这就是一个周期性定时器,在经历了由it_value指定的初次到期后,会按这些子字段指定的频率周期性到期。如果it_interval的下属字段均为0,那么这个定时器将只到期一次。
系统调用timerfd_gettime()返回文件描述符fd所标识定时器的间隔及剩余时间。间隔以及距离下次到期的时间均返回curr_value指向的结构itimerspec中。即使是以TFD_TIMER_ABSTIME标志创建的绝对时间定时器,curr_value.it_value字段中返回的意义也会保持不变。如果返回的结构curr_value.it_value中所有字段值均为0,那么该定时器已经被解除。如果返回的结构curr_value.it_interval中两字段值均为0,那么定时器只会到期一次,到期时间在curr_value.it_value中给出。
一旦以timerfd_settime()启动了定时器,就可以从相应文件描述符中调用read()来读取定时器的到期信息,出于这一目的,传给read()的缓冲区必须足以容纳一个无符号8字节整形(uint64_t)数。在上次使用timerfd_settime()修改设置以后,或是最后一次执行read()后,如果发生了一起到多起定时器到期时间,那么read()会立即返回,且返回的缓冲区中包含了已发生的到期次数。
一个非常简单的例子:
#include <sys/timerfd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
int main()
{
struct itimerspec ts;
ts.it_value.tv_sec = 1;
ts.it_value.tv_nsec = 0;
ts.it_interval.tv_sec = 1;
ts.it_interval.tv_nsec = 0;
int fd = timerfd_create(CLOCK_MONOTONIC, 0);
if (fd == -1)
{
perror("timerfd_create");
exit(EXIT_FAILURE);
}
if (timerfd_settime(fd, 0, &ts, NULL) == -1)
{
perror("timerfd_settime");
exit(EXIT_FAILURE);
}
for (int i = 0; i < 100; i++)
{
uint64_t numExp;
ssize_t s = read(fd, &numExp, sizeof(uint64_t));
if (s != sizeof(uint64_t))
{
perror("read");
exit(EXIT_FAILURE);
}
printf("hello world\n");
}
exit(EXIT_SUCCESS);
}
该程序每隔1s打印一次hello world,打印100次后退出。