La tempistica di determinati eventi è un'attività comune per uno sviluppatore. Gli scenari comuni per i timer sono watchdog, esecuzione ciclica di attività o pianificazione di eventi per un tempo specifico. In questo articolo, mostro come creare un timer a intervalli conforme a POSIX utilizzando timer_create(...).
Puoi scaricare il codice sorgente per i seguenti esempi da GitHub.
Prepara Qt Creator
Ho usato Qt Creator come IDE per questo esempio. Per eseguire ed eseguire il debug del codice di esempio in Qt Creator, clonare il repository GitHub, aprire Qt Creator e andare su File -> Apri file o progetto... e scegli CMakeLists.txt :
Dopo aver selezionato la toolchain, fai clic su Configura progetto . Il progetto contiene tre esempi indipendenti (ne tratteremo solo due in questo articolo). Con il menu contrassegnato in verde, passa da una configurazione all'altra per ciascun esempio e attiva Esegui nel terminale per ciascuno di essi (vedi il segno giallo sotto). L'esempio attualmente attivo per la creazione e il debug può essere selezionato su Debug pulsante nell'angolo in basso a sinistra (vedi il segno arancione in basso):
Timer di filettatura
Diamo un'occhiata a simple_threading_timer.c esempio. Questo è il più semplice:mostra come viene creato un intervallometro, che chiama la funzione scaduto alla scadenza. Ad ogni scadenza viene creato un nuovo thread in cui compare la funzione scadenza viene chiamato.
#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());
}
Il vantaggio di questo approccio è il suo ingombro ridotto, in termini di codice e semplice debugging. Lo svantaggio è l'overhead aggiuntivo dovuto alla creazione di un nuovo thread alla scadenza e, di conseguenza, il comportamento meno deterministico.
Più risorse Linux
- Comandi Linux cheat sheet
- Cheat sheet sui comandi avanzati di Linux
- Corso online gratuito:Panoramica tecnica RHEL
- Cheat sheet della rete Linux
- Cheat sheet di SELinux
- Cheat sheet dei comandi comuni di Linux
- Cosa sono i container Linux?
- I nostri ultimi articoli su Linux
Timer segnale di interruzione
Un'altra possibilità per essere avvisati da un timer scaduto si basa su un segnale del kernel. Invece di creare un nuovo thread ogni volta che scade il timer, il kernel invia un segnale al processo, il processo viene interrotto e viene chiamato il corrispondente gestore del segnale.
Poiché l'azione predefinita quando si riceve un segnale è terminare il processo (vedere la pagina man del segnale), dobbiamo preparare in anticipo Qt Creator in modo che sia possibile eseguire correttamente il debug.
Il comportamento predefinito di Qt Creator quando il debuggee riceve un segnale è:
- Interrompi l'esecuzione e passa al contesto del debugger.
- Visualizza una finestra pop-up che notifica all'utente la ricezione di un segnale.
Entrambe le azioni non sono desiderate in quanto la ricezione di un segnale fa parte della nostra applicazione.
Qt Creator utilizza GDB in background. Per impedire a GDB di interrompere l'esecuzione quando il processo riceve un segnale, vai a Strumenti -> Opzioni , seleziona Debugger e vai a Locali ed espressioni . Aggiungi la seguente espressione a Personalizzazione dell'helper di debug :
handle SIG34 nostop pass
Puoi trovare maggiori informazioni sulla gestione del segnale GDB nella documentazione GDB.
Successivamente, vogliamo sopprimere la finestra pop-up che ci avvisa ogni volta che viene ricevuto un segnale quando ci fermiamo nel gestore del segnale:
Per farlo, vai alla scheda GDB e deseleziona la casella contrassegnata:
Ora puoi eseguire correttamente il debug di signal_interrupt_timer . L'effettiva implementazione del timer del segnale è un po' più complessa:
#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());
}
A differenza del timer di threading, dobbiamo inizializzare il segnale e registrare un gestore del segnale. Questo approccio è più performante in quanto non causerà la creazione di thread aggiuntivi. Per questo motivo, anche l'esecuzione del gestore del segnale è più deterministica. Lo svantaggio è chiaramente lo sforzo di configurazione aggiuntivo per eseguire correttamente il debug.
Riepilogo
Entrambi i metodi descritti in questo articolo sono implementazioni di timer vicine al kernel. Anche se la funzione timer_create(...) fa parte della specifica POSIX, non è possibile compilare il codice di esempio su un sistema FreeBSD a causa di piccole differenze nelle strutture dati. Oltre a questo inconveniente, tale implementazione offre un controllo granulare per applicazioni di temporizzazione generiche.