I comandi Bash per gestire i processi in background e foreground

5 ottobre 2015 Nozioni di base di Linux, I comandi di Linux

Introduzione

In questa guida parleremo di come bash, l’intero sistema Linux ed il terminale si uniscano per offrire il controllo dei processi e dei job. In un’altra guida si è discusso di come i comandi ps, kill e nice possono essere utilizzati per controllare i processi sul proprio sistema.

Questo articolo è incentrato sulla gestione dei processi di foreground e background e dimostrerà come sfruttare le funzioni di controllo dei job della vostra shell per ottenere una maggiore flessibilità su come eseguire i comandi.

Gestione dei processi in foreground

La maggior parte dei processi che avvierete su una macchina Linux verrà eseguita in foreground. Il comando inizierà l’esecuzione bloccando l’uso della shell per la durata del processo. Il processo può consentire l’interazione dell’utente o può solo completare la sua eseguzione e poi terminare. Di default, ogni tipo di output verrà visualizzato nella finestra del terminale. Di seguito, discuteremo i fondamenti per gestire i processi di foreground.

Avvio di un processo

Di default, i processi vengono avviati in foreground. Fino a quando il programma non termina o non cambia di stato, non sarà in grado di interagire con la shell.

Alcuni comandi in foreground terminano molto rapidamente e vi restituiscono il prompt della shell quasi immediatamente. Per esempio, questo comando:

$ echo "Hello World"

Questo stampa “Hello World” sul terminale e poi vi restituisce il prompt dei comandi.

Altri comandi in foreground impiegano più tempo per essere eseguiti, bloccando l’accesso alla shell per tutta la durata dell’operazione. Ciò potrebbe dipendere dal fatto che il comando sta eseguendo un’operazione più lunga o perché è configurato per essere eseguito finché non viene esplicitamente interrotto o non vengono ricevuti altri input dall’utente.

Un comando che viene eseguito a tempo indeterminato è l’utilità top. Dopo l’avvio, continuerà a funzionare ed aggiornare il display fino a quando l’utente non termina il processo:

$ top

Potete uscire digitando “q”. Alcuni processi non hanno una funzione dedicata per la chiusura. Per fermarli, dovrete usare un altro metodo.

Terminazione di un processo

Supponiamo di far partire un semplice loop bash sulla linea di comando. Possiamo iniziare un ciclo che stamperà “Hello World” ogni dieci secondi. Questo ciclo continuerà per sempre, fino a quando non sarà terminato esplicitamente:

$ while true; do echo "Hello World"; sleep 10; done

I loop non hanno alcun tasto “quit”. Dovremo interrompere il processo con l’invio di un segnale. In Linux, il kernel può inviare dei segnali ai processi per chiederne la terminazione o il cambio di stato. I terminali Linux di solito sono configurati per inviare il segnale “SIGINT” (solitamente il segnale numero 2) per mandare in foreground il processo corrente quando si preme la combinazione di tasti CTRL-C. Il segnale SIGINT indica al programma che l’utente ha richiesto la terminazione usando la tastiera.

Per fermare il ciclo che abbiamo iniziato, tenete premuto il tasto control e premete il tasto “c”:

CTRL-C

Il ciclo terminerà, restituendo il controllo alla shell.

Il segnale SIGINT inviato dalla combinazione CTRL-C è uno dei tanti segnali che possono essere inviati ai programmi. La maggior parte dei segnali non hanno combinazioni di tasti ad essi associati e devono essere invece inviati tramite il comando kill (che tratteremo in seguito).

Sospensione dei processi

Qui sopra abbiamo detto che i processi in foreground vi bloccano l’accesso alla shell per tutta la durata della loro esecuzione. Che cosa succede se si avvia un processo in foreground, ma poi ci si rende conto si ha bisogno di accedere al terminale?

Un altro segnale che siamo in grado di inviare è il segnale “SIGTSTP” (solitamente il segnale numero 20). Quando digitiamo  CTRL-Z , il nostro terminale registra un comando di “sospensione” poi invia il segnale SIGTSTP al processo in foreground. Questo in pratica sospenderà l’esecuzione del comando e restituirà il controllo al terminale.

Per dimostrarlo, usiamo ping per connettersi a google.com ogni 5 secondi. Faremo precedere il comando ping con command, che ci permetterà di bypassare eventuali alias di shell (alias dei comandi di shell) che impostano al comando un numero massimo di linee di output:

