[successivo] [precedente] [inizio] [fine] [indice generale] [violazione GPL] [translators] [docinfo] [indice analitico] [volume] [parte]
Un programma singolo, nel momento in cui viene eseguito, è un processo. La nascita di un processo, cioè l'avvio di un programma, può avvenire solo tramite una richiesta da parte di un altro processo già esistente. Si forma quindi una sorta di gerarchia dei processi organizzata ad albero. Il processo principale (root) che genera tutti gli altri, è quello dell'eseguibile init che a sua volta è attivato direttamente dal kernel.
In linea di principio, il programma avviato dal kernel come processo principale, può essere qualunque cosa, anche una shell (tenendo conto, comunque, che il kernel predilige l'eseguibile /sbin/init
), ma in tal caso si tratta di applicazioni specifiche e non di un sistema standard.
Qui si preferisce utilizzare il nome Init per identificare il processo principale, tenendo conto che questo si concretizza generalmente nell'eseguibile init.
Il kernel gestisce una tabella dei processi che serve a tenere traccia del loro stato. In particolare sono registrati i valori seguenti:
il nome dell'eseguibile in funzione;
gli eventuali argomenti passati all'eseguibile al momento dell'avvio attraverso la riga di comando;
il numero di identificazione del processo;
il numero di identificazione del processo che ha generato quello a cui si fa riferimento;
il nome del dispositivo di comunicazione se il processo è controllato da un terminale;
il numero di identificazione dell'utente;
il numero di identificazione del gruppo;
Il kernel Linux rende disponibile i dati della tabella dei processi attraverso un file system virtuale montato nella directory /proc/
. Dalla presenza di questo file system virtuale dipendono la maggior parte dei programmi che si occupano di gestire i processi.
In particolare, a partire da questa directory se ne diramano altre, tante quanti sono i processi in esecuzione, ognuna identificata dal numero del processo stesso. Per esempio, /proc/1/
contiene una serie di file virtuali che rappresentano lo stato del processo numero uno, ovvero Init che è sempre il primo a essere messo in funzione. Il listato seguente mostra il contenuto che potrebbe avere il file /proc/1/status
.
Name: init State: S (sleeping) Pid: 1 PPid: 0 Uid: 0 0 0 0 Gid: 0 0 0 0 Groups: VmSize: 764 kB VmLck: 0 kB VmRSS: 16 kB VmData: 64 kB VmStk: 4 kB VmExe: 24 kB VmLib: 628 kB SigPnd: 0000000000000000 SigBlk: 0000000000000000 SigIgn: 0000000057f0d8fc SigCgt: 00000000280b2603 CapInh: 00000000fffffeff CapPrm: 00000000ffffffff CapEff: 00000000fffffeff
Come è già stato accennato, la nascita di un processo, cioè l'avvio di un programma, può avvenire solo tramite una richiesta da parte di un altro processo già esistente, utilizzando la chiamata di sistema fork(). Per esempio, quando si avvia un programma attraverso il terminale, è l'interprete dei comandi (la shell) che genera il processo corrispondente.
Quando un processo termina, lo fa attraverso la chiamata di sistema exit(), trasformandosi in un cosiddetto zombie. È poi il processo che lo ha generato che si deve occupare di eliminarne le tracce.
Il processo genitore, per avviare l'eliminazione dei suoi processi zombie, deve essere avvisato che ne esiste la necessità attraverso un segnale SIGCHLD. Questo segnale viene inviato proprio dalla funzione di sistema exit(), ma se il meccanismo non funziona come previsto, si può inviare manualmente un segnale SIGCHLD al processo genitore. In mancanza d'altro, si può far terminare l'esecuzione del processo genitore stesso.
Il processo che termina potrebbe avere avviato a sua volta altri processi (figli). In tal caso, questi vengono affidati al processo numero uno, cioè Init.
A volte, l'interruzione di un processo provoca il cosiddetto scarico della memoria o core dump. In pratica si ottiene un file nella directory corrente, contenente l'immagine del processo interrotto. Per tradizione, questo file è denominato core
, in onore dei primo tipo di memoria centrale che sia stato utilizzato: la memoria a nuclei magnetici, ovvero core memory.
Questi file servono a documentare un incidente di funzionamento e a permetterne l'analisi attraverso strumenti diagnostici opportuni. Solitamente possono essere cancellati tranquillamente.
La proliferazione di questi file va tenuta sotto controllo: di solito non ci si rende conto se un processo interrotto ha generato o meno lo scarico della memoria. Ogni tanto vale la pena di fare una ricerca all'interno del file system per rintracciare questi file, come nell'esempio seguente:
#
find / -name core -type f -print
Ciò che conta è di non confondere core con spazzatura: ci possono essere dei file chiamati core
per qualche motivo, che nulla hanno a che fare con lo scarico della memoria.
Nel momento in cui l'attività di un processo dipende da quella di un altro ci deve essere una forma di comunicazione tra i due. Ciò viene definito IPC, o Inter process communication, ma questa definizione viene confusa spesso con un tipo particolare di comunicazione definito IPC di System V.
I metodi utilizzati normalmente sono di tre tipi: invio di segnali, pipe e IPC di System V.
I segnali sono dei messaggi elementari che possono essere inviati a un processo, permettendo a questo di essere informato di una condizione particolare che si è manifestata e di potersi uniformare. I programmi possono essere progettati in modo da intercettare questi segnali, allo scopo di compiere alcune operazioni prima di adeguarsi agli ordini ricevuti. Nello stesso modo, un programma potrebbe anche ignorare completamente un segnale, o compiere operazioni diverse da quelle che sarebbero prevedibili per un tipo di segnale determinato. Segue un elenco dei segnali più importanti.
SIGINT
È un segnale di interruzione intercettabile, inviato normalmente attraverso la tastiera del terminale, con la combinazione [Ctrl+c], al processo che si trova a funzionare in primo piano (foreground). Di solito, il processo che riceve questo segnale viene interrotto.
SIGQUIT
È un segnale di interruzione intercettabile, inviato normalmente attraverso la tastiera del terminale, con la combinazione [Ctrl+\], al processo che si trova a funzionare in primo piano. Di solito, il processo che riceve questo segnale viene interrotto.
SIGTERM
È un segnale di conclusione intercettabile, inviato normalmente da un altro processo. Di solito, provoca la conclusione del processo che ne è il destinatario.
SIGKILL
È un segnale di interruzione non intercettabile, che provoca la conclusione immediata del processo. Non c'è modo per il processo destinatario di eseguire alcuna operazione di salvataggio o di scarico dei dati.
SIGHUP
È un segnale di aggancio che rappresenta l'interruzione di una comunicazione. In particolare, quando un utente esegue un logout, i processi ancora attivi avviati eventualmente sullo sfondo (background) ricevono questo segnale. Può essere generato anche a causa della «morte» del processo controllante.
La tabella 39.1 elenca i segnali descritti dallo standard POSIX.1, mentre l'elenco completo può essere ottenuto consultando signal(7).
Tabella 39.1. Segnali gestiti da GNU/Linux secondo lo standard POSIX.1.
Le lettere contenute nella seconda colonna rappresentano il comportamento predefinito dei programmi che ricevono tale segnale:
A termina il processo;
B il segnale viene ignorato;
C la memoria viene scaricata (core dump);
D il processo viene fermato;
E il segnale non può essere catturato;
F il segnale non può essere ignorato.
L'utente ha a disposizione in particolare due mezzi per inviare segnali ai programmi:
la combinazione di tasti [Ctrl+c] che di solito genera l'invio di un segnale SIGINT al processo in esecuzione sul terminale o sulla console attiva;
l'uso di kill (programma o comando interno di shell) per inviare un segnale particolare a un processo stabilito.
Attraverso la shell è possibile collegare più processi tra loro in una pipeline, come nell'esempio seguente, in modo che lo standard output di uno sia collegato direttamente con lo standard input del successivo.
$
cat mio_file | sort | lpr
Ogni connessione tra un processo e il successivo, evidenziata dalla barra verticale (pipe), si comporta come un serbatoio provvisorio di dati ad accesso FIFO (First in first out -- il primo a entrare è il primo a uscire).
È possibile creare esplicitamente dei serbatoi FIFO di questo genere, in modo da poterli gestire senza dover fare ricorso alle funzionalità della shell. Questi, sono dei file speciali definiti proprio «FIFO» e vengono creati attraverso il programma mkfifo. Nell'esempio seguente viene mostrata una sequenza di comandi con i quali, creando due file FIFO, si può eseguire la stessa operazione indicata nella pipeline vista poco sopra.
$
mkfifo fifo1 fifo2
Crea due file FIFO: fifo1
e fifo2
.
$
cat mio_file >> fifo1 &
Invia mio_file
a fifo1
senza attendere (&).
$
sort < fifo1 >> fifo2 &
Esegue il riordino di quanto ottenuto da fifo1
e invia il risultato a fifo2
senza attendere (&).
$
lpr < fifo2
Accoda la stampa di quanto ottenuto da fifo2
.
Quando un processo viene interrotto all'interno di una pipeline di qualunque tipo, il processo che inviava dati a quello interrotto riceve un segnale SIGPIPE e si interrompe a sua volta. Dall'altra parte, i processi che ricevevano dati da quello interrotto, vedono concludersi il flusso di questi dati e terminano la loro esecuzione in modo naturale. Quando questa situazione viene segnalata, si potrebbe ottenere il messaggio broken pipe.
L'IPC di System V è un sistema di comunicazione tra processi sofisticato che permette di gestire code di messaggi, semafori e memoria condivisa.
La gestione simultanea dei processi è ottenuta normalmente attraverso la suddivisione del tempo di CPU, in maniera tale che a turno ogni processo abbia a disposizione un breve intervallo di tempo di elaborazione. Il modo con cui vengono regolati questi turni è lo scheduling, ovvero la pianificazione di questi processi.
La maggiore o minore percentuale di tempo di CPU che può avere un processo è regolata dalla priorità espressa da un numero. Il numero che rappresenta una priorità deve essere visto al contrario di come si è abituati di solito: un valore elevato rappresenta una priorità bassa, cioè meno tempo a disposizione, mentre un valore basso (o negativo) rappresenta una priorità elevata, cioè più tempo a disposizione.(1)
Sotto questo aspetto diventa difficile esprimersi in modo chiaro: una bassa priorità si riferisce al numero che ne esprime il valore o alle risorse disponibili? Si può solo fare attenzione al contesto per capire bene il significato di ciò che si intende.
La priorità di esecuzione di un processo viene definita in modo autonomo da parte del sistema e può essere regolata da parte dell'utente sommandovi il cosiddetto valore nice. Di conseguenza, un valore nice positivo aumenta il valore della priorità, mentre un valore negativo lo diminuisce.
Nei sistemi operativi Unix c'è la necessità di distinguere i privilegi concessi agli utenti, definendo un nominativo e un numero identificativo riferito all'utente e al gruppo (o ai gruppi) a cui questo appartiene. L'utente fisico è rappresentato virtualmente dai processi che lui stesso mette in esecuzione; pertanto, un'informazione essenziale riferita ai processi è quella che stabilisce l'appartenenza a un utente e a un gruppo. In altri termini, ogni processo porta con sé l'informazione del numero UID e del numero GID, in base ai quali ottiene i privilegi relativi e gli viene concesso o meno di compiere le operazioni per cui è stato avviato.
Appunti di informatica libera 2003.01.01 --- Copyright © 2000-2003 Daniele Giacomini --daniele @ swlibero.org
1) Il concetto di priorità fa riferimento a una sequenza ordinata di elementi: il primo, cioè quello che ha precedenza sugli altri, è quello che ha il valore inferiore.
Dovrebbe essere possibile fare riferimento a questa pagina anche con il nome introduzione_ai_processi_di_elaborazione.html
[successivo] [precedente] [inizio] [fine] [indice generale] [violazione GPL] [translators] [docinfo] [indice analitico]