--- 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)