$ command ping -i 5 google.com

Invece di terminare il comando con CTRL-C , digitate invece CTRL-Z:

CTRL-Z

Vedrete un output che assomiglia a questo:

Output
[1]+  Stopped                 ping -i 5 google.com

Il comando ping è stato temporaneamente interrotto, dandovi di nuovo accesso ad una shell. Per mostrarlo, possiamo usare ps, il tool per i processi:

$ ps T
Output
  PID TTY      STAT   TIME COMMAND
26904 pts/3    Ss     0:00 /bin/bash
29633 pts/3    T      0:00 ping -i 5 google.com
29643 pts/3    R+     0:00 ps t

Possiamo vedere che il processo ping è ancora in elenco, ma che la colonna “STAT” ha una “T”. La manpage di ps  ci dice che questo rappresenta un job che è stato “fermato da (un) segnale di controllo dei job”.

Discuteremo più approfonditamente su come cambiare stato al processo ma, per ora, siamo nuovamente in grado di riprendere l’esecuzione del comando in foreground digitando:

$ fg

Una volta che il processo è stato ripristinato, terminatelo con CTRL-C :

CTRL-C

Gestione dei processi in background

La principale alternativa all’esecuzione di un processo in foreground è quello di permettere di eseguirlo in background. Un processo in background è associato con lo specifico terminale che l’ha avviato ma non blocca l’accesso alla shell. Viene invece eseguito in background lasciando l’utente in grado di interagire con il sistema mentre il comando è in funzione.

A causa del modo in cui un processo in foreground interagisce con il suo terminale, ci può essere solo un singolo processo in foreground per ogni finestra di terminale. Poiché i processi in background restituiscono il controllo alla shell immediatamente senza attendere il completamento del processo, possono essere eseguiti molti processi in background allo stesso momento.

Avvio dei processi

È possibile avviare un processo in background aggiungendo un carattere di e commerciale (“&”) alla fine dei comandi. Questo dice alla shell di non aspettare il completamento del processo ma di iniziarne invece l’esecuzione e di restituire immediatamente un prompt all’utente. L’output del comando sarà ancora visualizzato nel terminale (a meno che non venga reindirizzato), ma è possibile digitare comandi aggiuntivi mentre il processo in background continua l’esecuzione.

Per esempio, siamo in grado di avviare in background lo stesso processo ping dell’ultima sezione digitando:

$ command ping -i 5 google.com &

Dal sistema di controllo dei job di bash si vedrà in output che assomiglia a questo:

Output
[1] 4287

Si vedrà anche l’usuale output del comando ping:

Output
PING google.com (74.125.226.71) 56(84) bytes of data.
64 bytes from lga15s44-in-f7.1e100.net (74.125.226.71): icmp_seq=1 ttl=55 time=12.3 ms
64 bytes from lga15s44-in-f7.1e100.net (74.125.226.71): icmp_seq=2 ttl=55 time=11.1 ms
64 bytes from lga15s44-in-f7.1e100.net (74.125.226.71): icmp_seq=3 ttl=55 time=9.98 ms

Tuttavia, allo stesso tempo è anche possibile digitare dei comandi. L’output del processo in background verrà mixato tra l’input e l’output dei processi in foreground, ma non interferirà con l’esecuzione dei processi in foreground.

Elenco dei processi in background

Per visualizzare tutti i processi sospesi o in background, potete utilizzare il comando jobs:

$ jobs

Se c’è il comando ping in esecuzione in background, vedrete qualcosa che assomiglia a questo:

Output
[1]+  Running                 command ping -i 5 google.com &

Questo dimostra che in questo momento in esecuzione c’è un singolo processo in background. L’[1] rappresenta la “job spec” del comando o il numero di job. Siamo in grado di fare riferimento a questo numero con altri comandi per i job e di controllo dei processi, come kill, fg e bg, facendo precedere il numero di job con un segno di percentuale. In questo caso, possiamo fare riferimento a questo job come %1 .

Arresto dei processi in background

Siamo in grado di fermare il processo in background in corso in diversi modi. La via più diretta da seguire è quella di utilizzare il comando kill con il numero di job associato. Per esempio, siamo in grado di terminare il processo in background in esecuzione digitando:

