[successivo] [precedente] [inizio] [fine] [indice generale] [indice ridotto] [indice analitico] [volume] [parte]


Capitolo 89.   Introduzione ai processi di elaborazione

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.

89.1   Tabella dei processi

Il kernel gestisce una tabella dei processi che serve a tenere traccia del loro stato. In particolare sono registrati i valori seguenti:

89.1.1   Directory «/proc/»

Il kernel Linux rende disponibile i dati della tabella dei processi attraverso un file system virtuale innestato 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

89.2   Nascita e morte di un processo

Come già 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 processo «defunto», o «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 defunti, 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.

89.2.1   Scarico della memoria: «core dump»

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 con un sistema Unix: 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 questi file 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[Invio]

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.

89.3   Comunicazione tra processi

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, condotti (flussi di dati FIFO) e IPC di System V.

89.3.1   Segnali

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.

Segnale Descrizione
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 89.3 elenca i segnali descritti dallo standard POSIX.1, mentre l'elenco completo può essere ottenuto consultando signal(7).

Tabella 89.3. Segnali gestiti dai sistemi GNU/Linux secondo lo standard POSIX.1.

Segnale Azione Descrizione
SIGHUP A Il collegamento con il terminale è stato interrotto.
SIGINT A Interruzione attraverso un comando dalla tastiera.
SIGQUIT A Conclusione attraverso un comando dalla tastiera.
SIGILL A Istruzione non valida.
SIGABRT C Interruzioni di sistema.
SIGFPE C Eccezione in virgola mobile.
SIGKILL AEF Conclusione immediata.
SIGSEGV C Riferimento non valido a un segmento di memoria.
SIGPIPE A Condotto (flusso FIFO) interrotto.
SIGALRM A Timer.
SIGTERM A Conclusione.
SIGUSR1 A Primo segnale definibile dall'utente.
SIGUSR2 A Secondo segnale definibile dall'utente.
SIGCHLD B Eliminazione di un processo figlio.
SIGCONT Riprende l'esecuzione se in precedenza è stato fermato.
SIGTSTOP DEF Ferma immediatamente il processo.
SIGTSTP D Stop attraverso un comando della tastiera.
SIGTTIN D Processo sullo sfondo che richiede dell'input.
SIGTTOU D Processo sullo sfondo che deve emettere dell'output.

Le lettere contenute nella seconda colonna rappresentano il comportamento predefinito dei programmi che ricevono tale segnale:

L'utente ha a disposizione in particolare due mezzi per inviare segnali ai programmi:

89.3.2   Condotti

Attraverso la shell è possibile collegare più processi tra loro in un condotto, 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[Invio]

Ogni connessione tra un processo e il successivo, evidenziata dalla barra verticale, 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 nel condotto visto poco sopra.

mkfifo fifo1 fifo2[Invio]

Crea due file FIFO: fifo1 e fifo2.

cat mio_file >> fifo1 &[Invio]

Invia mio_file a fifo1 senza attendere (&).

sort < fifo1 >> fifo2 &[Invio]

Esegue il riordino di quanto ottenuto da fifo1 e invia il risultato a fifo2 senza attendere (&).

lpr < fifo2[Invio]

Accoda la stampa di quanto ottenuto da fifo2.

I file FIFO, data la loro affinità di funzionamento con i condotti gestiti dalla shell, vengono anche chiamati «pipe con nome», contrapponendosi ai condotti normali che a volte vengono detti «pipe anonimi».

Quando un processo viene interrotto all'interno di un condotto 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 ricevono dati da un processo che si interrompe, 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.

89.3.3   IPC di System V

L'IPC di System V è un sistema di comunicazione tra processi sofisticato che permette di gestire code di messaggi, semafori e memoria condivisa. Teoricamente un sistema Unix può essere privo di questa gestione, per esempio un kernel Linux può essere compilato senza questa funzionalità, ma in pratica conviene che sia presente, perché molti programmi dipendono da questa.

Una delle questioni ricorrenti che riguardano la gestione dell'IPC di System V è la gestione della memoria condivisa. Si accede a queste informazioni con l'aiuto del programma sysctl:

sysctl -a | less[Invio]

...
kernel.shmmni = 4096
kernel.shmall = 2097152
kernel.shmmax = 33554432
...

