Perché SIGKILL non termina un programma arrestato (sì)?


8

Sto usando Ubuntu 14.04 e sto riscontrando questo comportamento che non riesco a capire:

  1. Esegui il yescomando (nella shell predefinita: Bash )
  2. Digitare CtrlZper interrompereyes
  3. Corri jobs. Produzione:
    [1]+ Stopped yes
  4. Corri kill -9 %1per fermarti yes. Produzione:
    [1]+ Stopped yes
  5. Corri jobs. Produzione:
    [1]+ Stopped yes

Questo è su Ubuntu in 3.16.0-30-genericesecuzione in una macchina virtuale parallela.

Perché il mio kill -9comando non ha terminato il comando yes ? Pensavo che SIGKILL non potesse essere catturato o ignorato? E come posso terminare il comando yes ?


1
Interessante. SIGKILL dovrebbe funzionare e funziona sul mio Linux Mint 17. Per qualsiasi altro segnale, normalmente dovresti inviarlo SIGCONT in seguito per assicurarti che il segnale venga ricevuto dal target arrestato.
PSkocik,

Bash stampa davvero "Stopped" per un processo sospeso ?
Edmz,

Versione del kernel ( uname -a) per favore
roaima,

Linux ubuntu 3.16.0-30-generic #40~14.04.1-Ubuntu SMP Thu Jan 15 17:43:14 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux. Sto eseguendo Ubuntu in Parallels Desktop.
s1m0n,

1
@black la maggior parte delle shell dice "Stopped". tcsh dice "sospeso" e zsh dice "sospeso". Una differenza cosmetica. Un po 'più importante è il fatto che bash stampa un messaggio identico per STOP e TSTP, dove tutte le altre shell contrassegnano l'annotazione del messaggio STOP in (signal)modo da poter dire la differenza.

Risposte:


10

I segnali sono bloccati per i processi sospesi. In un terminale:

$ yes
...
y
y
^Zy

[1]+  Stopped                 yes

In un secondo terminale:

$ killall yes

Nel primo terminale:

$ jobs
[1]+  Stopped                 yes

$ fg
yes
Terminated

Tuttavia SIGKILLnon può essere bloccato. Fare la stessa cosa con killall -9 yesdal secondo terminale dà immediatamente questo nel yesterminale:

[1]+  Killed                  yes

Di conseguenza, se kill -9 %1non termina immediatamente il processo, o in bashrealtà non sta inviando il segnale fino a quando non si esegue fgil processo o non si è scoperto un bug nel kernel.


4
Alcuni dettagli di base: quando si rilascia Ctrl + Z nel terminale bash invia un SIGTSTP(che è la versione bloccabile di SIGSTOP) al processo attivo. Questo mette il processo in uno stato congelato in cui il kernel non lo pianificherà. Ciò inibisce anche l'elaborazione del segnale (ad eccezione del SIGCONTsegnale che sblocca il processo) e quindi impedisce che il processo venga ucciso immediatamente.
mreithub,

1
SIGKILL, a differenza di altri segnali, non è bloccato per i processi sospesi. L'invio del segnale KILL a un processo sospeso lo uccide, in modo asincrono, ma praticamente praticamente immediatamente.
Gilles 'SO- smetti di essere malvagio' il

1
@Gilles Questo è quello che stavo cercando di illustrare sopra: SIGTERMè bloccato, ma SIGKILLnon lo è. Ad ogni modo, secondo un commento di OP, il problema sembra essere che jobsnon rileva che il processo è morto, non il processo da cui non è stato ucciso kill -9 %1.
lcd047

1
Ma posso riprodurre il comportamento di s1m0n sul mio sistema (Debian, amd64, bash 4.3.30).
Gilles 'SO- smetti di essere malvagio' il

1
Sebbene SIGKILLnon possa essere bloccato, non vi è alcuna garanzia che verrà consegnato in qualsiasi momento significativo. Se un processo viene sospeso in attesa di bloccare l'I / O, ad esempio, SIGKILLnon arriverà fino a quando il processo non si riattiva. Questo potrebbe potenzialmente non essere mai, se non si verifica alcun I / O.
sapi,

7

Non fatevi prendere dal panico.

Non c'è niente di strano che sta succedendo. Non ci sono bug del kernel qui. Questo è un comportamento perfettamente normale dalla shell Bourne Again e da un sistema operativo multitasking.

La cosa da ricordare è che un processo si uccide , anche in risposta a SIGKILL. Quello che sta succedendo qui è che la shell Bourne Again si sta avvicinando alle cose prima che il processo che ha appena detto di uccidere si aggiri per uccidersi.

Considera cosa succede dal punto in cui yesè stato interrotto SIGTSTPe hai appena eseguito il killcomando con la shell Bourne Again:

  1. La shell invia SIGKILLal yesprocesso.
  2. In parallelo :
    1. Il yesprocesso è pianificato per l'esecuzione e si uccide immediatamente.
    2. La shell Bourne Again continua, emettendo un altro prompt.

