The timing of certain events is a common task for a developer. Common scenarios for timers are watchdogs, cyclic execution of tasks, or scheduling events for a specific time. In this article, I show how to create a POSIX-compliant interval timer using timer_create(...).
You can download the source code for the following examples from GitHub.
Prepare Qt Creator
I used Qt Creator as the IDE for this example. To run and debug the example code in Qt Creator, clone the GitHub repository, open Qt Creator, and go to File -> Open File or Project... and choose the CMakeLists.txt:
After selecting the toolchain, click on Configure Project. The project contains three independent examples (we will only cover two of them in this article). With the green-marked menu, switch between the configurations for each example and activate Run in terminal for each of them (see the yellow mark below). The currently active example for building and debugging can be selected over the Debug button on the bottom left corner (see the orange mark below):
Threading timer
Let's take a look at the simple_threading_timer.c example. This is the simplest one: It shows how an interval timer is created, which calls the function expired on expiration. On each expiration, a new thread is created in which the function expiration is called.
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
void expired(union sigval timer_data);
pid_t gettid(void);
struct t_eventData{
int myData;
};
int main()
{
int res = 0;
timer_t timerId = 0;
struct t_eventData eventData = { .myData = 0 };
/* sigevent specifies behaviour on expiration */
struct sigevent sev = { 0 };
/* specify start delay and interval
* it_value and it_interval must not be zero */
struct itimerspec its = { .it_value.tv_sec = 1,
.it_value.tv_nsec = 0,
.it_interval.tv_sec = 1,
.it_interval.tv_nsec = 0
};
printf("Simple Threading Timer - thread-id: %d\n", gettid());
sev.sigev_notify = SIGEV_THREAD;
sev.sigev_notify_function = &expired;
sev.sigev_value.sival_ptr = &eventData;
/* create timer */
res = timer_create(CLOCK_REALTIME, &sev, &timerId);
if (res != 0){
fprintf(stderr, "Error timer_create: %s\n", strerror(errno));
exit(-1);
}
/* start timer */
res = timer_settime(timerId, 0, &its, NULL);
if (res != 0){
fprintf(stderr, "Error timer_settime: %s\n", strerror(errno));
exit(-1);
}
printf("Press ETNER Key to Exit\n");
while(getchar()!='\n'){}
return 0;
}
void expired(union sigval timer_data){
struct t_eventData *data = timer_data.sival_ptr;
printf("Timer fired %d - thread-id: %d\n", ++data->myData, gettid());
}
The advantage of this approach is its small footprint, in terms of code and simple debugging. The disadvantage is the additional overhead due to the creation of a new thread on expiration and, consequently, the less deterministic behavior.
Interrupt Signal Timer
Another possibility to be notified by an expired timer is based on a kernel signal. Instead of creating a new thread each time the timer expires, the kernel sends a signal to the process, the process is interrupted, and the corresponding signal handler is called.
As the default action when receiving a signal is to terminate the process (see signal man page), we have to prepare Qt Creator in advance so that properly debugging is possible.
The default behavior of Qt Creator when the debuggee receives a signal is:
- Interrupt execution and switch to the debugger context.
- Display a pop-up window that notifies the user about the reception of a signal.
Both actions are not wanted as the reception of a signal is part of our application.
Qt Creator uses GDB in the background. In order to prevent GDB from stopping the execution when the process receives a signal, go to Tools -> Options, select Debugger, and navigate to Locals & Expressions. Add the following expression to Debugging Helper Customization:
handle SIG34 nostop pass
You can find more information about GDB signal handling in the GDB documentation.
Next, we want to suppress the pop-up window that notifies us every time a signal is received when we stop in the signal handler:
To do so, navigate to the tab GDB and uncheck the marked checkbox:
Now you can properly debug the signal_interrupt_timer. The actual implementation of the signal timer is a bit more complex:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#define UNUSED(x) (void)(x)
static void handler(int sig, siginfo_t *si, void *uc);
pid_t gettid(void);
struct t_eventData{
int myData;
};
int main()
{
int res = 0;
timer_t timerId = 0;
struct sigevent sev = { 0 };
struct t_eventData eventData = { .myData = 0 };
/* specifies the action when receiving a signal */
struct sigaction sa = { 0 };
/* specify start delay and interval */
struct itimerspec its = { .it_value.tv_sec = 1,
.it_value.tv_nsec = 0,
.it_interval.tv_sec = 1,
.it_interval.tv_nsec = 0
};
printf("Signal Interrupt Timer - thread-id: %d\n", gettid());
sev.sigev_notify = SIGEV_SIGNAL; // Linux-specific
sev.sigev_signo = SIGRTMIN;
sev.sigev_value.sival_ptr = &eventData;
/* create timer */
res = timer_create(CLOCK_REALTIME, &sev, &timerId);
if ( res != 0){
fprintf(stderr, "Error timer_create: %s\n", strerror(errno));
exit(-1);
}
/* specifz signal and handler */
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = handler;
/* Initialize signal */
sigemptyset(&sa.sa_mask);
printf("Establishing handler for signal %d\n", SIGRTMIN);
/* Register signal handler */
if (sigaction(SIGRTMIN, &sa, NULL) == -1){
fprintf(stderr, "Error sigaction: %s\n", strerror(errno));
exit(-1);
}
/* start timer */
res = timer_settime(timerId, 0, &its, NULL);
if ( res != 0){
fprintf(stderr, "Error timer_settime: %s\n", strerror(errno));
exit(-1);
}
printf("Press ENTER to Exit\n");
while(getchar()!='\n'){}
return 0;
}
static void
handler(int sig, siginfo_t *si, void *uc)
{
UNUSED(sig);
UNUSED(uc);
struct t_eventData *data = (struct t_eventData *) si->_sifields._rt.si_sigval.sival_ptr;
printf("Timer fired %d - thread-id: %d\n", ++data->myData, gettid());
}
In contrast to the threading timer, we have to initialize the signal and register a signal handler. This approach is more performant as it won't cause the creation of additional threads. For this reason, the execution of the signal handler is also more deterministic. The drawback is clearly the extra configuration effort to debug this properly.
Summary
Both methods described in this article are close-to-the-kernel implementations of timers. Even if the timer_create(...) function is part of the POSIX specification, it is not possible to compile the sample code on a FreeBSD system due to small differences in data structures. Besides this drawback, such an implementation gives you fine-grained control for general-purpose timing applications.
Comments are closed.