Semplice gestione del segnale Linux

Ho un programma che crea molti thread e corre fino a quando non si spegne il computer embedded, o l’utente usa kill o ctrl c per terminare il processo.

Ecco un codice e come appare l’aspetto principale ().

 static int terminate = 0; // does this need to be volatile? static void sighandler(int signum) { terminate = 1; } int main() { signal(SIGINT, sighandler); // ... // create objects, spawn threads + allocate dynamic memory // ... while (!terminate) sleep(2); // ... // clean up memory, close threads, etc. // ... signal(SIGINT, SIG_DFL); // is this necessary? } 

Mi sto chiedendo alcune cose:

  1. È necessaria una gestione del segnale?
    Ho letto in questo thread “Linux C che cattura il segnale di uccisione per terminazione elegante” , che apparentemente il sistema operativo gestirà la pulizia per me. Pertanto, posso semplicemente sostituire il gestore di segnale con un loop infinito e lasciare che il sistema operativo esca con garbo dai thread, de-allocare la memoria, ecc.?

  2. Ci sono altri segnali di cui dovrei preoccuparmi per quanto riguarda la terminazione pulita? Questa discussione “Come si collega SIGINT agli altri segnali di terminazione?” , è stato utile elencare tutti i segnali di cui potrei essere interessato, ma quanti sono effettivamente necessari per la gestione?

  3. La variabile terminate nel mio esempio deve essere volatile? Ho visto molti esempi in cui questa variabile è volatile e altri in cui non lo è.

  4. Ho letto che signal() ora è deprecato e usare sigaction() . Ci sono dei buoni esempi per mostrare come convertire dalla precedente chiamata a signal() ? Sto avendo problemi con la nuova struttura che devo creare / passare e in che modo combaci.

  5. È necessaria la seconda chiamata a signal() ?
    C’è qualcosa di simile di cui dovrei occuparmi per sigaction() ?

Per essere chiari, tutto quello che sto cercando di ottenere è che il mio ciclo principale venga eseguito fino a quando ctrl c o power non viene disconnesso o succede qualcosa di veramente brutto.

[Q-3] La variabile terminate nel mio esempio deve essere volatile ? Ho visto molti esempi in cui questa variabile è volatile e altri in cui non lo è.

La bandiera terminate dovrebbe essere volatile sig_atomic_t :

Poiché le funzioni del gestore possono essere chiamate in modo asincrono. Cioè, un gestore può essere chiamato in qualsiasi punto del programma, in modo imprevedibile. Se arrivano due segnali durante un intervallo molto breve, un handler può correre all’interno di un altro. Ed è considerata una pratica migliore per dichiarare volatile sig_atomic_t , questo tipo è sempre accessibile a livello atomico, evitare l’incertezza sull’interruzione dell’accesso a una variabile. volatile dice al compilatore di non ottimizzare e metterlo in registro. (leggi: Atomic Data Access e Signal Handling per l’espiazione dei dettagli).
Un altro riferimento: 24.4.7 Accesso ai dati atomici e gestione dei segnali . Inoltre, lo standard C11 in 7.14.1.1-5 indica che è ansible accedere solo agli oggetti di volatile sig_atomic_t da un gestore di segnali (l’accesso ad altri ha un comportamento non definito).

[Q-4] Ho letto che signal() è ora deprecato e usare sigaction() . Ci sono dei buoni esempi per mostrare come convertire dalla precedente chiamata a signal() ? Sto avendo problemi con la nuova struttura che devo creare / passare e in che modo combaci.

L’esempio seguente (e il link nei commenti) può essere utile:

 // 1. Prepare struct struct sigaction sa; sa.sa_handler = sighandler; // 2. To restart functions if interrupted by handler (as handlers called asynchronously) sa.sa_flags = SA_RESTART; // 3. Set zero sigemptyset(&sa.sa_mask); /* 3b. // uncomment if you wants to block // some signals while one is executing. sigaddset( &sa.sa_mask, SIGINT ); */ // 4. Register signals sigaction( SIGINT, &sa, NULL ); 

