GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Erstellen Sie einen Timer unter Linux

Das Timing bestimmter Ereignisse ist eine häufige Aufgabe für einen Entwickler. Gängige Szenarien für Timer sind Watchdogs, die zyklische Ausführung von Aufgaben oder das Planen von Ereignissen für eine bestimmte Zeit. In diesem Artikel zeige ich, wie man mit timer_create(...) einen POSIX-kompatiblen Intervalltimer erstellt.

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ählen Sie die CMakeLists.txt :

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). Wechseln Sie mit dem grün markierten Menü zwischen den Konfigurationen für jedes Beispiel und aktivieren Sie Im Terminal ausführen für jeden von ihnen (siehe die gelbe Markierung unten). Das gerade aktive Beispiel zum Bauen und Debuggen kann über Debug ausgewählt werden Schaltfläche in der unteren linken Ecke (siehe die orangefarbene Markierung unten):

Threading-Timer

Werfen wir einen Blick auf simple_threading_timer.c Beispiel. Dies ist die einfachste:Sie zeigt, wie ein Intervalltimer erstellt wird, der die Funktion expired aufruft bei Ablauf. Bei jedem Ablauf wird ein neuer Thread erstellt, in dem die Funktion expiration heißt.

#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());
}

Der Vorteil dieses Ansatzes ist sein geringer Platzbedarf in Bezug auf Code und einfaches Debugging. Der Nachteil ist der zusätzliche Overhead durch die Erstellung eines neuen Threads bei Ablauf und folglich das weniger deterministische Verhalten.

Weitere Linux-Ressourcen

  • Spickzettel für Linux-Befehle
  • Spickzettel für fortgeschrittene Linux-Befehle
  • Kostenloser Online-Kurs:RHEL Technical Overview
  • Spickzettel für Linux-Netzwerke
  • SELinux-Spickzettel
  • Spickzettel für allgemeine Linux-Befehle
  • Was sind Linux-Container?
  • Unsere neuesten Linux-Artikel

Unterbrechungssignal-Timer

Eine andere Möglichkeit, von einem 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 Signal-Handler wird 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 Debugging möglich ist.

Das Standardverhalten von Qt Creator, wenn das zu debuggende Programm ein Signal empfängt, ist:

  • Ausführung unterbrechen und in den Debugger-Kontext wechseln.
  • Ein Popup-Fenster anzeigen, 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 erhält, gehen Sie zu Extras -> Optionen , wählen Sie Debugger aus , und navigieren Sie zu Locals &Expressions . Fügen Sie den folgenden Ausdruck zu Debugging Helper Customization hinzu :

handle SIG34 nostop pass

Weitere Informationen zum GDB-Signalhandling finden Sie in der GDB-Dokumentation.

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

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

Jetzt können Sie den signal_interrupt_timer richtig debuggen . 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: %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());
}

Im Gegensatz zum Threading-Timer müssen wir das Signal initialisieren und einen Signalhandler registrieren. Dieser Ansatz ist leistungsfähiger, da er nicht zur Erstellung zusätzlicher Threads führt. Aus diesem Grund ist auch die Ausführung des Signalhandlers deterministischer. Der Nachteil ist eindeutig der zusätzliche Konfigurationsaufwand, um dies richtig zu debuggen.

Zusammenfassung

Beide in diesem Artikel beschriebenen Methoden sind Kernel-nahe Implementierungen von Timern. 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 Kontrolle für allgemeine Timing-Anwendungen.


Linux
  1. Erstellen Sie einen verschlüsselten Dateitresor unter Linux

  2. So erstellen Sie ein Skript eines Linux-Befehls

  3. So erstellen Sie ein Linux-RPM-Paket

  4. So erstellen Sie einen Swap unter Linux

  5. So erstellen Sie einen Systemd-Dienst unter Linux

So erstellen Sie Verknüpfungen auf dem Linux-Desktop

So erstellen Sie einen SSH-Alias ​​unter Linux

So erstellen Sie einen Alias ​​unter Linux

4 Möglichkeiten zum Erstellen einer neuen Datei unter Linux

Der Timer-Befehl in Linux

Eine Partition unter Linux erstellen – Eine Schritt-für-Schritt-Anleitung