Appunti di Sistemi Operativi Enzo Mumolo e-mail address ... · MULTICS e' stato un precursore dei...

28

Transcript of Appunti di Sistemi Operativi Enzo Mumolo e-mail address ... · MULTICS e' stato un precursore dei...

Appunti di Sistemi Operativi

Enzo Mumolo

e-mail address :[email protected] address :www.units.it/mumolo

Indice

1 Introduzione al sistema operativo Unix 11.1 Storia del sistema operativo Unix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.2 Caratteristiche generali del sistema operativo Unix . . . . . . . . . . . . . . . . . . . 3

2 Controllo e gestione dei processi in Unix 52.1 Operazione di attivazione del sistema Unix (bootstrap) . . . . . . . . . . . . . . . . . 52.2 Accesso Utenti e identi�catori in Unix . . . . . . . . . . . . . . . . . . . . . . . . . . 5

3 Generalitá sul �le system di Unix 73.1 Il processo di Shell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123.2 I processi in Unix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143.3 Gli stati di un processo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163.4 Esecuzione dei processi utente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21

i

Capitolo 1

Introduzione al sistema operativo Unix

1.1 Storia del sistema operativo Unix

La �loso�a di Unix segna un passo in avanti nell'avvicinamento tra il linguaggio umano ed il linguag-gio di programmazione della macchina. La logica astrusa della macchina sta facendo quindi pianpiano posto ad una logica piu' vicina al modo di pensare dell'essere umano. Per cui, rispetto aglianni passati, si cerca di avvicinare la macchina al linguaggio dell'uomo piuttosto che il contrario,invertendo quindi la tendenza.

Durante gli anni Sessanta, alcuni ricercatori dei Laboratori Bell della AT&T, lavoravano al MITsu di un progetto chiamato MULTICS (MULtiplexed Information and Computing Service).

MULTICS e' stato un precursore dei sistemi operativi a divisione di tempo e presentava molticoncetti tipici dei sistemi concorrenti odierni, ma sfortunatamente risulto' piu' complesso ed intricatodel necessario, forse a causa del suo ruolo innovativo tanto che alla �ne del decennio, si decise diabbandonare il progetto MULTICS. Nel 1969, due di questi ricercatori, Ken Thompson e DennisRitchie, svilupparono, su un progetto di Rudd Canadat, il primo Unix che era soltanto un piccolosistema operativo codi�cato in assembly per un mini computer della serie Digital PDP-7.

Come accade per la maggior parte dei progetti migliori, Thompson e Ritchie scrissero inizial-mente un gioco per il Digital PDP-7, in particolare scrissero un gioco di navigazione spaziale dalnome Space Travel. Dopo questa eperienza, decisero di concentrare i loro sforzi verso obiettivipiu' appaganti e poco dopo crearono un nuovo �le system che assomigliava moltissimo a quelliodierni. Successivamente potenziarono il sistema operativo Unix de�nendo un ambiente a processicon scheduling.

Agli inizi degli anni Settanta Unix veniva supportato soltanto dalla serie Digital PDP-7 ed ameta' degli anni Settanta anche dalla classica e di�usissima serie Digital PDP-11, in particolare daicomputer Digital PDP-11/44, Digital PDP-11/60 e Digital PDP-11/70.

Per parecchi anni l'utilizzo di Unix e' stato circoscritto prima all'interno della AT&T e poi adambienti di ricerca ed universitari, che hanno utilizzato il sistema operativo Unix a supporto deicorsi di scienza dei calcolatori e che hanno contribuito non poco a far conoscere ed apprezzare Unixin ambienti applicativi e gestionali.

Dal 1969 Unix e' passato attraverso molte versioni ed e' tuttora in fase di ricerca di nuove imple-mentazioni ed aggiunte. In particolare ricordiamo che nel 1973 il kernel del sistema operativo Unixe' stato riscritto quasi completamente nel linguaggio di programmazione C, infatti tuttora soltantopochissime routine di kernel, che necessitano di elevate prestazioni, risultano ancora scritte in lin-guaggio assembly. Nel 1977 fu sviluppata la prima edizione facilmente trasferibile cioe' portabile supiu' elaboratori. La prima Universita' a fare il porting su altri sistemi fu la Wollongong Universityin Australia. La portabilita' deriva dal fatto che il sistema operativo Unix e' stato scritto quasicompletamente in linguaggio di programmazione C e non in codice macchina. Il codice macchina

1

1.1 Storia del sistema operativo Unix 2

e' infatti relativo al particolare hardware, mentre il linguaggio di programmazione C e' identico sututte le macchine.

A partire dal 1978 la Berkeley University in California sviluppo' una versione del sistema oper-ativo Unix su Digital VAX e PDP, nell'ambito del progetto DARPA (Defense Advanced ResearchProjects Agency), conosciuto anche come ARPA (Advanced Research Projects Agency), �nanzi-ato dal Dipartimento della Difesa degli Stati Uniti d'America e lo chiamo' BSD (Berkeley SoftwareDistribution). La versione di Berkeley rappresenta tuttora una pietra miliare per lo sviluppo delsistema operativo Unix e gioca un ruolo importante anche nelle piu' recenti versioni del sistemaoperativo Unix.

Nel frattempo diverse compagnie costruttici di hardware iniziarono ad e�ettuare il porting delsistema operativo Unix sui loro microprocessori. Ogni casa costruttrice cerco' di migliorare il sistemaoperativo Unix aggiungendo qualche caratteristica e qualche miglioramento rispetto alla versioneoriginale. Tutto ció ha portato ad una proliferazione di versioni spesso incompatibili tra di loro percui si preferisce parlare della famiglia dei sistemi operativi tipo Unix.

Nel 1982 la Western Electric della AT&T inizio' a commercializzazione di Unix System III enell'anno 1983 presento' anche la versione V di Unix su Digital VAX.

Nel 1982 la Berkeley University produsse invece la versione 4.1 di Unix e nel 1982 la versione 4.2piu' nota come BSD 4.2. Nella BSD 4.2 fu riprogettato il kernel del sistema operativo introducendoil protocollo TCP/IP e l'interfaccia socket.

Nel 1984 il sistema operativo Unix divenne un brevetto della AT&T e fu commercializzatala Unix System V Revisione 2, sulla quale si sono poi basate quasi tutte le versioni distribuitedalle case costruttrici di sistemi di elaborazione. Le caratteristiche principali di Unix System VRevisione 2, sono un'interfaccia a menu' per l'Amministratore di Sistema (System Administrator),la gestione di diversi livelli di shell (shell layers) ed in�ne l'autocon�gurazione del sistema operativo,che permette al sistema operativo Unix System V Revisione 2 di riconoscere in maniera automaticala con�gurazione hardware della macchina e quindi di generare il kernel con i device driver necessari.In Unix System V Revisione 2.1 e' stata introdotta la gestione della memoria con la tecnica a pagine(demand paging).

Nel 1986 apparve sul mercato Unix System V Revisione 3 con nuove sostanziali caratteristicheper quanto riguarda l'ambiente di rete.

Nel 1986 la Berkeley University produsse invece la versione 4.3 di Unix e ne concluse subitolo sviluppo per il termine dei �nanziamenti per il progetto DARPA. L'implementazione Unix dellaBerkeley University non e' stata pero' abbandonata del tutto, in quanto ha trovato seguito anchenel sistema operativo SunOs della Sun Microsystem.

Nel 1987 la AT&T ha sviluppato Unix System V Revisione 3.1 con la quale e' possibile scaricarenella swap area della memoria di massa le regioni dei processi non attivi in memoria centrale.

Nel 1990 la AT&T ha formato una nuova organizzazione denominata USL (Unix SystemLaboratory) che continua a sviluppare ed a commercializzare il sistema operativo Unix. E' danotare che quando ci si riferisce a questo speci�co Unix prodotto dalla USL scriveremo il nome inlettere maiuscole. Il nome UNIX e' infatti un marchio registrato dalla AT&T e dalla USL.

Attualmente la versione di ultima generazione di UNIX e' UNIX System V Revisione 4 a cuici si riferisce spesso come System V.4 dove V e' il realta' il numero romano 5 e si pronuncia comesystem �ve-dot-four. Si puo' anche trovare SVR4 (System V Revisione 4).

Quando si parla di Unix in termini di sistema operativo non si intende generalmente il prodottoUNIX della USL, ma ci si riferisce ad ogni sistema operativo membro della famiglia dei sistemioperativi tipo Unix. Come gia' osservato, il nome u�ciale dell'Unix di Berkeley e' BSD (BerkeleySoftware Distribution). La versione piu' recente di questo sistema operativo e' BSD 4.4.

Osserviamo in�ne che recentemente USL ha sviluppato una nuova versione completa di UNIXdenominata UNIX System V Revisione 8 oppure Research UNIX System che, sebbene non sia ancoracommercializzata, e' stata largamente distribuita nelle Universitá.

1.2 Caratteristiche generali del sistema operativo Unix 3

Con la recente nascita di versioni di Unix destinate a piccoli calcolatori, il mercato ha cominciatoa di�ondersi anche in ambienti di elaborazione di portata piu' limitata come gli u�ci, i piccoli studicommerciali ed applicazioni domestiche.

Un esempio per tutti di versione di Unix destinata a piccoli calcolatori é il sistema operativoLINUX. LINUX e' un'implementazione liberamente distribuita (free software) di Unix. LINUXpuo' coesistere con altri sistemi operativi come ilMS/DOS (MicroSoft / Disk Operating System)della Microsoft, i sistemi Windows della Microsoft oppure l'OS/2 dell'IBM.

La storia di Unix e' unica in confronto ad altri sistemi operativi, in quanto il suo sviluppo e'dovuto alle idee creative di singole persone oltre che alle necessita' degli utenti e non deriva in alcunmodo da decisioni burocratiche. Questo e' ancora vero oggi, tanto che il sistema operativo Unixpuo' essere considerato un ambiente molto favorevole per la de�nizione di nuovi concetti di pro-grammazione. Unix e' un sistema operativo utilizzato in tutto il Mondo e che, almeno virtualmente,puo' essere utilizzato su ogni tipo di computer. Al giorno d'oggi Unix e' diventato una cultura suscala mondiale e, come ogni vera cultura, comprende idee, strumenti e consuetudini.

1.2 Caratteristiche generali del sistema operativo Unix

Il sistema operativo Unix é multiprogrammato (multi-tasking) ed a divisione di tempo (time-sharing). Il sistema operativo Unix consente quindi l'uso della CPU a molti processi contempo-raneamente, ma cio' va inteso nel senso che questi processi sono eseguiti uno alla volta per unafettina di tempo (time slice) limitata. Tale azione detta schedulazione non e' in alcun modopercettibile dal processo, che infatti puo' benissimo pensare di essere l'unico in esecuzione.

