Yes, we're now running our Black Friday Sale. All Access and Pro are 33% off until 2nd December, 2025:
How to Use Interval Timers in Linux With C
Last updated: April 5, 2025
1. Overview
We use interval timers to generate events at a given time or periodically. The timer itself is a system resource. The process can ask for such a resource, obtain it, use it, and finally return it to the system.
In this tutorial, we’ll learn how to use interval timers with the C language.
2. Interval Timers
Throughout this tutorial, we’ll use the glibc implementation for POSIX timer system calls. In addition to the timer, we need to define an event, to be triggered when the timer fires. With it, we decide how to inform the process about the timer’s expiration. One way is to assign a real-time signal and a handler. Such a signal is similar to standard signals, e.g., SIGINT. The other way is to provide a function that runs in a separate thread.
We’ll compile C examples with the GCC compiler in the same directory where the source file is located:
$ gcc <source_file.c>
This produces an output executable file, namely a.out. We can then run it:
$ ./a.out
This will execute the program and display its output.
3. Setting up a Timer With a Signal Handler
The timer’s setting consists of three steps, each related to a specific structure. These structures are sigaction, sigevent and itimerspec. Let’s examine them one by one.
3.1. The sigaction Structure
With this structure, we describe how to react to a signal:
struct sigaction sigact;
sigemptyset(&sigact.sa_mask);
sigact.sa_sigaction = timer_signal_handler;
sigact.sa_flags = SA_SIGINFO;
First, we clear the blocked signals mask with the sigemptyset function so that all signals are received. Next, we set sa_flags to SA_SIGINFO. With this flag set, we specify the signal handler in the sa_sigaction field. Then, we fill the sa_sigaction field with the name of our signal handler, timer_signal_handler.
Finally, we register our sigact structure to the first available real-time signal number, SIGRTMIN+0.
sigaction(SIGRTMIN+0, &sigact, NULL);
With the glibc library, we should use the SIGRTMIN macro, referring to the nth signal as SIGRTMIN+n.
3.2. The sigevent Structure
This structure describes how the process is notified about an event. We’ll set the sigev_notify field to SIGEV_SIGNAL. Then, we provide the signal number in the sigev_signo member. It’s SIGRTMIN+0, as per the sigaction call. Finally, the sival_prt value contains additional data to be passed with the notification. We set it to NULL in this case:
struct sigevent sigevt;
sigevt.sigev_notify = SIGEV_SIGNAL;
sigevt.sigev_signo = SIGRTMIN+0;
sigevt.sigev_value.sival_ptr = NULL;
With this setup, the timer is now configured to notify the process using the specified real-time signal whenever it expires.
3.3. The itimerspec Structure
With the itimerspec structure, we set the alarm. It contains two structures of type timespec: it_value and it_interval. The it_value field specifies the initial expiration time, while the it_interval field defines the interval for periodic firings of the timer. Both structures accept seconds in the tv_sec field and nanoseconds in the tv_nsec field:
struct itimerspec alarm;
alarm.it_value.tv_sec = 5;
alarm.it_value.tv_nsec = 0L;
alarm.it_interval.tv_sec = 0L;
alarm.it_interval.tv_nsec = 0L;
In this case, the timer will expire once after 5 seconds, and there will be no periodic repetition since it_interval is set to 0.
3.4. Accessing the Timer
We can control the timer using three functions: timer_create, timer_settime, and timer_delete. The first one provides a timer identified by an ID. The second function sets the timer, while the last function disarms and deletes it.
When creating a timer with timer_create, we’ll set it to measure the real time by passing CLOCK_REALTIME:
timer_create(CLOCK_REALTIME, &evt, &timeout_timer);
In summary, these functions allow for precise control over timers.
4. Signal Notification Example
Now, let’s put all these pieces together into a program named one_shot_timer.c:
#include <time.h> /* for timer functions and itimerspec */
#include <signal.h> /* for sigaction */
#include <errno.h> /* for errno */
#include <string.h> /* for strerror */
#include <stdio.h> /* for printf */
#include <unistd.h> /* for sleep */
static void timer_signal_handler(int signum, siginfo_t *info, void *context)
{
printf("Signal handler starts!\n");
}
int main ()
{
timer_t timeout_timer;
/* setting signal and its handler */
struct sigaction sigact;
sigemptyset(&sigact.sa_mask);
sigact.sa_sigaction = timer_signal_handler;
sigact.sa_flags = SA_SIGINFO;
if (sigaction(SIGRTMIN+0, &sigact, NULL))
return errno;
/* setting timer event */
struct sigevent sigevt;
sigevt.sigev_notify = SIGEV_SIGNAL;
sigevt.sigev_signo = SIGRTMIN+0;
sigevt.sigev_value.sival_ptr = NULL;
if (timer_create(CLOCK_REALTIME, &sigevt, &timeout_timer))
return errno;
/* setting alarm time */
struct itimerspec alarm;
alarm.it_value.tv_sec = 5;
alarm.it_value.tv_nsec = 0L;
alarm.it_interval.tv_sec = 0L;
alarm.it_interval.tv_nsec = 0L;
/* starting timer */
if (timer_settime(timeout_timer, 0, &alarm, NULL))
return errno;
printf("Sleeping\n");
sleep(60); /* operation that can be interrupted */
if (errno)
printf("Resuming with error: %s\n", strerror(errno));
else
printf("Waking up!\n");
timer_delete(timeout_timer); /* release timer to system */
return 0;
}
Note that we use the sleep function to wait for 60 seconds. This function is interrupted by the signal triggered by the clock. We also see the output of the signal handler:
$ ./a.out
Sleeping
Signal handler starts!
Resuming with error: Interrupted system call
This demonstrates how the timer signal interrupts the sleeping process.
4.1. Interrupting Blocked Function or Call
The real-time signal may interrupt a blocked system call or function, which in turn sets errno to EINTR. We can find a list of such functions and interfaces in the manual for signal:
$ man 7 signal
Note that we need a signal handler function to interrupt a blocked function or system call. We can even leave it empty, but calling it always precedes the interruption of the blocked function.
4.2. Async-Signal-Safe Functions
In the timer_signal_handler function, we used the printf function. However, calling this function in a signal handler may lead to undefined behavior, so we shouldn’t do that in a real application. To find the list of so-called async-signal-safe functions, we should go through the documentation regarding signal safety:
$ man 7 signal-safety
In practice, signal handlers should only call functions that are guaranteed to be safe in the context of signals.
5. Timer and Thread Notification
Instead of sending a signal, we can specify a function to be executed in a new thread. This setup won’t interrupt any blocked functions. Let’s examine essential settings in the sigevent structure. First, we set sigev_notify to SIGEV_THREAD. Next, we assign the name of the thread function to the sigev_notify_function field. Finally, we set sigev_notify_attributes to NULL as it’s used to specify attributes for the newly created thread:
struct sigevent evt;
evt.sigev_notify = SIGEV_THREAD;
evt.sigev_notify_function = timer_thread;
evt.sigev_notify_attributes = NULL;
Since we’re not using signals, we can skip the sigev_signo field. We also don’t need to define a sigaction structure with a signal handler either. Instead of async signal safety, we now need to ensure thread safety.
Let’s write a simple program to report the progress of an operation. We require that the progress reports come in at regular intervals, so we use a periodic timer. However, the program’s main loop will update its progress irregularly.
Since the progress variable is updated in one thread and read in another, we need to make it thread-safe. So, we use atomic types, which come with the C11 standard. We declare progress as atomic_int. To increment it, we apply the atomic_fetch_add function. Let’s check the implementation of this program in timer_thread.c:
#include <time.h> /* for timer functions and itimerspec */
#include <errno.h> /* for errno */
#include <string.h> /* for strerror */
#include <signal.h> /* for sigaction and sigevt */
#include <stdio.h> /* for printf */
#include <unistd.h> /* for sleep */
#include <stdlib.h> /* for random */
#include <stdatomic.h> /* for atomic_fetch_add */
#include <sys/syscall.h> /* for syscall */
atomic_int progress = 0;
void timer_thread(union sigval arg)
{
pid_t tid = syscall(SYS_gettid); /* get thread ID */
printf("Report from thread %d: progress %d\n", tid, progress);
}
int main ()
{
timer_t periodic_timer;
/* setting timer event */
struct sigevent sigevt;
sigevt.sigev_notify = SIGEV_THREAD;
sigevt.sigev_notify_function = timer_thread;
sigevt.sigev_notify_attributes = NULL;
if (timer_create(CLOCK_REALTIME, &sigevt, &periodic_timer))
return errno;
int start_time = 1; /* to synchronize timer and reporting operation */
/* setting initial fire time and periodic time, every two and a half seconds */
struct itimerspec alarm;
alarm.it_value.tv_sec = start_time;
alarm.it_value.tv_nsec = 0L;
alarm.it_interval.tv_sec = 2L;
alarm.it_interval.tv_nsec = 500000L;
int max_sleep = 5;
int nsteps = 10;
srand(time(NULL)); /* randomize seed */
/* starting timer */
if (timer_settime(periodic_timer, 0, &alarm, NULL))
return errno;
sleep(start_time);
for (int i = 1; i <= nsteps; i++)
{
int rnum = (int) max_sleep*((1.0 * random()) / RAND_MAX) + 1; /* random sleep time */
printf("Step %d. Sleeping for %d\n", i, rnum);
sleep(rnum);
atomic_fetch_add(&progress, 1);
}
return 0;
}
Now, let’s examine its output:
$ ./a.out
Step 1. Sleeping for 3
Report from thread 8495: progress 0
Report from thread 8496: progress 0
Step 2. Sleeping for 2
Report from thread 8497: progress 1
Step 3. Sleeping for 1
Step 4. Sleeping for 1
Report from thread 8499: progress 3
...
Notably, each function call takes place in a new thread.
6. Conclusion
In this article, we learned the basics of the C POSIX interval timers. We studied how to set up a timer and define its event. We also discussed both the signal and thread timer notifications. Then, we prepared an example showing how the single-shot timer interrupted a system call.
In addition, we touched upon the signal safety concept. Finally, regarding thread notification, we wrote a simple program that concurrently reported the progress of an ongoing operation.