Timer mechanisms let you schedule the OS kernel to notify an application when a predetermined time has elapsed. You'll typically use them by providing two pieces of information. First, you'll need to specify how much time the timer should take before notifying. Secondly, you'll need to prepare a callback function to act when that notification occurs.
Traditional Approach to Timers
Timer mechanisms in Linux and Unix-based systems have evolved to serve various needs. Different approaches can help you solve different types of problem. However, you'll often see the first version of the alarm() mechanism still in use.
The alarm function is the simplest way to use a timer; here's its prototype:
unsigned int alarm(unsigned int seconds);
Using this method, you can only specify the time in whole seconds. When the time is up, the operating system sends the SIGALRM signal to your application. To process the timer's expiry in your application, you should also define a callback function.
Here is an example of a signal handler function:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <time.h> void timer_callback(int signum)
{
time_t now = time(NULL);
printf("Signal %d caught on %li", signum, now);
}
int main()
{
signal(SIGALRM, timer_callback);
alarm(1);
sleep(3);
return 0;
}
This code raises a SIGALRM signal after 1 second. If you want to increase the timer delay to five seconds, just call alarm(5) instead. To stop the timer, pass a value of 0: alarm(0).
When the time is up, the timer you use will not restart periodically. For example, if you want to delay for another second, you should restart the mechanism with another call to alarm().
Despite its ease of use, this method has some disadvantages:
- Only one timer at a time.
- No periodic timer support.
- You can only give the time period in multiples of whole seconds.
- No way to know how much time remains on a timer.
Save the sample code given above as alarm.c. When you compile and run it, the program will call the timer_callback function after one second. It will then wait for the remaining two seconds due to the sleep(3) line, then terminate.
$ gcc -o alarm alarm.c
$ time ./alarm
Signal 14 caught on 1653490465
real 0m1.004s
user 0m0.000s
sys 0m0.003s
The reason for using the time command is to be able to see the times. But if you look at the result, the total running time is not three seconds. This is due to the SIGALRM signal from alarm(1) when the first second is up, while the syscall caused by the sleep(3) function is running. When this signal arrives, it interrupts the syscall initiated for sleep(3).
Using an Interval Timer
The interval timer mechanism was first available in version 4.2 BSD. It was later standardized by POSIX. Its main advantages over the traditional alarm() based timer method are:
- Provides microsecond resolution.
- It allows controlling the time measurement in more detail over three different modes.
- It is possible to set it once and make it work periodically.
- It is possible to find out how long it is present at any given moment.
Function prototypes used for interval timer operations are as follows:
#include <sys/time.h>int setitimer(int which, const struct itimerval *newValue, struct itimerval *oldValue);
int getitimer(int which, struct itimerval *value);
struct itimerval
{
struct timeval itInterval; // next value
struct timeval itValue; // current value
};
struct timeval
{
long tv_sec;
long tv_usec;
};
If you want to set up an interval timer, you'll need to use the itimerval struct. You'll need to pass a value using this struct as the second argument to the settimer function.
For example, an interval timer that will notify your application for 1 second and then every 300 milliseconds can be set up as follows:
struct itimerval newTimer;
struct itimerval oldTimer;newTimer.itValue.tv_sec = 1;
newTimer.itValue.tv_usec = 0;
newTimer.itInterval.tv_sec = 0;
newTimer.itInterval.tv_usec = 300 * 1000;
setitimer(ITIMER_REAL, &newTimer, &oldTimer);
If there is an interval timer active before the new values are set, its values are transferred to the variable address of the itimerval type given to the third parameter of the function.
You can set up three different types of timers with the interval timer mechanism. Specify the timer type in the first parameter of setitimer():
| Timer Type | Signal | Explanation |
|---|---|---|
| ITIMER_REAL | SIGALRM | Independent of the time spent by the application, calculated over the total elapsed time. |
| ITIMER_VIRTUAL | SIGVTALRM | Calculated over the time the application is running in user mode only. |
| ITIMER_PROF | SIGPROF | Calculated over the sum of the time spent by the application in both user and system modes. |
You can see from this table that the ITIMER_REAL type sends a SIGALRM signal, just like the alarm() function.
Using an interval timer and alarm() in the same application will be confusing. Although you can make a second check on the remaining time with gettimer(), it does not make sense to use them simultaneously.
Here is an example of defining the signal handler function with the debug header:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <sys/time.h>
#include <errno.h>
#include <string.h>
#include "./debug.h"void timer_callback(int signum)
{
struct timeval now;
gettimeofday(&now, NULL);
printf("Signal %d caught on %li.%03li ", signum, now.tv_sec, now.tv_usec / 1000);
}
int main()
{
unsigned int remaining = 3;
struct itimerval new_timer;
struct itimerval old_timer;
new_timer.it_value.tv_sec = 1;
new_timer.it_value.tv_usec = 0;
new_timer.it_interval.tv_sec = 0;
new_timer.it_interval.tv_usec = 300 * 1000;
setitimer(ITIMER_REAL, &new_timer, &old_timer);
signal(SIGALRM, timer_callback);
while (sleep(remaining) != 0)
{
if (errno == EINTR)
debugf("sleep interrupted by signal");
else
errorf("sleep error %s", strerror(errno));
}
return 0;
}
The above code uses the sleep() function to wait for three seconds. During this time, an interval timer runs, first for one second, then on an interval of 300 milliseconds.
For better understanding, save and compile the sample code with the name interval.c:
$ gcc -o interval interval.c
$ time ./interval
Signal 14 caught on 1653493614.325
debug: sleep interrupted by signal (main interval.c:36)
Signal 14 caught on 1653493614.625
debug: sleep interrupted by signal (main interval.c:36)
Signal 14 caught on 1653493614.925
debug: sleep interrupted by signal (main interval.c:36)
Signal 14 caught on 1653493615.225
debug: sleep interrupted by signal (main interval.c:36)
Signal 14 caught on 1653493615.525
...
As you can see from the output after the timer runs, it calls the callback function every 300 milliseconds.
However, after waiting a little longer, you'll notice that the application does not terminate. It continues to run the callback function every 300 milliseconds. If you increase the interval value in milliseconds, you will see that the application terminates. This is because of the usage area of the sleep() function.
Importance of Using Timers in Linux
Especially for real-time applications, the timer mechanism is of great importance. This is also a solution used for performance optimizations. You can even use it to measure uptime or latency in your application. It is important to use timer mechanisms to keep track of elapsed time and time transition events.