File e processi sono le due caratteristiche principali della �loso�a del sistema operativo Unix.Un �le prima di Unix era soltanto una sequenza di informazioni memorizzate su di una memoria dimassa. File, terminali, memorie di massa, drive, praticamente ogni unita' e' invece vista da Unixcome un �le. Questo rende omogenea la struttura del sistema operativo Unix. Un programma éun �le contenente la descrizione ad alto livello del l'algoritmo che si intende eseguire. Un processoe' un'istanza del programma in esecuzione. In altre parole in Unix, un programma e' un entita'passiva che descrive le azioni da compiere, mentre il relativo processo é l'entitá attiva che rappresental'esecuzione di tali azioni.

Il �le system costituisce invece l'interfaccia tra l'utente ed i dispositivi di I/O (Input edOutput). Un �le system e' strutturato in una sequenza di blocchi logici, ognuno contenente 512byte, 1024 byte, 2048 byte, oppure qualsiasi multiplo di 512 byte a seconda delle necessitá.

Qualsiasi entitá di Unix é vista come un �le, per cui tutto l'I/O e' indipendente dal dispositivo�sico in cui avviene ed e' trattato in maniera identica. La dimensione di un blocco logico é omogeneaall'interno di un �le system per cui un �le di 513 caratteri occupa due blocchi di memoria. L'uso diblocchi logici grandi aumenta la velocita' e�ettiva di trasferimento dei dati tra disco e memoria, mariduce la capacita' e�ettiva di memorizzazione per cui e' necessario raggiungere un compromesso.E' stato notato che il sistema operativo Unix da' l'illusione che il �le system abbia posti e che iprocessi abbiano vita. I �le del �le system sono di tre tipi di�erenti: �le normali, direttori e �lespeciali.

Un �le normale e' una sequenza di blocchi logici.Un direttorio (directory) e' una sequenza di blocchi logici, ognuno contenente 512 byte, e

de�nisce il legame tra i nomi dei �le ed i �le stessi. Un direttorio e' praticamente un elenco di�le che viene aggiornato automaticamente dal sistema operativo Unix a seconda delle richieste diogni utente. Il �le system e' organizzato ad albero cioe' in senso gerarchico. La radice dell'alberoe' il direttorio root, rappresentato dal carattere ASCII / (slash) di codice decimale 47, che e' ildirettorio di sistema da cui parte l'intero �le system.

I �le speciali detti device sono una sequenza di blocchi logici, e rappresentano tutti i dispositivi�sici e logici di I/O. Il sistema operativo Unix possiede due tipi di device di I/O: device di I/O a

1.2 Caratteristiche generali del sistema operativo Unix 4

blocchi e device di I/O a caratteri. I device di I/O a blocchi sono i nastri ed i dischi e per questomotivo sono detti anche device di I/O di memoria secondaria. I device di I/O a blocchi sonovisti dal resto del sistema come normali device ad accesso diretto cioe' random. I device di I/Oa caratteri includono invece tutti gli altri device come stampanti, schermo video del terminale,tastiera del terminale e dispositivi di rete e sono detti anche raw device. I device di I/O a caratteresono visti dal resto del sistema come normali device ad accesso sequenziale. Il sistema operativoUnix memorizza i �le normali ed i direttori su device di I/O a blocchi cioe' su nastri e su dischi.A causa della grossa di�erenza nel tempo di accesso tra i due device di I/O a blocchi, ben pochisistemi operativi Unix usano i nastri per i loro �le system.

I device driver sono invece il software di gestione ad interruzione (interrupt) dei device di I/Oe sono memorizzati sempre nel direttorio /dev. I device di I/O a blocchi sono unita' di memoriasecondaria ad accesso diretto cioe' random, tuttavia, ad esempio, i loro device driver possono farlivedere al resto del sistema come unita' ad accesso sequenziale.

Ogni installazione del sistema operativo Unix puo' essere prevista su diversi dischi, ognunocontenente uno o piu' �le system. La suddivisione di un disco in piu' �le system rende piu' agevolel'amministrazione dei dati memorizzati. Il kernel tratta infatti a livello logico con i �le system,piuttosto che con i dischi, trattando ognuno di essi come un device logico indipendente da quello�sico.

L'architettura del sistema operativo Unix puo' essere descritta da un livello shell, da un livelloutente, da un livello kernel e da un livello hardware.

Capitolo 2

Controllo e gestione dei processi in Unix

2.1 Operazione di attivazione del sistema Unix (bootstrap)

La procedura di login é descritta dal seguente pseudocodice:

• come prima cosa viene caricato il blocco 0 del disco (Boot block) che contiene il programmadi caricamento del kernel

• dopo che il codice del Kernel é caricato in memoria, l'esecuzione viene fatta partire dall'entrypoint del kernel attivando cosí quello che é chiamato Processo 0, che esegue in modalitá sistema

• Il Processo 0 crea un altro processo con la chiamata di sistema fork, carica ed esegue il processo/etc/init creando il Processo 1 che esegue in modalitá utente

• il Processo 1 esegue un ciclo in�nito crea il processo gettty per ogni terminale esistente.

• La getty aspetta �no a quando rileva un collegamento. A questo punto chiede lo usernamee la password e, se sono entrambi veri�cati, esegue il programma di shell che costituiscel'interfaccia tra l'utente e il sistema

2.2 Accesso Utenti e identi�catori in Unix

Il primo passo nella registrazione di un nuovo utente é quello di de�nire lo username, la password,un identi�catore numerico che é lo user ID e il gruppo al quale l'utente appartien, descritto da unaaltro identi�catore numerico. Inoltre, altre informazioni necessarie per l'attivazione dell'ambientedell'utente sono: in quale directory l'utente si troverá una volta avuto l'accesso al sistema e il nomedel programma di shell. Queste informazioni sono memorizzate in un �le (password �le) che érealizzato secondo questa struttura:

Username:PasswordCrittografata:UserID:GroupID:Nome reale:HomeDirectory:Shell

Naturalmente ogni utente é descritto da una di queste righe. Normalmente la password crit-tografata non é presente in questo �le ma viene scritta in un altro �le (�le shadow) che puó essereletto solo dal processo con privilegi di amministratore.

Gli utenti hanno un loro identi�catore numerico (UID) e appartengono ad un gruppo (GID),secondo quanto memorizzato nel �le di password. Possono cambiare di gruppo con una chiamatadi sistema.

I processi sono caratterizzati da un denti�catore numerico, PID, da un identi�catore di gruppo(PGID) e, visto che ogni processo é generato da un alro processo, sono caratterizzati dall'identi�-catore del processo padre (PPID). Questi identi�catori sono generati dal kernel. Se un processo ha

5

2.2 Accesso Utenti e identi�catori in Unix 6

il PGID uguale al PID, é un Processo Leader. Inizialmente tutti i processi sono Leader. Durantel'esecuzione i processi possono essere distribuiti in gruppi con una chiamata di sistema. L'organiz-zazione di processi in gruppi puó essere vantaggioso perché si possono organizzare certe funzionisecondo la divisione in gruppi.

I processi peró hanno anche alcuni altri identi�catori, cioé il Real Process User ID e il RealProcess Group ID. Questi valori provengono dal �le password dell'utente che ha avuto l'accesso alsistema. Cosí l'utente con l'UID 40 esegue processi che hanno un RPUID pari a 40; in questo modoé possibile risalire alla identitá degli utenti che hanno eseguito un certo processo, cosa necessariaper consentire di svolgere funzioni di contabilitá d'uso delle risorse.

Esistono altri identi�catori, cioé il E�ective Process User ID e il E�ective Process GroupID che sono normalmente uguali agli identi�catori Real ma in qualche caso sono diversi. Questiidenti�catori sono usati per stabilire i permessi ai �le.

Visto che i �le hanno il UID e GID del proprietario del �le, il meccanismo dei permessi é ilseguente:

Se EPUID == UID del proprietario del �le oppure EPGID ==GID del proprietario la protezionedei �le é stabilita dai bit di protezione corrispondenti vuoi al proprietario vuoi al gruppo. Altrimenti,la prtezione é stabilita dai bit di protezione corrispondenti al campo 'Other'.

Quando gli identi�catori E�ective sono diversi da quelli Real? Ci sono casi inei quali é necessariodare il permesso d'accesso ai �le bypassando il meccanismo della protezione dei �le ora vista. Questoé il caso del processo passwd che appartiene all'utente Root. Il suo compito é di mdi�care lepassword, cioé di modi�care il contenuto del �le Shadow che non é visibile a nessuno. La soluzioneé di modi�care temporaneamente l'EPUID del processo passwd (che, quando attivato da un utenteha come EPUID quello dell'utente che lo ha attivato e quindi non potrebbe accedere il �le Shadow).Questo viene fatto mediante il bit SetUserID che caratterizza il �le.

Capitolo 3

Generalitá sul �le system di Unix

Il disco di Unix é diviso in partizioni, in ognuna delle quali viene caricato un �le system. La par-tizione Unix é composta dal Boot Block, che contiene il codice per il bootstrap, poi dal SuperBlocco,che contiene informazioni generali sul �le system. Segue una lista di blocchi di Inode, oi i blocchidi dati.

