⏱️ POSIX Interval Timers

⏱️ POSIX Interval Timers
Chapter 23 | Part 7 of 9 — timer_create(), timer_settime(), timer_delete()
🔢 Multiple
Timers/Process
🔔 Notify
Signal or Thread
🎯 Overrun
Detection

Why POSIX Timers Over setitimer()?

Classical setitimer() has severe limitations: only one timer per type, only signal delivery, no overrun count, microsecond limit.

POSIX.1b timers (Linux 2.6+) fix all of these: multiple timers, choice of notification method, overrun detection, and nanosecond resolution.

📊 setitimer() vs POSIX Timers
Feature setitimer() POSIX timer_*()
Number of timers 1 per type (3 total) Many (limited by queued RT signals)
Resolution Microseconds Nanoseconds
Notification method Signal only Signal, thread function, or none
Overrun detection No Yes — timer_getoverrun()
Clock selection REAL, VIRTUAL, PROF only Any POSIX clock
fork() inheritance Not inherited Not inherited, deleted on exec()

🔄 POSIX Timer Lifecycle
📌

timer_create()
Create & define
notification

▶️

timer_settime()
Arm (start)
or disarm

🔔

Notify
Signal or
Thread

🔍

timer_gettime()
Query
remaining

🗑️

timer_delete()
Free
resources

📋 All Function Signatures
#define _POSIX_C_SOURCE 199309
#include <signal.h>
#include <time.h>

/* Create a new timer */
int timer_create(clockid_t clockid,
                 struct sigevent *evp,
                 timer_t *timerid);

/* Arm or disarm a timer */
int timer_settime(timer_t timerid,
                  int flags,             /* 0 or TIMER_ABSTIME */
                  const struct itimerspec *value,
                  struct itimerspec *old_value);  /* NULL if unused */

/* Get current timer state */
int timer_gettime(timer_t timerid,
                  struct itimerspec *curr_value);

/* Delete a timer */
int timer_delete(timer_t timerid);

/* Get overrun count */
int timer_getoverrun(timer_t timerid);

/* All return 0 on success, -1 on error */

/* itimerspec uses nanosecond precision: */
struct itimerspec {
    struct timespec it_interval;  /* Repeat interval */
    struct timespec it_value;     /* First expiry */
};

/* Compile: gcc prog.c -o prog -lrt */

🔔 sigevent — Choosing the Notification Method
sigev_notify value Action on expiry Standard?
SIGEV_NONE No notification — poll manually via timer_gettime() ✅ SUSv3
SIGEV_SIGNAL Send signal sigev_signo to process ✅ SUSv3
SIGEV_THREAD Call sigev_notify_function in a new thread ✅ SUSv3
SIGEV_THREAD_ID Send signal to specific thread (Linux only) ❌ Linux-specific
💡 Tip: If evp is passed as NULL, defaults to SIGEV_SIGNAL with SIGALRM.

💻 Example 1: POSIX Timer with Signal Notification
#define _POSIX_C_SOURCE 199309
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <string.h>

#define TIMER_SIG  SIGRTMIN      /* Use first realtime signal */

static timer_t timerid;
static volatile int expiry_count = 0;

void timer_handler(int sig, siginfo_t *si, void *uc) {
    (void)sig; (void)uc;
    expiry_count++;
    /* si->si_value.sival_ptr points to our timerid */
    printf("Timer fired! expiry #%d, overrun=%d\n",
           expiry_count,
           timer_getoverrun(*(timer_t *)si->si_value.sival_ptr));
}

int main(void) {
    struct sigaction sa;
    struct sigevent  sev;
    struct itimerspec ts;

    /* Install signal handler (SA_SIGINFO to get siginfo_t) */
    sa.sa_flags     = SA_SIGINFO;
    sa.sa_sigaction = timer_handler;
    sigemptyset(&sa.sa_mask);
    sigaction(TIMER_SIG, &sa, NULL);

    /* Configure: signal on expiry */
    sev.sigev_notify            = SIGEV_SIGNAL;
    sev.sigev_signo             = TIMER_SIG;
    sev.sigev_value.sival_ptr   = &timerid;  /* Pass timer ID to handler */

    /* Create timer using CLOCK_REALTIME */
    if (timer_create(CLOCK_REALTIME, &sev, &timerid) == -1) {
        perror("timer_create"); return 1;
    }
    printf("Timer ID: %ld\n", (long)timerid);

    /* Arm: first expiry after 1s, repeat every 1s */
    ts.it_value.tv_sec    = 1;
    ts.it_value.tv_nsec   = 0;
    ts.it_interval.tv_sec  = 1;
    ts.it_interval.tv_nsec = 0;

    if (timer_settime(timerid, 0, &ts, NULL) == -1) {
        perror("timer_settime"); return 1;
    }

    /* Wait for 5 expirations */
    while (expiry_count < 5)
        pause();

    /* Clean up */
    timer_delete(timerid);
    printf("Done.\n");
    return 0;
}

/* Compile: gcc -o posix_timer posix_timer.c -lrt
   Output:
   Timer ID: 140366...
   Timer fired! expiry #1, overrun=0
   Timer fired! expiry #2, overrun=0
   Timer fired! expiry #3, overrun=0
   Timer fired! expiry #4, overrun=0
   Timer fired! expiry #5, overrun=0
   Done.
*/