Il motivo per cui stai vedendo una cosa e altre persone stanno vedendo un'altra è una semplice corsa tra due processi pronti per l'esecuzione, il cui vincitore è interamente dovuto a cose che variano sia da macchina a macchina che nel tempo. Il carico del sistema fa la differenza, così come il fatto che la tua CPU è virtuale.

Nel caso interessante, il dettaglio del passaggio 2 è questo:

  1. La shell Bourne Again continua.
  2. Come parte degli interni del killcomando integrato , contrassegna la voce nella sua tabella dei lavori come se fosse necessario un messaggio di notifica stampato nel successivo punto disponibile.
  3. Termina il killcomando e poco prima di stampare nuovamente il prompt controlla se deve stampare messaggi di notifica su eventuali lavori.
  4. Il yesprocesso non ha ancora avuto la possibilità di uccidersi, quindi per quanto riguarda la shell il lavoro è ancora in stato di arresto. Quindi la shell stampa una riga di stato del lavoro "Interrotto" per quel lavoro e reimposta il flag di notifica in sospeso.
  5. Il yesprocesso viene pianificato e si uccide da solo.
  6. Il kernel informa la shell, che è occupata a eseguire il suo editor da riga di comando, che il processo si è ucciso. La shell rileva il cambiamento di stato e contrassegna il lavoro come notifica ancora in sospeso.
  7. Basta premere enterper scorrere nuovamente il prompt di stampa per dare alla shell la possibilità di stampare il nuovo stato del lavoro.

I punti importanti sono:

  • I processi si uccidono. SIGKILLnon è magico. I processi verificano la presenza di segnali in sospeso quando si torna alla modalità applicazione dalla modalità kernel, che si verifica alla fine degli errori di pagina, interruzioni (non nidificate) e chiamate di sistema. L'unica cosa speciale è che il kernel non consente all'azione in risposta SIGKILLdi essere qualcosa di diverso dal suicidio immediato e incondizionato, senza tornare alla modalità applicazione. È importante sottolineare che i processi devono entrambi effettuare transizioni dalla modalità kernel a quella dell'applicazione ed essere pianificati per essere eseguiti al fine di rispondere ai segnali.
  • Una CPU virtuale è solo un thread su un sistema operativo host. Non è garantito che l'host abbia pianificato l'esecuzione della CPU virtuale. I sistemi operativi host non sono neanche magici.
  • I messaggi di notifica non vengono stampati quando si verificano cambiamenti dello stato del processo (a meno che non si utilizzi set -o notify). Vengono stampati quando la shell successiva raggiunge un punto nel suo ciclo di esecuzione che controlla per verificare se sono presenti notifiche in sospeso.
  • Il flag di notifica in sospeso viene impostato due volte, una killvolta per volta dal SIGCHLDgestore del segnale. Ciò significa che si possono vedere due messaggi se la shell è in esecuzione prima che il yesprocesso venga riprogrammato per uccidersi; uno un messaggio "Interrotto" e uno un messaggio "Ucciso".
  • Ovviamente, il /bin/killprogramma non ha alcun accesso alla tabella dei lavori interni della shell; quindi non vedrai questo comportamento con /bin/kill. Il flag di notifica in sospeso viene impostato solo una volta dal SIGCHLDgestore.
  • Per lo stesso motivo, non vedrai questo comportamento se esegui killil yesprocesso da un'altra shell.

3
Questa è una teoria interessante, ma l'OP inizia a digitare jobse la shell vede ancora il processo come vivo. Sarebbe una condizione di gara di programmazione insolitamente lunga. :)
lcd047

3
Prima di tutto, grazie per la tua risposta elaborativa! Sicuramente ho senso e chiarisco alcune cose .. Ma come detto sopra, posso eseguire moltiplicare i jobscomandi dopo i killquali tutti indicano ancora che il processo è appena stato interrotto. Mi hai comunque ispirato a continuare a sperimentare e l'ho scoperto: il messaggio [1]+ Terminated yesviene stampato non appena eseguo un altro comando esterno (non una shell incorporata come echoo jobs). Quindi posso correre jobsquanto mi piace e continua a stampare [1]+ Stopped yes. Ma non appena corro, lsper esempio, Bash stampa[1]+ Terminated yes
s1m0n

lcd047 non ha letto il tuo commento alla domanda; che era importante e avrebbe dovuto essere modificato all'inizio della domanda, correttamente. È facile sovraccaricare un sistema operativo host in modo che gli ospiti sembrino programmare in modo molto strano, dall'interno. Proprio così, e altro ancora. (Una volta sono riuscito a causare una pianificazione piuttosto strana con un Bing Desktop in fuga che consumava la maggior parte del tempo della CPU host.)
JdeBP

