2015-12-26 18 views
6

Sono ancora un po 'confuso sul motivo per cui non è sicuro ricevere un segnale e chiamare una funzione di sicurezza asincrona dall'interno di quel gestore di segnale. Qualcuno potrebbe spiegare il ragionamento alla base di questo ed eventualmente provare a darmi alcuni riferimenti che posso seguire per leggere di più su questo me stesso?Perché solo le funzioni di sicurezza del segnale asincrono possono essere chiamate dai gestori di segnale in modo sicuro?

In altre parole, sto chiedendo perché non è sicuro dire call printf da un gestore di segnale. È a causa di problemi intra-processo e possibili condizioni di gara derivanti da due possibili chiamate a printf senza protezione o è a causa di gare tra processi alla stessa risorsa (in questo esempio stdout). Dire un thread nel processo A sta chiamando printf e un altro thread riceve il segnale e poi chiama printf. Forse perché il kernel qui non saprà cosa fare perché non sarà in grado di distinguere tra le due chiamate.

+1

Il formato breve è "perché lo standard lo dice" o "perché la tua libreria lo dice". Il formato lungo è che il codice non è stato scritto con capacità di accesso asincrono: ci sono una miriade di modi in cui il codice può essere scritto in modo che non gestisca async bene Quasi tutti i modi di gestire async hanno dei costi quando vengono eseguiti in modo sincrono, quindi avere le funzioni di gestione asincrone spesso ha un senso delle prestazioni spesso – Yakk

+0

Grazie ! Se strutturo il mio codice in modo tale da non chiamare funzioni sicure non asincrone in modo parallelo ma nel contesto di un gestore di segnale non restituito. – Curious

+0

versione specifica 'malloc': https://stackoverflow.com/questions/3366307/why-is-malloc-not-async-sync-safe –

risposta

6

Dire che un thread nel processo A sta chiamando printf e un altro thread riceve il segnale e quindi chiama printf. È forse perché il kernel qui non saprà cosa fare perché non sarà in grado di distinguere tra le due chiamate in .

Non è il kernel che avrà problemi. È la tua applicazione stessa. printf non è una funzione del kernel. È una funzione nella libreria C utilizzata dall'applicazione. printf è in realtà una funzione abbastanza complicata. Supporta un'ampia varietà di formattazione di output.

Il risultato finale di questa formattazione è una stringa di output formattata scritta sullo standard output. Quel processo in sé implica anche un po 'di lavoro. La stringa di output formattata viene scritta nel buffer di output dell'handle di file stdout interno. Il buffer di output viene svuotato (e solo a questo punto il kernel prende il sopravvento e scrive un blocco definito di dati in un file) ogni volta che si verificano determinate condizioni, vale a dire quando il buffer di output è pieno e/o ogni volta che un carattere di nuova riga viene scritto in il flusso di output.

Tutto ciò è supportato dalle strutture dati interne del buffer di output, che non devi preoccuparti perché è il lavoro della libreria C. Ora, un segnale può arrivare in qualsiasi momento mentre printf funziona. E intendo, in qualsiasi momento. Potrebbe arrivare molto bene mentre printf nel mezzo dell'aggiornamento della struttura interna dei dati del buffer di output e si trovano in uno stato temporaneo incoerente perché printf non ha ancora finito di aggiornarlo.

Esempio: sulle moderne implementazioni C/C++, printf potrebbe non essere sicuro per i segnali, ma è thread-safe. Più thread possono utilizzare printf per scrivere sullo standard output. È responsabilità dei thread coordinare questo processo tra di loro, per assicurarsi che l'output finale abbia effettivamente senso, e non sia confuso, a caso, dall'output di più thread, ma questo è oltre il punto.

Il punto è che printf è thread-safe, e che in genere significa che da qualche parte c'è un mutex coinvolto nel processo. Così, la sequenza di eventi che potrebbero verificarsi è:

  • printf acquisisce il mutex interno.

  • printf procede con il suo lavoro con la formattazione della stringa e la scrittura nel buffer di output di stdout.

  • prima che lo printf sia completato e possa rilasciare il mutex acquisito, arriva un segnale.

Ora, l'interno mutex è bloccato. Il problema dei gestori di segnale è che generalmente non viene specificato quale thread, in un processo, può gestire il segnale. Una determinata implementazione potrebbe scegliere un thread a caso, oppure potrebbe sempre selezionare il thread attualmente in esecuzione. In ogni caso, può certamente scegliere il thread che ha bloccato lo printf, qui, per gestire il segnale.

Così ora, il tuo gestore di segnale gira, e decide anche di chiamare printf. Poiché il mutex interno di printf è bloccato, il thread deve attendere che il mutex venga sbloccato.

E attendere.

E attendere.

Perché, se si tenesse traccia di cose: il mutex è bloccato dal thread che è stato interrotto per servire il segnale. Il mutex non verrà sbloccato finché il thread non riprenderà a girare. Ma ciò non accadrà fino a quando il gestore del segnale non termina e il thread riprende a funzionare, ma il gestore del segnale è ora in attesa che il mutex si sblocchi.

Sei disossato.

Ora, naturalmente, printf potrebbe utilizzare l'equivalente C++ di std::recursive_mutex, per evitare questo problema, ma anche questo non risolverà tutti i deadlock possibili che potrebbero essere introdotti da un segnale.

Per riassumere, il motivo per cui è "non sicuro ricevere un segnale e chiamare una funzione sicura asincrona dall'interno di quel gestore di segnale" è perché non lo è, per definizione. Non è sicuro chiamare una funzione sicura non asincrona dal gestore del segnale "perché il segnale è un evento asincrono e poiché non è una funzione asincrona, non è possibile, per definizione, l'acqua è bagnata perché è acqua, e una funzione asincrona non sicura non può essere chiamata da un gestore di segnali asincroni

+1

Grazie! ciò ha senso. Finché è un problema di processo intra penso di poter aggirare il problema. – Curious

+0

mutex ricorsivo è peggio in un certo senso: stai modificando la struttura dei dati, e ora stai modificando di nuovo allo stesso tempo. – Yakk