💻 Example 2: Multiple Simultaneous Timers

Create two POSIX timers with different intervals running at the same time.

#define _POSIX_C_SOURCE 199309
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>

#define SIG_T1 SIGRTMIN
#define SIG_T2 (SIGRTMIN + 1)

void handler_t1(int sig, siginfo_t *si, void *uc) {
    (void)sig; (void)si; (void)uc;
    printf("Timer 1 (500ms) fired\n");
}

void handler_t2(int sig, siginfo_t *si, void *uc) {
    (void)sig; (void)si; (void)uc;
    printf("  Timer 2 (1500ms) fired\n");
}

timer_t create_timer(int signo, void (*handler)(int,siginfo_t*,void*),
                     long first_ms, long interval_ms) {
    struct sigaction sa;
    struct sigevent  sev;
    struct itimerspec ts;
    timer_t tid;

    sa.sa_flags     = SA_SIGINFO;
    sa.sa_sigaction = handler;
    sigemptyset(&sa.sa_mask);
    sigaction(signo, &sa, NULL);

    sev.sigev_notify          = SIGEV_SIGNAL;
    sev.sigev_signo           = signo;
    sev.sigev_value.sival_int = signo;

    timer_create(CLOCK_REALTIME, &sev, &tid);

    ts.it_value.tv_sec     = first_ms / 1000;
    ts.it_value.tv_nsec    = (first_ms % 1000) * 1000000L;
    ts.it_interval.tv_sec  = interval_ms / 1000;
    ts.it_interval.tv_nsec = (interval_ms % 1000) * 1000000L;

    timer_settime(tid, 0, &ts, NULL);
    return tid;
}

int main(void) {
    timer_t t1, t2;

    printf("Starting two timers...\n");

    /* Timer 1: every 500ms */
    t1 = create_timer(SIG_T1, handler_t1, 500, 500);

    /* Timer 2: every 1500ms */
    t2 = create_timer(SIG_T2, handler_t2, 1500, 1500);

    /* Run for ~4 seconds */
    sleep(4);

    timer_delete(t1);
    timer_delete(t2);
    printf("Both timers deleted.\n");
    return 0;
}

/* Expected output:
   Timer 1 (500ms)  fired
   Timer 1 (500ms)  fired
   Timer 1 (500ms)  fired   <-- T=1500ms
     Timer 2 (1500ms) fired
   Timer 1 (500ms)  fired
   ... interleaved based on timing
*/

💻 Example 3: Query and Disarm a Timer
#define _POSIX_C_SOURCE 199309
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>

int main(void) {
    timer_t timerid;
    struct sigevent  sev;
    struct itimerspec ts, curr;

    /* SIGEV_NONE: no notification — just poll with timer_gettime() */
    sev.sigev_notify = SIGEV_NONE;

    if (timer_create(CLOCK_REALTIME, &sev, &timerid) == -1) {
        perror("timer_create"); return 1;
    }

    /* Arm for 10 seconds */
    ts.it_value.tv_sec    = 10;
    ts.it_value.tv_nsec   = 0;
    ts.it_interval.tv_sec  = 0;
    ts.it_interval.tv_nsec = 0;
    timer_settime(timerid, 0, &ts, NULL);

    /* Poll remaining time every second */
    for (int i = 0; i < 3; i++) {
        sleep(1);
        timer_gettime(timerid, &curr);
        printf("Remaining: %ld.%09ld s\n",
               curr.it_value.tv_sec,
               curr.it_value.tv_nsec);
    }

    /* Disarm by setting it_value to 0 */
    memset(&ts, 0, sizeof(ts));
    timer_settime(timerid, 0, &ts, NULL);

    timer_gettime(timerid, &curr);
    printf("After disarm — remaining: %ld s\n",
           curr.it_value.tv_sec);

    timer_delete(timerid);
    return 0;
}

/* Output:
   Remaining: 8.999... s
   Remaining: 7.999... s
   Remaining: 6.999... s
   After disarm — remaining: 0 s
*/

🎓 Interview Questions
Q1. What are the four steps of the POSIX timer lifecycle?

Create (timer_create), Arm (timer_settime with nonzero it_value), Receive notification, Delete (timer_delete). Optionally query via timer_gettime.

Q2. How do you disarm a POSIX timer?

Call timer_settime() with both subfields of it_value set to 0.

Q3. What happens to POSIX timers on exec()?

They are disarmed and deleted. Unlike setitimer() which persists across exec().

Q4. What does evp=NULL mean in timer_create()?

It defaults to SIGEV_SIGNAL with signal SIGALRM and sival_int set to the timer ID — same as the old alarm() behavior.

Q5. Why must SA_SIGINFO be set when handling POSIX timer signals?

SA_SIGINFO enables the siginfo_t argument in the handler, which gives access to si_code (SI_TIMER), si_value (your custom data passed at timer_create time), and si_overrun (Linux extension). Without it, you lose all this context.

Next: Timer Overruns & Thread Notification →

Part 8: Overruns & Thread Notify ← Part 6: clock_nanosleep()

Leave a Reply

Your email address will not be published. Required fields are marked *