Master Linux Timers: From alarm() to timerfd – Choose the Right One
This article walks through the evolution of Linux timers—from the simple alarm() function, through setitimer() and POSIX timer APIs, to the modern timerfd interface—explaining their APIs, code examples, advantages, limitations, performance trade‑offs, and practical selection guidance for different projects.
First generation: classic alarm()
The original Linux timer is the alarm() function, which schedules a SIGALRM after a given number of seconds.
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
void timeout_handler(int sig) {
printf("Time's up!
");
}
int main() {
signal(SIGALRM, timeout_handler);
alarm(5); // trigger after 5 seconds
pause(); // wait for signal
return 0;
}Drawbacks: only second granularity, only one alarm per process (later calls overwrite earlier ones), and it interferes with system calls such as sleep() and read() that can be interrupted by the signal.
Typical pitfall
Using several alarm() calls in a multi‑module project causes timers to overwrite each other, leading to unexpected behavior.
Second generation: setitimer()
setitimer()adds microsecond precision and periodic triggering. It supports three timer types: ITIMER_REAL, ITIMER_VIRTUAL, and ITIMER_PROF.
#include <sys/time.h>
#include <signal.h>
#include <stdio.h>
void timer_handler(int sig) {
static int count = 0;
printf("Timer fired %d times!
", ++count);
}
int main() {
struct itimerval timer;
signal(SIGALRM, timer_handler);
// first trigger after 1 second, then every 0.5 seconds
timer.it_value.tv_sec = 1;
timer.it_value.tv_usec = 0;
timer.it_interval.tv_sec = 0;
timer.it_interval.tv_usec = 500000; // 500 ms
setitimer(ITIMER_REAL, &timer, NULL);
while (1) pause();
return 0;
}Advantages: microsecond precision, periodic mode, three timer categories. New issues: still signal‑based (with associated pitfalls), only one ITIMER_REAL per process, and signals can be lost while a handler runs.
Third generation: POSIX timers
POSIX timers created with timer_create() provide nanosecond precision and allow multiple independent timers. Notification can be a signal, a thread, or none (polling).
#include <time.h>
#include <signal.h>
#include <stdio.h>
timer_t timerid;
int timer_count = 0;
void timer_handler(int sig, siginfo_t *si, void *uc) {
timer_t *tidp = si->si_value.sival_ptr;
printf("POSIX timer %p fired %d times!
", tidp, ++timer_count);
}
int main() {
struct sigevent sev;
struct itimerspec its;
struct sigaction sa;
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = timer_handler;
sigemptyset(&sa.sa_mask);
sigaction(SIGUSR1, &sa, NULL);
sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_signo = SIGUSR1;
sev.sigev_value.sival_ptr = &timerid;
if (timer_create(CLOCK_REALTIME, &sev, &timerid) == -1) {
perror("timer_create failed");
return -1;
}
its.it_value.tv_sec = 1; its.it_value.tv_nsec = 0;
its.it_interval.tv_sec = 0; its.it_interval.tv_nsec = 500000000; // 500 ms
timer_settime(timerid, 0, &its, NULL);
printf("POSIX timer started, press Ctrl+C to exit
");
while (1) pause();
timer_delete(timerid);
return 0;
}Benefits: multiple timers, nanosecond precision, flexible notification, ability to pass extra data via siginfo_t. Drawbacks: still relies on signals, more complex code, and portability varies (full support on recent Linux, limited on macOS/BSD, may require linking with -lrt on older glibc).
Fourth generation: timerfd
Since Linux 2.6.25, timerfd turns a timer into a file descriptor, enabling integration with select(), poll(), or epoll().
#include <sys/timerfd.h>
#include <unistd.h>
#include <stdio.h>
#include <stdint.h>
int main() {
int timer_fd = timerfd_create(CLOCK_REALTIME, 0);
if (timer_fd == -1) { perror("timerfd_create"); return -1; }
struct itimerspec timer_spec;
timer_spec.it_value.tv_sec = 2; timer_spec.it_value.tv_nsec = 0; // first trigger after 2 s
timer_spec.it_interval.tv_sec = 1; timer_spec.it_interval.tv_nsec = 0; // then every 1 s
timerfd_settime(timer_fd, 0, &timer_spec, NULL);
printf("Timer started, waiting for expirations...
");
for (int i = 0; i < 5; ++i) {
uint64_t expirations;
ssize_t s = read(timer_fd, &expirations, sizeof(expirations));
if (s == sizeof(expirations))
printf("Timer fired %llu times
", expirations);
}
close(timer_fd);
return 0;
}Key advantages: represented as a file descriptor, nanosecond precision, unlimited number of timers (limited only by file‑descriptor limits), no signal handling, and seamless use in event‑driven programs.
Example combining two timerfd objects with epoll for high‑performance monitoring:
#include <stdio.h>
#include <unistd.h>
#include <sys/timerfd.h>
#include <sys/epoll.h>
#include <stdint.h>
int main() {
int timerfd1 = timerfd_create(CLOCK_REALTIME, 0);
int timerfd2 = timerfd_create(CLOCK_REALTIME, 0);
int epollfd = epoll_create1(0);
struct epoll_event ev, events[10];
ev.events = EPOLLIN;
ev.data.fd = timerfd1; epoll_ctl(epollfd, EPOLL_CTL_ADD, timerfd1, &ev);
ev.data.fd = timerfd2; epoll_ctl(epollfd, EPOLL_CTL_ADD, timerfd2, &ev);
struct itimerspec its;
// timerfd1: 1‑second interval
its.it_value.tv_sec = 1; its.it_value.tv_nsec = 0;
its.it_interval.tv_sec = 1; its.it_interval.tv_nsec = 0;
timerfd_settime(timerfd1, 0, &its, NULL);
// timerfd2: 2‑second interval
its.it_value.tv_sec = 2; its.it_interval.tv_sec = 2;
timerfd_settime(timerfd2, 0, &its, NULL);
printf("High‑performance timer system started!
");
while (1) {
int nfds = epoll_wait(epollfd, events, 10, -1);
for (int n = 0; n < nfds; ++n) {
uint64_t exp;
read(events[n].data.fd, &exp, sizeof(exp));
if (events[n].data.fd == timerfd1)
printf("⚡ Fast timer (1 s) triggered
");
else if (events[n].data.fd == timerfd2)
printf("🐌 Slow timer (2 s) triggered
");
}
}
return 0;
}Practical decision guide
Simple one‑off delay : use alarm() – minimal code, sufficient for tiny scripts.
Periodic timer with modest precision : setitimer() – works on all Unix‑like systems.
Multiple timers, nanosecond precision, portable POSIX : timer_create() / timer_settime().
Modern Linux server or high‑concurrency network program : timerfd_create() combined with epoll() – best performance and clean integration.
Performance comparison
alarm(): only one global timer; new calls overwrite old ones. setitimer(): one per type (REAL, VIRTUAL, PROF) – up to three concurrent timers.
POSIX timers: multiple instances (hundreds typical), but signal handling overhead. timerfd(): many instances limited only by file‑descriptor limits; no signals.
Compatibility notes
alarm()/ setitimer(): universally available on Unix.
POSIX timers: standardized but support varies; Linux may need -lrt on older glibc. timerfd(): Linux‑only (kernel 2.6.25+).
Advanced tips
High‑precision timers: use CLOCK_MONOTONIC with timerfd_create() to avoid jumps caused by system‑time changes.
One‑shot timers: set it_interval fields to zero.
Timer manager pattern (simplified):
typedef struct {
int fd;
void (*callback)(void *data);
void *data;
} Timer;
Timer* create_timer(int interval_ms, void (*cb)(void*), void *data);
void destroy_timer(Timer *t);
void handle_timer_event(Timer *t);Encapsulating timer creation and callback handling keeps large projects tidy.
Conclusion
The evolution from alarm() to timerfd() mirrors broader trends in system programming: moving from simple, global, signal‑driven APIs toward fine‑grained, file‑descriptor‑based, event‑driven designs that scale in high‑performance, concurrent applications.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Liangxu Linux
Liangxu, a self‑taught IT professional now working as a Linux development engineer at a Fortune 500 multinational, shares extensive Linux knowledge—fundamentals, applications, tools, plus Git, databases, Raspberry Pi, etc. (Reply “Linux” to receive essential resources.)
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
