Quando un processo è in modalità utente, può essere interrotto in qualsiasi momento (passando alla modalità kernel). Quando il kernel ritorna in modalità utente, controlla se ci sono segnali in sospeso (inclusi quelli usati per terminare il processo, come SIGTERM
e SIGKILL
). Ciò significa che un processo può essere terminato solo al ritorno alla modalità utente.
Il motivo per cui un processo non può essere ucciso in modalità kernel è che potrebbe potenzialmente corrompere le strutture del kernel utilizzate da tutti gli altri processi nella stessa macchina (allo stesso modo l'uccisione di un thread può potenzialmente corrompere le strutture di dati utilizzate da altri thread nello stesso processo) .
Quando il kernel ha bisogno di fare qualcosa che potrebbe richiedere molto tempo (aspettando una pipe scritta da un altro processo o aspettando che l'hardware faccia qualcosa, per esempio), dorme contrassegnandosi come dormiente e chiamando lo scheduler per passare a un altro processo (se non c'è un processo non dormiente, passa a un processo "fittizio" che dice alla cpu di rallentare un po' e si siede in un ciclo — il ciclo inattivo).
Se un segnale viene inviato a un processo dormiente, deve essere risvegliato prima che ritorni nello spazio utente e quindi elabori il segnale in attesa. Qui abbiamo la differenza tra i due principali tipi di sonno:
TASK_INTERRUPTIBLE
, il sonno interrompibile. Se un'attività è contrassegnata da questo flag, sta dormendo, ma può essere svegliata da segnali. Ciò significa che il codice che ha contrassegnato l'attività come dormiente si aspetta un possibile segnale e dopo che si è svegliato lo verificherà e tornerà dalla chiamata di sistema. Dopo che il segnale è stato gestito, la chiamata di sistema può potenzialmente essere riavviata automaticamente (e non entrerò nei dettagli su come funziona).TASK_UNINTERRUPTIBLE
, il sonno ininterrotto. Se un'attività è contrassegnata da questo flag, non si aspetta di essere svegliata da qualcosa di diverso da ciò che sta aspettando, perché non può essere riavviata facilmente o perché i programmi si aspettano che la chiamata di sistema sia atomica. Questo può essere utilizzato anche per i sonni noti per essere molto brevi.
TASK_KILLABLE
(menzionato nell'articolo LWN collegato dalla risposta di ddaa) è una nuova variante.
Questo risponde alla tua prima domanda. Per quanto riguarda la tua seconda domanda:non puoi evitare le pause ininterrotte, sono una cosa normale (succede, ad esempio, ogni volta che un processo legge/scrive da/sul disco); tuttavia, dovrebbero durare solo una frazione di secondo. Se durano molto più a lungo, di solito significa un problema hardware (o un problema del driver del dispositivo, che sembra lo stesso al kernel), in cui il driver del dispositivo sta aspettando che l'hardware faccia qualcosa che non accadrà mai. Può anche significare che stai utilizzando NFS e che il server NFS è inattivo (è in attesa del ripristino del server; puoi anche utilizzare l'opzione "intr" per evitare il problema).
Infine, il motivo per cui non puoi ripristinare è lo stesso motivo per cui il kernel attende fino al ritorno in modalità utente per inviare un segnale o terminare il processo:potrebbe potenzialmente corrompere le strutture dati del kernel (il codice in attesa di una sospensione interrompibile può ricevere un errore che gli dice per tornare nello spazio utente, dove il processo può essere terminato; il codice in attesa di una sospensione non interrompibile non si aspetta alcun errore).
I processi non interrompibili sono SOLITAMENTE in attesa di I/O a seguito di un errore di pagina.
Considera questo:
- Il thread tenta di accedere a una pagina che non è nel core (o un eseguibile che è caricato a richiesta, una pagina di memoria anonima che è stata scambiata o un file mmap() caricato a richiesta, che sono più o meno la stessa cosa)
- Il kernel ora sta (provando a) caricarlo
- Il processo non può continuare finché la pagina non è disponibile.
Il processo/attività non può essere interrotto in questo stato, perché non può gestire alcun segnale; se lo facesse, si verificherebbe un altro errore di pagina e tornerebbe dov'era.
Quando dico "processo", intendo davvero "attività", che sotto Linux (2.6) si traduce approssimativamente in "thread" che può avere o meno una singola voce "thread group" in /proc
In alcuni casi, potrebbe essere in attesa per molto tempo. Un tipico esempio di ciò sarebbe dove il file eseguibile o mmap'd si trova su un filesystem di rete in cui il server ha fallito. Se l'I/O alla fine ha esito positivo, l'attività continuerà. Se alla fine fallisce, l'attività generalmente otterrà un SIGBUS o qualcosa del genere.
Un processo non interrompibile è un processo che si trova in una chiamata di sistema (funzione del kernel) che non può essere interrotto da un segnale.
Per capire cosa significa, è necessario comprendere il concetto di chiamata di sistema interrompibile. L'esempio classico è read()
. Questa è una chiamata di sistema che può richiedere molto tempo (secondi) poiché può potenzialmente comportare la rotazione di un disco rigido o lo spostamento di teste. Durante la maggior parte di questo tempo, il processo sarà inattivo, bloccando l'hardware.
Mentre il processo è dormiente nella chiamata di sistema, può ricevere un segnale asincrono Unix (ad esempio, SIGTERM), quindi accade quanto segue:
- La chiamata di sistema termina prematuramente ed è impostata per restituire -EINTR allo spazio utente.
- Il gestore del segnale viene eseguito.
- Se il processo è ancora in esecuzione, ottiene il valore restituito dalla chiamata di sistema e può effettuare nuovamente la stessa chiamata.
Il ritorno anticipato dalla chiamata di sistema consente al codice dello spazio utente di modificare immediatamente il proprio comportamento in risposta al segnale. Ad esempio, terminare in modo pulito in reazione a SIGINT o SIGTERM.
D'altra parte, alcune chiamate di sistema non possono essere interrotte in questo modo. Se il sistema chiama in stallo per qualche motivo, il processo può rimanere indefinitamente in questo stato irreversibile.
LWN ha pubblicato un bell'articolo che ha toccato questo argomento a luglio.
Per rispondere alla domanda originale:
-
Come evitare che ciò accada:scopri quale driver ti sta causando problemi e smetti di usarlo oppure diventa un hacker del kernel e risolvilo.
-
Come terminare un processo ininterrotto senza riavviare:in qualche modo fai terminare la chiamata di sistema. Spesso il modo più efficace per eseguire questa operazione senza premere l'interruttore di alimentazione è tirare il cavo di alimentazione. Puoi anche diventare un hacker del kernel e fare in modo che il driver utilizzi TASK_KILLABLE, come spiegato nell'articolo LWN.
Alla tua terza domanda:penso che tu possa terminare i processi non interrompibili eseguendo sudo kill -HUP 1
. Riavvierà init senza terminare i processi in esecuzione e dopo averlo eseguito, i miei processi non interrompibili erano spariti.