timerfd是linux提供的定時器機制,基於文件描述符,定時器精度最高可達納秒級別,接口包括定時器創建、啓動定時器、關閉定時器和刪除定時器。下面介紹一下timerfd API接口和一個結合epoll使用的定時器demo。
1. 創建定時器
#include <sys/timerfd.h>
/*
* 功能 : 創建定時器
* 返回值:成功返回定時器文件描述符,失敗返回-1
* 參數: clockid可以是CLOCK_REALTIME(實時時鐘)或者CLOCK_MONOTONIC(遞增時鐘),實時時鐘可以被系統時間改變,後者不會。
* 如果這裏使用實時時鐘,當手動更改系統時間定時器也會受影響,而遞增時鐘則只受設置的時間值影響。
* flags : 可選項包括TFD_NONBLOCK(非阻塞)和TFD_CLOEXEC,阻塞指的是當定時器未超時的時候,如果調用read(timerfd)會阻塞直
* 到定時器超時,如果設置TFD_NONLOCK,則會直接返回並返回-1. 這與套接字描述符類似。
*/
int timerfd_create(int clockid, int flags);
2. 啓動和停止定時器
/*
* 功能 定時器啓動和關閉
* 參數: fd: 定時器描述符
* flags: 0 或者TFD_TIMER_ABSTIME,0代表相對時間,即相對於當前時間多少,後者是絕對時間。
* new_value: 當new_value.it_value非0時,用於設置定時器第一次超時時間,爲0代表停止定時器
new_value.it_interval:表示第一次超時後下一次超時的時間,爲0代表定時器只超時一次
* old_value: 如果不爲NULL,則用來存儲當前時間。
int timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value);
3. 關閉定時器
/*
* 功能: 和普通描述符一樣,用完後使用close釋放
* 參數:timerfd爲timerfd_create()創建的定時器描述符
*/
close(timerfd);
4. 時間結構體
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 */
};
下面是一個結合epoll使用的定時器demo,創建一個每隔2s超時的定時器並加入到epoll監聽隊列中,每當定時器到期超時時產生一個讀事件,之後可以去執行相應的定時器回調函數。
/*
* Description : linux 應用層編程之定時器timerfd的使用
* Date :20180611
* Author :mason
* Mail : [email protected]
*
*/
#include <sys/timerfd.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include<errno.h>
#include <time.h>
#define TIME_MAX 2
#define log(fmt, arg...) printf(""fmt, ##arg)
void main() {
int tfd; //定時器描述符
int efd; //epoll描述符
int fds, ret;
uint64_t value;
struct epoll_event ev, *evptr;
struct itimerspec time_intv; //用來存儲時間
tfd = timerfd_create(CLOCK_MONOTONIC, 0); //創建定時器
if(tfd == -1) {
log("create timer fd fail \r\n");
return ;
}
time_intv.it_value.tv_sec = TIME_MAX; //設定2s超時
time_intv.it_value.tv_nsec = 0;
time_intv.it_interval.tv_sec = time_intv.it_value.tv_sec; //每隔2s超時
time_intv.it_interval.tv_nsec = time_intv.it_value.tv_nsec;
log("timer start ...\n");
timerfd_settime(tfd, 0, &time_intv, NULL); //啓動定時器
efd = epoll_create1(0); //創建epoll實例
if (efd == -1) {
log("create epoll fail \r\n");
close(tfd);
return ;
}
evptr = (struct epoll_event *)calloc(1, sizeof(struct epoll_event));
if (evptr == NULL) {
log("epoll event calloc fail \r\n");
close(tfd);
close(efd);
return ;
}
ev.data.fd = tfd;
ev.events = EPOLLIN; //監聽定時器讀事件,當定時器超時時,定時器描述符可讀。
epoll_ctl(efd, EPOLL_CTL_ADD, tfd, &ev); //添加到epoll監聽隊列中
while(1) {
fds = epoll_wait(efd, evptr, 1, -1); //阻塞監聽,直到有事件發生
if(evptr[0].events & EPOLLIN){
ret = read(evptr->data.fd, &value, sizeof(uint64_t));
if (ret == -1)
log("read return -1, errno :%d \r\n", errno);
else
log("*** timer up *** \n");
}
}
return ;
}
實驗截圖:
代碼路徑:
[email protected]:FuYuanDe/timerfd.git
參考資料:
https://linux.die.net/man/2/timerfd_create