I comandi Linux: i processi | ALTRI capitoli | |||||||||
Un processo viene definito come " una istanza di un programma in esecuzione ". Un programma è una sequenza di istruzioni. Se più utenti eseguono contemporaneamente lo stesso programma, il sistema operativo dovrà aprire più istanze di quello stesso programma, vale a dire, più processi. Quando un programma viene eseguito, il kernel carica il codice del programma nella memoria virtuale, alloca spazio per le variabili del programma, imposta una struttura dati in cui registra varie informazioni relative al programma (process ID, termination status, user ID, group ID). Il kernel deve distribuire le risorse del computer tra i vari processi in esecuzione. Per le risorse limitate, quali la memoria, il kernel inizialmente alloca una certa quantità di risorsa al processo, per poi aggiustare questa assegnazione, nel corso dell'esecuzione, in base alle esigenze del processo stesso e a quelle del sistema nella sua interezza. Una volta terminato il processo, tutte le risorse vengono liberate.
Un processo esegue una sequenza di istruzioni all'interno di un determinato spazio degli indirizzi. Lo spazio degli indirizzi è l'insieme degli indirizzi di memoria ( RAM ) assegnati, dal sistema operativo, ad un singolo processo. Ogni utente, quindi, dovrà poter eseguire la propria istanza di uno stesso programma, alla quale istanza il sistema operativo assegnerà uno spazio degli indirizzi esclusivo. Se dieci utenti eseguono, contemporaneamente, uno stesso programma, quindi, avremo dieci istanze dello stesso programma, cioè dieci processi separati, ai quali il sistema operativo assegnerà dieci spazi degli indirizzi, uno per ogni processo. Un processo è logicamente diviso nelle parti (segmenti) seguenti:
Il C supporta, a livello di linguaggio, soltanto due modalità di allocazione della memoria:
Esiste però un terzo tipo di allocazione, l'allocazione dinamica della memoria, che non è prevista direttamente all'interno del linguaggio C, ma che è necessaria quando il quantitativo di memoria che serve è determinabile solo durante il corso dell'esecuzione del programma. Il C non consente di usare variabili allocate dinamicamente, non è possibile cioè definire in fase di programmazione una variabile le cui dimensioni possano essere modificate durante l'esecuzione del programma. Per questo le librerie del C forniscono una serie opportuna di funzioni per eseguire l'allocazione dinamica di memoria (in genere nello heap). Le variabili il cui contenuto è allocato in questo modo non potranno essere usate direttamente come le altre, ma l'accesso sarà possibile solo in maniera indiretta, attraverso dei puntatori.
Nei sistemi operativi Linux, è un processo che può creare un nuovo processo e per farlo utilizza la funzione:
Il processo che ha invocato
che carica ed esegue un nuovo programma, assolutamente diverso dal programma corrente, eliminando i segmenti testo (codice), data, stack, heap esistenti e rimpiazzandoli con i nuovi segmenti, derivati dal copdice del nuovo programma. Quindi, per eseguire il codice di un nuovo programma, sono necessari, in Linux, due passaggi: un
Di default, i processi vengono eseguiti in foreground: questo significa che fino al termine dell'esecuzione del processo corrente, l'utente non può interagire con la shell ne, tantomeno, lanciare altri processi o comandi. Provate ad eseguire il comando:
In questi casi, è possibile inviare un segnale CTRL+Z, per interromperne l'esecuzione e riappropriarsi della shell, per poi riprenderne l'esecuzione, eseguendo il comando:
Un processo eseguito in background, invece, viene, si, associato al terminale dal quale è stato lanciato, ma non blocca l'accesso alla shell, lasciando all'utente la possibilità di interagire con il sistema, anche durante l'esecuzione. Un comando eseguito in background non viene connesso alla tastiera e al terminale, ma viene eseguito e completato, senza l'intervento dell'utente. Il comando fg porta in foreground un job in esecuzione in background, mentre il comando bg porta in background un job in esecuzione in foreground (dopo averne interrotto l'esecuzione):
È possibile anche specificare il numero di job su cui operare:
Il numero di job (job_id) è quello restituito dall'interruzione di esecuzione (CTRL+Z):
oppure dal comando:
che restituisce l'elenco dei job in esecuzione nella shell corrente. Il comando può restituire anche il process ID di ciascun job:
oppure solo il process ID:
oppure solo i job in esecuzione (running):
oppure solo i job fermati (stopped):
Il segno + che appare a fianco del numero di job:
identifica il job su cui opereranno, di default, i comandi
Il segno - che appare a fianco del numero di job:
identifica il job su cui opereranno, di default, i comandi
Un processo può terminare in soli due modi: richiedendo direttamente la propria terminazione, usando la chiamata di sistema:
oppure venendo chiuso (killed, ucciso) da un segnale. In ogni caso, il processo produce un "termination status", un numero positivo disponibile al processo padre, che usa la chiamata di sistema:
per verificarlo. Se il processo invoca:
specifica anche il suo "termination status". Nel caso di un segnale, invece, il "termination status" viene impostato in base al tipo di segnale che ha prodotto la chiusura del processo. Per convenzione, un "termination status" pari a zero (0) indica che il processo è stato concluso con successo, mentre un valore diverso da zero indica che è intervenuto un qualche errore. Molte shell rendono disponibile il "termination status" dell'ultimo programma eseguito nella variabile:
Se
avremo, come risultato, un output simile al seguente:
Le righe di output che iniziano con "Padre:" sono le righe prodotte dall'esecuzione del loop for all'interno del processo padre, mentre le righe di output che iniziano con:
sono le righe prodotte dall'esecuzione del loop for all'interno del processo figlio.
Eseguiamo il comando:
ed otterremo un output simile al seguente:
che ci dice che sul nostro terminale sono in esecuzione due soli processi:
Per sapere a quali processi viene assegnato il PID di
ed otterremo il seguente risultato:
da cui risulta che
Un processo può essere eseguito in due modalità distinte: in "kernel mode" ed in "user mode". La modalità kernel è la modalità, protetta, in cui viene eseguito tutto il software di sistema: il kernel, infatti, è il cuore del sistema operativo, un insieme di funzioni che operano direttamente sull'hardware. Qualsiasi applicazione di livello più alto, "user mode", non è in grado di accedere alle risorse fisiche del computer ( leggere da una periferica, aprire un file, etc. ), se non utilizzando una serie di "finestre", che permettono di inviare richieste specifiche al kernel: le system call, o funzioni di sistema. In altre parole, un'applicazione utente non può eseguire direttamente codice sorgente del kernel, né manipolare, in alcun modo, i dati utilizzati dal kernel. Attraverso una chiamata di sistema, invece, un'applicazione utente può chiedere, al kernel, di eseguire un pezzo di codice del kernel. Questa rigida separazione tra lo spazio utente e lo spazio del kernel permette alle applicazioni utente di interagire con il sistema, senza arrecare danni. Le System Call ( chiamate di sistema ) rappresentano l'unica via di accesso, per le applicazioni utente ( User Mode ), alle risorse hardware ( basso livello ), quindi allo spazio del kernel ( Kernel Mode ). Ogni processore ( CPU ) mette a disposizione alcune istruzioni speciali, il cui scopo è proprio quello di passare da una modalità all'altra, dallo User Mode al Kernel Mode e viceversa. Un programma, normalmente, viene eseguito in modalità User Mode e passa alla modalità Kernel Mode solo quando invia una richiesta di una funzionalità del kernel. Una volta volta soddisfatta la richiesta del programma, il kernel stesso riporta l'applicazione alla modalità User Mode. Ogni sistema operativo mette a disposizione un elenco di system call, il cui numero varia dalle 300 circa dei sistemi operativi Linux alle diverse migliaia dei sistemi Windows. Per i sistemi Linux, l'elenco delle system call varia anche da architettura ad architettura. Quindi, è possibile che una system call non sia disponibile in un particolare sistema, mentre lo è per una differente architettura. Tuttavia, è possibile affermare che un buon 90% delle system call esistenti per i sistemi Linux sia comune a tutte le architetture. Ad ogni system call è associato un numero. Per avere una lista delle system call disponibili per il proprio sistema e dei numeri ad esse associati, è sufficiente leggere i file:
Per avere una lista delle system call disponibili nel proprio sistema, ma senza alcun riferimento ai numeri associati ad esse, aprire la pagina di manuale:
Per aprire la pagina di manuale di una singola system call, usare il comando
Il kernel deve offrire un meccanismo attraverso il quale un'applicazione utente possa segnalargli la volontà di invocare una chiamata di sistema ( system call ). Nei processori Intel, il meccanismo di segnalazione avviene attraverso l'invio, da parte dell'applicazione utente, di una istruzione interrupt,
Come abbiamo visto, il kernel utilizza una serie di funzioni ( routine ) per creare, eseguire, terminare i processi attivi nel sistema. Oltre ai processi eseguiti dagli utenti, nei sistemi Unix-like ( Linux incluso ) vengono eseguiti una serie di processi, chiamati kernel thread, che vengono eseguiti nello spazio del kernel e non richiedono alcuna interazione con l'utente. Solitamente, questi processi privilegiati vengono aperti in fase di startup del sistema operativo e restano in esecuzione fino al suo shutdown, oppure possono essere eseguiti a seguito di una eccezione ( segnalazione di una condizione imprevista, quale un'istruzione errata o non valida ) inviata dalla CPU, oppure di un segnale di interrupt, inviato alla CPU da una periferica collegata. Sia l'interrupt che l'eccezione sono eventi che alterano la sequenza delle istruzioni eseguite dalla CPU e vengono trattate, dalla CPU, in modo simile. Non appena ricevuto un segnale di Interrupt o di Exception, la CPU sospende immediatamente l'esecuzione del programma corrente, per invocare ed eseguire un programma speciale chiamato Interrupt Handler o ISR ( Interrupt Service Routine ). Una volta terminata l'esecuzione di questo programma speciale, la CPU riprende l'esecuzione del programma precedentemente sospeso. La differenza tra interrupt ed exception ( eccezioni ) sta nel fatto che gli interrupt vengono usati per gestire eventi asincroni ( che possono accadere in qualsiasi momento, a prescindere dall'attività della CPU e dal ciclo del suo clock ) esterni alla CPU ( gli interrupt vengono inviati dalle periferiche collegate al sistema, quali la stampante, il mouse, la tastiera ), mentre le exception gestiscono anomali condizioni riscontrate dallo stesso processore, nel corso dell'esecuzione di un'istruzione. Essendo prodotti dalla stessa CPU, i segnali di exception vengono definiti sincroni, perchè emessi al termine di una qualsiasi istruzione.
Un interrupt o un'eccezione viene attivato inviando un segnale, attraverso la IRQ (Interrupt Request Line), ad un chip speciale, installato nella scheda madre ( motherboard ), lo interrupt controller, il quale, poi, invierà il segnale di interrupt alla CPU. Ogni volta in cui premiamo un tasto della tastiera, per esempio, la tastiera informa la CPU dell'avvenuto evento. Nel caso la CPU fosse già impegnata in altre faccende, la tastiera creerebbe un voltaggio sulla linea IRQ a lei assegnata. Questa modifica di voltaggio segnala alla CPU che la periferica Tastiera ha bisogno della sua attenzione. Un interrupt o un'eccezione viene identificato da un numero compreso tra 0 e 255. Intel chiama questi numeri a 8 bit vettori. Per ogni eccezione, il kernel deve prevedere un'azione di risposta. L'azione di risposta consiste, nella maggior parte dei casi, nell'esecuzione di un processo differente da quello in esecuzione al momento dell'arrivo dell'eccezione, un processo che sia in grado di gestire l'eccezione sollevata. Il kernel conserva, in un indirizzo fiss di memoria, una tabella delle eccezioni e degli interrupt, la Interrupt Descriptor Table ( IDT ), che è una lista degli interrupt e delle eccezioni supportati, con la corrispondente funzione da eseguire ( interrupt handler o exception handler ), un puntatore che indica l'area di memoria contenente le istruzioni per la gestione dell'evento. Linux è in grado di tenervi costantemente informati su qualsiasi processo attivo, in un determinato istante, nel sistema, mettendo a disposizione un mare di informazioni sulle risorse utilizzate da ciascuno di questi processi. Il sistema operativo, il kernel, crea, addirittura, una vera e propria directory, alla quale invia una immagine dei processi così come strutturati nella memoria fisica del sistema:
In questa cartella viene visualizzato un filesystem che, ovviamente, non ha alcuna corrispondenza con il filesystem presente nel disco fisso. Questa cartella, infatti, viene usata dal kernel del sistema per depositare, in tempo reale, le informazioni relative a tutti i processi in esecuzione. Molti comandi e utility di sistema fanno riferimento proprio ai file contenuti in questa cartella, per reperire le informazioni richieste. A ciascun processo viene riservata una sottodirectory, il cui nome è rappresentato dal ProcessID ( PID ) del processo:
Ricordate di utilizzare il comando
Per avere le informazioni relative ad un singolo processo, occorre, quindi, ottenere il Process ID ( PID ) del processo:
per poi entrare nella cartella relativa al processo:
ed ottenere un listato del suo contenuto:
Nonostante la gran parte dei file contenuti nella cartella
In realtà, i file contenuti nella cartella
La gran parte dei file contenuti nella cartella
viene lanciato direttamente dal kernel, il cuore del sistema operativo, ed è responsabile dell'attivazione di qualsiasi altro processo. Il PID ( Process ID ) di
è il file di configurazione di
dove, per ciascun servizio ( Job ), vengono specificate le condizioni necessarie alla sua esecuzione e il file da eseguire, una volta presentatesi le condizioni attese. Il file di configurazione
dove:
è un identificativo che univocamente rappresenta una riga del file;
indica il " runlevel " al quale il comando dovrà essere eseguito;
rappresenta l'azione da intraprendere, mentre l'ultimo campo riporta il comando da eseguire:
Linux definisce differenti livelli di funzionamento del computer, a ciascuno dei quali vengono associati alcuni programmi. I differenti livelli di funzionamento vengono chiamati runlevel. I runlevel corrispondono a determinati livelli di servizi. Pur essendoci differenze, tra un sistema Linux e l'altro, immaginate, per esempio, che il livello 0 sia riservato ai programmi di Halting ( arresto del sistema e spegnimento del computer ) e che il livello 6 sia riservato al reboot del sistema, mentre i livelli intermedi definiscono differenti livelli di operatività: modalità testuale e utente singolo, per il livello 1, modalità multiutente, con capacità di rete, ma senza alcuni servizi di rete ( quali NFS, o Network File System ), per il livello 2, etc. Per conoscere il runlevel corrente e quello precedente ( se esistente ), eseguire il comando:
oppure il comando:
A ciascun runlevel corrisponde o uno script, contenente le istruzioni da eseguire per rendere operativo il runlevel:
oppure una directory, all'interno della quale vengono salvati gli script di esecuzione dei programmi, da eseguire sia in ingresso al runlevel, sia in uscita dal runlevel:
oppure, ancora, una sezione di un unico script, al quale viene trasmesso il runlevel, in base al quale lo script selezionerà i programmi da eseguire:
Al di la delle differenze tra un sistema e l'altro, normalmente in questi script di partenza c'è scritto di eseguire altri script, il cui nome inizi con la lettera "S" o la lettera "K", che si trovano in una particolare directory. Nell'esempio precedente di
che significa che al runlevel 2 va eseguito (
dove lo script è
Visto che la variabile
Gli script, presenti in questa directory, che iniziano con la lettera "S" devono essere eseguiti quando il sistema accede al runlevel corrispondente, mentre gli script che iniziano con la lettera "K" devono essere eseguiti quando il sistema abbandona il runlevel corrispondente. Gli script contenuti nelle cartelle
I servizi, o demoni, aperti dagli script di inizializzazione potranno essere chiusi o eseguiti in qualsiasi momento, rieseguendo gli script di inizializzazione, con il parametro appropriato:
I parametri accettati da uno script di inizializzazione sono i parametri impostati ( contenuti ) nello script stesso. Naturalmente, affinchè il sistema operativo possa essere avviato, Linux deve prevedere un runlevel di default, che viene riportato proprio nel file di configurazione
Se la riga di
Con questo comando,
Non appena
effettuerà lo shutdown ( chiusura ) del sistema ed è equivalente al comando:
Il comando
chiama il comando:
che arresta il computer, dopo lo shutdown. Il comando:
chiama il comando:
che esegue il reboot del sistema. Il comando:
senza una delle due opzioni
che esegue il sistema operativo in modalità testuale e utente singolo ( root ), una modalità necessaria alle funzioni amministrative. Normalmente, in questa modalità, il sistema operativo non esegue alcun demone, nè alcuna funzione di rete. Il runlevel 1 è sinonimo del runlevel:
Quest'ultimo runlevel (
oppure dopo un certo numero di minuti ( due nel nostro seguente esempio ):
oppure ad un determinato momento ( alle 14.02, nel nostro esempio seguente ):
Specificare il tempo di esecuzione non è facoltativo: è assolutamente obbligatorio. Per annullare uno shutdown preimpostato, utilizzare l'opzione
Il comando
È possibile chiedere a
Tornando, ora, al file di configurazione
rappresenta l'azione da intraprendere:
dice di rieseguire il comando che segue, ogni volta in cui, per qualsiasi motivo, fosse stato terminato. Per esempio:
In questo esempio, il comando
Solitamente, si tratta della shell associata all'utente:
ma potrebbe trattarsi di un qualsiasi altro programma. L'azione:
dice a
dice a
dice a
dice a
dice a
In fase di boot, il sistema operativo decide il runlevel da eseguire, in base a questa riga.
dice a
dice a
dice a
dice a Con Upstart,
Ogni Job viene definito in un file di configurazione:
|
||||||||||
I comandi Linux: i processi | Le guide di .bit: contenuto originale |