In che modo Linux "uccide" un processo?


91

Mi sorprende spesso il fatto che, sebbene io abbia lavorato in modo professionale con i computer per diversi decenni e Linux per un decennio, in realtà tratta la maggior parte delle funzionalità del sistema operativo come una scatola nera, non diversamente dalla magia.

Oggi ho pensato al killcomando e mentre lo uso più volte al giorno (sia nella sua "normalità" che nel suo -9sapore), devo ammettere che non ho assolutamente idea di come funzioni dietro le quinte.

Dal mio punto di vista, se un processo in esecuzione è "bloccato", invoco killil suo PID e improvvisamente non è più in esecuzione. Magia!

Cosa succede davvero lì? Manpage parla di "segnali" ma sicuramente è solo un'astrazione. L'invio kill -9a un processo non richiede la cooperazione del processo (come la gestione di un segnale), lo uccide e basta.

  • In che modo Linux impedisce al processo di continuare ad occupare il tempo della CPU?
  • È stato rimosso dalla pianificazione?
  • Disconnette il processo dai suoi handle di file aperti?
  • Come viene rilasciata la memoria virtuale del processo?
  • Esiste qualcosa come una tabella globale in memoria, in cui Linux mantiene i riferimenti a tutte le risorse occupate da un processo e quando "uccido" un processo, Linux passa semplicemente attraverso quella tabella e libera le risorse una per una?

Mi piacerebbe davvero sapere tutto questo!



Questa mia risposta su una domanda su SIGKILL potrebbe essere rilevante anche qui.
telcoM,

Risposte:


72

L'invio di kill -9 a un processo non richiede la cooperazione del processo (come la gestione di un segnale), ma lo uccide.

Stai presumendo che, poiché alcuni segnali possono essere catturati e ignorati, tutti implicano una cooperazione. Ma secondo man 2 signal"i segnali SIGKILL e SIGSTOP non possono essere catturati o ignorati". SIGTERM può essere catturato, motivo per cui la pianura killnon è sempre efficace - in genere questo significa che qualcosa nel gestore del processo è andato storto. 1

Se un processo non definisce (o non può) un gestore per un determinato segnale, il kernel esegue un'azione predefinita. Nel caso di SIGTERM e SIGKILL, questo serve a terminare il processo (a meno che il suo PID non sia 1; il kernel non terminerà init) 2 significa che i suoi handle di file sono chiusi, la sua memoria restituita al pool di sistema, il suo genitore riceve SIGCHILD, il suo orfano i bambini sono ereditati da init, ecc., proprio come se avesse chiamato exit(vedi man 2 exit). Il processo non esiste più - a meno che non finisca come uno zombi, nel qual caso è ancora elencato nella tabella dei processi del kernel con alcune informazioni; ciò accade quando il suo genitore nowaite gestire correttamente queste informazioni. Tuttavia, ai processi di zombi non è più assegnata alcuna memoria e pertanto non possono continuare ad essere eseguiti.

C'è qualcosa come una tabella globale in memoria in cui Linux mantiene i riferimenti a tutte le risorse assorbite da un processo e quando "uccido" un processo Linux passa semplicemente attraverso quella tabella e libera le risorse una per una?

Penso che sia abbastanza preciso. La memoria fisica viene tracciata per pagina (una pagina di solito equivale a un blocco di 4 KB) e tali pagine vengono prese e restituite a un pool globale. È un po 'più complicato il fatto che alcune pagine liberate vengano memorizzate nella cache nel caso in cui i dati in esse contenuti siano nuovamente necessari (ovvero dati letti da un file ancora esistente).

Manpage parla di "segnali" ma sicuramente è solo un'astrazione.

Certo, tutti i segnali sono un'astrazione. Sono concettuali, proprio come i "processi". Sto giocando un po 'di semantica, ma se vuoi dire che SIGKILL è qualitativamente diverso da SIGTERM, allora sì e no. Sì nel senso che non può essere colto, ma no nel senso che sono entrambi segnali. Per analogia, una mela non è un'arancia, ma mele e arance sono, secondo una definizione preconcetta, entrambi frutti. SIGKILL sembra più astratto poiché non puoi prenderlo, ma è ancora un segnale. Ecco un esempio di gestione SIGTERM, sono sicuro che hai già visto questi prima:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>

void sighandler (int signum, siginfo_t *info, void *context) {
    fprintf (
        stderr,
        "Received %d from pid %u, uid %u.\n",
        info->si_signo,
        info->si_pid,
        info->si_uid
    );
}

int main (void) {
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_sigaction = sighandler;
    sa.sa_flags = SA_SIGINFO;
    sigaction(SIGTERM, &sa, NULL);
    while (1) sleep(10);
    return 0;
}

Questo processo dormirà per sempre. Puoi eseguirlo in un terminale e inviarlo con SIGTERM kill. Sputa cose come:

Received 15 from pid 25331, uid 1066.

1066 è il mio UID. Il PID sarà quello della shell da cui killviene eseguito, o il PID di kill se lo fork ( kill 25309 & echo $?).