Riferimenti:

  1. Inizio della Programmazione Linux, 4a Edizione : in questo libro, esattamente il tuo codice è spiegato con sigaction() in “Capitolo 11: Processi e Segnali”.
  2. La documentazione di sigaction , incluso un esempio (apprendimento rapido).
  3. La libreria GNU C: gestione dei segnali
    * Ho iniziato da 1 , Attualmente sto leggendo 3 librerie GNU

[Q-5] È necessaria la seconda chiamata a signal() ? C’è qualcosa di simile di cui dovrei occuparmi per sigaction() ?

Perché l’hai impostato su azione predefinita prima che la chiusura del programma non sia chiara per me. Penso che il seguente paragrafo ti darà una risposta:

Gestione dei segnali

La chiamata al segnale stabilisce la gestione del segnale per una sola occorrenza di un segnale. Prima che venga chiamata la funzione di gestione del segnale, la libreria ripristina il segnale in modo che l’azione predefinita venga eseguita se lo stesso segnale si ripresenta . Il ripristino della gestione del segnale aiuta a prevenire un loop infinito se, ad esempio, un’azione eseguita nel gestore del segnale aumenta di nuovo lo stesso segnale. Se si desidera che il gestore venga utilizzato per un segnale ogni volta che si verifica, è necessario chiamare il segnale all’interno del gestore per ripristinarlo. Dovresti essere cauto nel ripristinare la gestione del segnale. Ad esempio, se si ripristina continuamente la gestione di SIGINT , si potrebbe perdere la capacità di interrompere e terminare il programma.

La funzione signal() definisce solo il gestore del prossimo segnale ricevuto, dopo di che viene ripristinato il gestore predefinito . Quindi è necessario che il gestore di signal() chiami signal() se il programma deve continuare a gestire i segnali usando un gestore non predefinito.

Leggi una discussione per ulteriori riferimenti: quando ritriggersre i gestori di segnale .

[Q-1a] È necessaria una gestione del segnale?

Sì, Linux farà la pulizia per te. Ad esempio, se non si chiude un file o un socket, Linux eseguirà la pulizia al termine del processo. Ma Linux potrebbe non essere necessario eseguire immediatamente la pulizia e potrebbe richiedere del tempo (potrebbe essere necessario mantenere alte le prestazioni del sistema o altri problemi). Ad esempio, se non si chiude un socket tcp e il programma termina, il kernel non chiuderà immediatamente il socket per garantire che tutti i dati siano stati trasmessi, se ansible TCP garantisce la consegna.

[Q-1b] Pertanto, posso semplicemente sostituire il gestore di segnale con un loop infinito e lasciare che il sistema operativo esca con garbo dai thread, de-allocare la memoria, ecc.?

No, il sistema operativo esegue la pulizia solo dopo la fine del programma. Durante l’esecuzione di un processo, le risorse allocate a quel processo non vengono richieste dal sistema operativo. (Il sistema operativo non può sapere se il tuo processo si trova in un ciclo infinito o meno – questo è un problema irrisolvibile ). Se si desidera che la terminazione successiva al processo, il sistema operativo esegua le operazioni di pulizia, non è necessario gestire i segnali (anche nel caso in cui il processo venga interrotto in modo anomalo da un segnale).

[Q] Tutto quello che sto cercando di ottenere è che il mio: main loop venga eseguito fino a quando ctrl c o power non vengono scollegati o succede qualcosa di veramente brutto.

No, c’è una limitazione! Non puoi prendere tutti i segnali. Alcuni segnali non sono catchable ad es. SIGKILL e SIGSTOP ed entrambi sono segnali di terminazione. Citando uno:

– Macro: int SIGKILL

Il segnale SIGKILL viene utilizzato per causare la chiusura immediata del programma. Non può essere gestito o ignorato ed è quindi sempre fatale. Inoltre, non è ansible bloccare questo segnale.