1
@Gilles Il problema sembra essere che jobsnon nota che il processo è effettivamente morto ... Non so cosa fare riguardo allo stato in corso di aggiornamento eseguendo un altro comando.
lcd047

1
Perfino Gilles non ha visto il commento. Questo è il motivo per cui dovresti mettere questo tipo di cose importanti nella domanda , non seppellirlo in un commento. Gilles, la risposta parla chiaramente di ritardi nella consegna di un segnale, non ritardi l'invio di esso. Li hai confusi. Inoltre, leggi il commento dell'interrogante (e in effetti il ​​punto elenco che viene fornito qui) e osserva l'importantissimo presupposto fondamentale sbagliato che stai facendo. I processori virtuali non funzionano necessariamente in modalità passo-passo e non sono magicamente in grado di funzionare sempre alla massima velocità.
JdeBP,

2

Potrebbe succedere qualcosa di strano sul tuo sistema, sul mio la tua ricetta funziona bene sia con che senza -9:

> yes
...
^Z
[1]+  Stopped                 yes
> jobs
[1]+  Stopped                 yes
> kill %1
[1]+  Killed                  yes
> jobs
> 

Prendi il pid jobs -pe prova a ucciderlo come root.


Posso chiederti quale versione di distribuzione / kernel / bash stai usando? Forse il killcomando interno di bash fa il miglio supplementare e controlla se il lavoro è bloccato (potresti voler provare a scoprire il PID del lavoro e ucciderlo usando env kill <pid>. In questo modo utilizzerai il killcomando vero e non l'integrato bash.
mreithub,

bash-4.2-75.3.1.x86_64 su opensuse 13.2. Il kill cmd non è quello interno:which kill /usr/bin/kill
Dan Cornilescu,

1
whichnon è un built-in bash, quindi which <anything>ti darà sempre il percorso per il comando effettivo. Ma provate a confronto kill --helpvs /usr/bin/kill --help.
mreithub,

Ah giusto. In effetti, è incorporato kill.
Dan Cornilescu,

2

Quello che stai osservando è un bug in questa versione di bash.

kill -9 %1uccide immediatamente il lavoro. Puoi osservarlo con ps. È possibile tracciare il processo bash per vedere quando killviene chiamata la chiamata di sistema e tracciare il sottoprocesso per vedere quando riceve ed elabora i segnali. Ancora più interessante, puoi andare a vedere cosa sta succedendo al processo.

bash-4.3$ sleep 9999
^Z
[1]+  Stopped                 sleep 9999
bash-4.3$ kill -9 %1

[1]+  Stopped                 sleep 9999
bash-4.3$ jobs
[1]+  Stopped                 sleep 9999
bash-4.3$ jobs -l
[1]+  3083 Stopped                 sleep 9999
bash-4.3$ 

In un altro terminale:

% ps 3083
  PID TTY      STAT   TIME COMMAND
 3083 pts/4    Z      0:00 [sleep] <defunct>

Il sottoprocesso è uno zombi . È morto: tutto ciò che rimane è una voce nella tabella dei processi (ma nessuna memoria, codice, file aperti, ecc.). La voce viene lasciata in giro fino a quando il suo genitore non prende atto e recupera il suo stato di uscita chiamando la waitchiamata di sistema o uno dei suoi fratelli .

Una shell interattiva dovrebbe verificare la presenza di bambini morti e raccoglierli prima di stampare un prompt (se non configurato diversamente). Questa versione di bash non riesce a farlo in alcune circostanze:

bash-4.3$ jobs -l
[1]+  3083 Stopped                 sleep 9999
bash-4.3$ true
bash-4.3$ /bin/true
[1]+  Killed                  sleep 9999

Potresti aspettarti che bash riporti "Ucciso" non appena stampa il prompt dopo il killcomando, ma ciò non è garantito, perché c'è una condizione di competizione. I segnali vengono consegnati in modo asincrono: la killchiamata di sistema ritorna non appena il kernel ha capito a quali processi inviare il segnale, senza aspettare che venga effettivamente consegnato. È possibile, e in pratica accade, che bash abbia il tempo di controllare lo stato del suo sottoprocesso, scoprire che non è ancora morto ( wait4non segnala la morte di alcun bambino) e stampare che il processo è ancora interrotto. Ciò che è sbagliato è che prima del prossimo prompt, il segnale è stato consegnato ( pssegnala che il processo è morto), ma bash non ha ancora chiamatowait4(possiamo vederlo non solo perché riporta ancora il lavoro come "Interrotto", ma perché lo zombi è ancora presente nella tabella dei processi). In effetti, bash raccoglie lo zombi solo la prossima volta che deve chiamare wait4, quando viene eseguito un altro comando esterno.

Il bug è intermittente e non sono riuscito a riprodurlo mentre viene tracciato bash (presumibilmente perché è una condizione di gara in cui bash deve reagire rapidamente). Se il segnale viene inviato prima dei controlli bash, tutto accade come previsto.

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.