15. Echtzeit¶
Linux hat verschiedene Timer-Interfaces,
Folgendes Programm stoppt den Prozess mit clock_nanosleep
für 5000 ns. Gleichzeitig wird die Zeit gemessen und ausgegeben.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | /* http://stackoverflow.com/questions/4986818/how-to-sleep-for-a-few-microseconds
Compile with
arm-linux-gnueabi-gcc -lrt -lm -o test_clock test_clock.c
*/
#include <stdio.h>
#include <time.h>
#include <math.h>
// Compile with gcc -lrt -lm -o test_clock test_clock.c
#define CLOCK CLOCK_MONOTONIC
int main(int argc, char** argv) {
double temp, elapsed;
int j;
struct timespec requestStart, requestEnd, req;
// Pseudo-sleep
clock_gettime(CLOCK, &requestStart);
temp = 0;
for(j=0; j < 40; j++)
temp += sin(j);
clock_gettime(CLOCK, &requestEnd);
elapsed = ( requestEnd.tv_sec - requestStart.tv_sec ) / 1e-6
+ ( requestEnd.tv_nsec - requestStart.tv_nsec ) / 1e3;
printf("Elapsed: %lf us\n", elapsed);
// Nanosleep
clock_gettime(CLOCK, &requestStart);
req.tv_nsec = 5000;
req.tv_sec = 0;
clock_nanosleep(CLOCK, 0, &req, NULL);
clock_gettime(CLOCK, &requestEnd);
elapsed = ( requestEnd.tv_sec - requestStart.tv_sec ) / 1e-6
+ ( requestEnd.tv_nsec - requestStart.tv_nsec ) / 1e3;
printf("Elapsed: %lf us\n", elapsed);
}
|
$ ./test_clock
Elapsed: 160.120000 us
Elapsed: 193.994000 us
Die 5000 ns werden hier deutlisch überschritten. Das liegt zum Teil an der Rechenzeit-Optimierung des Schedulers.
Wenn man sicherstellen will, dass Aufgaben tatsächlich in bestimmten Zeitabständen ausgeführt werden gibt es mehrere Möglichkeiten, zum Beispiel timerfd, POSIX timers oder setitimer.
Folgendes timerfd Beispiel wartet 1000 mal jeweils 10ms:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | /* timerfd_demo.c, modified */
/* Original demo is from
C. Simmonds, Over and over again: periodic tasks in Linux, 2009
http://www.embedded-linux.co.uk/tutorial/periodic_threads/
https://2net.co.uk/tutorial/periodic_threads
*/
#include <sys/timerfd.h>
#include <stdio.h>
#include <unistd.h>
struct periodic_info {
int timer_fd;
unsigned long long wakeups_missed;
};
static int make_periodic(unsigned int period, struct periodic_info *info)
{
int ret;
unsigned int ns;
unsigned int sec;
int fd;
struct itimerspec itval;
/* Create the timer */
fd = timerfd_create(CLOCK_MONOTONIC, 0);
info->wakeups_missed = 0;
info->timer_fd = fd;
if (fd == -1)
return fd;
/* Make the timer periodic */
sec = period / 1000000;
ns = (period - (sec * 1000000)) * 1000;
itval.it_interval.tv_sec = sec;
itval.it_interval.tv_nsec = ns;
itval.it_value.tv_sec = sec;
itval.it_value.tv_nsec = ns;
ret = timerfd_settime(fd, 0, &itval, NULL);
return ret;
}
static void wait_period(struct periodic_info *info)
{
unsigned long long missed;
int ret;
/* Wait for the next timer event. If we have missed any the
number is written to "missed" */
ret = read(info->timer_fd, &missed, sizeof(missed));
if (ret == -1) {
perror("read timer");
return;
}
/* "missed" should always be >= 1, but just to be sure, check it is not 0 anyway */
if (missed > 0)
info->wakeups_missed += (missed - 1);
}
int main()
{
struct periodic_info info;
make_periodic(10000, &info);
for (int i = 0; i < 1000; i++)
wait_period(&info);
return 0;
}
|
Die erwartete Laufzeit ist dementsprechend ca. 10s und tatsächlich wird dies eingehalten:
$ time ./timerfd_demo
real 0m10.010s
user 0m0.000s
sys 0m0.027s
Mit POSIX-Timern lässt sich das folgendermaßen erreichen:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | /* posix_timer_demo.c, modified */
/* Original demo is from
C. Simmonds, Over and over again: periodic tasks in Linux, 2009
https://2net.co.uk/tutorial/periodic_threads
*/
#include <signal.h>
#include <time.h>
struct periodic_info
{
int sig;
sigset_t alarm_sig;
int wakeups_missed;
timer_t timer_id;
};
static int make_periodic (int unsigned period, struct periodic_info *info)
{
static int next_sig;
int ret;
unsigned int ns;
unsigned int sec;
struct sigevent sigev;
timer_t timer_id;
struct itimerspec itval;
/* Initialise next_sig first time through. We can't use static
initialisation because SIGRTMIN is a function call, not a constant */
if (next_sig == 0)
next_sig = SIGRTMIN;
/* Check that we have not run out of signals */
if (next_sig > SIGRTMAX)
return -1;
info->sig = next_sig;
next_sig++;
info->wakeups_missed = 0;
/* Create the signal mask that will be used in wait_period */
sigemptyset (&(info->alarm_sig));
sigaddset (&(info->alarm_sig), info->sig);
/* Create a timer that will generate the signal we have chosen */
sigev.sigev_notify = SIGEV_SIGNAL;
sigev.sigev_signo = info->sig;
sigev.sigev_value.sival_ptr = (void *) &timer_id;
ret = timer_create (CLOCK_MONOTONIC, &sigev, &timer_id);
if (ret == -1)
return ret;
/* Make the timer periodic */
sec = period/1000000;
ns = (period - (sec * 1000000)) * 1000;
itval.it_interval.tv_sec = sec;
itval.it_interval.tv_nsec = ns;
itval.it_value.tv_sec = sec;
itval.it_value.tv_nsec = ns;
ret = timer_settime (timer_id, 0, &itval, NULL);
return ret;
}
static void wait_period (struct periodic_info *info)
{
int sig;
sigwait (&(info->alarm_sig), &sig);
info->wakeups_missed += timer_getoverrun (info->timer_id);
}
int main(int argc, char *argv[])
{
struct periodic_info info;
sigset_t alarm_sig;
int i;
/* Block all real time signals so they can be used for the timers.
Note: this has to be done in main() before any threads are created
so they all inherit the same mask. Doing it later is subject to
race conditions */
sigemptyset (&alarm_sig);
for (i = SIGRTMIN; i <= SIGRTMAX; i++)
sigaddset (&alarm_sig, i);
sigprocmask (SIG_BLOCK, &alarm_sig, NULL);
make_periodic(10000, &info);
for (int i = 0; i < 1000; i++)
wait_period(&info);
return 0;
}
|
Auch hier passt die real vergangene Zeit:
$ time ./posix_timer_demo
real 0m10.012s
user 0m0.000s
sys 0m0.032s
Und abschließend mit setitimer
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | /* settimer_demo.c, modified */
/* Original demo is from
C. Simmonds, Over and over again: periodic tasks in Linux, 2009
https://2net.co.uk/tutorial/periodic_threads
*/
#include <signal.h>
#include <sys/time.h>
#include <time.h>
#include <stdio.h>
struct periodic_info
{
sigset_t alarm_sig;
};
static int make_periodic (unsigned int period, struct periodic_info *info)
{
int ret;
struct itimerval value;
/* Block SIGALRM in this thread */
sigemptyset (&(info->alarm_sig));
sigaddset (&(info->alarm_sig), SIGALRM);
pthread_sigmask (SIG_BLOCK, &(info->alarm_sig), NULL);
/* Set the timer to go off after the first period and then
repetitively */
value.it_value.tv_sec = period/1000000;
value.it_value.tv_usec = period%1000000;
value.it_interval.tv_sec = period/1000000;
value.it_interval.tv_usec = period%1000000;
ret = setitimer (ITIMER_REAL, &value, NULL);
if (ret != 0)
perror ("Failed to set timer");
return ret;
}
static void wait_period (struct periodic_info *info)
{
int sig;
/* Wait for the next SIGALRM */
sigwait (&(info->alarm_sig), &sig);
}
int main(int argc, char *argv[])
{
struct periodic_info info;
make_periodic(10000, &info);
for (int i = 0; i < 1000; i++)
wait_period(&info);
return 0;
}
|
$ time ./setitimer_demo
real 0m10.010s
user 0m0.009s
sys 0m0.021s