Quindi non puoi creare un programma che non possa essere interrotto (un programma ininterrotto) !

Non sono sicuro, ma potrebbe essere che tu possa fare qualcosa di simile nei sistemi Windows: scrivendo TSR (una sorta di hook in modalità kernel). Ricordo dai tempi della mia tesi che alcuni virus non potevano essere terminati nemmeno dal task manager, ma credo anche che ingannassero l’utente per permessi di amministratore.

Spero che questa risposta ti possa aiutare.

Per usare invece sigaction , potresti usare una funzione come questa:

 /* * New implementation of signal(2), using sigaction(2). * Taken from the book ``Advanced Programming in the UNIX Environment'' * (first edition) by W. Richard Stevens. */ sighandler_t my_signal(int signo, sighandler_t func) { struct sigaction nact, oact; nact.sa_handler = func; nact.sa_flags = 0; # ifdef SA_INTERRUPT nact.sa_flags |= SA_INTERRUPT; # endif sigemptyset(&nact.sa_mask); if (sigaction(signo, &nact, &oact) < 0) return SIG_ERR; return oact.sa_handler; } 

1. È necessaria una gestione dei segnali?

  • Nel caso particolare del link che hai postato, sì. Le esecuzioni di software che riguardano la rete necessitano di particolari operazioni, come ad esempio il client di avviso della chiusura di un socket, per non disturbare la sua esecuzione.
  • Nel tuo caso particolare, non hai bisogno di gestire alcun segnale perché il processo sia chiaro con grazia: il tuo sistema operativo lo farà per te.

2. Ci sono altri segnali di cui dovrei preoccuparmi per quanto riguarda la terminazione pulita?

  • Per prima cosa, dai un’occhiata alla sua pagina: I segnali della GNU Library I segnali di terminazione sono ciò che stai cercando. Ma date un’occhiata a SIGUSR1 e SIGUSR2, anche se non li troverete mai in alcun software, tranne che per scopi di debug.

  • Tutti questi segnali di terminazione devono essere gestiti se non si desidera che il soft si interrompa all’improvviso.

3. La variabile di terminazione nel mio esempio deve essere volatile?

  • Assolutamente no.

4. Ho letto che signal() è ora deprecato e usare sigaction()

  • Sigaction() è POSIX mentre il segnale è uno standard C.

  • Signal() funziona bene per me, ma se vuoi un esempio: IBM Example

Non è necessario usare segnali per questo. Non è necessario catturare un terminato standard. Potresti avere dei motivi per catturarlo, ma questo sarebbe dovuto alla tua applicazione piuttosto che a qualsiasi altra cosa richiesta dal supervisore.

In termini di segnali generalmente si dovrebbe usare sigaction non segnale in questi giorni, si ottiene un problema di standardizzazione.

I gestori del segnale devono essere scritti per essere rientranti. Ciò non richiede che la variabile terminata sia volatile ma potrebbe, a seconda di come la si utilizza!

Il libro di W. Richard Stevens “Programmazione avanzata nell’ambiente UNIX” ha un buon esempio del perché e di come gestire i segnali.

E no, non devi rimettere il gestore predefinito prima che l’applicazione termini, il tuo gestore è valido solo per la tua applicazione, quindi se stai solo eliminando l’app, non è necessario.

Prima di tutto – se non lo sai, se dovresti gestire qualche segnale, allora probabilmente no. I segnali richiedono la gestione in alcuni casi specifici, come la chiusura di socket, l’invio di un messaggio ad altri processi connessi prima di uscire, o la gestione del segnale SIGPIPE da write () (e probabilmente molti altri).

Secondariamente – questo while (!terminate) sleep(2); non è molto buono – nel peggiore dei casi potrebbe rendere impaziente l’utente (o anche il sistema) di dover aspettare 2 secondi e inviare al tuo programma un SIGKILL, che non puoi gestire.

IMHO la soluzione migliore sarebbe utilizzare signalfd e select , in modo da poter terminare il programma senza dover attendere 2 secondi.