Ancora una volta, non ha senso impostare un gestore per SIGKILL perché non funzionerà. 3 Se I kill -9 25309il processo terminerà. Ma questo è ancora un segnale; il kernel ha le informazioni su chi ha inviato il segnale , che tipo di segnale è, ecc.


1. Se non hai guardato l'elenco dei possibili segnali , vedi kill -l.

2. Un'altra eccezione, come cita Tim Post di seguito, si applica ai processi in modalità di sospensione ininterrotta . Questi non possono essere riattivati ​​fino a quando il problema di fondo non viene risolto, quindi tutti i segnali (incluso SIGKILL) vengono rinviati per la durata. Un processo non può creare quella situazione di proposito, tuttavia.

3. Questo non significa che usare kill -9sia una cosa migliore da fare in pratica. Il mio gestore di esempio è negativo nel senso che non porta a exit(). Il vero scopo di un gestore SIGTERM è di dare al processo la possibilità di fare cose come ripulire i file temporanei, quindi uscire volontariamente. Se lo usi kill -9, non ha questa possibilità, quindi fallo solo se la parte "esci volontariamente" sembra aver fallito.


Ok, ma con cosa uccidi il processo -9perché questo è il vero problema di chi si spaccia per questo! ;)
Kiwy,

@Kiwy: il kernel. IPC compresi i segnali lo attraversano; il kernel implementa le azioni predefinite.
Riccioli d'oro

12
Vale la pena ricordare che la sospensione del disco (D) impedisce tutti i segnali, mentre il processo è in quello stato. Quindi provare a kill -9certi processi di I / O associati non funzionerà, almeno non immediatamente.
Tim Post

7
Aggiungo che poiché kill -9non è possibile rilevare, un processo che lo riceve non può eseguire alcuna ripulitura (ad esempio rimozione di file temporanei, liberazione della memoria condivisa, ecc.) Prima che venga chiuso. Quindi, utilizzare kill -9(aka kill -kill) solo come ultima risorsa. Inizia con un kill -hupe / o kill -termprima e poi usa kill -killcome colpo finale.
JRFerguson,

"Il processo non esiste più - a meno che non finisca come uno zombi, nel qual caso è ancora elencato nella tabella dei processi del kernel con alcune informazioni" in realtà, tutti i processi passano allo stato di zombi quando muoiono e lo zombi scompare quando il genitore aspetta il bambino, di solito succede troppo in fretta perché tu lo veda accadere
intelligente

3

Ogni processo viene eseguito per un tempo programmato e quindi viene interrotto dal timer hardware, per fornire il core della CPU per altre attività. Questo è il motivo per cui è possibile avere molti più processi rispetto ai core della CPU, o persino eseguire tutti i sistemi operativi con molti processi su una singola CPU core.

Dopo che il processo è stato interrotto, il controllo torna al codice del kernel. Tale codice può quindi decidere di non riprendere l'esecuzione del processo interrotto, senza alcuna cooperazione da parte del processo. kill -9 può finire l'esecuzione in qualsiasi riga del programma.


0

Ecco una descrizione idealizzata di come funziona l'uccisione di un processo. In pratica, qualsiasi variante Unix avrà molte ulteriori complicazioni e ottimizzazioni.

Il kernel ha una struttura di dati per ogni processo che memorizza informazioni su quale memoria sta mappando, quali thread ha e quando sono programmati, quali file ha aperto, ecc. Se il kernel decide di terminare un processo, prende nota in la struttura dei dati del processo (e forse nella struttura dei dati di ciascuno dei thread) che il processo deve essere ucciso.

Se uno dei thread del processo è attualmente programmato su un'altra CPU, il kernel potrebbe innescare un interruzione su quell'altra CPU per impedire a quel thread di interrompere l'esecuzione più rapidamente.

Quando lo scheduler nota che un thread è in un processo che deve essere ucciso, non lo pianificherà più.

Quando nessuno dei thread del processo è più programmato, il kernel inizia a liberare le risorse del processo (memoria, descrittori di file, ...). Ogni volta che il kernel libera una risorsa, controlla se il suo proprietario ha ancora risorse attive. Quando il processo non ha più risorse attive (mapping di memoria, descrittore di file aperto, ...), la struttura dei dati per il processo stesso può essere liberata e la voce corrispondente può essere rimossa dalla tabella del processo.

Alcune risorse possono essere liberate immediatamente (ad es. Deallocazione della memoria non utilizzata da un'operazione di I / O). Altre risorse devono attendere, ad esempio i dati che descrivono un'operazione di I / O non possono essere liberati mentre l'operazione di I / O è in corso (mentre è in corso un DMA , la memoria a cui sta accedendo è in uso e la cancellazione del DMA richiede contattare la periferica). Il driver per tale risorsa viene avvisato e può tentare di affrettare la cancellazione; una volta che l'operazione non è più in corso, il driver completerà la liberazione di quella risorsa.

(La voce nella tabella dei processi è in realtà una risorsa che appartiene al processo padre, che viene liberata quando il processo termina e il padre riconosce l'evento .)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.