Si è iniziato a parlare dell'inode, ovvero index node: vediamo brevemente il perché di questadenominazione. Il disco agli occhi dell'utente appare un array di blocchi logici che corrispondono aisettori del disco (lunghi ad esempio 4096 byte l'uno), numerati e accessibili in maniera diretta

Figura 3.1 Rappresentazione dei blocchi di un �le in un Inode

Un blocco è la più piccola unità di allocazione in un �le system interno. In questo contesto l'inodedi un particolare �le contiene, oltre alle informazioni viste prima, anche una lista di puntatori chepuntano ai blocchi che compongono il �le in questione: pertanto la funzione principale dell'inodeconsiste nell'indicare la posizione dei blocchi contenenti i dati (un indice, appunto).

(vedi �gura ??).Il meccanismo che sta alla base del �le system di Unix si basa appunto sull'inode: consideriamo

per esempio il PCB; parlando di ��le aperti� si intende far riferimento a una struttura globale, unarray (�gura ??) chiamato User File Descriptor Table che è fondamentale in quanto rappresentauna descrizione per utente dei �le aperti. I primi tre elementi della UFDT (0, 1 e 2) sono de�nitidal sistema operativo e puntano allo standard input (tastiera), output (monitor) ed error (monitor):è importante osservare che per il sistema Unix questi device sono visti come dei �le (i driver, messinella cartella \etc) e come tali vengono trattati.

Ogni entry della UFDT punta ad una struttura globale chiamata File Table: al suo internoper ogni processo che accede a un �le sono presenti (assieme ad altre informazioni) un o�set � chedescrive il punto in cui il processo è arrivato a leggere o a scrivere il �le � e un puntatore all'inodeche descrive il �le.

Consideriamo ora il caso in cui ci siano due processi: anche il secondo processo avrà una sua

7

CAPITOLO 3. GENERALITÁ SUL FILE SYSTEM DI UNIX 8

Figura 3.2 Rappresentazione dei blocchi di un �le in un Inode

PCB il cui campo File Aperti punterà ad un'altra UFDT (ogni processo ha una propria User FileDescriptor Table); il terzo campo della UFDT farà riferimento ad un elemento della File Table (che èuna tabella globale); può ora accadere che quest'ultimo elemento punti ad uno stesso inode puntatodal primo processo: ovvero i due processi accedono allo stesso �le (e ciò è possibile in quanto i �lesono risorse condivisibili).

User ID e Group ID

Ad ogni utente del sistema sono associati due numeri non negativi chiamati:

- UID: User ID (numero di utente)

- GID: Group ID (numero di gruppo)

Questi due numeri sono stabiliti una volta per tutte dal responsabile tecnico del sistema (e sono ingenere presenti nel �le /etc/passwd a cui solo l'utente root ha accesso). Lo User ID è unico edidenti�ca quindi l'utente. Lo User ID è univocamente associato allo username mentre il Group IDal groupname (anche questi vengono assegnati dal responsabile tecnico del sistema).

D'altra parte anche per ogni processo creato (per esempio al login ne viene creato uno) sonode�niti i due numeri:

- Process Real User ID

- Process Real Group ID

che vengono ereditati dallo User ID e Group ID dell'utente che ha creato il processo: questi duenumeri pertanto caratterizzano gli aspetti legati al proprietario del processo. È infatti necessarioconmoscere l'utente che ha generato un dato processo per motivi di accesso alla rete, per motivilegati a statistiche sull'uso delle risorse etc. . . Normalmente �Process Real User ID� e �Process RealGroup ID� rimangono inalterati per tutta la vita del processo.

Il processo è però caratterizzato anche da:

CAPITOLO 3. GENERALITÁ SUL FILE SYSTEM DI UNIX 9

Figura 3.3 Esempio di File System di Unix.

- Process E�ective User ID

- Process E�ective Group ID

che normalmente sono uguali rispettivamente a �Process Real UserID� e �Process Real Group ID� edanch'essi solitamente rimangono costanti per l'intero processo. A di�erenza dei Real ID gli e�ectiveID vengono utilizzati in tutto quello che concerne la protezione all'accesso dei �le.

Ora possiamo analizzare come avviene l'accesso e la protezione dei �le. Quando un utente creaun �le, tra le caratteristiche del �le (presenti nel descrittore di �le: l'inode, index node) vengonoregistrati anche il relativo User ID e Group ID. In questo modo anche il �le possiede uno User ID eGroup ID. L'utente che corrisponde allo User ID del �le viene anche detto owner (proprietario) del�le. Quando un processo tenta di accedere in qualche modo ad un �le, UNIX confronta lo User IDed il Group ID del �le con quelli �E�ective� del processo. Da questo confronto viene determinatose il processo può, ed eventualmente in che misura, accedere al �le.

Nei confronti di un �le, gli utenti (e di conseguenza i loro processi) si dividono in tre insiemi:

1. il proprietario (indicato con U, user) cioé l'unico utente che è proprietario (owner) del �le,quello il cui User ID coincide con quello del �le;

2. il gruppo (indicato con G, group) cioé l'insieme di tutti gli utenti che hanno lo stesso GroupID del �le;

3. gli altri (indicato con O, other) cioé tutti gli utenti.

Nei �le Unix per ciascuna di queste tre categorie di utenti sono de�niti tre permessi (quindi intotale nove permessi, descritti in nove bit all'interno dell'inode): lettura (Read), scrittura (Write)ed esecuzione (eXecute), quest'ultimo detto anche di ricerca nel caso dei �le direttorio.

Questi permessi possono essere listati richiedendo il formato lungo (opzione -l) del comandols:

$ ls -l ... -rw-r----- 1 mumolo 12 Oct 2 10:52 dati01 ...

CAPITOLO 3. GENERALITÁ SUL FILE SYSTEM DI UNIX 10

Figura 3.4 Rappresentazione dei permessi nel �le mode.

Il primo campo del listato è una stringa di dieci caratteri. Il primo carattere identi�ca il tipodi �le (�d� per i direttori, �-� per i �le ordinari ed altri caratteri per �le di tipo speciale). Irimanenti nove caratteri identi�cano appunto i permessi sopra descritti. I primi tre dei nove caratterirelativi ai premessi si riferiscono al proprietario, i secondi tre al gruppo ed i terzi agli altri. Quindi,nell'esempio, i nove permessi sono così assegnati:

proprietario gruppo altri

RWX RWX RWX

rw- r-- ---

I permessi accordati vengono indicati con una lettera mentre quelli negati con un tratto. Quindinel caso in esame il proprietario del �le ha permesso di lettura (r) e scrittura (w) sul �le ma non diesecuzione (x). Gli utenti del gruppo hanno solo il permesso di lettura e gli altri nessun permesso.

In binario e in ottale la loro rappresentazione sarà:

shell: rw- r-- --- binario: 110 100 000 ottale: 6 4 0

Es.: il �le /etc/passwd appartenente all'utente root avrà come �le mode 7 0 0.

I permessi vanno interpretati in modo di�erente a seconda che il �le sia ordinario oppure sia un direttorio.

Caso 1: �le ordinario

r → è possibile esaminare il contenuto del �le o copiarlo. Quasi ogni comando che usa un �le esistente habisogno del permesso di lettura su quel �le. Per esempio anche possedendo il permesso di esecuzionenon è possibile eseguire un �le senza possedere anche il permesso di lettura;

w → è possibile modi�care il contenuto del �le cioé creare, alterare o cancellare (in una parola �editare�)il contenuto del �le (il contenuto, non il nome, per questo si vedano le interpretazioni dei permessiassociate ai direttori). Esiste anche una relazione tra il permesso in scrittura di un �le e la suacancellabilità (verrà introdotto tra poco);

x → è possibile eseguire il �le.

Caso 2: �le direttorio

r → è possibile accedere in lettura al contenuto del direttorio cioè elencare (con il comando ls senza opzioni)i nomi dei �le contenuti. Anche l'espansione di nomi di �le (per es. con il metacarattere *) da partedelle shell ha bisogno di questo permesso per poter operare. Se si desiderano maggiori informazioni sui�le (per esempio le informazioni ottenibili con un comando ls -l) è necessario avere accesso anche inesecuzione. In ogni caso l'accesso al direttorio non è su�ciente a garantire l'accesso al contenuto deisingoli �le che esso lista: per esaminare il contenuto di uno speci�co �le del direttorio si devono averei permessi opportuni per quel �le;

w → è possibile modi�care il direttorio cioé inserire o cancellare �le (più precisamente link). Per modi�careinvece il contenuto di uno dei �le del direttorio si deve possedere il permesso di scrittura su quel �le;

CAPITOLO 3. GENERALITÁ SUL FILE SYSTEM DI UNIX 11

x → a parte quanto è stato già detto in proposito nel caso del permesso in lettura (caso ls -l), il permessoin esecuzione per un direttorio consente ad un utente di e�ettuare un cd nel direttorio. Quando sicita un pathname relativo o assoluto per un �le tutti i direttori citati nel pathname devono essereaccessibili in esecuzione per poter ottenere l'accesso al �le.

Problema: Supponiamo ora che l'utente A faccia eseguire un suo processo (di A) e che questo processo(contenuto in un �le eseguibile) abbia �le mode rwx --x --x (quindi l'utente A permette aglialtri utenti di eseguire quel processo); supponiamo ora che questo processo crei un �le e chequest'ultimo (in quanto generato dal processo di A) abbia �le mode rwx --- ---.

Supponiamo ora che l'utente B (un other) esegua il processo di A: questa è un'operazionesenza dubbio lecita, però non appena il processo (che in questo caso ha come E�ective UserID quello dell'utente B) tenta di accedere al �le, il processo torna errore in quanto non c'ècongruenza tra l'utente che ha generato il processo e il proprietario del �le.

Soluzione: Nel �le mode (parola binaria formata da 16 bit) esistono dei bit aggiuntivi rispetto a quellivisti sin'ora tra i quali il set user id che se è settato a 1 allora l'e�ective user id del processoviene posto uguale allo user id del proprietario del �le che contiene il codice eseguibile.

Figura 3.5

Il set group id funziona nel medesimo modo, con l'unica di�erenza che ha a che fare conl'e�ective group id del processo e con il group id del �le.

Da Unix a Windows: la FAT

Figura 3.6 Esempio di FAT.

Se il File System di Unix si basa sulconcetto di inode, quello di Windows si basa sulla FileAllocation Table (FAT) che (sempli�cando) descrive la posizione del �le sulla memoria di massa.Nell'esempio in �gura un particolare �le è memorizzato nei settori 3 - 7 - 5 - 2.

A seconda se ogni entry della FAT è su 16 o 32 bit si parla di FAT16 o FAT32.I processi interagiscono con il sottoinsieme di gestione dei processi per mezzo di un insieme

di particolari chiamate di sistema, come fork( ) che alloca un ingresso (entry) nella tabella deiprocessi del kernel e duplica le regioni del processo chiamante detto processo padre (parentprocess) senza liberare la memoria occupata dal processo padre in modo che due copie (processopadre e processo �glio) del processo padre siano in esecuzione allo stesso momento, exec( ) che

3.1 Il processo di Shell 12

sovrappone un programma al processo �glio (child process) in esecuzione, exit( ) che conclude ilprocesso �glio in esecuzione che allora assume lo stato di zombie cioe' di morto vivente in quantolascia una traccia nella tabella dei processi ed esegue il compito previsto da un'eventuale precedenteattivazione della chiamata di sistema wait( ) cioe' libera lo spazio occupato nella tabella deiprocessi del kernel dal processo �glio nello stato di zombie e sveglia il processo padre che dallostato di pronto (ready) in attesa di usare la CPU transita nello stato di esecuzione (running),wait( ) che mette il processo padre in stato di pronto (ready) in attesa di usare la CPU e nesincronizza la ripresa dello stato di esecuzione (running) con la exit( ) del processo �glio rimastoin esecuzione, brk( ) che controlla la dimensione della memoria dedicata la processo e signal( ) checontrolla il ritorno del processo per eventi inattesi.

Il sottoinsieme di gestione dei �le ed il sottoinsieme di gestione dei processi interagis-cono tra di loro durante il caricamento di un �le dalla memoria secondaria alla memoria principaleper l'esecuzione. Il modulo di comunicazione tra processi infatti deve attendere la lettura dei�le eseguibili in memoria secondaria prima di eseguirli in memoria principale.

3.1 Il processo di Shell

Vediamo ora di chiarire cosa accade quando una linea di comando Unix data per mezzo dellatastiera del terminale oppure attraverso un �le viene intercettata dal processo di shell dettoanche processo di interfaccia utente.

Il processo di shell, per prima cosa esegue tre chiamate di sistema e cioe' una fork( ) che allocaun ingresso (entry) nella tabella dei processi del kernel e duplica le regioni del processo di shellchiamante detto processo padre (parent process) senza liberare la memoria occupata dal processopadre in modo che due copie (processo padre e processo �glio) del processo padre siano in esecuzioneallo stesso momento, una wait( ) che mette il processo padre in stato di pronto (ready) in attesadi usare la CPU e ne sincronizza la ripresa dello stato di esecuzione (running) con la exit( ) delprocesso �glio (child process) rimasto in esecuzione ed una exec( ) che sovrappone il programmaspeci�cato dalla linea di comando al processo �glio in esecuzione.

A questo punto, ci sono tre possibilita': la prima possibilita' e' che la linea di comando speci�chiun programma non esistente nel direttorio corrente di lavoro (process's working directory) oppurein $PATH, che e' il valore della variabile d'ambiente (environment) PATH, cioe' nella lista didirettori (cammino di ricerca) in cui cercare i programmi oppure speci�chi un programma esistentenel direttorio corrente di lavoro oppure in $PATH che non e' eseguibile, la seconda possibilita' e'che la linea di comando speci�chi un programma esistente nel direttorio corrente di lavoro oppurein $PATH che e' eseguibile e la terza possibilita' e' che la linea di comando speci�chi un comandoUnix prede�nito (built-in) oppure un comando shell prede�nito.

