Advertisements

Einen Timer unter Linux erstellen

Das Timing bestimmter Ereignisse ist eine häufige Aufgabe für einen Entwickler. Übliche Szenarien für Timer sind Watchdogs, die zyklische Ausführung von Tasks oder das Planen von Ereignissen für eine bestimmte Zeit. In diesem Artikel zeige ich, wie Sie mit timer_create(…) einen POSIX-kompatiblen Intervall-Timer erstellen.

Sie können den Quellcode für die folgenden Beispiele von GitHub herunterladen.

Qt Creator vorbereiten

Ich habe Qt Creator als IDE für dieses Beispiel verwendet. Um den Beispielcode in Qt Creator auszuführen und zu debuggen, klonen Sie das GitHub-Repository, öffnen Sie Qt Creator und gehen Sie zu Datei -> Datei oder Projekt öffnen… und wähle die CMakeLists.txt:

posix_timers_open_project.png

Öffnen Sie ein Projekt in Qt Creator (CC-BY-SA 4.0)

Klicken Sie nach Auswahl der Toolchain auf Projekt konfigurieren. Das Projekt enthält drei unabhängige Beispiele (wir werden in diesem Artikel nur zwei davon behandeln). Mit dem grün markierten Menü zwischen den Konfigurationen für jedes Beispiel wechseln und aktivieren Im Terminal ausführen für jeden von ihnen (siehe die gelbe Markierung unten). Das aktuell aktive Beispiel zum Erstellen und Debuggen kann über das Debuggen Schaltfläche in der unteren linken Ecke (siehe orangefarbene Markierung unten):

posix_timers_project_configuration_2.png

Projektkonfiguration

Projektkonfiguration (CC-BY-SA 4.0)

Einfädel-Timer

Werfen wir einen Blick auf die simple_threading_timer.c Beispiel. Dies ist die einfachste: Sie zeigt, wie ein Intervall-Timer erstellt wird, der die Funktion aufruft abgelaufen bei Ablauf. Bei jedem Ablauf wird ein neuer Thread erstellt, in dem die Funktion Ablauf wird genannt.

#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: %dn", 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: %sn", strerror(errno));
        exit(-1);
    }

    /* start timer */
    res = timer_settime(timerId, 0, &its, NULL);

    if (res != 0){
        fprintf(stderr, "Error timer_settime: %sn", strerror(errno));
        exit(-1);
    }

    printf("Press ETNER Key to Exitn");
    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: %dn", ++data->myData, gettid());
}

Der Vorteil dieses Ansatzes ist sein geringer Platzbedarf in Bezug auf Code und einfaches Debugging. Nachteilig ist der zusätzliche Overhead durch das Anlegen eines neuen Threads beim Ablauf und damit das weniger deterministische Verhalten.

Weitere Linux-Ressourcen

Unterbrechungssignal-Timer

Eine weitere Möglichkeit, über einen abgelaufenen Timer benachrichtigt zu werden, basiert auf einem Kernel-Signal. Anstatt jedes Mal, wenn der Timer abläuft, einen neuen Thread zu erstellen, sendet der Kernel ein Signal an den Prozess, der Prozess wird unterbrochen und der entsprechende Signalhandler aufgerufen.

Da die Standardaktion beim Empfang eines Signals darin besteht, den Prozess zu beenden (siehe Signal-Manpage), müssen wir Qt Creator im Voraus vorbereiten, damit ein ordnungsgemäßes Debuggen möglich ist.

Das Standardverhalten von Qt Creator, wenn der Debuggee ein Signal empfängt, ist:

  • Unterbrechen Sie die Ausführung und wechseln Sie in den Debugger-Kontext.
  • Zeigen Sie ein Popup-Fenster an, das den Benutzer über den Empfang eines Signals informiert.

Beide Aktionen sind nicht erwünscht, da der Empfang eines Signals Teil unserer Anwendung ist.

Qt Creator verwendet GDB im Hintergrund. Um zu verhindern, dass GDB die Ausführung stoppt, wenn der Prozess ein Signal empfängt, gehen Sie zu Werkzeuge -> Optionen, auswählen Debugger, und navigieren Sie zu Einheimische & Ausdrücke. Fügen Sie den folgenden Ausdruck zu . hinzu Anpassung des Debugging-Helpers:

handle SIG34 nostop pass

posix_timers_sig34_nostop_pass.png

Kein Stopp mit Fehler signalisieren

Sig 34 kein Stopp mit Fehler (CC-BY-SA 4.0)

Weitere Informationen zum Umgang mit GDB-Signalen finden Sie in der GDB-Dokumentation.

Als nächstes möchten wir das Popup-Fenster unterdrücken, das uns jedes Mal benachrichtigt, wenn ein Signal empfangen wird, wenn wir im Signalhandler anhalten:

posix_timers_sig34_pop_up_2.png

Signal 34 Popup-Fenster

Popup-Fenster Signal 34 (CC-BY-SA 4.0)

Navigieren Sie dazu zum Reiter GDB und deaktivieren Sie das markierte Kontrollkästchen:

posix_timers_signal_windows.png

Timer-Signalfenster

Timer-Signalfenster (CC-BY-SA 4.0)

Jetzt können Sie die Fehler richtig debuggen signal_interrupt_timer. Die eigentliche Implementierung des Signaltimers ist etwas komplexer:

#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: %dn", 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: %sn", 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 %dn", SIGRTMIN);

    /* Register signal handler */
    if (sigaction(SIGRTMIN, &sa, NULL) == -1){
        fprintf(stderr, "Error sigaction: %sn", strerror(errno));
        exit(-1);
    }

    /* start timer */
    res = timer_settime(timerId, 0, &its, NULL);

    if ( res != 0){
        fprintf(stderr, "Error timer_settime: %sn", strerror(errno));
        exit(-1);
    }

    printf("Press ENTER to Exitn");
    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: %dn", ++data->myData, gettid());
}

Im Gegensatz zum Threading-Timer müssen wir das Signal initialisieren und einen Signalhandler registrieren. Dieser Ansatz ist leistungsfähiger, da keine zusätzlichen Threads erstellt werden. Aus diesem Grund ist auch die Ausführung des Signalhandlers deterministischer. Der Nachteil ist eindeutig der zusätzliche Konfigurationsaufwand, um dies ordnungsgemäß zu debuggen.

Zusammenfassung

Beide in diesem Artikel beschriebenen Methoden sind Kernel-nahe Implementierungen von Zeitgebern. Auch wenn die Funktion timer_create(…) Teil der POSIX-Spezifikation ist, ist es aufgrund kleiner Unterschiede in den Datenstrukturen nicht möglich, den Beispielcode auf einem FreeBSD-System zu kompilieren. Abgesehen von diesem Nachteil bietet Ihnen eine solche Implementierung eine feinkörnige Steuerung für allgemeine Timing-Anwendungen.

Einen Timer unter Linux erstellen

Leave a Reply

Your email address will not be published. Required fields are marked *

Scroll to top