Generalmente è meglio non toccare questi valori, ma in alcuni documenti si fa riferimento a questa possibilità, per poter utilizzare dei programmi particolarmente pesanti dal punto di vista dell'utilizzo della memoria. A titolo di esempio, volendo raddoppiare il valore della memoria condivisa massima, si potrebbe intervenire così:

sysctl -w kernel.shmmax=67108864[Invio]

Tabella 89.5. Parametri di IPC di System V.

Parametro Descrizione
SHMMAX Dimensione massima, in byte, di un segmento di memoria condivisa.
SHMMIN Dimensione minima di un segmento di memoria condivisa, espresso in byte.
SHMALL Quantità massima di memoria condivisa che si può usare, espressa byte o in pagine di memoria.
SHMSEG Quantità massima di segmenti di memoria condivisa per ogni processo elaborativo.
SHMMNI Quantità massima di segmenti di memoria condivisa per tutto il sistema.
SEMMNI Quantità massima di insiemi di semafori.
SEMMNS Quantità massima di semafori per tutto il sistema.
SEMMSL Quantità massima di semafori per insieme.
SEMMAP Quantità massima di voci nella mappa dei semafori.
SEMVMX Valore massimo di semaforo.

89.4   Scheduling e priorità

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? Evidentemente 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.

89.5   Privilegi dei processi

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.

89.6   Variabili di sistema

I sistemi Unix possono includere una funzionalità denominata Sysctl, con la quale è possibile accedere a delle «variabili di sistema», ovvero dei parametri che controllano il comportamento del kernel. Nei sistemi GNU/Linux è possibile realizzare un kernel sprovvisto di tale funzionalità, ma in generale è meglio che sia inclusa.

L'utente root può accedere alla lettura e alla modifica di queste variabili di sistema attraverso il programma sysctl:

sysctl [opzioni]
sysctl [opzioni] variabile...
sysctl [opzioni] -w variabile=valore...

Per elencare la situazione di tutte le variabili di sistema si usa normalmente l'opzione -a:

sysctl -a[Invio]

sunrpc.nlm_debug = 0
sunrpc.nfsd_debug = 0
sunrpc.nfs_debug = 0
sunrpc.rpc_debug = 0
...
fs.quota.lookups = 0
fs.lease-break-time = 45
fs.dir-notify-enable = 1
fs.leases-enable = 1
fs.overflowgid = 65534
fs.overflowuid = 65534
fs.dentry-state = 200   13      45      0       0       0
fs.file-max = 8192
fs.file-nr = 315        47      8192
fs.inode-state = 216    6       0       0       0       0       0
fs.inode-nr = 216       6

L'elenco che si ottiene è comunque più lungo di come si vede da questo esempio. Per conoscere in modo particolare lo stato di una o di alcune variabili basta indicare i loro nomi alla fine della riga di comando:

sysctl kernel.shmmax kernel.domainname[Invio]

kernel.shmmax = 33554432
kernel.domainname = (none)

Per modificare una variabile si usa l'opzione -w:

sysctl -w kernel.shmmax=67108864[Invio]

Generalmente non c'è alcuna necessità di cambiare i valori delle variabili accessibili attraverso Sysctl; tuttavia, se così fosse, potrebbe essere utile fare in modo che tali modifiche vengano ripristinate ogni volta che si riavvia il sistema operativo. Oltre alla possibilità di realizzare uno script eseguito automaticamente in fase di avvio, è possibile intervenire nel file /etc/sysctl.conf, che si compone semplicemente di direttive di assegnamento a variabili che fanno parte di Sysctl:

# /etc/sysctl.conf - Configuration file for setting system variables
# See sysctl.conf (5) for information.

kernel.domainname = brot.dg

L'esempio mostra l'assegnamento alla variabile kernel.domainname della stringa brot.dg. Si osservi che gli spazi prima e dopo il valore assegnato vengono ignorati. Come si può intuire, il carattere # introduce un commento, che viene ignorato fino alla fine della riga, così come vengono ignorate le righe vuote e quelle bianche.

È bene ribadire che generalmente non c'è alcun bisogno di intervenire nella modifica delle variabili di sistema controllate attraverso Sysctl, pertanto è normale che sia presente il file /etc/sysctl.conf, ma commentato completamente.

Appunti di informatica libera 2007.02 --- Copyright © 2000-2007 Daniele Giacomini -- <daniele (ad) 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.htm

[successivo] [precedente] [inizio] [fine] [indice generale] [indice ridotto] [indice analitico]

Valid ISO-HTML!

CSS validator!