La prima possibilita' e' dunque che la linea di comando speci�chi un programma non esistentenel direttorio corrente di lavoro (process's working directory) oppure in $PATH cioe' nella lista didirettori in cui cercare i programmi oppure speci�ca un programma esistente nel direttorio correntedi lavoro oppure in $PATH che non e' eseguibile, allora il sistema operativo stampa un messaggio didignostica sullo schermo video del terminale, esegue la chiamata di sistema exit( ) dal processo �glio(child process) in esecuzione che allora assume lo stato di zombie cioe' di morto vivente in quantolascia una traccia nella tabella dei processi ed esegue il compito previsto dalla precedente attivazionedella chiamata di sistema wait( ) cioe' libera lo spazio occupato nella tabella dei processi delkernel dal processo �glio nello stato di zombie e sveglia il processo di shell padre che dallo statodi pronto (ready) in attesa di usare la CPU transita nello stato di esecuzione (running).

La seconda possibilita' e' che la linea di comando speci�chi un programma esistente nel direttoriocorrente di lavoro (process's working directory) oppure in $PATH che e' eseguibile allora la chiamatadi sistema exec( ) sovrappone, come gia' osservato, il programma speci�cato dalla linea di comandoal processo �glio in esecuzione. A questo punto ci altre sono due possibilita': la linea di comando

3.1 Il processo di Shell 13

speci�ca un programma esistente nel direttorio corrente di lavoro oppure in $PATH che e' eseguibilee scritto in linguaggio di programmazione C oppure la linea di comando speci�ca un programmaesistente nel direttorio corrente di lavoro oppure in $PATH che e' eseguibile e scritto in linguaggiodi programmazione Assembler.

Se il nuovo processo �glio in esecuzione e' relativo ad un programma scritto in linguaggio diprogrammazione C allora alcune chiamate di sistema, come per esempio exit( ), sono realizzate condelle chiamate di funzione di libreria relative alla libreria standard di I/O. La libreria standard di I/Oaggiunge allora del codice, in fase di linking, a queste chiamate di funzione di libreria e le trasformanelle omonime chiamate di sistema costituite da una procedura codi�cata in linguaggio macchinadirettamente nel kernel. La maggior parte delle chiamate di sistema, come open( ), read( ) ewrite(), sono naturalmente costituite da una procedura codi�cata in linguaggio macchina direttamentenel kernel e quando sono invocate da un programma scritto in linguaggio di programmazione Csomigliano a normali chiamate di funzione.

Analogo discorso se il processo in esecuzione e' relativo ad un programma scritto in linguaggiodi programmazione Assembler.

Il nuovo processo �glio, relativo per esempio ad un programma scritto in linguaggio di pro-grammazione C, puo' terminare naturalmente la sua esecuzione, forzare la �ne della sua esecuzionecon una chiamata alla funzione di libreria exit( ), oppure terminare la sua esecuzione per causeesterne in quanto e' stato intercettato un segnale di sistema. Nel sistema operativo Unix esisteinfatti la possibilita' di comunicare ai processi il veri�carsi di determinati eventi asincroni, cioe' dieventi che richiedono conferma (acknowledgment). Questi eventi asincroni sono detti segnali. Iprocessi possono predisporre la gestione dei segnali tramite la chiamata di sistema signal( ), che inlinguaggio di programmazione C somiglia ad una normale chiamata di funzione. I segnali possonoriguardare le eccezioni indotte dal processo come, per esempio, il segnale SEGV (SEGmentationViolation) che scatta quando un processo tenta di accedere ad un indirizzo esterno al suo spazioindirizzi di memoria virtuale, quando cerca di scrivere in una locazione di memoria centrale a solalettura oppure per errori hardware. I segnali possono riguardare condizioni non piu' recuperabilidurante l'esecuzione di una chiamata di sistema come, per esempio, durante l'esecuzione di unafork( ) al di fuori delle risorse del sistema. I segnali possono essere causati da una condizionedi errore non attesa durante una chiamata di sistema come, per esempio, la scrittura di una pipeche non ha processi consumatori. I segnali possono essere causati da interazioni con il terminalecome, per esempio, la sconnessione di un terminale da parte dell'utente, la caduta della portantesu una linea e la pressione sulla tastiera del terminale dei tasti <break> oppure <delete> da partedell'utente. Va osservato che sarebbe preferibile restituire un messaggio di errore anziche' generareun segnale, ma l'uso di segnali per uccidere i processi che si comportano male e' piu' pragmatico.

Ebbene il kernel esegue una chiamata di sistema exit( ) quando il nuovo processo �glio terminala sua esecuzione sia naturalmente, sia con una chiamata alla funzione di libreria exit( ), sia perche'e' stato intercettato un segnale di sistema. Quando il kernel esegue una chiamata di sistema exit( )libera tutti i bu�er di I/O relativi al processo �glio, costruisce lo status di uscita del processo �glio,assegna al processo �glio lo stato di zombie cioe' di morto vivente ed esegue il compito previstodalla precedente attivazione della chiamata di sistema wait( ) cioe' libera lo spazio occupato nellatabella dei processi del kernel dal nuovo processo �glio nello stato di zombie e sveglia il processodi shell padre che dallo stato di pronto (ready) in attesa di usare la CPU transita nello stato diesecuzione (running). Un processo nello stato di zombie e' un morto vivente. E' morto perche' lasua esecuzione e' terminata ed i suoi segmenti testo e dati non esistono piu', ma e' vivente perche'occupa un posto nella tabella dei processi del kernel. Lo stato di un processo viene trasformatoin zombie per poter consentire al processo padre di ottenere informazioni sui processi �gli mortiper mezzo della chiamata di sistema wait( ), che in linguaggio di programmazione C somiglianaturalmente ad una normale chiamata di funzione. Se infatti al termine dell'esecuzione del nuovoprocesso �glio il relativo elemento nella tabella dei processi del kernel venisse immediatamente

3.2 I processi in Unix 14

cancellato allora il processo padre perderebbe ogni traccia dello status di uscita e dei tempi diesecuzioni del nuovo processo �glio morto. Osserviamo subito che le informazioni sullo status diuscita e sui tempi di esecuzione sono contenuti in un'estensione dell'ingresso (entry) nella tabelladei processi del kernel che e' relativo al processo.

La terza possibilita' e' che la linea di comando speci�chi, come si puo' osservare nella precedente�gura, un comando Unix prede�nito (built-in) oppure un comando shell prede�nito allora la chiama-ta di sistema exec( ) sovrappone, come gia' osservato, il comando prede�nito (built-in) speci�catodalla linea di comando al processo �glio in esecuzione. Il kernel esegue naturalmente una chiamatadi sistema exit( ) quando il nuovo processo �glio termina la sua esecuzione sia naturalmente, siaperche' e' stato intercettato un segnale di sistema.

Per aspettare la terminazione di un processo �glio é possibile far seguire la chiamata di sistemafork( ) da una chiamata di sistema wait( ) in modo da mettere il processo padre chiamantein stato di pronto (ready) in attesa di usare la CPU e di sincronizzarne la ripresa dello statodi esecuzione (running) con la exit( ) del processo �glio rimasto in esecuzione, che il processopadre chiamante ha creato con la chiamata di sistema per la gestione dei processi fork( ), che inlinguaggio di programmazione C somiglia naturalmente ad una normale chiamata di funzione. Ilprogramma eseguibile processi, descritto nei prossimi tre paragra�, segue proprio questa �loso�a.Osserviamo in�ne che i programmi eseguibili possono essere suddivisi in programmi eseguibili utenteed in programmi eseguibili applicativi. I programmi eseguibili utente sono forniti con il sistemaoperativo e permettono di e�ettuare operazioni sui �le e sui processi. I programmi eseguibiliapplicativi sono invece scritti dagli utenti e permettono di risolvere le problematiche piu' disparatecome la contabilita', il controllo della gestione ed i problemi di ingegneria.

3.2 I processi in Unix

Un processo puó essere de�nito come un programma in esecuzione, anzi é l'ambiente nel qualeesegue un programma. Un processo consiste di codice, dati e stack (naturalmente un processo puóleggere e scrivere i suoi dati e stack ma non puó leggere o scrivere i dati o lo stack di altri).

Ogni processo ha un ingresso (entry) in una tabella detta tabella dei processi del kernel.Questo ingresso nella tabella dei processi del kernel contiene cinque campi. Il primo campocontiene un puntatore ad una tabella detta area u (AREA User) oppure area u block. Ogniprocesso possiede infatti una tabella privata detta area u, che in realta' e' un'estensione dell'ingressorelativo al processo nella tabella dei processi del kernel. L'area u contiene informazioni localisul processo, come ad esempio, i puntatori ai �le aperti dal processo stesso e le informazioni sullostatus di uscita e sui tempi di esecuzione. Il kernel accede all'area u del processo in esecuzionecome se questa fosse l'unica area u del sistema. Il kernel, tramite il modulo della gestione dellamemoria, cambia infatti la sua mappa di traduzione degli indirizzi di memoria virtuale a secondadel processo in esecuzione per accedere all'area u corretta. Anche un processo puo' accedere allasua area u, ma soltanto quando e' in esecuzione in modalita' sistema. Per questa caratteristical'area u e' una tabella di sistema. Poiche' il kernel puo' accedere ad una sola area u alla volta,l'area u de�nisce parzialmente il contesto del processo in esecuzione. Quando il kernel schedula unprocesso per l'esecuzione, trova l'area u corrispondente nella memoria centrale e la rende accessibile.

Ogni elemento della tabella processi contiene puntatori al codice, ai dati e allo stack e contienel'area U del processo. Tutti i processi di Unix (tranne il primo processo, il processo 0) sono creaticon la system call fork.

La tabella dei processi contiene le seguenti informazioni:

• stato del processo

• UID

3.2 I processi in Unix 15

Figura 3.7 La tebella dei processi in Unix

• Area U

L'area U contiene le seguenti informazioni:

• Puntatore alla tabella dei processi

• tabelle pregion

• descrittori di tutti i �le aperti

• directory corrente

• radice corrente

• Parametri di I/O

• Limiti del processo e dei �le

Un processo in Unix e' composto da tre regioni: la regione codice (text nella terminologiaUnix), la regione dati e la regione stack. La regione codice e' composta da tutte le istruzioniche sono relative al processo in esecuzione. La regione dati e' costituita dalle variabili globalidel processo. Se un processo tenta di uscire dalla propria regione dati, per esempio tentandoper mezzo di un puntatore di accedere ad un indirizzo esterno al suo spazio indirizzi di memoriavirtuale o di scrivere memoria a sola lettura, il kernel genera un segnale SEGV (SEGmentationViolation), che come azione di default fa terminare l'esecuzione del processo e stampa sullo schermovideo del terminale il messaggio Segmentation violation (coredump). La regione stack e' in�ne uninsieme di lunghezza variabile di locazioni di memoria nella quale vengono memorizzate le variabililocali durante l'esecuzione del processo. La dimensione della regione stack viene inoltre aggiustatadinamicamente dal kernel durante l'esecuzione del processo.

Poiche' un processo puo' essere in esecuzione in modalita' utente oppure in modalita' sis-tema vengono in realta' riservate due regioni stack e cioe' una regione stack dell'utente e dellaregione stack di sistema per cui il processo risulta composto da quattro regioni.

Il formato delle regioni di un processo dipende dalle versioni del sistema operativo Unix. Adesempio a partire dal sistema operativo UNIX System V Revisione 4 il formato e' ELF (ExtensibleLinking Format), mentre con le Revisione precedenti era COFF (Common Object File Format).

3.3 Gli stati di un processo 16

In particolare il kernel del sistema operativo UNIX System V divide lo spazio di indirizzi di memoriavirtuale di un processo in regioni logiche. Una regione e' un'area contigua dello spazio di indirizzidi memoria virtuale di un processo che puo' essere trattata come un unico oggetto da condividereoppure da proteggere. Una regione puo' essere condivisa contemporaneamente da piu' processidiversi, ad esempio vari processi possono eseguire lo stesso programma e quindi condividono unacopia della regione testo. Analogamente diversi processi possono cooperare a condividere unaregione comune di memoria condivisa.

Ogni processo ha dunque un ingresso (entry) nella tabella dei processi del kernel e perogni processo e' allocata un'area u. L'ingresso nella tabella dei processi del kernel e l'areau contengono tutte le informazioni di controllo e di stato del relativo processo. Il secondo campocontiene un �ag che descrive lo stato del processo, cioe' che informa in quale degli otto stati si trovail processo. Il terzo campo contiene l'identi�catore UID (User IDentity) dell'utente proprietario(Owner) del processo. Il quarto campo contiene un insieme di descrittori di eventi di I/O validiquando il processo e' nello stato di bloccato (blocked) in memoria centrale in attesa di un eventodi I/O, ad esempio di leggere dati dalla tastiera del terminale. In�ne il quinto campo contiene unpuntatore ad un ingresso (entry) nella tabella detta tabella delle regioni per ogni processooppure per brevita' pregion.

L'ingresso della pregion relativo al processo contiene, per ognuna delle tre regioni relative alprocesso, quattro campi. Il primo campo contiene l'indirizzo di memoria virtuale di partenza dellaregione. Il secondo campo contiene una descrizione degli attributi della regione, per esempio secontiene testo oppure dati, se e' condivisa oppure privata al processo. Il terzo campo contiene unadescrizione del tipo di accesso alla regione che e' consentito al processo, cioe' sola lettura oppurelettura e scrittura. In�ne il quarto campo contiene un puntatore ad un ingresso (entry) nellatabella detta tabella delle regioni attive.

Ebbene l'ampiezza della regione codice e' la di�erenza tra gli indirizzi di memoria virtualedi partenza della regione dati e della regione codice stessa, l'ampiezza della regione dati e'la di�erenza tra gli indirizzi di memoria virtuale di partenza della regione stack e della regionedati stessa, mentre, come gia' osservato, la dimensione della regione stack viene aggiustata di-namicamente dal kernel durante l'esecuzione del processo. L'ingresso relativo ad un altro processoha infatti un campo di indirizzamento virtuale che non ha nulla a che fare con quello del nostroprocesso anzi, come gia' osservato, una regione puo' essere condivisa contemporaneamente da piu'processi diversi.

3.3 Gli stati di un processo

Il sistema operativo Unix e', come abbiamo gia' osservato, multiprogrammato (multi-tasking) eda divisione di tempo (time-sharing). Il sistema operativo Unix consente quindi l'uso della CPU amolti processi contemporaneamente, ma cio' va inteso nel senso che questi processi sono eseguitiuno alla volta per una fettina di tempo (time slice) limitata. Tale azione detta schedulazione none' in alcun modo percettibile dal processo, che infatti puo' benissimo pensare di essere l'unico inesecuzione. Ebbene quando il kernel decide di schedulare un altro processo, e�ettua un contextswitch, cosi' da potere eseguire nel contesto un altro processo. Il kernel permette un context switchsolo in certe condizioni ed assicura l'integrita' e la coerenza delle strutture dati vietando i contextswitch arbitrari. Quando esegue un context switch il kernel salva le informazioni necessarie perpoter poi tornare ad eseguire il processo abbandonato.

Si osservi che anche quando un processo passa dall'esecuzione in modalita' utente all'ese-cuzione in modalita' sistema il kernel salva le informazioni per poter ritornare all'esecuzione inmodalita' utente e proseguire l'esecuzione da dove l'ha interrotta, ma attenzione che cio' non e'un context switch. Il kernel permette il context switch soltanto in quattro particolari circostanze:quando un processo entra nello stato di sospensione perche' prima che il processo si svegli potrebbe

3.3 Gli stati di un processo 17

passare molto tempo ed altri processi possono eseguire nel frattempo, quando termina l'esecuzionecon una chiamata di sistema exit( ) se non altro perche' non c'e nulla altro da fare, quando unprocesso torna in modalita' utente da una chiamata di sistema ma non e' piu' il processo prioritariooppure quando un processo torna alla modalita' utente dopo che il kernel ha completato la gestionedelle interruzioni (interrupt) ma non e' piu' il processo prioritario.

Il kernel e' responsabile anche della gestione delle interruzioni (interrupt), sia che essi provenganodall'hardware, come le interruzioni generate dal clock e le interruzioni generate dalle periferiche (peresempio da uno dei dischi), sia che si tratti di un'interruzione programmata cioe' di un interruptsoftware oppure di eccezioni come sono gli errori di paginazione. Il kernel gestisce le interruzioni conil seguente protocollo: salva il contenuto attuale dei registri del processo in esecuzione e crea un nuo-vo contesto, determina la causa dell'interruzione, identi�cando il tipo di interruzione (clock oppureperiferica, per esempio disco) ed il numero di unita' di interruzione se possibile (come per esempioquale disco ha provocato l'interruzione), chiama il relativo gestore delle interruzioni ed attende cheil gestore delle interruzioni completi il suo compito e ritorni il controllo al processo. Il kernel esegueuna sequenza di istruzione speci�ca per la macchina, per recuperare il contesto dei registri e lo stackkernel del precedente contesto cosi' come erano prima dell'interruzione e riprende l'esecuzione delcontesto recuperato Il comportamento del processo puo' essere pero' alterato dalla gestione delleinterruzioni poiche' tale gestione puo' aver alterato le strutture dati del kernel e svegliato processisospesi, normalmente pero' il processo continua la sua esecuzione come se l'interruzione non fossemai avvenuta.

La procedura di context switch e' simile alla procedura di gestione delle interruzioni ed allaprocedura delle chiamate di sistema, tranne per il fatto che il kernel recupera lo stato di contesto diun altro processo, anziche' lo stato di contesto precedente dello stesso processo. La scelta di qualeprocesso schedulare dopo un context switch e' una decisione di strategia che non tocca i meccanismidi context switch.

Esistono concettualmente due stati della vita di un processo: lo stato di esecuzione (running)e lo stato di bloccato (blocked) cioe' lo stato di sospensione in attesa di un evento esterno. Unprocesso in stato di sospensione non e' eseguibile anche se la CPU e' libera. A questi due statisi aggiunge lo stato di pronto (ready) in attesa di usare la CPU naturalmente per motivi dilimitazione di risorse della CPU stessa. Ogni processo transita, durante la sua vita, tra questi trestati ed e' compito del sistema operativo Unix gestire queste transizioni. Nella pratica invece sipossono distinguere per la vita di un processo ben otto stati, che dipendono dal particolare istantedi elaborazione del processo, dalla sua storia precedente, dal fatto che al processo sia assegnatao meno la CPU, dal fatto che esso sia residente in memoria centrale o in memoria di massa inparticolare nella swap area, oppure dal fatto che sia o meno in stato di pronto per l'esecuzione. Lostato di esecuzione puo' essere inoltre suddiviso in esecuzione in modalita' di sistema (kernel mode)ed in esecuzione in modalita' utente (user mode). La modalita' utente si riferisce a quella partedi codice che puo' essere eseguita dal programma. Quando invece il processo richiede servizi alsistema operativo, ad esempio l'apertura o la lettura di un �le di dati, entra inmodalita' sistema,ovvero viene eseguito il codice del kernel. Le routine del kernel permettono di espletare tutti i servizirichiesti dai processi. Alla �ne di tutti questi servizi, il processo ritorna in modalita' utente.

Si osservi che la �gura riportata nella precedente pagina, che descrive gli otto possibili statidella vita di un processo e le relative transizioni, potrebbe dare un'idea statica dell'esecuzione delprocesso mentre, in realta', ogni processo cambia continuamente stato, secondo regole ben precisate.La �gura riportata nella precedente pagina e' un grafo direzionato i cui nodi rappresentano gli statiche un processo puo' assumere ed i cui cammini rappresentano gli eventi che provocano le transizionidi stato. Le transizioni di stato sono permesse soltanto se esiste un arco dal primo al secondo stato.A partire da uno stato, per esempio dallo stato 4, possono essere possibili diverse transizioni, ma perogni processo vi sara' una ed una sola transizione per ogni evento di sistema. Il kernel permette uncontext switch soltanto quando un processo passa dallo stato 2 di esecuzione in modalita' sistema

3.3 Gli stati di un processo 18

allo stato 1 di esecuzione in modalita' utente.I processi vanno invece in stato 4 di sospensione in memoria centrale perche' aspettano il ver-

i�carsi di certi eventi, come il termine di un'operazione di I/O da parte di un'unita' periferica, la�ne dell'esecuzione di un processo e la disponibilita' di risorse del sistema operativo Unix. Questiprocessi sono allora detti in attesa del veri�carsi di un evento. Quando si veri�ca l'evento attesoquesti processi si risvegliano ed entrano nello stato 3 di pronto ad eseguire in memoria centrale(ready to run), dove attendono di essere scelti piu' tardi dal modulo di scheduling.

Il kernel gestisce i �le su device di I/O a blocchi, che sono i nastri ed i dischi, e quindi permette aiprocessi di immagazinare nuove informazioni oppure di recuperare le informazioni precedentementecaricate. Quando un processo desidera accedere ai dati contenuti in un �le, il kernel copia questidati in memoria centrale dove il processo puo' esaminarli, elaborarli ed eventualmente richiedere chei dati vengano nuovamente immagazzinati nello stesso �le oppure in un �le diverso che puo' esseregia' esistente o meno nel �le system.

Il kernel potrebbe compiere le operazioni di lettura e di scrittura direttamente su device di I/Oa blocchi, che sono i nastri ed i dischi, ma i tempi di risposta non sarebbero accettabili a causa dellabassa velocita' di trasferimento su e da device di I/O a blocchi.

Il kernel minimizza la frequenza degli accessi al disco mantenendo un insieme di bu�er datial suo interno, chiamato bu�er cache, che contiene i dati dei blocchi di device di I/O a blocchipiu' recentemente usati. Si faccia molta attenzione che il bu�er cache e' una struttura softwaredel kernel da non confondere con la cache hardware che serve invece a velocizzare i richiami inmemoria.

Quando il kernel legge i dati da device di I/O a blocchi, che sono i nastri ed i dischi, cerca inrealta' di leggerli dal bu�er cache. I dati memorizzati nel bu�er cache sono immediatamentedisponibili e non serve leggerli dal device di I/O a blocchi. Se i dati non sono nel bu�er cache ilkernel li legge dal device di I/O a blocchi e li copia nel bu�er cache utilizzando, per entrambe leoperazioni, un algoritmo di ottimizzazione.

Quando l'operazione di I/O del processo termina, l'hardware interrompe la CPU ed il gestoredelle interruzioni sveglia il processo, che si trova nello stato 4, provocando il suo ingresso nello stato3 di pronto ad eseguire in memoria centrale (ready to run) dove attende di essere scelto piu' tardidal modulo di scheduling.

Se il kernel sta eseguendo tanti processi da superare la disponibilita' di memoria centrale alloralo swapper, cioe' il processo 0 (zero), scarica dalla memoria centrale almeno un processo per farposto ad un altro processo che e' nello stato 3 di pronto ad eseguire in memoria centrale (ready torun).

Quando viene scaricato dalla memoria centrale ogni processo passa nello stato 6 di bloccato inswap area.

Quando lo swapper lo scegliera' come processo da caricare in memoria centrale il processoritornera' nello stato 3 di pronto ad eseguire in memoria centrale (ready to run), il modulo discheduling lo fara' poi partire ed esso entra' nello stato 2 di esecuzione in modalita' sistema(running kernel mode) per poi proseguire.

Quando un processo �nisce la sua esecuzione il kernel esegue la chiamata di sistema exit( ), chefa transitare il processo dallo stato 2 di esecuzione in modalita' sistema (running kernel mode) allostato 8 di zombie.

Descriviamo ora dettagliatamente gli otto stati possibili di un processo. Lo stato 1 e' relativo al-l'esecuzione in modalita' utente (running user mode) mentre lo stato2 e' relativo all'esecuzionein modalita' sistema (running kernel mode). Il processo transita dallo stato 1 allo stato 2 quan-do un processo esegue una chiamata di sistema (system call) oppure in seguito ad un'interruzione(interrupt). La transizione dallo stato 2 allo stato 1 e' invece gestita direttamente dal kernel stessoche puo' in tal caso decidere di realizzare anche un context switching in attesa che il modulo discheduling dia via libera al processo dopo aver eventualmente dato la precedenza, per esempio,

3.3 Gli stati di un processo 19

Figura 3.8 Gli stati di un processo in Unix

ad un processo con priorita' maggiore. Lo stato3 e' lo stato di pronto in memoria centrale inattesa di usare la CPU, cioe' di pronto per l'esecuzione (ready to run). Un processo nello stato3 non e' in esecuzione, ma e' pronto a partire non appena verra' schedulato dal kernel. E' nellostato 3 un processo che ha terminato lo stato 4 di bloccato in memoria centrale, oppure che e' statoappena creato nello stato 7 e caricato in memoria centrale. Lo stato 4 di bloccato in memoriacentrale indica un processo in attesa di un evento di I/O, ad esempio di leggere dati dalla tastieradel terminale. Lo stato5 di pronto in swap area in attesa di usare la CPU indica un processoche, pur essendo pronto a partire, deve prima essere caricato in memoria centrale. Lo stato di unprocesso bloccato in swap area e' lo stato 6 di bloccato. Lo stato 7 di partenza e lo stato 8 dizombie si riferiscono, rispettivamente, alla creazione di un processo ed alla sua �ne.

La transizione di un processo dallo stato 2 di esecuzione in modalita' sistema allo stato 4 dibloccato in memoria centrale in attesa che termini l'I/O, prevede quasi sempre un context switchingin modo che altri processi possano intanto utilizzare la CPU, che altrimenti resterebbe disoccupatain attesa che termini l'I/O.

Se per esempio nella tabella dei processi del kernel c'e' almeno un ingresso libero allora unachiamata di sistema fork( ) ha successo ed il processo appena creato e' nello stato 7 di partenza. Aquesto punto ci sono due alternative e cioe' la transizione del processo dallo stato 7 verso lo stato 3

3.3 Gli stati di un processo 20

oppure la transizione del processo dallo stato 7 allo stato 5. La transizione del processo dallo stato7 di partenza verso lo stato 3 di pronto in attesa di usare la CPU in memoria centrale puo' avveniresoltanto se in memoria centrale c'e' su�ciente spazio. La transizione del processo dallo stato 7di partenza verso lo stato 5 di pronto in attesa di usare la CPU ma in swap area avviene invecese in memoria centrale non c'e' su�ciente spazio. Un processo nello stato 5 di pronto in attesa diusare la CPU ma in swap area, per poter essere e�ettivamente eseguito deve prima essere caricato inmemoria centrale. Appena c'e' spazio libero in memoria centrale il kernel infatti recupera il processoin stato 5, copia velocemente le tre regioni di tale processo in memoria centrale e cambia lo statoda 5 in 3. Sia allora comunque lo stato 3 quello di partenza. Quando il modulo di schedulingseleziona il processo per l'esecuzione, il processo passa dallo stato 3 allo stato 2 di esecuzione inmodalita' sistema dove completera' la sua parte di chiamata di sistema fork( ). Finito il suocompito il kernel cambia nuovamente lo stato del processo da 2 ad 1, attraverso una transizioneche gestisce direttamente e durante la quale puo' decidere di realizzare un context switching inattesa che il modulo di scheduling dia via libera al processo, dopo aver eventualmente dato laprecedenza, per esempio, ad un altro processo a priorita' maggiore. In�ne il kernel inizializza ilpuntatore alla swap area in modo da liberare spazio su disco per il corrente utente o per altriutenti. Osserviamo che il processo e' ora nello stato 1 di esecuzione in modalita' utente, ma none' �nita perche' dopo un certo tempo il clock puo' interrompre il processo che passera' nuovametenello stato 2 di esecuzione in modalita' sitema. Quando il gestore di clock concludera' la gestione diquesta interruzione (interrupt), il kernel potra' decidere di schedulare anche un altro processo perl'esecuzione, in questo modo il primo processo passera' nello stato 4 di bloccato in memoria centralee l'altro processo andra' in esecuzione.

Consideriamo ora un processo che e' in attesa della �ne di un'operazione di input, per esempio e'in attesa che venga letto un carattere dalla tastiera del terminale, allora e' nello stato 4 di bloccatoin memoria centrale. A questo punto ci sono due alternative: la transizione risveglio del processodallo stato 4 verso lo stato 3 oppure la transizione scaricamento in swap area dallo stato 4 verso lostato 6 di bloccato in swap area. Ebbene lo stato del processo transita verso lo stato 3 di pronto inmemoria centrale se si conclude l'operazione di input prima che altri processi richiedano l'accessoalla memoria centrale. Tuttavia per veloce che sia la �ne dell'input, la CPU potrebbe eseguire nelfrattempo milioni di operazioni per cui il kernel produce un context switching in modo che un altroprecesso, eventualmente di un altro utente, possa utilizzare la CPU. In tal caso la CPU non e' piu'disoccupata, ma il processo nello stato 4 occupa memoria centrale. Se un terzo processo eseguela chiamata di sistema fork( ), allora il kernel puo' aver bisogno di spazio in memoria centrale eallora copia in swap area tutte le regioni dei processi che non sono attivi in memoria centrale,compreso il nostro processo nello stato 4, ed aggiorna il puntatore alla memoria centrale in mododa liberare spazio. In particolare il nostro processo transita dallo stato 4 di bloccato in memoriacentrale allo stato 6 di bloccato in swap area e le sue tre regioni vengono copiate nella swap area,che e' una memoria di massa su disco gestita molto velocemente perche' non usa la paginazione,ma memorizza le tre regioni del processo in maniera contigua. Una volta che la memoria centrale e'di nuovo libera il kernel recupera il processo, che dallo stato 6 di bloccato in swap area e' passatoallo stato 5 di pronto in swap area, e lo copia in memoria centrale con le stesse modalita' descrittenel precedente esempio.

Come gia' osservato e come appare nella precedente �gura, che rappresenta gra�camente gli ottopossibili stati di un processo e le relative transizioni, la transizione dallo stato 1 di esecuzione inmodalita' utente (running user mode) allo stato 2 di esecuzione in modalita' sistema (running kernelmode) viene provocata dalle chiamate di sistema (system call) oppure dalle interruzioni (interrupt).Ebbene, come esempio �nale, consideriamo due processi, che possono essere benissimo relativi anchea due utenti diversi. Il processo1 sia nello stato 1 di esecuzione in modalita' utente ed il processo2sia nello stato 4 di bloccato in memoria centrale in attesa di un evento di I/O, per esempio della�ne di una stampa. Quando la stampante termina il suo compito manda la relativa interruzione

3.4 Esecuzione dei processi utente 21

(interrupt), che viene ricevuta dal kernel. Per poter realizzare la routine di interrupt il kernel fatransitare in stato 2 di esecuzione in modalita' sistema il processo1, esegue toccata e fuga la routinedi interrupt e fa nuovamente transitare il processo1 nello stato 1 di esecuzione in modalita' utente.Il sistema operativo Unix permette infatti a device, come le periferiche di I/O oppure al clock disistema, di interrompere la CPU in modo asincrono. All'arrivo di una interruzione (interrupt), ilkernel salva il contesto corrente cioe' un'immagine congelata di cio' che il processo stava facendo,cerca la causa dell'interruzione e la gestisce. Dopo aver gestito l'interruzione, il kernel ripristinail contesto interrotto e continua come se niente fosse successo. Il processo2 evolve con le stessemodalita' descritte nei due precedenti esempi.

3.4 Esecuzione dei processi utente

Diciamo ora qualche cosa di piu' sull'esecuzione dei programmi utente che, come abbiamo gia' osser-vato, sono forniti con il sistema operativo Unix e permettono di e�ettuare operazioni sui �le. Ebbene,come abbiamo gia' osservato, l'esecuzione dei programmi utente, sul sistema operativo Unix, e' dif-ferenziata tra esecuzione in modalita' utente (running user mode) ed esecuzione in modalita' sistema(running kernel mode). Quando un processo utente esegue una chiamata di sistema, l'esecuzione delprocesso cambia da esecuzione in modalita' utente ad esecuzione in modalita' sistema ed il sistemaoperativo Unix tenta di soddisfare tutte le richieste di ogni utente, restituendo eventualmente uncodice d'errore. Anche se l'utente non fa richieste esplicite dei servizi del sistema operativo, il sis-tema operativo Unix compie comunque operazioni di amministrazione, che si riferiscono ai processiutente, alla gestione delle interruzioni (interrupt), allo scheduling dei vari processi, alla gestionedella memoria centrale, eccetera. I processi in esecuzione in modalita' utente possono accedere alleproprie istruzioni ed ai propri dati, ma non ai dati ed alle istruzioni del kernel oppure a quelle dialtri processi. I processi in esecuzione in modalita' sistema possono invece accedere a tutto. Peresempio, la memoria virtuale di un processo puo' essere suddivisa in parti di cui alcune accessibilied altre inacessibili in modalita' utente, ma certamente tutte accessibili in modalita' sistema.

Alcune istruzioni macchina sono privilegiate e danno errore se vengono eseguite in modalita'utente. Per esempio, se una macchina contiene un'istruzione che manipola il registro di stato delprocessore, i processi eseguiti in modalita' utente non devono poter far uso di questa possibilita'.Sebbene il sistema operativo Unix esegua un processo alternativamente in una sola delle due modal-ita' di utente (running user mode) oppure di sistema (running kernel mode), il kernel lavora sempreper conto di un processo utente. Il kernel infatti non e' un insieme di processi eseguiti parallelamenteai processi utente, ma e' parte di ciascun processo utente. Quando si dice che il kernel alloca dellerisorse oppure compie diverse operazioni signi�ca in realta' che un processo, che e' in esecuzione inmodalita' sistema, alloca le risorse e compie le diverse operazioni. Per esempio, il processo di shelllegge l'input dal terminale dell'utente per mezzo delle chiamate di sistema, il processo di shell e'allora in modo sistema ed il kernel e' parte del processo di shell stesso. Il kernel restituisce poi allaschell i caratteri digitati sulla tastiera del terminale oppure letti da un �le. Il processo di shell quin-di ritorna in modalita' utente, interpreta il �usso di caratteri digitato dall'utente sulla tastiera delterminale oppure letto da un �le ed esegue l'insieme di operazioni speci�cate, il che puo' richiederel'uso di altre chiamate di sistema e quindi nuovi cambiamenti di stato del processo di shell.

Ogni processo e' generato da un altro processo, secondo lo schema gerarchico padre-�glio. Ogniprocesso, eccetto swapper cioe' eccetto il processo 0 (zero), viene creato quando un altro processoesegue una chiamata di sistema fork( ). Anzi, l'unico modo in cui l'utente puo' creare un nuovoprocesso nel sistema operativo Unix e' quello di eseguire la chiamata di sistema per la gestione deiprocessi fork( ). In linguaggio di programmazione C, ad esempio, alcune chiamate di sistema, comead esempio la chiamata di sistema per la gestione dei processi fork( ), sono realizzate con delleparticolari chiamate di funzione di libreria relative alla libreria standard di I/O.

3.4 Esecuzione dei processi utente 22

#include <sys/types.h>

pid_t fork ( void );

.....

main ()

{}

....

pid = fork( );

....

}

Il �le di dichiarazioni /usr/include/sys/types.h viene raggiunto da ogni programma scrittoin linguaggio di programmazione DEC C attraverso una richiesta di inclusione interna e collocatosempre in testa al programma in modo che il preprocessore sostituisca la linea

#include <sys/types.h>con il contenuto del �le /usr/include/sys/types.h del quale interessa sapere soltanto che il tipopid_t e' de�nito come int cioe' come intero.

La funzione di libreria fork( ), come abbiamo gia' osservato, alloca un ingresso (entry) nellatabella dei processi del kernel, crea un nuovo processo detto processo �glio (child process)duplicando le regioni del processo chiamante detto processo padre (parent process), cioe' copia ilcontesto del processo padre, senza pero' liberare la memoria occupata dal processo padre in modoche due copie del processo padre siano in esecuzione allo stesso momento. In caso di successo lafunzione di libreria fork( ) restituisce il valore intero 0 (zero) al processo �glio ed il numero delprocesso o identi�catore di processo PID (Process IDentity) del processo �glio al processo padre.Il numero intero PID e' molto importante perche' il kernel identi�ca ogni processo per mezzo delrelativo PID. Se la funzione fork( ) fallisce restituisce il valore intero -1 al processo padre ed ilprocesso �glio non viene creato.

La funzione di libreria fork( ), che realizza la chiamata di sistema fork( ), fa parte della libreriastandard di I/O lib.a per cui non e' necessario comunicare esplicitamente al loader il nome di questalibreria. Se invece vengono utilizzate funzioni di libreria che non fanno parte della libreria standarddi I/O lib.a, allora bisogna rendere esplicita la necessita' della nuova libreria dichiarandone il nomesulla linea di comando del compilatore.

Il processo �glio e' un clone (copia identica) del processo padre da cui di�erisce soltanto per unparticolare: il valore intero di ritorno della funzione di libreria fork( ) che e' 0 (zero) per il processo�glio, mentre e' diverso da 0 (zero) per il processo padre infatti, come gia' osservato, la funzionedi libreria fork( ) restituisce al processo padre il PID (Proces IDentity) del processo �glio. Alritorno dalla chiamata di sistema fork( ), i due processi padre e �glio hanno copie identiche del lorocontesto a livello utente, eccettuato il valore di ritorno del PID. Se non ci sono risorse disponibili lachiamata di sistema fork invece fallisce. Il processo �glio si riconosce come tale basandosi propriosu questo valore PID ritornato dalla funzione di libreria fork( ). Di norma il processo �glio chiede diessere trasformato in un altro processo che sia relativo ad un �le binario eseguibile memorizzato nel�le system. Ebbene esiste un'intera famiglia di funzioni di libreria che trasformano il processo �glioin un nuovo processo sovrascrivendo il contesto del processo �glio con una copia di un programmaeseguibile: la famiglia delle sei funzioni di libreria che realizzano la chiamata di sistema exec( ).Se e' conosciuto l'esatto pathname del �le binario eseguibile relativo al nuovo processo allora ilprocesso padre puo' eseguire la funzione di libreria execl( ), oppure la funzione di libreria execv(), oppure la funzione di libreria execle( ), oppure la funzione di libreria execve( ). La funzionedi libreria execlp( ) e la funzione di libreria execvp( ) cercano invece il pathname del �le binarioeseguibile relativo al nuovo processo in tutte i direttori speci�cati dal valore $PATH della variabiled'ambiente (environment) PATH. Tutte queste sei funzioni di libreria che realizzano la chiamata di

3.4 Esecuzione dei processi utente 23

sistema exec( ) fanno parte della libreria standard di I/O lib.a per cui non e' necessario comunicareesplicitamente al loader il nome di questa libreria.

#include <unistd.h>

extern char **environ;

int execl (}

const char *path,

const char *arg0,

const char *arg1,

const char *arg2,

................,

const char *argN);

dove la variabile path e' un puntatore al pathname che identi�ca un �le binario eseguibile memoriz-zato nel �le system e che la chiamata alla funzione di libreria standard di I/O execl( ) sovrascriveal processo �glio, mentre le variabili arg0, arg1, arg2, ... ed argN sono dei puntatori a caratterecioe' ad una stringa che termina con il carattere ASCII di codice 0, che di solito viene indicatocon NULL. Il linguaggio di programmazione C non fornisce esplicitamnte meccanismi per la ma-nipolzione delle stringhe. Esistono pero' due convenzioni fondamentali, che sono rispettate anchedal particolare sistema operativo Unix utilizzato. La prima convenzione e' che il nome di un vettore(array) di caratteri e' una costante non modi�cabile di tipo puntatore e che questo puntatore puntaal primo carattere del vettore. La seconda convenzione e' che una stringa di lunghezza pari ad ncaratteri, cioe' una successione di n caratteri viene memorizzata in un vettore, per esempio, ab dialmeno n + 1 caratteri, assegnando ad ab[ 0 ], ab[ 1 ], ..., ab[ n - 1 ] ordinatamente i caratteridella stringa ed ad ab[ n ] il carattere NULL. In linguaggio di programmazione C esiste inoltre unacomoda abbreviazione per inizializzare le stringhe, infatti il frammento di programma

char *pp;

...

pp="ciao mondo";

puts( pp );}

