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