---
tags: LINUX KERNEL, LKI
---
# 透過 timerfd 處理週期性任務
## timerfd
[timerfd](http://man7.org/linux/man-pages/man2/timerfd_settime.2.html) 是 Linux 系統呼叫,允許用 file descriptor 的方式來操作 timer,並可搭配原本的 I/O Multiplexor 機制。一旦 timerfd_create() 將時間轉為 fd,後者在 timeout 時即可 read,這樣我們透過 epoll 一類的系統呼叫,就能處理週期性任務。
> [Linux 核心設計: 檔案系統概念及實作手法](https://hackmd.io/@sysprog/linux-file-system)
[timerfd](http://man7.org/linux/man-pages/man2/timerfd_settime.2.html) 和 [select](https://man7.org/linux/man-pages/man2/select.2.html) 的timeout 區別:
- `timerfd_create` 將計時器轉換為檔案,一遇到超時,該特別的檔案即可讀取,於是就可搭配 I/O multiplexor,用一致的方式來處理 I/O 事件和超時事件,參見[高效 Web 伺服器開發](https://hackmd.io/@sysprog/fast-web-server)
- [select](https://man7.org/linux/man-pages/man2/select.2.html) 系統呼叫的 `timeout` 的精度較低
範例程式碼:
```cpp
#include <stdint.h> /* Definition of uint64_t */
#include <stdio.h>
#include <stdlib.h>
#include <sys/timerfd.h>
#include <time.h>
#include <unistd.h>
#define handle_error(msg) \
do { \
perror(msg); \
exit(EXIT_FAILURE); \
} while (0)
static void print_elapsed_time(void)
{
static struct timespec start;
struct timespec curr;
static int first_call = 1;
int secs, nsecs;
if (first_call) {
first_call = 0;
if (clock_gettime(CLOCK_MONOTONIC, &start) == -1)
handle_error("clock_gettime");
}
if (clock_gettime(CLOCK_MONOTONIC, &curr) == -1)
handle_error("clock_gettime");
secs = curr.tv_sec - start.tv_sec;
nsecs = curr.tv_nsec - start.tv_nsec;
if (nsecs < 0) {
secs--;
nsecs += 1000000000;
}
printf("%d.%03d: ", secs, (nsecs + 500000) / 1000000);
}
int main(int argc, char *argv[])
{
struct itimerspec new_value;
int max_exp, fd;
struct timespec now;
uint64_t exp, tot_exp;
ssize_t s;
if ((argc != 2) && (argc != 4)) {
fprintf(stderr, "%s init-secs [interval-secs max-exp]\n", argv[0]);
exit(EXIT_FAILURE);
}
if (clock_gettime(CLOCK_REALTIME, &now) == -1)
handle_error("clock_gettime");
/* Create a CLOCK_REALTIME absolute timer with initial
expiration and interval as specified in command line */
new_value.it_value.tv_sec = now.tv_sec + atoi(argv[1]);
new_value.it_value.tv_nsec = now.tv_nsec;
if (argc == 2) {
new_value.it_interval.tv_sec = 0;
max_exp = 1;
} else {
new_value.it_interval.tv_sec = atoi(argv[2]);
max_exp = atoi(argv[3]);
}
new_value.it_interval.tv_nsec = 0;
fd = timerfd_create(CLOCK_REALTIME, 0);
if (fd == -1) handle_error("timerfd_create");
if (timerfd_settime(fd, TFD_TIMER_ABSTIME, &new_value, NULL) == -1)
handle_error("timerfd_settime");
print_elapsed_time();
printf("timer started\n");
for (tot_exp = 0; tot_exp < max_exp;) {
s = read(fd, &exp, sizeof(uint64_t));
if (s != sizeof(uint64_t)) handle_error("read");
tot_exp += exp;
print_elapsed_time();
printf("read: %llu; total=%llu\n", (unsigned long long) exp,
(unsigned long long) tot_exp);
}
exit(EXIT_SUCCESS);
}
```
預期輸出: (參數: `1 1 10`)
```
0.000: timer started
1.000: read: 1; total=1
2.000: read: 1; total=2
3.000: read: 1; total=3
4.000: read: 1; total=4
5.000: read: 1; total=5
6.000: read: 1; total=6
7.000: read: 1; total=7
8.000: read: 1; total=8
9.000: read: 1; total=9
10.000: read: 1; total=10
```
搭配 epoll 的示範:
```cpp
#include <pthread.h>
#include <stdio.h>
#include <sys/epoll.h>
#include <sys/timerfd.h>
#include <time.h>
#include <unistd.h>
static void add_event(int epoll_fd, int fd, int state)
{
struct epoll_event ev = {
.events = state,
.data.fd = fd,
};
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev);
return;
}
static int calc_proc_time(int *start)
{
struct timespec end;
clock_gettime(CLOCK_REALTIME, &end);
if (start)
return end.tv_sec * 1000 + end.tv_nsec / 1000000 - *start;
return end.tv_sec * 1000 + end.tv_nsec / 1000000;
}
static void *worker(void *user)
{
int epoll_fd = *(int *) user;
struct epoll_event events[1] = {0};
while (1) {
int ms = calc_proc_time(NULL);
int fire_events = epoll_wait(epoll_fd, events, 1, -1);
ms = calc_proc_time(&ms);
if (fire_events > 0) {
printf("time out: %d ms\n", ms);
uint64_t exp;
ssize_t size = read(events[0].data.fd, &exp, sizeof(uint64_t));
if (size != sizeof(uint64_t))
perror("read error");
}
}
return NULL;
}
int timer_update(int timer_fd, int ms)
{
struct itimerspec its = {
.it_interval = {.tv_sec = 2, .tv_nsec = 0},
.it_value.tv_sec = ms / 1000,
.it_value.tv_nsec = (ms % 1000) * 1000 * 1000,
};
if (timerfd_settime(timer_fd, 0, &its, NULL) < 0)
return -1;
printf("timer update: %d\n", ms);
return 0;
}
int main()
{
/* create epoll */
int epoll_fd = epoll_create(1);
/* create timer */
int timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
/* add timer fd to epoll monitor event */
add_event(epoll_fd, timer_fd, EPOLLIN);
/* create thread to monitor */
pthread_t tid;
pthread_create(&tid, NULL, &worker, (void *) &epoll_fd);
timer_update(timer_fd, 1000);
while (1)
usleep(1000000);
return 0;
}
```
練習題:
* [2020q1 第 15 週測驗題](https://hackmd.io/@sysprog/linux2020-quiz15)