ha i seguenti e�etti: viene allocato un puntatore pp a carattere, viene riservata un'area di 11 byte (ciao mondo + NULL ) nella zona statica dell'area dati, gli 11 byte della zona statica dell'area dativengono inizializzati con i caratteri ciao mondo, l'indirizzo del primo carattere viene assegnato alpuntatore pp, la funzione di libreria standard di I/O puts( ) viene chiamata con argomento pp eviene percio' stampata sullo schermo video del terminale la linea ciao mondo

La chiamata di sistema exec é disponibile in diverse versioni:

#include <unistd.h>

extern char **environ;

int execv (const char *path, char * const argv[ ]);

#include <unistd.h>

extern char **environ;

int execle (const char *path,const char *arg0,...,const char *argN,char * const envp[ ] );

3.4 Esecuzione dei processi utente 24

#include <unistd.h>}

extern char **environ;}

int execve (const char *path,char * const argv[ ],char * const envp[ ] );

#include <unistd.h>}

extern char **environ;}

int execlp (const char *file,const char *arg0,...,const char *argN );

#include <unistd.h>}

extern char **environ;}

int execvp (const char *file,char * const argv[ ] );

dove la variabile path e' un puntatore al pathname che identi�ca un �le binario eseguibile memoriz-zato nel �le system e che la chiamata alla funzione di libreria standard di I/O execv( ) sovrascriveal processo �glio, mentre la variabile argv e' un vettore (array) di puntatori a carattere cioe' aduna stringa che termina con il carattere NULL.

