L’uso dell’interfaccia a riga di comando è strettamente legato all’uso di un terminale sul quale opera una shell, il terminale di controllo, all’interno di quella che viene solitamente chiamata una “sessione di lavoro”. Questo ci aveva gia portato a tracciare una distinzione fra i processi che avevamo classi cato come interattivi, perchè associati ad un terminale di controllo, visibile nell’output di ps alla colonna TTY, e quelli non interattivi, o meglio quelli che non lo sono tramite l’interfaccia a riga di comando, che non hanno nessun terminale di controllo.
Per spiegare le modalità in cui viene realizzata l’interazione tramite il terminale, occorre anticipare una caratteristica fondamentale dell’interfaccia a riga di comando. Tutte le volte che si lancia un programma che deve interagire con la riga di comando, questo si aspetta di avere a disposizione e gia aperti tre file che convenzionalmente sono chiamati standard input, standard output e standard error. Dato che questi sono i primi tre le aperti e lo sono in questa sequenza, ad essi vengono sempre assegnati rispettivamente i file descriptor 0, 1 e 2.
Convenzionalmente un programma legge i suoi dati in ingresso dal primo le descriptor, scrive i dati in uscita sul secondo, e gli eventuali errori sul terzo. Si tenga presente che e solo una convenzione, anche se seguita in maniera universale, si può tranquillamente scrivere un programma che si comporta in modo diverso, scrivendo e leggendo da un file descriptor qualunque, ma ovviamente si avranno problemi di interazione con gli altri programmi.
Quando un processo è interattivo nel senso appena illustrato, tutti e tre questi le corrispon-dono al terminale di controllo, e l’interfaccia dei terminali (cioe quella dei dispositivi di questo tipo) fa sì che in lettura il kernel faccia arrivare sullo standard input quanto scritto sulla tastiera e che quanto scritto sullo standard output venga stampato sullo schermo, o sulla nestra dentro l’interfaccia grafica, qualora si usi un emulatore di terminale, o trasmesso via rete, se si usa un terminale associato ad una connessione remota Per capire meglio questa infrastruttura vediamo un esempio della sua applicazione. Come accennato una delle modalità per utilizzare l’interfaccia a riga di comando e quella di collegarsi ad una console con la tradizionale procedura di login su terminale; nei risultati del comando ps si puo notare come sia presente il processo getty associato ad alcuni terminali virtuali (da tty1 a tty6).
In questo caso è getty che si cura dell’apertura iniziale dei tre le di standard input, standard output e standard error facendo riferimento al terminale che gli e stato indicato, nel caso una delle console virtuali, corrispondenti ai terminali da /dev/tty1 a /dev/tty6, che diventera il terminale di controllo di tutta la seguente sessione di lavoro.
Poi il programma stampa un messaggio di benvenuto e la linea “login: ” e si pone in attesa che un utente scriva sul terminale il suo “username” (il cosiddetto nome utente o nome di login), che come accennato e il nome con cui un utente si identifica presso il sistema.
Una volta che questo sia stato scritto, getty esegue direttamente il programma login che si cura di chiedere la password ed e ettuare l’autenticazione. Se questa ha successo e login che si incarica di impostare il valore del session ID per il processo in corso, cambiare gli identi cativi ad esso associati (cioe l’user ID ed il group ID per farli corrispondere a quelli dell’utente che si e collegato, ed in ne lanciare quella che si chiama una “shell di login“.
In tutta questa procedura i tre file di standard input, standard output e standard error resteranno aperti ed associati al terminale di controllo da cui si e partiti, che e quello su cui verra utilizzata la shell.
Una volta completata la procedura di collegamento avremo a disposizione una shell con un certo valore del session ID, e dato che anche questa e una proprietà che viene conservata quando si creano processi gli e si lanciano altri comandi, si avrà come conseguenza che anche tutti i processi lanciati da detta shell saranno identificati dallo stesso session ID. Tutto ciò vale anche per tutte le altre modalità con cui si può iniziare una sessione di lavoro su un terminale, che sia attraverso un emulatore ad interfaccia gra ca o un programma di connessione remota. La modalità piu comune di uso della shell e quella in cui si lancia un comando, si aspetta che questo completi le operazioni, e poi se ne lancia un altro, e cos via. Quello che succede in questo caso e che per ogni comando la shell crea un nuovo processo e si ferma in attesa che questo completi le sue operazioni. Alla conclusione del programma la shell riprende il possesso del terminale e consente di lanciare un altro comando. In genere si lavora in questo modo perch tutte le volte che si esegue un programma interattivo questo deve avere a disposizione il terminale di controllo per ricevere i dati in ingresso ed inviare i dati in uscita.Ovviamente se fosse possibile lavorare solo in questo modo si perderebbe la possibilità di eseguire contemporaneamente piu comandi e di sfruttare le capacità multitasking del sistema. Se pero si osserva il precedente risultato di ps si potrà notare come tutti i comandi citati (evince ed emacs), pur facendo riferimento allo stesso terminale, vengono eseguiti insieme: essi infatti sono stati lanciati come suol dirsi in background, in una modalità cioè in cui essi non sono agganciati al terminale.
Questa è un’altra funzionalità della shell che consente, quando si termina una linea di co-mando con il carattere “&“, di mandare in esecuzione i processi sganciati dal terminale, così che questo resti a disposizione della shell stessa per eseguire altri comandi. Se, come accade per i citati evince ed emacs con cui si interagisce attraverso l’interfaccia grafica, il programma non ha bisogno di accedere al terminale per funzionare, esso potra continuare ad eseguire le sue operazioni restando appunto “sullo sfondo”.
Vediamo allora un esempio di questo comportamento, se si lancia in background un editor testuale come jed, otterremo che l’esecuzione del comando viene immediatamente fermata, dato che ovviamente un editor necessita di interagire con il terminale per poter essere utilizzato e la shell stamper un avviso per avvisarci dell’avvenuto blocco del programma;6 avremo cioe qualcosa del tipo:
piccardi@anarres:~/truedoc/corso$ jed prova &
[3] 22913
[3]+ Stopped
jed prova
Tutte le volte che, come nell’esempio, si manda un processo in background, la shell stampa sul terminale un valore numerico fra parentesi quadre, detto job ID (dato che in gergo si suole chiamare job un processo che lavora in background) seguito dal PID del processo. La shell infatti identi ca i processi attivi all’interno di una stessa sessione di lavoro assegnando loro un numero progressivo, il job ID, che poi puo essere utilizzato come riferimento nei vari comandi di gestione del job control. L’esempio ci mostra anche come la shell provveda a stampare una noti ca sul terminale tutte le volte che un processo viene bloccato, utilizzando di nuovo il job ID. Oltre a lanciare un processo in background ci sono casi in cui puo esser necessario fermare temporaneamente un comando in esecuzione, per eseguire un controllo o un qualche altro compito piu urgente. Abbiamo visto che è possibile fermare l’esecuzione di un processo con l’invio dei segnali SIGSTOP o SIGTSTP, il sistema del controllo di sessione sempli ca questa operazione permettendo di inviare SIGTSTP ai processi che sono attivi su un terminale, tramite la combinazione di tasti C-z, cioe l’uso del tasto \control” insieme alla lettera “z“
L’elenco dei processi in background o bloccati sul terminale puo essere stampato con il co-mando jobs, che mostra la lista e lo stato di ciascuno di essi. Si tenga presente che questo, come i seguenti bg, fg e wait, e un comando interno della shell, e non corrisponde a nessun eseguibile su disco, (vedremo la di erenza in sez. 2.1.3). Di default il comando stampa l’elenco ordinandolo per job ID, ma gli si possono fare stampare anche i PID con l’opzione -l. L’elenco riporta il job ID in prima colonna, seguito dallo stato e dal comando eseguito, nella forma:
piccardi@anarres:~/truedoc/corso$ jobs
[1] | Running | emacs struttura.tex & |
[2]- | Running | evince corso.pdf & |
[3]+ | Stopped | jed prova |
dove il carattere “+” indica il cosiddetto job corrente, cioe il processo su cui si e operato per ultimo, mentre il carattere “–” indica il job precedente (per maggiori dettagli consultare il manuale della bash alla sezione \JOB CONTROL“).
Quando un programma e stato fermato con C-z la shell ci consente di farlo ripartire in background con il comando bg, ovviamente se questo dovesse cercare di eseguire un accesso a terminale si bloccherebbe immediatamente, per questo e inutile usare bg su un comando che si e fermato per questo motivo: si fermera di nuovo. Un processo puo essere riassociato al terminale, cioe essere rimesso, come suol dirsi, in foreground, con il comando fg, se il processo era stato fermato in attesa di poter disporre del terminale in questo modo viene anche fatto ripartire.
Se non si specifica nulla sia bg che fg che tutti gli altri comandi del controllo di sessione agiscono sul job corrente. Qualora si voglia invece agire su uno specifico job si puo speci care come parametro sia il testo della riga di comando, che il numero di job ID stampato da jobs e questo puo essere indicato sia direttamente che preceduto da un carattere \%“. Quest’ultima sintassi puo essere usata anche con il comando kill, se questo e realizzato come comando interno della shell come avviene nel caso di bash, per mandare i segnali ad un processo identi cato per job ID piuttosto che per PID. Se si vuole aspettare la fine di uno specifico comando che si è mandato in background si puo usare il comando wait, passandogli di nuovo il job ID come argomento.
Una volta che si voglia terminare una sessione ed uscire dalla shell si puo usare il comando exit o la combinazione di tasti C-d che, corrisponde ad inviare un carattere di end-of- file sul terminale, cosa che segnala alla shell che lo si vuole chiudere e con questo terminare la sessione di lavoro. Se si cerca di uscire dalla shell e chiudere la sessione ma ci sono job in background o bloccati, la shell stamperà un avviso senza uscire, così da poter provvedere a risolvere la situazione. Se però si ripete immediatamente la richiesta di chiusura, questa verrà onorata e la shell verrà terminata insieme ai processi pendenti.
Il comando exit prende anche un argomento opzionale che costituisce lo stato di uscita e viene usato principalmente per uscire all’interno degli script.