POSIX Timers and timer_create
POSIX per-process timers are the standardized userspace API for “notify me when a chosen clock reaches a chosen value, optionally on a repeating interval.” The flow is three calls:
timer_create(clockid, &sigevent, &timerid)allocates a timer armed against any clock — wall-clock (CLOCK_REALTIME), monotonic (CLOCK_MONOTONIC), boot-inclusive (CLOCK_BOOTTIME), or a CPU-time clock (CLOCK_PROCESS_CPUTIME_ID,CLOCK_THREAD_CPUTIME_ID) — and thestruct sigeventit is handed decides how expiry is reported;timer_settime(timerid, flags, &new, &old)arms or disarms it, with an initial expiration (it_value) and an optional reload interval (it_interval); andtimer_delete(timerid)frees it (per timer_create(2)). Three notification modes are available throughsigevent:SIGEV_SIGNALdelivers a real-time signal (carrying a caller-chosensi_value),SIGEV_THREADruns a callback “as if” in a new thread (a glibc construction, not a kernel feature), andSIGEV_NONEdelivers nothing — you poll progress withtimer_gettime. Underneath, the kernel backs clock-based timers with hrtimers and CPU-time timers with the cputimer accounting checked off the scheduler tick — two completely different engines hidden behind one uniform API.
Mental Model
A POSIX timer is a small kernel object (struct k_itimer) that ties together three things: which clock it watches, when it should fire (and how often), and how it tells you. The “which clock” choice silently selects the entire backing engine — pick a wall-clock and you get an hrtimer on a red-black tree; pick a CPU-time clock and you get a threshold compared against accumulated CPU time at every tick. The “how it tells you” choice is the sigevent, and it is genuinely orthogonal to the clock: any clock can deliver via signal, via glibc thread, or via nothing-at-all.
flowchart TB TC["timer_create(clockid, sigevent, &id)"] TC --> KI["struct k_itimer<br/>(kclock + sigevent + state)"] KI -->|"clockid = REALTIME /<br/>MONOTONIC / BOOTTIME"| HR["backed by hrtimer<br/>(red-black tree, ns deadline)"] KI -->|"clockid = PROCESS_CPUTIME /<br/>THREAD_CPUTIME"| CPU["backed by cputimer<br/>(checked on scheduler tick)"] TS["timer_settime(id, flags, new, old)"] -->|"it_value (initial)<br/>it_interval (reload)<br/>TIMER_ABSTIME?"| KI HR -->|"expiry"| NOTIFY{"sigev_notify"} CPU -->|"threshold crossed"| NOTIFY NOTIFY -->|SIGEV_SIGNAL| SIG["queue signal<br/>+ si_value, si_overrun"] NOTIFY -->|SIGEV_THREAD| TH["glibc helper:<br/>spawn/notify thread"] NOTIFY -->|SIGEV_NONE| POLL["nothing<br/>poll via timer_gettime"]
The anatomy of a POSIX timer: clock choice selects the engine, sigevent selects the notification. What it shows: timer_create builds a k_itimer; the clockid routes it to an hrtimer (wall/monotonic clocks) or the cputimer accounting (CPU-time clocks); timer_settime arms it with an initial value and reload interval; on expiry the sigevent mode decides whether a signal is queued, a glibc thread is notified, or nothing happens. The insight to take: the clock and the notification are independent axes — and the kernel runs two entirely different timer engines behind the single timer_create facade.
Creating a Timer — timer_create
The signature is (timer_create(2)):
#include <signal.h>
#include <time.h>
int timer_create(clockid_t clockid,
struct sigevent *restrict sevp,
timer_t *restrict timerid);The clockid names the clock the timer watches (see Clock IDs and the POSIX Clock Interface for the full clockid taxonomy). The man page enumerates the supported values: CLOCK_REALTIME (“a settable system-wide real-time clock”), CLOCK_MONOTONIC (“a nonsettable monotonically increasing clock”), CLOCK_BOOTTIME (like monotonic but counts suspend time, Linux 2.6.39+), CLOCK_PROCESS_CPUTIME_ID (CPU time consumed by the whole process), CLOCK_THREAD_CPUTIME_ID (CPU time consumed by the calling thread), and the suspend-waking variants CLOCK_REALTIME_ALARM / CLOCK_BOOTTIME_ALARM (which require the CAP_WAKE_ALARM capability), plus CLOCK_TAI. The choice is consequential: a CLOCK_MONOTONIC timer is immune to administrators or NTP stepping the wall clock, whereas a CLOCK_REALTIME absolute timer will re-target itself if someone sets the date.
The sevp argument (a struct sigevent *) decides the notification mode. If sevp is NULL, the man page specifies the default: behave as SIGEV_SIGNAL with signal SIGALRM and sigev_value.sival_int set to the timer ID. timerid is an out-parameter receiving an opaque timer_t handle used by every subsequent call.
On the kernel side, timer_create is a real syscall (kernel/time/posix-timers.c, v6.12):
SYSCALL_DEFINE3(timer_create, const clockid_t, which_clock,
struct sigevent __user *, timer_event_spec,
timer_t __user *, created_timer_id)It delegates to do_timer_create(), which: looks up the clock operations with clockid_to_kclock(which_clock); allocates a struct k_itimer (and pre-allocates a sigqueue entry so signal delivery later cannot fail for lack of memory); validates the sigevent via good_sigevent(); stores the clock ops vector in new_timer->kclock; and finally calls kc->timer_create(new_timer) for clock-specific setup. The k_itimer is the kernel’s per-timer object — it carries it_clock, the kclock ops table, it_interval (the reload period or 0 for one-shot), the overrun accumulators it_overrun / it_overrun_last, the pre-allocated sigq, the target it_pid, the it_sigev_notify mode, and (for clock-based timers) an embedded struct hrtimer.
Crucially, POSIX timers are per-process and ephemeral across the process lifecycle: the man page states they “are not inherited by the child of a fork(2), and are disarmed and deleted during an execve(2).” A timer is bound to the creating process’s signal_struct; it does not survive into a new program image and is not duplicated into children.
Arming It — timer_settime and struct itimerspec
A freshly created timer is disarmed — it does nothing until you arm it (timer_settime(2)):
int timer_settime(timer_t timerid, int flags,
const struct itimerspec *restrict new_value,
struct itimerspec *restrict old_value);The schedule lives in struct itimerspec, which holds two struct timespec fields:
struct itimerspec {
struct timespec it_interval; /* reload / period */
struct timespec it_value; /* initial expiration */
};it_value is the initial expiration. If it is nonzero (either the seconds or nanoseconds subfield), the timer is armed; if both subfields are zero, the timer is disarmed (this is how you stop a timer without deleting it). it_interval is the reload value: when the timer fires, it is automatically re-armed for it_interval from the moment it expired, producing a periodic timer. If it_interval is zero, the timer fires exactly once — a one-shot. This two-field design is the same shape as the older setitimer interval — see Interval Timers and setitimer — but with nanosecond rather than microsecond resolution and the freedom to bind to any clock.
The flags argument is normally 0, meaning it_value is interpreted relative to the clock’s current value. Setting TIMER_ABSTIME instead makes it_value an absolute target: the timer “will expire when the clock value reaches the value specified by new_value->it_value” (timer_settime(2)). Absolute timers matter for two reasons. First, they avoid the relative-timer drift you get if you keep computing “now + delta” in userspace across a sleep. Second, for CLOCK_REALTIME absolute timers there is a special behavior: “if the value of the CLOCK_REALTIME clock is adjusted while an absolute timer based on that clock is armed, then the expiration of the timer will be appropriately adjusted” — i.e., the timer tracks the wall clock, so setting the date forward can make an absolute realtime timer fire early (or, set backward, fire late).
old_value, if non-NULL, returns the timer’s previous setting — the time that was remaining and the old interval — letting you atomically read-and-replace.
The companion timer_gettime(timerid, &curr_value) reports the current state. A subtlety: the returned curr_value->it_value is “always a relative value, regardless of whether the TIMER_ABSTIME flag was used” — it is the time remaining until next expiration, not the absolute deadline you set. A returned it_value of zero means the timer is currently disarmed.
In the kernel, arming a clock-based timer flows through common_timer_set() → common_hrtimer_arm(), which initializes an hrtimer with the timer’s clock, points its callback at posix_timer_fn, and calls hrtimer_start_expires() (posix-timers.c, v6.12). The TIMER_ABSTIME flag and the relative/absolute conversion are handled here before the hrtimer is started.
The Notification — struct sigevent and Its Three Modes
The sigevent is the heart of “how do I find out?” Its definition (sigevent(3type)):
struct sigevent {
int sigev_notify; /* notification method */
int sigev_signo; /* signal number (SIGEV_SIGNAL) */
union sigval sigev_value; /* data passed with notification */
void (*sigev_notify_function)(union sigval); /* SIGEV_THREAD */
pthread_attr_t *sigev_notify_attributes; /* SIGEV_THREAD */
pid_t sigev_notify_thread_id; /* SIGEV_THREAD_ID (Linux) */
};
union sigval {
int sival_int;
void *sival_ptr;
};The union sigval is the small payload you choose at create time and that comes back to you on every expiry — typically you stuff a pointer to your own per-timer context into sival_ptr (or the timer’s index into sival_int) so a single handler can disambiguate which of several timers fired.
SIGEV_SIGNAL is the classic mode: on expiry, the kernel generates the signal sigev_signo for the process. If the handler is installed with sigaction(2) and SA_SIGINFO, it receives a siginfo_t whose si_code is SI_TIMER, whose si_value is your sigev_value, and (on Linux) whose si_overrun carries the overrun count. Use a real-time signal (SIGRTMIN..SIGRTMAX) rather than a classic signal here: real-time signals queue and carry a payload, whereas classic signals do not queue, so multiple expirations of multiple timers using the same classic signal would collapse indistinguishably.
SIGEV_THREAD asks for the expiry to “invoke sigev_notify_function as if it were the start function of a new thread” (timer_create(2)), passing it sigev_value as its sole argument, with optional thread attributes from sigev_notify_attributes. The critical, frequently-missed fact: this mode is implemented in glibc, not in the kernel. The man page is explicit — “Much of the functionality for SIGEV_THREAD is implemented within glibc, rather than the kernel… the NPTL implementation uses a sigev_notify value of SIGEV_THREAD_ID along with a real-time signal.” Concretely, glibc’s timer_create quietly creates a dedicated helper thread that blocks waiting for an internal real-time signal targeted via SIGEV_THREAD_ID; when the timer fires, that helper wakes and spawns (or invokes) your callback thread. So “spawn a thread on expiry” is a userspace convenience layered on top of the kernel’s signal mechanism — there is no kernel-level “run this function in a thread” primitive.
SIGEV_NONE asks for no asynchronous notification at all. The timer still runs and still counts time; you simply observe its progress by polling timer_gettime() (timer_create(2)). This is occasionally useful as a low-overhead elapsed-time / deadline source you sample on your own schedule, with no signal-handling complexity.
The Linux-specific SIGEV_THREAD_ID is the fourth mode: it behaves like SIGEV_SIGNAL but directs the signal at a specific thread (named by sigev_notify_thread_id) rather than the whole process. The man page notes it “is intended only for use by threading libraries” — it is the very mechanism glibc uses to build SIGEV_THREAD.
Overrun — When Expirations Outrun Delivery
Periodic timers can fire faster than their signals can be delivered — if the target signal is blocked, or the handler is slow, or the system is loaded, a timer may expire several times before its one queued signal is finally accepted. POSIX deliberately does not queue one signal per expiration (that could overflow the signal queue); it queues at most one and tracks the rest as an overrun count. timer_getoverrun(timerid) returns “the number of additional timer expirations that occurred between the time when the signal was generated and when it was delivered or accepted” (timer_getoverrun(2)). A count of 0 means the signal was handled promptly; a count of N means you “missed” N extra ticks and should account for them (e.g., advance your logical clock by N+1 periods).
On Linux you can also read the same number, without a syscall, from the si_overrun field of the siginfo_t delivered to a SIGEV_SIGNAL handler — a non-portable but convenient shortcut. Since Linux 4.19, if the true overrun exceeds INT_MAX, timer_getoverrun returns DELAYTIMER_MAX (== INT_MAX) rather than wrapping (timer_getoverrun(2)).
The kernel accumulates this in the hrtimer expiry callback (posix-timers.c, v6.12):
static enum hrtimer_restart posix_timer_fn(struct hrtimer *timer)
{
struct k_itimer *timr = ...;
if (posix_timer_queue_signal(timr)) {
if (timr->it_interval != 0) {
timr->it_overrun += hrtimer_forward(timer, now, timr->it_interval);
ret = HRTIMER_RESTART;
}
}
}hrtimer_forward() advances the timer past all the intervals that have elapsed since the deadline and returns how many it skipped — that count is added to it_overrun. When the signal is finally dequeued, posixtimer_rearm() freezes the running total into it_overrun_last (which is what timer_getoverrun reports) and resets it_overrun.
Two Backing Engines — hrtimers vs the cputimer
The single most important kernel-side fact about POSIX timers is that the clock you pick selects a completely different timer engine. Clock-based timers — CLOCK_REALTIME, CLOCK_MONOTONIC, CLOCK_BOOTTIME, CLOCK_TAI — are driven by hrtimers: the k_itimer embeds a struct hrtimer, it is inserted into the per-CPU hrtimer red-black tree keyed on an absolute nanosecond deadline, and it fires via a clockevent interrupt when that deadline arrives. This is why such timers can be precise to the nanosecond.
CPU-time timers — CLOCK_PROCESS_CPUTIME_ID and CLOCK_THREAD_CPUTIME_ID — cannot use hrtimers, because they do not fire at a wall-clock instant; they fire when a quantity of CPU time has been consumed, which only advances while the task is actually running. These live in a separate file (kernel/time/posix-cpu-timers.c, v6.12) and are checked by run_posix_cpu_timers(), “called from the timer interrupt handler” after the per-task CPU-time counts have been updated. Rather than sitting on a deadline tree, a CPU-time timer stores an expiry threshold; at each scheduler tick the kernel samples accumulated CPU time (cpu_clock_sample per-thread, cpu_clock_sample_group process-wide) and fires any timer whose threshold has been crossed. Overrun for these is computed by bump_cpu_timer(). On configs with CONFIG_POSIX_CPU_TIMERS_TASK_WORK, the actual expiry handling is deferred from interrupt context to task work to avoid doing signal delivery in the tick. The practical upshot: a CLOCK_PROCESS_CPUTIME_ID timer measuring “10 seconds of CPU” will take longer than 10 wall-clock seconds if the process is ever descheduled, and its resolution is bounded by the tick, not by the hrtimer hardware.
A Worked Example — a Periodic Real-Time Signal Timer
#include <signal.h>
#include <time.h>
#include <stdio.h>
#define SIG SIGRTMIN
static void handler(int sig, siginfo_t *si, void *uc) {
timer_t *tid = si->si_value.sival_ptr; /* our payload */
printf("tick; overrun=%d\n", si->si_overrun); /* Linux extension */
}
int main(void) {
timer_t timerid;
/* 1. install an SA_SIGINFO handler for our real-time signal */
struct sigaction sa = { .sa_flags = SA_SIGINFO, .sa_sigaction = handler };
sigemptyset(&sa.sa_mask);
sigaction(SIG, &sa, NULL);
/* 2. create a MONOTONIC timer that delivers SIG, payload = &timerid */
struct sigevent sev = {
.sigev_notify = SIGEV_SIGNAL,
.sigev_signo = SIG,
.sigev_value.sival_ptr = &timerid,
};
timer_create(CLOCK_MONOTONIC, &sev, &timerid);
/* 3. arm: first fire in 1 s, then every 200 ms */
struct itimerspec its = {
.it_value = { .tv_sec = 1, .tv_nsec = 0 },
.it_interval = { .tv_sec = 0, .tv_nsec = 200 * 1000 * 1000 },
};
timer_settime(timerid, 0, &its, NULL);
for (;;) pause(); /* handler runs on each expiry */
}Reading it: step 1 installs the handler with SA_SIGINFO so it receives the siginfo_t (without that flag you would not see si_value or si_overrun). Step 2 chooses CLOCK_MONOTONIC (so an admin setting the date cannot disturb our cadence), mode SIGEV_SIGNAL, signal SIGRTMIN (a queuing real-time signal), and stashes &timerid as the payload so the handler knows which timer fired. Step 3’s it_value of 1 s is the initial delay and the it_interval of 200 ms is the reload, giving “first tick at 1 s, then every 200 ms.” Compile with -lrt on older glibc (the man page notes glibc 2.17+ folds these into the main C library, so the explicit -lrt is often unnecessary now). The reported si_overrun will be nonzero if the process was ever unable to take the signal promptly between ticks.
Failure Modes and Common Misunderstandings
Expecting SIGEV_THREAD to be a kernel feature. It is glibc. The hidden helper thread it spawns has real costs (a thread per notification, signal plumbing) and its error behavior differs from the kernel modes. For high-rate timers, SIGEV_THREAD is often the wrong choice; a single thread blocking in sigwaitinfo() on a SIGEV_SIGNAL timer is leaner.
Using a non-real-time signal. If you pick SIGALRM or another classic signal for SIGEV_SIGNAL and run several timers (or fire faster than you handle), expirations collapse and you lose the ability to tell them apart or count them — classic signals do not queue. Use SIGRTMIN..SIGRTMAX.
Forgetting overruns on periodic timers. A loaded or signal-blocked process will miss ticks; if your logic assumes exactly one expiry per signal, it drifts. Always consult si_overrun / timer_getoverrun() and advance by overrun + 1 periods.
Assuming timers survive fork/exec. They do not — not inherited across fork, deleted across execve. Re-create them in the child or after exec.
Confusing CPU-time and wall-clock semantics. A CLOCK_PROCESS_CPUTIME_ID timer does not fire after N seconds of wall time; it fires after N seconds of consumed CPU, with tick-bounded resolution, and never advances while the process sleeps.
Relative-timer drift. Re-arming a one-shot with now + delta in userspace accumulates the time spent in your own code between fire and re-arm; prefer a periodic it_interval (the kernel re-arms from the scheduled expiry, not from when you got around to it) or TIMER_ABSTIME.
Alternatives and When to Choose Them
If you want a timer that composes with an event loop (epoll/poll/select) instead of interrupting you with a signal, use timerfd — it turns expirations into readable bytes on a file descriptor, which is far easier to multiplex than asynchronous signal handlers and avoids all the async-signal-safety hazards. If you only need the old, coarser BSD-style one timer per category with setitimer/getitimer, see Interval Timers and setitimer — POSIX timers are the strict superset (any clock, many timers, nanosecond resolution, per-thread targeting). For one-shot sleeping rather than asynchronous notification, clock_nanosleep is simpler. And for choosing which clock in the first place — monotonic vs realtime vs boottime vs the CPU-time clocks — the trade-offs are catalogued in Clock IDs and the POSIX Clock Interface.
See Also
- High-Resolution Timers and hrtimers — the engine behind clock-based POSIX timers
- timerfd — the file-descriptor alternative that composes with
epoll - Interval Timers and setitimer — the older, coarser predecessor API
- Clock IDs and the POSIX Clock Interface — how the
clockidargument selects the clock and backend - Linux Time and Timers MOC — parent map (section F, The Userspace Timing Interface)