Una volta che e' terminata l'azione di una delle sei funzioni di libreria che realizzano la chiamatadi sistema exec( ), il nuovo processo �glio viene allora schedulato dal kernel.

Il nuovo processo �glio puo' terminare naturalmente la sua esecuzione, forzare la �ne della suaesecuzione con una chiamata alla funzione di libreria exit( ), che la relativa libreria mappera' nellacorrispondente chiamata di sistema exit( ) costituita da una procedura codi�cata in linguaggioAssembler, oppure terminare la sua esecuzione per cause esterne in quanto e' stato intercettato unsegnale di sistema. Nel sistema operativo Unix esiste infatti la possibilita' di comunicare ai processi ilveri�carsi di determinati eventi asincroni, cioe' di eventi che richiedono conferma (acknowledgment).Questi eventi asincroni sono detti segnali. Ai segnali e' dedicato un omonimo primo paragrafo delcapitolo Il comando prede�nito KornShell trap. I processi, che hanno il sorgente scritto inlinguaggio di programmazione C, possono predisporre la gestione dei segnali tramite la funzione dilibreria signal( ), che la relativa libreria mappera' nella corrispondente chiamata di sistema signal() costituita da una procedura codi�cata in linguaggio Assembler. I segnali possono riguardare leeccezioni indotte dal processo come, per esempio, il segnale SEGV (SEGmentation Violation)che scatta quando un processo tenta di accedere ad un indirizzo esterno al suo spazio di indirizzivirtuali, quando cerca di scrivere memoria a sola lettura oppure per errori hardware. I segnalipossono riguardare condizioni non piu' recuperabili durante l'esecuzione di una chiamata di sistemacome, per esempio, durante l'esecuzione di una fork( ) al di fuori delle risorse del sistema. I segnalipossono essere causati da una condizione di errore non attesa durante una chiamata di sistemacome, per esempio, la scrittura di una pipe che non ha processi consumatori. I segnali possonoessere causati da interazioni con il terminale come, per esempio, la sconnessione di un terminale daparte dell'utente, la caduta della portante su una linea e la pressione sulla tastiera del terminale deitasti break oppure delete da parte dell'utente. Va osservato che sarebbe preferibile restituire unmessaggio di errore anziche' generare un segnale, ma l'uso di segnali per uccidere i processi che sicomportano male e' piu' pragmatico.

