目录
提问:
- 如何使用timerfd API?
- 什么时候需要使用?
系统调用:
1、创建
#include <sys/timerfd.h>
int timerfd_create(int clockid, int flags)
成功返回文件描述符,失败返回-1 。
参数clockid
1、设置为CLOCK_REALTIME 可设定的系统级实时时钟,用于度量真实时间。
2、设置为CLOCK_MONOTONIC 不可设定的恒定态时钟,始于“未予规范的过去某一时间点”,系统启动后就不会改变。该时钟适用于那些无法容忍系统时钟发生跳跃性变化(如手工改变了系统时间)的应用程序。
参数flags 一般设置为0,Linux内核2.6.27版本后支持下面两种flags标志
1、TFD_CLOEXEC 为新文件描述符设置运行时关闭标志
2、TFD_NONBLOCK 为底层的打开文件描述设置为非阻塞。
- 定时器使用完毕后,应调用close()关闭相应的文件描述符,以便于内核释放与定时器相关的资源。
2、设置
#include <sys/timerfd.h>
int timerfd_settime(int fd, int flags, const struct itimerspec * new_value,
struct itimerspec * old_value);
参数new_value 为定时器指定新设置。
参数old_value 可用来返回定时器的前一设置
参数flags
1、可以为0,将new_value.it_value的值视为相对于调用timerfd_settime()时间点的相对时间。
2、可以为TFD_TIMER_ABSTIME,将其视为一个绝对时间(从时钟值0开始测量)。一旦时钟超过了这一时间,定时器会立即到期。
结构体:
struct itimerspec
{
struct timespec it_interval; //定时器的周期性到期时间
struct timespec it_value; //第一次到期时间
};
struct timespec
{
time_t tv_sec; //秒
long tv_nsec; //纳秒 = 10^(-9) 秒
};
//秒 毫秒 微秒 纳秒
- 如果it_interval的下属字段均为0,那么这个定时器将只到期一次。
3、获取定时器时间间隔和剩余时间
#include <sys/timerfd.h>
int timerfd_gettime(int fd, struct itimerspec * curr_value);
成功返回0,失败返回-1 。
4、timerfd与fork()及exec()之间的交互
- 调用fork()期间,子进程会继承timerfd_create()所创建文件描述符的拷贝。这些描述符与父进程的对应描述符均指代相同的定时器对象,任一进程都可读取定时器的到期信息。
- timerfd_create()创建的文件描述符能够跨越exec()得以保存(除非将文件描述符设置为运行时关闭),已配备的定时器在exec()之后会继续生成到期通知。
5、通过read()从timefd文件描述符读取到期信息
- 一旦用timerfd_create()启动了定时器,就可以从相应文件描述符调用read()来读取定时器到期信息。出于这一目的,传给read()的缓冲区必须足以容纳一个无符号8字节整形数。
- 在上次使用timerfd_settime()修改设置以后,或是最后一次执行read()后,如果发生了一起到多起定时器到期事件,那么read()会立即返回,且返回的缓冲区包含了已经发生的到期次数。如果无定时器到期,read()会一直阻塞直至产生下一个到期。可以就将文件描述符设置为O_NONBLOCK标志,这时读操作是非阻塞式的,且如果没有定时器到期,则返回错误,并将errno值置为EAGAIN。
- 可以利用select()、poll()、epoll()对timefd文件描述符进行监控。如果定时器到期,会将对应的文件描述符标记为可读。
测试代码:
#include <sys/timerfd.h>
#include <time.h>
#include <unistd.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
int main()
{
struct itimerspec ts;
struct timespec start, now;
int secs, nanosecs;
memset(&ts, 0, sizeof(struct itimerspec));
ts.it_value.tv_sec = 0;
ts.it_value.tv_nsec = 10000000; //10ms一次
ts.it_interval.tv_sec = 0;
ts.it_interval.tv_nsec = 5000000; //5ms一次
int fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK); //设置为非阻塞
if(-1 == fd)
perror("timerfd_create");
if(timerfd_settime(fd, 0, &ts, NULL) == -1)
perror("timefd_settime");
if(clock_gettime(CLOCK_MONOTONIC, &start) == -1)
perror("clock_gettime");
ssize_t s;
uint64_t numExp, totalExp, maxExp = 10;
for(totalExp = 0; totalExp < maxExp;)
{
s = read(fd, &numExp, sizeof(uint64_t));
if(s != sizeof(uint64_t))
perror("read");
totalExp += numExp;
if(clock_gettime(CLOCK_MONOTONIC, &now) == -1)
perror("clock_gettime");
secs = now.tv_sec - start.tv_sec;
nanosecs = now.tv_nsec - start.tv_nsec;
if(nanosecs < 0)
{
secs--;
nanosecs += 1000000000;
}
printf("%d.%03d: expirations read: %lu; total=%lu\n",
secs, (nanosecs + 500000)/1000000,(unsigned long)numExp,
(unsigned long)totalExp);
}
return 0;
}
何时使用?
- 进程可以使用设置定时器,以便在经历指定的一段实际时间后收到信号通知。
- 为系统调用的阻塞设定时间上限。
- 应用程序如需暂停执行一段特定间隔的实际时间,可以使用各种合适的休眠函数。然后通过 定时器 来唤醒。
参考资料:
- Linux/UNIX系统编程手册(上册)