$ kill %1

A seconda di come è stato configurato il terminale si vedrà lo stato di terminazione del job all’istante o la prossima volta che si preme INVIO:

Output
[1]+  Terminated              command ping -i 5 google.com

Se controlliamo di nuovo il comando jobs non vedremo alcun job in corso.

Cambiare gli stati del processo

Ora che sappiamo come avviare ed arrestare i processi in background, vediamo come cambiare il loro stato.

In precedenza abbiamo dimostrato un cambiamento di stato quando abbiamo descritto come fermare o sospendere un processo con CTRL-Z . Quando i processi sono in questo stato di blocco, siamo in grado di spostare un processo da foreground a background o viceversa.

Spostare i processi da foreground a background

Se quando avviamo un comando ci dimentichiamo di terminarlo con &, possiamo ancora spostare il processo in background.

Nuovamente, il primo passo è quello di fermare il processo con CTRL-Z:

CTRL-Z

Una volta che il processo viene interrotto, si può utilizzare il comando bg per farlo ripartire in background:

$ bg

Vedrete di nuovo la riga di stato del job, questa volta con la e commerciale in coda:

Output
[1]+ ping -i 5 google.com &

Di default, il comando bg opera sul processo interrotto più recentemente. Se avete interrotto più processi di fila senza riavviarli, per mandare in background il processo corretto è possibile fare riferimento al processo in base al numero di job.

Si noti che non tutti i comandi possono essere mandati in background. Alcuni processi termineranno automaticamente se rileveranno che sono stati avviati con il loro standard input e il loro standard output collegati direttamente ad un terminale attivo.

Spostare i processi da background a foreground

Possiamo anche spostare i processi da background a foreground digitando fg:

$ fg

Questo opera sul vostro processo mandato in background più recentemente (indicato dal “+” nell’input di jobs). Sospende immediatamente il processo e lo mette in foreground. Per specificare un diverso job, usate il suo numero di job:

$ fg %2

Una volta che un job è in foreground, lo si può terminare con CTRL-C , lasciare che completi ciò che deve fare, o sospenderlo e mandarlo di nuovo in background.

Gestire i SIGHUP

Sia che un processo si trovi in background che in foreground, è strettamente legato con l’istanza del terminale che l’ha avviato. Quando un terminale si chiude, in genere invia un segnale SIGHUP a tutti i processi (foreground, background, o sospesi) che sono legati al terminale stesso. Questo segnala ai processi di terminare perché il loro terminale di controllo presto sarà non più disponibile. Che cosa succede se si desidera chiudere un terminale ma mantenere i processi in background in esecuzione?

Ci sono un certo numero di modi per poterlo fare. I modi più flessibili sono in genere quelli di usare un terminale multiplexer come screen o tmux  oppure quello di utilizzare una utility che fornisce almeno la funzionalità di detach (la separazione dal terminale), come dtach .

Tuttavia, questo non è sempre possibile. A volte questi programmi non sono disponibili o il processo è già stato avviato ed è necessario continuarne l’esecuzione. A volte quelli appena elencati sono metodi esagerati per ciò che si vuole ottenere.

Usare nohup

Se nel momento in cui avviate il processo sapete che vorrete chiudere il terminale prima del completamento del processo stesso, è possibile avviarlo utilizzando il comando nohup. Ciò rende il processo avviato immune al segnale SIGHUP. Il processo continuerà a funzionare anche quando il terminale verrà chiuso. Sarà riassegnato come processo figlio del sistema di init:

$ nohup ping -i 5 google.com &

Vedrete una linea che assomiglia a quanto segue, il che indica che l’output del comando verrà scritto in un file chiamato nohup.out (nella directory corrente, se scrivibile, altrimenti nella vostra home directory):

Output
nohup: ignoring input and appending output to ‘nohup.out’

Questo per garantire che l’output non venga perso nel caso in cui la finestra del terminale si chiuderà.

Se si chiude la finestra del terminale e se ne apre un’altra, il processo sarà ancora in esecuzione. Non vedrete il processo nell’output del comando jobs perché ogni istanza del terminale ha la propria coda dei job indipendente. La chiusura del terminale fa sì che il job ping  sia distrutto anche se il processo ping è ancora in esecuzione.