Ebbene il kernel esegue una chiamata di sistema exit( ) quando il nuovo processo �glio terminala sua esecuzione sia naturalmente, sia con una chiamata alla funzione di libreria exit( ), sia perche'e' stato intercettato un segnale di sistema. Quando il kernel esegue una chiamata di sistema exit() libera tutti i bu�er relativi al processo, costruisce lo status di uscita del processo, assegna alprocesso lo stato di zombie ed esegue il compito previsto da un'eventuale precedente attivazionedella chiamata di sistema wait( ) cioe' libera lo spazio occupato nella tabella dei processi dal nuovoprocesso �glio nello stato di zombie e sveglia il processo padre che dallo stato di pronto (ready)in attesa di usare la CPU transita nello stato di esecuzione (running).

Un processo nello stato di zombie e' un morto vivente. E' morto perche' la sua esecuzione

3.4 Esecuzione dei processi utente 25

e' terminata ed i suoi segmenti testo e dati non esistono piu', ma e' vivente perche' occupa unposto nella tabella dei processi. Lo stato di un processo viene trasformato in zombie per poterconsentire al processo padre di ottenere informazioni, per mezzo della funzione di libreriawait( ), suiprocessi �gli morti. Se infatti al termine dell'esecuzione del nuovo processo �glio il relativo elementonella tabella dei processi del kernel venisse immediatamente cancellato allora il processo padreperderebbe ogni traccia dello status di uscita e dei tempi di esecuzioni del nuovo processo �gliomorto.

Ci potremmo chiedere perche' il linguaggio di programmazione C utilizza le librerie?Ebbene, le operazioni di I/O, cioe' lo scambio di informazioni tra un processo ed il mondo es-

terno al calcolatore, sono spesso le parti meno elaganti di un algoritmo. Infatti non solo si devonogestire da programma tutti gli aspetti architetturali e circuitali delle periferiche, come tastiera eschermo video del terminale, ma si deve fare i conti anche con le caratteristiche dei diversi dispositivid'I/O, che per loro natura sono variabili da un calcolatore ad un altro. L'inventore del linguaggiodi programmazione C, cioe' Dennis Ritchie, e' partito con l'idea di de�nire un insieme minimo direquisiti tra i quali non rientra alcuna speci�ca riguardante l'I/O delle informazioni. In altre paroleil linguaggio di programmazione C si presenta assai povero in quanto non fa alcuna ipotesi sullemodalita' di acquisizione dei dati dalle varie periferiche d'ingresso oppure sull'invio dei risultati allevarie periferiche di uscita. Naturalmente questo non signi�ca che il linguaggio di programmazioneC non preveda l'utilizzo di periferiche d'ingresso e di uscita. Il fatto che ogni programma scritto inlinguaggio di programmazione C deve essere sempre eseguito sotto il controllo del sistema operativoUnix ha permesso di delegare ad un programmatore di sistema il compito di scrivere un numeroopportuno di funzioni d'I/O. Queste funzioni d'ingresso e di uscita sono costituite di solito da proce-dure codi�cate in linguaggio Assembler e sono in grado di collegare un programma C con i supportialle operazioni d'I/O disponibili a livello di sistema operativo. Questa �loso�a di gestione dell'I/O,che e' tipica del linguaggio di programmazione C, ha portato alla codi�ca di alcune funzioni chesono di fatto diventate una dotazione standard che, sotto forma di funzioni di libreria, accompagnain pratica ogni versione di compilatore C. Gli implementatori del linguaggio di programmazione Channo preferito infatti mantenere il linguaggio piccolo e quindi facilmente portabile e delegare allefunzioni di libreria i compiti di varia utilita'.

I concetti piu' importanti da tenere presenti sono quelli di Standard Input e di Standard Output,che sono visti da ogni programma C come due �ussi di informazione, che raggiungono il programmaC senza che questo deva conoscere minimamente la natura dei dispositivi periferici. In altre parole,e' il sistema operativo che si occupa di gestire il collegamento tra di un particolare dispositivo�sico e lo Standard Input e lo Standard Output senza che sia necessario tenerne conto a livello diprogramma. Lo Standard Input e' di solito la tastiera del terminale, ma anche un lettore di nastrioppure di schede. Lo Standard Output e' di solito e' lo schermo video del terminale, ma anche unastampante oppure un perforatore di schede.

Il processo che invoca la chiamata di sistema fork( ) e' dunque il processo padre ed il processoappena creato e' il processo �glio. Un processo puo' generare piu' processi �gli, ma puo' avere soloun processo padre. Il kernel identi�ca ogni processo per mezzo di un numero intero associato alprocesso, chiamato ID (IDentity) del processo oppure, piu' brevemente, PID (Proces IDentity).Il processo 0 (zero) e' un processo speciale creato al bootstrap che dopo aver creato un processo�glio, il processo 1, con una fork( ), diventa il processo chiamato swapper. Il processo 1 e' notocome init, in alcune Revisione sched, e' il primo processo cioe' l'antenato di tutti gli altri processi.Il processo 1 cioe' init esegue un ciclo in�nito, durante il quale legge il �le /etc/inittab, dal qualeprende le informazioni particolari sulle azioni da intraprendere nel momemto in cui il sistema entrain stati particolari.

Il processo padre esegue, come abbiamo gia' osservato, anche la chiamata di sistema exec( ),passandole come parametro il nome del programma che deve essere eseguito. L'area di memoriadel processo �glio viene allora utilizzata per il nuovo processo. La shell stessa e' un processo il cui

3.4 Esecuzione dei processi utente 26

compito e' quello di interpretare i comandi interattivi che si inseriscono al prompt di un qualsiasiterminale oppure da un �le.

Un comando e' un programma del sistema operativo composto dal nome, eventuali opzionied una lista di argomenti (generalmente nomi di �le) su cui il comando ha e�etto. Il nome delcomando e' l'istruzione data e puo' essere il richiamo di un compilatore, dell'editor di testo disistema o la visualizzazione o la stampa del contenuto di un �le. Le opzioni del comando sonodelle variazioni al comando stesso. Gli argomenti del comando sono di solito i �le su cui deveintervenire il comando. Con questa logica e' possibile talvolta ricostruire alcuni comandi Unixsenza conoscerne la sintassi esatta in quanto la sintassi dei comandi Unix segue degli standard.L'architettura del sistema operativo Unix spinge i programmatori a scrivere piccoli programmimodulari che compiano soltanto poche operazioni e poi combinarli usando la primitiva di sistemapipe e la primitiva redirezione dell'I/O per compiere operazioni piu' so�sticate.