timerfd 是 Linux 系統呼叫,允許用 file descriptor 的方式來操作 timer,並可搭配原本的 I/O Multiplexor 機制。一旦 timerfd_create() 將時間轉為 fd,後者在 timeout 時即可 read,這樣我們透過 epoll 一類的系統呼叫,就能處理週期性任務。
timerfd_create
將計時器轉換為檔案,一遇到超時,該特別的檔案即可讀取,於是就可搭配 I/O multiplexor,用一致的方式來處理 I/O 事件和超時事件,參見高效 Web 伺服器開發timeout
的精度較低範例程式碼:
#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 的示範:
#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;
}
練習題:
回歸第一手資料,透過反思 C 語言程式設計的細節,重新學習電腦原理
Jul 21, 2025自 Linux 核心原始程式碼編譯出 User-mode Linux 並運用工具來建構實驗所需的檔案系統
Jul 20, 2025系統的 throughput 是評斷排程器優劣的重要效能
Jul 19, 2025無論是作業系統核心、C 語言函式庫內部、程式開發框架,到應用程式,都不難見到 linked list 的身影,包含多種針對效能和安全議題所做的 linked list 變形,又還要考慮到應用程式的泛用性 (generic programming),是很好的進階題材。
Jul 17, 2025or
By clicking below, you agree to our terms of service.
New to HackMD? Sign up