Per terminare il processo ping si dovrà cercare il suo ID di processo (o “PID”, Process IDentificator). È possibile farlo con il comando pgrep (c’è anche un comando pkill, ma tale sistema a due stadi assicura di terminare il solo processo che vogliamo). Per cercare il programma in esecuzione, usate pgrep ed il flag -a:

$ pgrep -a ping
Output
7360 ping -i 5 google.com

È quindi possibile terminare il processo facendo riferimento al PID restituito, che è il numero nella prima colonna:

$ kill 7360

Se non ne avete più bisogno, potete cancellate il file nohup.out.

Usare disown

Il comando nohup è utile, ma solo se si sa di doverlo usare al momento di far partire il processo. Il sistema di controllo dei job di bash fornisce altri metodi per ottenere risultati simili grazie al comando integrato disown.

Il comando disown, nella sua configurazione di default, rimuove un job dalla coda dei job di un terminale. Ciò significa che tale job non può più essere gestito utilizzando i meccanismi di controllo dei job discussi in questa guida (come fg, bg ,CTRL-Z ,CTRL-C). Il job sarà immediatamente rimosso dall’elenco dell’output del comando jobs e non sarà più associato con il terminale.

Il comando viene chiamato specificando il numero di un job. Per esempio, per usare subito disown sul job 2, potremmo scrivere:

$ disown %2

Ciò lascia il processo in uno stato non dissimile da quello di un processo nohup dopo che è stato chiuso il terminale che ne aveva il controllo. L’eccezione è che, se non viene reindirizzato ad un file, ogni output verrà perso nel momento in cui il terminale di controllo si chiude.

Di solito, se non si sta immediatamente chiudendo la finestra del terminale, non si vuole rimuovere completamente il processo dal controllo del job . È possibile invece passare il flag -h al processo disown al fine di indicare al processo di ignorare i segnali SIGHUP, ma di continuare invece come un regolare job:

$ disown -h %1

In questo stato, è possibile utilizzare i normali meccanismi di controllo del job per continuare a controllare il processo fino alla chiusura del terminale. Alla chiusura del terminale ci si troverà, ancora una volta, bloccati con un processo con un output perduto nel nulla se non è stato redirezionato all’avvio del processo stesso.

Per ovviare a ciò, si può provare a reindirizzare l’output del processo dopo che è già in esecuzione. Questo è al di fuori del campo di applicazione di questa guida, ma si può dare un’occhiata a questo post per avere un’idea di come si dovrebbe fare.

Usare l’opzione della shell huponexit

Bash ha anche un altro modo per evitare il problema del SIGHUP per i processi figli. L’opzione huponexit della shell controlla se bash invierà ai sui processi figli il segnale SIGHUP nel momento in cui uscirà.

Nota
L’opzione huponexit ha efetto sul comportamento di SIGHUP solo quando il termine della sessione della shell viene avviata all’interno della shell stessa. Alcuni esempi di quando questo viene applicato è quando all’interno della sessione viene dato il comando exit o un CTRL-D.

Quando una sessione di shell viene terminata tramite il programma terminale stesso (con la chiusura della finestra, ecc), il comando huponexit non avrà alcun effetto. Non sarà bash a decidere se inviare il segnale SIGHUP, il terminale stesso invierà il segnale SIGHUP a bash , che poi (correttamente) propagherà il segnale ai suoi processi figli.

Nonostante le precisazioni di cui sopra, l’opzione huponexit è forse una delle più facili. È possibile vedere se questa funzione sia attivata o meno digitando:

$ shopt huponexit

Per attivarla, digitate:

$ shopt -s huponexit

Ora, se si esce dalla sessione digitando exit , tutti i processi continueranno a funzionare:

$ exit

Questo ha le stesse precisazioni riguardo l’output del programma dell’ultima opzione, dunque assicuratevi di aver reindirizzato l’output dei processi, se vi serve, prima di chiudere il terminale.

Conclusioni

Quando si eseguono dei programmi sulla riga di comando, imparare il controllo dei job e la gestione dei processi in foreground e background vi darà una maggiore flessibilità. Invece di dover aprire tanti terminali o sessioni SSH, spesso è possibile cavarsela con alcuni comandi di sospensione e di background.

 






Home | Chi siamo | Chat | Contattaci | Whois

2 pensieri su “I comandi Bash per gestire i processi in background e foreground

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *