Anda di halaman 1dari 61

UNIVERSIT DEGLI STUDI DI BRESCIA

FACOLT DI INGEGNERIA
APPUNTI DI SISTEMI OPERATIVI
Prima versione:
Paolo Bellagente AA 2006/2007 gkerion@yahoo.it
Seconda versione:
Luca Seghetto AA 2014/2015

1 - Materiale aggiuntivo e introduzione:


Oltre ai presenti appunti possibile trovare ulteriore materiale:
nei file allegati.
sul sito del corso: tritone.ing.unibs.it/soa
su libri di testo, consigliati: "Sistemi Operativi - Stallings", "Sistemi
Operativi Tanenbau" entrambi in lingua inglese.
Lo scopo del corso quello di dare una panoramica generale sull'architettura e il funzionamento dei sistemi operativi.

2 - Il Sistema Operativo
Come detto sopra in questo corso si parler di Sistemi operativi, ma
cosa vuol dire sistema operativo (SO da qui in poi)? Il nome in s non
dice molto; andando a leggere alcuni libri di informatici teorici molto famosi si nota che anche loro fanno fatica a definire un concetto cos astratto; mettiamoci allora d'accordo su cosa intendiamo quando parliamo di SO:
"Definizione": un SO un software che fa da intermediario tra l'hardware (HW di seguito) e il software (SW di seguito) il cui scopo rendere
possibile il passaggio da Programmi a Processi.
La differenza tra Programma e processo abbastanza immediata:
Programma: specifica di un algoritmo scritta in un determinato linguaggio, entit statica ed astratta.
Processo: Programma in esecuzione su un esecutore; un'entit
concreta, dinamica e collocata nel
tempo.
Diamo un'occhiata all'architettura
generale, essa presenta una struttura a livelli:
Le applicazioni utente vengono tradotte dai compilatori (interpreti etc..)
in un linguaggio pi vicino alla macchina tuttavia di solito solo SO ad avere il
controllo completo del HW e non l'applicazione utente.
Per fare ci il SO maschera parzialmente l'ISA
(insieme delle istruzioni tipiche di ogni

processore) che viene cos diviso in due


parti: le istruzioni non critiche (es
somma tra due numeri) vengono eseguite direttamente mentre quelle pi
complesse o pi delicate (es accesso
periferica) vengono eseguite attraverso SO.
Questa struttura presenta grossi vantaggi in termini di:
facilit ed efficienza: le istruzioni complesse e delicate sono gi risolte
ed ottimizzate dai programmatori del SO

protezione: processi che tentano di usare le istruzioni privilegiate (spesso


con la finalit di eludere i controlli) vengono bloccate.

Ad esempio nel caso che un'applicazione utente per accedere al disco lo


chiede al sistema operativo tramite una Chiamata di Sistema (o System Call, syscall da ora i n poi), i l quale eseguir la lettura e restituir
il dato all'applicazione (operazione molto delicata perch consiste nell'individuazione della pozione fisica de dato sul disco, quindi numero di settore e
traccia, conseguente posizionamento della testina, lettura d e l dato ecc... se
ogni volta i l programmatore deve pensare a tutto questo...).
Oltre all'interfaccia con l'HW il SO gestisce anche la convivenza tra programmi diversi ed utenti diversi, infatti ogni processo deve poter accedere alle risorse HW e SW (es stampante o sezione critica) senza collidere con gli altri.
Nel caso che esistano utenti diversi, essi vanno identificati e autenticati,
per assegnare a ciascuno gli opportuni diritti d i ac c es s o .
In breve il SO fondamentalmente un:
fornitore di servizi: "dimmi quello che ti serve e poi ci penso io".
gestore delle risorse: come un vigile che controlla il traffico stradale (regola i passaggi agli incroci, assegna le multe ecc...).
Concetto riassuntivo: Il SO un VIRTUALIZZATORE. Maschera la realt fisica facendo credere all'entit di livello superiore che ne esiste un'altra diversa.
FRUITORE DELLA
VIRTUALIZZAZIONE

VIRTUALIZZATORE

REALT VIRTUALE

REALT "VERA"

Uomo

LSD

mondi fantastici

reazioni chimiche
(dannose) nel cervello

Uomo

Sistemi di realt
virtuale

scenari 3D

caschi, guanti, ...

Uomo

SO

macchina dedicata

macchina condivisa

Processi

SO

CPU dedicata

CPU condivisa

Processi

SO

Spazio in memoria
continuo e dedicato
(memoria virtuale)

memoria allocata a
pezzi ed in parte
su disco

Processi

SO

File

dispositivi HW diversi

Grazie a questo ci si pu concentrare su concetti astratti


(processi, file) ignorando la pi
complessa realt.
La virtualizzazione pu essere
operata ricorsivamente su pi livelli (es SO virtualizza HW, JVM
virtualizza a sua volta SO.... Virtualizzazione al cubo).

Ci viene operato per esigenze diverse, ad esempio, permette di confinare le


singole applicazioni in un ambienti protetti (con il conseguente incremento
della sicurezza) o, nella maggioranza dei casi, per aumentare la portabilit
delle stesse su sistemi HW e SW diversi.

Lo stesso SO consente agli sviluppatori di realizzare le loro applicazioni secondo solo le specifiche del SO e non dei diversi HW su quale vengono realmente
eseguite.

2.1 - Caratteristiche del SO


Vediamo di stilare una rapida "Carta d'identit" di un SO:

Nome: SO.
Et: circa 50 anni.
Sesso: SW (ma molto vicino al HW).
Professione: virtualizzare. Pu essere a pi livelli dove la "realt
vera" una virtuale generata dal livello inferiore (es Java Virtual Ma chi
ne). Per separare utenti che condividono la stessa macchina pu creare di
verse realt separate nelle quali pu fare tutto.
Domicilio: sistemi di elaborazione.

Segni particolari: dual mode.

2.2 - Classificazione
I SO possono essere suddivisi in categorie a seconda delle loro caratteristiche/piattaforme per cui sono progettati, ad esempio:
SO mono/multi-processore: Sono in base alla tipologia di CPU: Monocore (es Intel Pentium) o multi-core (es Intel Duo, I7, I3 ecc)
SO di rete: Possono connettersi ad una rete (es Internet)
SO distribuiti: S fruttando una rete di calcolatori, tramite la virtualizzazione, da l'impressione di avere un unico enorme calcolatore .
SO cluster: Virtualizza un enorme sistema, costituito da molti processori posto in una zona ben delimitata e circoscritta (Super-calcolatori, server di grandi dimensioni).
SO embedded: SO dedicati ed molto ottimizzati per sistemi con poche risorse di calcolo, generalmente dedicati ad applicazioni particolari (es lavatrici, PLC, automobili ecc...).
Generalmente gli SO comuni (es Windows o Ubuntu) sono a multiprocessore e di rete.

2.3 - Il DUAL MODE


I moderni processori sono Dual Mode, cio permettono o meno, l'esecuzione
di certe istruzioni da parte dell'applicazione a seconda del modo impostato (tramite lo stato di un bit di un particolare registro).
Ci permette di avere un certo controllo sia sul HW e sia sui processi che sono in
esecuzione.
Si distinguono perci due modi di funzionamento:
modo Kernel: (o privilegiato o superuser), il calcolatore esegue
qualsiasi tipo di istruzione, indica che st eseguendo istruzioni del SO.
modo Utente: In questo modo girano le applicazioni. Se durante
l'esecuzione viene richiesta l'esecuzione di codice privilegiato (istruzioni alle quali sarebbe opportuno sostituire una syscall) viene generata
un'eccezione ed subentra SO.
Durante la vita di un calcolatore si continua a passare tra i due modi, ed il
passaggio tra un modo e l'altro di appannaggio esclusivo del SO e tale fase
prende il nome di mode switch.

Es di vita di un calcolatore:

Durante un ciclo di vita (da quando viene acceso il calcolatore a quando viene spento) esso passa tre frasi principali: Boostrap (dove viene preparato il
sistema per l'utilizzo da parte dell'utente), si ha poi un funzionamento regolare e una fase di Shutdown al termine (si salvano i dati non salvati e si prepara il calcolatore alla prossima accensione).
2.3.1 - Bootstrap
Il bootstrap la sequenza di accensione di un calcolatore.
La CPU esegue solo le istruzioni che le vengono date, perci inizia dalla 0 che
si trova su una ROM (Read Only Memory). Tale frammento di codice, che chiamata BIOS (Basic Input Outpu System) serve per effettuare dei controlli HW
e preparare il sistema a caricare SO.
Esso si compone di diverse fasi; all'inzio viene effettuata una diagnostic,
rilevamento e configurazione dell'HW (verifica e certificazione della presenza di RAM, della scheda video, audio ecc...), una successiva inizializzazione dell'HW (lettura del SO del dispositivo, firmware) e la preparazione
delle routine di interrupt.
Fino ad adesso non stato ancora caricato e ne eseguito una singola riga del
SO.
Se questa fase fallisce, il dispositivo emette segnali audio o video e si blocca
in attesa di riparazioni (es mancanza della tastiera).
A questo punto viene identificato il dispositivo di Boot (es CD, DVD,
USB, HDD ecc) dal quale viene letta una locazione fissata dello stesso.
Questa locazione contiene un programmino d i boot (bootloader),il quale
consente all'utente di scegliere (se necessario) il SO da caricare e la posizione sul
disco fisica dove esso contenuto (l'indirizzo da cui partire per caricare SO).
Per fare ci deve quindi contenere anche informazioni sulle partizioni del disco.
Il caricamento del SO (compia del suo codice dal dispositivo di memorizzazione alla memoria centrale, RAM) detto a valanga, ed in questa fase
che verranno create tutte le strutture per il corrretto funzionamento dello
stesso (es struttura per la sessione utente con i permessi sui vari oggetti del
SO).
In questa fase verranno anche avviati i processi di sistema e quelli utente utili (es: login&password).
Se il processo di Boot si blocca in questa fase, SO parte in modalit provvisoria (un boot semplificato) che carica meno cose possibili (per es. invece
del driver di accelerazione grafica avvier il driver grafico base per tutte le
schede) sperando di escludere il programma che ha generato l'errore; si
consegner all'utente una macchina brutta ma funzionale, allo scopo di correggere gli errori.
2.3.2 - Shutdown
Procedura d i spegnimento, in cui SO salva le informazioni sul suo stato e
termina ordinatamente i processi per evitare la perdita di dati e salvaguardare l'integrit della macchina.

2.3.3 - Funzionamento Normale


Nella figura a sinistra viene
rappresentato il normale ciclo
di esecuzione di un programma
su un calcolatore.
Come mostrato, esistono particolari segnali che possono interrompere l'esecuzione del
programma, detti generalmente
interrupt, che forzano il SO
ad intervenire (con un conseguente mode switch).
Essi si dividono in circa 3 categorie d i eventi che possono causare un
mode switch:
INTERRUPT: Eventi esterni all'esecuzione dell'istruzione corrente
(eventi asincroni all'esecuzione del programma). Possono essere
"cattivi" (eventi indesiderati come errori HW, segnale di restart etc...)
oppure "Buoni" (eventi compresi nel normale funzionamento come il
clock, il segnale di I/O completato, page fault etc...)
TRAP: (anche dette eccezioni), eventi legati all'istruzione corrente (sincroni all'esecuzione del programma). Sono sempre "Cattivi" (divisione per zero, codice errato, violazione della memoria altrui, tentativo di esecuzione di istruzione privilegiata in modo utente
ecc...).
SUPERVISOR CALL: Chiamata di sistema, o syscall, in cui l'istruzione
chiede un servizio al SO (detti anche interrupt software, sincroni).
Quando avviene uno di questi eventi vengono eseguite alcune azioni HW:
1) Viene rilevato l'evento.
2) Viene identificato l'evento (operazione immediata nel caso di trap o syscall viceversa nel caso interrupt). Nel caso degli interrupt esterni
la rilevazione pu avvenire in due modi:
Polling: La CPU rileva il segnale di interrupt e passa tutte le pe riferiche in ricerca di quella che ha generato l'evento (molto
ineffi ciente).
Interrupt Vettorizzati: La periferica stessa creando il segnale
di interrupt fornisce anche il suo identificativo.
3) Vengono salvati i registri di esecuzione, ad esempio: il Program Counter (PC) e il Program Status Word (PSW).
4) Dopodich viene identificato il nuovo valore del Program Counter e viene cambiato il modo (mode switch e program switch).
In questa fase si sceglie anche il codice da eseguire in risposta all'interrupt e ci pu essere fatto in due modi: si esegue sempre ad un'unica
istruzione e da li si salta a quella specifica per l'interrupt (sempre
all'interno del codice del SO), oppure, se nel caso dei vettorizzati, si associa l'identificativo della periferica (o dell'interrupt) ad un punto specifico del codice del SO.
5) A questo punto si effettuano dei salvataggi ulteriori dei registri (quelli
del SO che verranno sovrascritti). Qui si ha la fine del mode switch.

6) Ora si avr la risposta specifica all'evento che ha generato l'interrupt.


Qui si ha la fine del mode kernel.
7) Quando sono finite tutte le operazioni richieste dall'interrupt si ha il ripristino di tutti i registri, il cambio di modo e la ripresa della normale
esecuzione dei programmi (pu essere diverso da quello che c'era in
precedenza, ad esempio se quest'ultimo ha fatto una richiesta di dati
dal disco che ha bisogno di molto tempo per essere completata).
ESEMPI:
Interrupt dall'orologio di sistema --> viene aggiornato i l contatore -->
torno a fare quello che facevo prima.
System call --> richiesta di acesso al disco --> intanto che aspetto
faccio continuare un'altro processo
2.3.4 - Process Switch
Un process switch l'insieme d e l l e operazioni necessarie per il cambio
del processo da eseguire tornando in modo utente.
Lo schema di vita del calcolatore diventa:

Il process switch salva tutto quello che riguarda P1, carica tutto c i
che riguarda P2 (Si ha sempre l'intervento del SO e quindi anche un
cambio di modo).
Un mode switch deve comunque avvenire ogni decina di microsecondi,
di conseguenza deve durare poco per non incidere eccessivamente sulle
prestazioni della macchina.
2.3.5 - Gestione degli interrupt
Si detto che sia i n un mode switch che i n un process switch avviene il salvataggio delle informazioni relative all'istruzione corrente, tuttavia nei calcolatori moderni il concetto di istruzione corrente potrebbe essere difficilmente determinabile d at o c he i l calcolatore non esegue
una sola istruzione alla volta, ma le esegue come in una catena di
montaggio (pipline).
Si hanno due tipi di microprocessori progettati per:
interrupt precisi: il microprocessore a risolvere tutte le istruzioni accavallate e ad individuare l'istruzione corrente.
interrupt imprecisi: il microprocessore passa tutto i l suo stato al
SO al quale resta l'onere dei calcoli per individuare l'istruzione corrente.
La seconda configurazione spesso non v a bene per applicazioni real
time (dove la durata del mode switch cruciale).
Solitamente il sistema in grado di gestire interrupt nidificati, ovvero interrupt durante la gestione d i un altro evento (il sistema gi in modo kernel).

In tale caso il codice del SO deve essere rientrante (il SO ha la capacit d i


interrompere se stesso e ricaricare lo stesso codice senza errori).
Per evitare che il sistema non progredisca (non esegua i programmi) a causa
dei troppi interrupt che genera una periferica, o peggio, non elabora un interrupt critico (es allarme della temperatura HW), con l'idea anche di ridurre
il numero di mode switch e process switch, per aumentare l'efficienza del sistema si assegnano dei livelli di priorit agli stessi e si lavora ad interrupt
disabilitati.
In questo modo un interrupt critico non verr interrotto da uno a priorit minore, anche se arriva dopo, e quelli critici potranno essere elaborati tutti in
una volta solo al process switch naturale del programma (vedere gestione
dei processi), quando deve comunque il SO.
Inoltre permette di gestire meglio periferiche che generano molti interrupt
(es scheda di rete o HDD), in quanto la prima richiesta ad media priorit e
attiva l'HW corrispondente e le successive verranno gestite direttamente
dall'HW dedicato, liberando la CPU da compiti inutili come il trasferimento
di dati.
Nei sistemi Linux le routine d i interrupt sono separate in:
Top half:parte urgente
Bottom half:parte meno urgente.
Nei sistemi Windows:
Routine di intterupt: parte urgente.
DPC/APC: (Deferred Procedure Call/Asincronous Procedure Call) parti meno urgenti.
Le APC sono pi legate al processo.

2.4 - Architetture dei SO


Ogni SO gestisce il rapporto dei modi User-Kernel in modo diverso in funzione
della sua architettura interna:
Kernel separato La memoria dei processi fisicamente un'altra area
rispetto a quella del SO.
Funzioni SO eseguite come funzioni di processo: O gni processo vede il SO come parte di s non devo cambiare nulla nella gestione
della memoria.
Funzioni SO eseguite come processo kernel: Non esistono
mode switch sono tutti process switch.
All'inizio t u t t i i SO erano software quasi senza struttura, scritti completamente in assembler, cosa che ne impediva completamente la portabilit
su architetture diverse.

Succcessivamnte si cercato di
implementare un modello a livelli, che per risultato impossibile da realizzare causa delle
troppe interazione fra le varie
parti.
Uno schema di principio potrebbe essere quello mostrato in figura.
Una delle parti pi critiche e complesse di un SO il livello kernel,
infatti esso si occupa principalmente della gestione tra i processi e
HW ed esso pu essere costruito
principalmente in tre modi:
kernel monolitico:Tutto il codice del kernel (cuore) del SO in unico
blocco. Le limitazioni pricipali di questo modello sono: tutto eseguito in
modalit Kernel, quindi c' un alto rischio che il sistema si blocchi in caso
di errore; l o sviluppo e l'estensione del progetto sono scarsamente facilitati dato che, per apportare anche una piccola modifica, si deve
mettere mano a migliaia di righe di codice; inoltre quando si accende
la macchina si carica sempre tutto anche quello che non serve con un
forte spreco d i tempo e risorse.
kernel modulari (Linux): Dove una parte resta monolitica ma la
maggior parte delle funzionalit racchiusa in moduli che possono
essere aggiunti o rimossi (anche a macchina accesa per es i driver).
Microkernel: Prevede un piccolissimo (micro) kernel che contiene le
funzioni d i controllo dell'HW e d i gestione degli interrupt, mentre tutto il resto (es gestione del disco ) si appoggiano sul microkernel come
processi eseguiti in modo utente.

I vantaggi di una struttura a microkernel sono notevoli:


La parte d i microkernel minima quindi minima la parte di codice che gira in modo privilegiato, la possibilit ches succedano
cose irreversibili minima.
Esiste un potenziale incremento di stabilit grazie all'elevato
modularit (se si pianta un modulo riavvio quello non tutta la macchina; la modularit completa riesco a caricare esattamente
ci che mi serve in quel momento.
Il prezzo da pagare per tutto ci per non trascurabile: sono molto pi
lenti a causa dei notevoli process e mode switch che ogni azione SO comporta.
Prendiamo il caso di dover gestire un accesso al disco: l'applicazione fa
una syscall al SO per la lettura nel File System; la sequenza delle operazioni :
Mode switch in modo kernel
process switch per il modulo SO del File System,
mode switch per l'esecuzione del codice del SO.

Ponendo il caso che riparta lo stesso processo di prima, alla fine della lettura si avr
lo stesso procedimento in verso opposto.
Totale alla fine dell'operazione: 4 mode switch e 2 process switch contro i 2 mode switch
del caso in cui il SO gira in modo kernel monolitico/modulare.
2.4.1 - Esempio: Microsoft Windows NT
Partiamo con un po' d i storia, l'avventura Microsoft nei sistemi operativi
parte nel 1981 con MS-DOS, da quel momento in poi si vista la nascita di
una lunga serie d i S O Microsoft. Considerando la moderna teoria dei SO, l'
MS-DOS, win95, win98, winME non possono essere definiti SO a pieno titolo in
quanto (sopratutto per retrocompatibilit con DOS) non supportano a pieno il
dual mode e i meccanismi di virtualizzazione dell'HW. MS-DOS si lasciava scavalcare tranquillamente dalle applicazioni utente nel controllo dell'HW per
motivi di compatibilit con processori non dual mode.

La svolta si ebbe con i sistemi della serie Windows NT (New Tecnology) della
quale fanno parte tutti i si sistemi MS moderni (winNT, win2000, winXP, winVista).
NT nacque con l'obiettivo d i essere portabile su architetture diverse, venne introdotto HAL, l'Hardware Abstraction Layer, i l livello che si appoggia direttamente al HW ed perci scritto in assembly ed specifico della macchina(I/O,memorie, timer, gestione delle informazioni contenute nel BIOS...).
Il livello kernel corrisponderebbe al microkernel, infatti il progetto iniziale prevedeva una struttura di questo tipo ma le prestazioni misurate nei test spinsero per una struttura ibrida.
Lo schema rimasto a microkernel ma il funzionamento no, tutto il SO gira
comunque in modo kernel per aumentarne le prestazioni (vengono tolti i Process switch in eccesso).
Interessanti risultano alcune caratteristiche del livello executive che contiene i vari moduli del SO. Si pensato di applicare la filosofia ad oggetti
nel programmare il SO nonostante il linguaggio utilizzato non supporti la OOP

(Objec Oriented Programming).


Il blocco WIN32 GDI il modulo che gestisce l'interfaccia grafica, compito
che concettualmente esula dal SO. La particolarit sta nel fatto che, nonostante sia la parte pi voluminosa come numero di righe di codice ( di conseguenza la pi sensibile ad errori), gira in modo kernel e scavalca HAL
appoggiandosi direttamente all'HW. Questo permette un grosso aumento
delle prestazioni a scapito di un aumento dell'instabilit del sistema (se si
pianta una finestra, evento tra i pi probabili per la dimensione e le caratteristiche del codice, si pianta il kernel); inoltre devo per forza caricare tutta
la grafica anche se non mi serve.
SYSTEM SERVICES il nome di Microsoft delle System Call, questo livello non
documentato, invece documentata la SYSTEM INTERFACE che fornisce
all'utente le librerie (DLL, Dynamic Link Library, collettivamente chiamate
WIN32 API, Application Process Interface) necessarie per implementare le
chiamate di sistema.
Un altro obiettivo del progetto iniziale, oltre alla portabilit del SO su architetture HW diverse, la portabilit d i applicazioni scritte per altri S O (UNIX,
OS/2 in particolare), implementate con dei Subsystem i n grado di ricevere
le chiamate da applicazioni UNIX e OS/2 e convertirle in chiamate WIN32. In
questo modo una chiamata diventa un processo molto articolato.

Per motivi di velocit i subsystem sono stati eliminati, per motivi economici
sono stati eliminati gli HAL multipiattaforma (il livello logico invece rimasto).
La compatibilit DOS stata invece eliminata per ragioni di sicurezza; stata
mantenuta un'emulazione DOS per i vecchi software scritti in modo migliore
rispetto agli standard dell'epoca.
Il simulatore in Windows crea un processo chiamato NTVDM-NT Virtual Dos
Machine che controlla il processo DOS. Finch il programma fa chiamate consentite esse vengono passate direttamente al SO, appena viene effettuata una chiamata privilegiata essa viene intercettata e inviata alla NTVDM
che, se possibile, traduce la chiamata privilegiata nella corretta Syscall di
Windows altrimenti genera un eccezione (trap) e termina il programma DOS.

3 - Input/Output
Passiamo ora ad analizzare come un SO gestisce le richieste di I/O.
Come si pu notare dalla figura esiste una chiara architettura a strati. Ogni
processo utente si appoggia ad un livello (I/O livello utente) che contiene le
funzioni di libreria per effettuare le syscall e l'implementazione di servizi di supporto come lo spooling (Simultaneous Periferical Operation On
Line):
ES: Eseguo la stampa d i un file d i 2 0 0 pagine, una volta dato i l comando di stampa esso termina prima della fine della stampa delle 200 pagine.
Un altro processo comunica con la stampante mentre l'utente pu continuare a fare altro.
Appena fuori dal modo utente, troviamo il
primo strato eseguito in modo kernel, del
SW indipendente dal dispositivo.
Questo livello implementa un'interfaccia
uniforme con i driver (SW fornito dal progettista del HW), permette la denominazione dei
dispositivi e ne gestisce la protezione (permessi), implementa le necessarie allocazioni
di memoria e permette l'allocazione dei dispositivi dedicati.
I dispositivi dedicati sono particolari periferiche che possono fare una cosa per volta
(la stampante, per esempio, bene che
stampi un documento alla volta), il sistema,
quindi, gestisce una tabella di stato dei dispositivi dove controlla se il determinato
dispositivo libero (quindi lo occupa e esegue l'operazione richiesta) o occupato
(quindi genera una coda delle richieste).
Il trasferimento dei dati da/per la periferica avviene in unit minime che possono essere caratteri nel caso della tastiera o blocchi nel caso
delle stampati o del HDD.
Esiste perci il problema di archiviare i dati in
transito e ci viene effettuato tramite il Bufering (implementato in questo livello).
Esso consiste in una memoria condivisa tra produttore e consumatore del dato.
Il produttore (es la scheda di rete o la tastiera), invece di inviare le informazioni immediatamente al programma utente le invia al sistema operativo i l
quale le memorizza i n un bufer aspettando che i l programma utente
sia pronto per processarle.

Se i buffer sono pi di uno il produttore pu permettersi d i produrre dati

mentre il consumatore (l'applicazione che ne ha fatto richiesta) sta ancora leggendo i dati vecchi. Questo f a si che le operazioni d i lettura/scrittura possano essere molto ottimizzate come mostra il prossimo grafico:

In questo modo si slega produttore e consumatore e ci si traduce nel avanzamento dell'applicazione, dopo aver dato il comando di stampa, senza che essa sia
stata realmente effettuata.
3.1 I / O c o n l ' u t e n t e
Per I/O con l'utente si intendono quelle operazioni che la macchina svolge
per ricevere istruzioni dall'utente umano.
Usualmente si distinguono 2 contesti:
Tastiera con video alfanumerico (CLI)
Grafica (GUI)
3.1.1 Tastiera con video alfanumerico
Metodo di I/O con l'utente pi tradizionale. Prevede l'utilizzo della sola tastiera e di un'interfaccia alfanumerica (come il terminale di Linux o del MSDOS, senza finestre o altri artifici grafici).
Questo metodo pu essere collegato al software in due modi:
RAW (letteralmente Crudo): tutti i tasti premuti vengono passati, uno
alla volta, direttamente all'applicazione, la quale avr l'onere di tradurli nei rispettivi caratteri/comandi.
COOKED (letteralmente cotto, cucinato): viene demandato al SO il
compito di effettuare l'editing di linea (traduzione e formattazione) solo
dopo il SO invia la linea all'applicazione.
3.1.2 - Grafica
Questo metodo si basa sulla presenza di
oggetti grafici con i quali l'utente interagisce.
L'applicazione utente chiede al SO se avvenuto qualche evento (un click del mouse per
esempio), il SO, come risposta passa all'applicazione un messaggio che definisce l'azione
svolta dall'utente.
Con la chiamata l'applicazione
utente viene interrotta e riprender solo dopo che il SO abbia risposto con l'apposito messaggio.
C on la callback il SO invia il messaggio ma l'applicazione non riprende da dove era stata interrotta,
ma dove punta la callback, cio a
quella parte di codice scritto per gestire il determinato evento.

Esistono vari approcci per la gestione di questo meccanismo,


analizziamo come esempio l'approccio UN1X (pi flessibile
e logico di altri).
L'X Terminal (ci che si vede a video) intercetta gli input dell'utente e li invia al Server X, i l quale gestisce la grafica e chiama le componenti del SO
adatte per eseguire la risposta all'evento generato dall'utente.
L'X server un'applicazione che gira in modo utente, questa soluzione ha
tutti i vantaggi della modularit (se non mi serve non lo carico, se si pianta
si blocca solo l'applicazione utente e non l'i ntero s istema ).

4 - Processi
Uno degli obiettivi di un S O fare i n modo che i programmi diventino processi, cio diventino un'entit ben definita e fisica che evolve in un calcolatore.
Ma come nascono i processi?
Tutti i processi attivi derivano da un processo padre, il quale li crea (il primo creato in fase di Boot a seguito del Bootstrap), tuttavia possibile distinguere due meccanismi che determinano la creazione di un nuovo processo:
Per un comando dell'utente (es: avvio un programma).
Generati direttamente dal SO (es: servizi di sistema).
In generale possibile descrivere la genealogia dei processi con una struttura
ad albero:

La situazione opposta, cio la morte di un processo, di solito avviene per suicidio, dove il processo comunica al S O: h o finito i l mio compito quindi eliminami".
Esistono, per, altri due casi in cui un processo pu morire:
per incidente a seguito di una trap
uccisi (kill) dall'operatore o dal SO (es nel caso di attesa infinita o
quando c' esigenza di liberare risorse) o per la morte del processo padre (non possono esistere senza il padre, quando si elimina un proces-

so si eliminano anche tutti quelli a lui riconducibili).

4.1-Ciclo di vita di un Processo


Una syscall ( o il bootstrap) f a nascere un nuovo processo. A seconda
dell'algoritmo di scheduling a lungo termine il passaggio allo stato
ready (pronto per essere eseguito) pu essere ritardato o meno (soprattutto nel caso che le risorse siano limitate).
Una volta che il processo si trova nello stato ready dopo un certo lasso di
tempo (nell'ordine dei ms), determinato dell'algoritmo di scheduling a breve termine, passer nello stato running e i l suo codice comincer
ad essere eseguito.

Ora se il processo viene eseguito completamente passa nello stato terminated e cos muore, se invece, deve effettuare un'operazione d i I / O o
aspettare un evento passer nello stato blocked, oppure, se semplicemente scade i l suo quanto d i tempo ritorna nello stato ready e
aspetta che g l i venga riassegnata la CPU.
Nello stato blocked il processo aspetta fino alla fine dell'operazione d i I/O
o fino al verificarsi d i un determinato evento ( es: click del mouse) dopodich si rimette nella coda dei processi pronti.
Seguendo un determinato algoritmo, detto d i scheduling a medio termine, viene deciso quale processo abbia o meno il diritto di restare in memoria centrale o quale debba essere momentaneamente spostato su disco per
far spazio in RAM ad altri (se necessario).
Quando un processo viene eletto per essere messo su disco (sospeso) si
passa dagli stati rispettivamente ready e blocked ai corrispettivi suspended ready e suspended blocked.
Immaginiamo un processo che mentre viene eseguito faccia una richiesta

di I/O, automaticamente passa nello stato blocked e cede il controllo


della CPU ad un altro. Ad un certo punto viene eletto per la sospensione, visto che l'operazione di I/O non ancora finita, lo scheduler lo mette nello
stato suspended blocked.
Ora se l'operazione di I/O termina prima che i l processo venga rimesso
in memoria centrale, esso torner pronto ma sospeso (suspended ready), se invece viene prima rimesso in RAM (resume) torner blocked
fino alla fine dell'operazione d i I/O.
Lo schema di vita di un processo nei sistemi UNIX ha la stessa forma generale ma introduce una particolarit che vale la pena di osservare nello schema seguente:

Presenta gli stessi elementi fondamentali del ciclo di vita classico, la particolarit si trova nello sdoppiamento dello stato ready i n due code: una per
i processi utente ed una per i processi del super-utente con priorit
maggiore.

4.2 - Struttura in memoria di un Processo


Vediamo ora come un processo strutturato in memoria.
L'area di memoria assegnata ad un processo suddivisa
in 4 aree:

User program o campo Text: contiene il codice


eseguibile caricato in memoria, campo non modificabile.

User data: area che contiene i dati del processo. Area modificabile quindi la sua dimensione potrebbe variare nel tempo.

Stack: pila delle chiamate di procedura fatte dal


programma, struttura dati dinamica a volte supportata da apposito HW.

4.2.1 - Process Control Block (PCB)


Process Control Block ( PCB): contiene una serie d i informazioni per la
gestione del processo:
Identificazione del processo: quando il processo nasce gli viene
dato un numero chiamato PID (Process ID), vengono inoltre memorizzati il PID del processo padre e l'UID (User ID) dell'utente proprietario del
processo.
Stato del Processore: l'insieme di dati che vengono salvati durante
un process switch (registri come il program counter ecc...)
Process control info: questo campo del P C B contiene le informazioni sullo scheduling di breve termine ( e s l o stato del processo, gli
eventi attesi, le priorit...); contiene informazioni sui privilegi del processo (sistema di protezione, cosa i l processo autorizzato a fare
oppure no); contiene informazioni/puntatori sulle risorse assegnate
(strutture dati per sapere quale memoria ha i l processo, se sta su disco, i file aperti, strutture dati per la comunicazione come gli IPC: Inter Process Communication, puntatori veri e propri ad altri processi/PCB).

4.3 - Thread
Ogni processo pu avere assegnati pi flussi di controllo, cio programmini
separati che condividono lo stesso codice eseguibile (sia tra di loro che
con il processo) e servono per una migliore gestione dello stesso.
Per capire meglio, iniziamo questo paragrafo, con due esempi di struttura
di un moderno programma:
Text Editor: Il codice di un applicazione di
text editing (MS- Word o OOWriter per esempio) in realt un insieme di funzionalit diverse a volte separate l'una dalle altre. Per
esempio il codice che gestisce l'interazione
con l'utente separato dall'impaginatore
che separato dal controllo automatico
dell'ortografia.
Sono

flussi di esecuzione diversi che:


lavorano sugli stessi dati
fanno parte dello stesso programma
hanno priorit diverse

Web Server: Il funzionamento di un web server ha un problema generale,


il server non fa altro che aspettare l'arrivo delle richieste dalla rete, e una
volta ricevute, le legge, le elabora (es cerca sul disco le informazioni richieste) e l e r e s t i t u i s c e . Il problema nasce dal fatto che un solo processo
(web server) potrebbe avere molte richieste contemporaneamente, e ogni
richiesta genera un flusso di controllo separato.
Se questi flussi d i controllo venissero eseguiti uno alla volta le prestazioni
di questi sistemi sarebbero inaccettabili per la commercializzazione (ad
esempio, nel caso dell'impaginatore del Text editor, se nell'aggiungere 20 righe
nella prima pagina d i u n documento, per ogni carattere digitato si dovesse
aspettare che tutto i l documento venga rii-impaginato, l'uso dell'applicazione sarebbe estremamente lento e laborioso).

La soluzione viene dallo spezzamento della gestione delle richieste i n


User Space, c o n si s te nti i n pi fussi separati chiamati Thread che
lavorano in "Parallelo", sfruttando i tempi morti dell'esecuzione.
Esistono due tipi di Thread:
User Level Thread (ULT): sono librerie/VM che permettono di implementare la gestione dei flussi di esecuzione (alternanza tra i flussi,
scheduling, comunicazione/sincronizzazione tra threads), anche su SO
a processi tradizionali. I thread esistono solo i n user mode, quindi il
SO non sa che esistono e perci non li distingue.
Kernel Level Treaeds (KLT): i l S O c h e c r e a t h r e a d a l i v e l l o
kernel, quindi chi cambia stato (ready, blocked ecc...) non pi il processo intero ma il thread. In questo modo se si blocca il thread non si
impianta pi l'intero processo. Creare questi threads costa un po' di pi
di quelli user, perch si deve fare un syscall e deve intervenire il SO,
tuttavia costa sicuramente meno che creare un nuovo processo.
Generalmente, si ha la corrispondenza 1:1 tra User e Kernel Thread (caso P1),
tuttavia possibile assegnare pi User a un solo Kernel Thread (caso P2) o viceversa (meno comune).

4.3.1 - Ciclo di vita di un ULT


Lo stato Runnable il corrispondente dello stato ready per i processi.
Si entra nello stato stopped a seguito di un comando utente o di un altro
thread (es: finestra minimizzata), mentre si entra nello stato sleeping
con uno stop volontario (usualmente quando si si sta aspettando qualcosa
da un altro thread).
Nello stato active l'ULT viene associato ad un KLT ottenendo quindi la

potenzialit di essere eseguito se lo il KLT associato ( i l quale pu essere


a sua volta in qualunque dei sui stati.
4.3.2 - Stati di un KLT
Gli stati di un KLT sono simili a quelli di un ULT con la differenza che ora viene
eseguito se si trova nello stato running.
Da notare che il passaggio tra stati dei thread avviene solo se il processo in
esecuzione tuttavia certe operazioni riguardanti le risorse ( come lo Swap su
disco) vengono eseguite sull'intero processo mentre il flusso di controllo (stati del processo attivo o scheduling d i breve termine) viene gestito per thread.
Il numero di thread lo decide il programmatore.

4.4 - Generazione di processi figli in UNIX


In UNIX la syscall per generare un processo figlio fork(); con essa il padre
crea un doppione di se tramite il meccanismo di copy on write.
Dopo la fork() padre e figlio sono identici (tanto da essere nello stesso punto
di esecuzione); per distinguerli si fa riferimento al valore ritornato dalla
fork().
La chiamata sar quindi: var=fork().
A questo punto si ha una struttura (nel codice) del tipo:
if (fork()==0)
then caso padre
else caso figlio
Il meccanismo copy on write prevede che il figlio non sia altro che un puntatore al codice del padre fino al momento in cui non viene effettuata una
modifica (per esempio il byte 1000), a quel punto copio solo un intorno del
padre a cavallo del byte 1000 e lo modifico (nella copia che fa parte del processo figlio).
A questo punto creo un meccanismo tale che il processo figlio uguale al
padre dal byte 0 al byte 1000, modifico il 1000 e dal byte 1001 fino alla fine
ancora uguale al padre.
Esiste una libreria standard in UNIX per creare i thread: pthread.
Essa, per, non distingue tra ULT e KLT, quindi per ovviare alla limitazione in
Linux hanno creato clone(), la quale sostanzialmente una fork() che aggiunge al figlio informazioni sulla memoria, sui file aperti e sulle comunicazioni
di inter-processo (IPC Inter Process Communication).
Gli UTL vengono anche chiamati Fiber, mentre il termine Thread indica
solo i KLT.
Con process si indica l'unit di assegnazione delle risorse (contenitore di
thread) mentre con Job si indica un insieme di processi.

4.5 - Scheduling (a breve termine)


Lo scheduling la modalit di individuazione del processo (o thread di livello kernel) che abbia diritto di prendere il controllo della CPU e per quanto tempo mantenerlo.
un concetto fondamentale per le prestazioni di un calcolatore.
Possiamo dividere il tempo di progressione di un processo in due fasi:

CPU Burst: tempo utilizzato per fare calcoli sulla CPU:


I/O Burst: tempo utilizzato per operazioni di I/O.

Nel caso che esiste un solo processo (tipico dei sistemi embedded) la vita di
un calcolatore potrebbe essere schematizzata come una sequenza alternata di CPU Burst e I/O Burst.
I n base a questo possibile suddividere i processi in due categorie:

Processi CPU Bound: Sono processi legati alla CPU, passano la maggior parte della loro vita a caricare di calcoli la CPU. Le operazioni di I/O
sono ridotte al minimo (es codice Matlab/Octave)
Processi I/O Bound: Sono processi interattivi, passano la maggior parte della loro vita ad aspettare operazioni di I/O. I calcoli richiesti alla CPU
sono minimi.

L'obiettivo di un algoritmo di scheduling

l'ottimizzazione della ge-

stione dei processi e delle risorse HW messe a disosizione.


Per confrontare gli algoritmi che venivano sviluppati, si sono cercati dei parametri suddivisi in categorie considerando i diversi ambienti in cui il sistema viene
ad operare, quindi considerando il punto di vista dell'utente (user) o del sistema (system), in base all'ottimizzazione delle performance o altro (other).
Ecco i criteri raggruppati secondo questa classificazione:
User/Performance: analizza il sistema dal punto di vista delle
prestazioni avvertite dall'utente.
Response Time: Tempo di risposta percepito dall'utente. Applicabile ai
sistemi spiccatamente interattivi, t u t t a v i a f o r n i s c e u n ' indicazione
troppo grossolana.
Tournaround Time: Misura i l t e m p o d i completamento d i c i a s c u n
processo. Solitamente normalizzato alla dimensione del compito da
svolgere; inadeguato per i processi interattivi nei quali il tempo di
completamento dipende pesantemente dall'utente.
Scandenze e priorit utente: esistono utenti pi importanti di altri
(es amministratore) e processi pi critici di altri (es antivirus).
User/Other: Analizza aspetti importanti dal punto di vista dell'utente.
Predictability: Predicibilit. Indica "pi o meno il tempo di esecuzione"
al posto del tempo assoluto, infatti pi utile sapere che un processo impiega sempre mezz'ora, al posto di, una volta finisce in 10 minuti, la volta dopo in un'ora.
System/Performance: Analizza il valore delle prestazioni dal punto di
vista della velocit reale del sistema.
Throughtput: Numero di processi completati per unit di tempo.
Utilizzo dell CPU: ottenere un uso della CPU vicino al 100%.
System/Other: analizza altri aspetti importanti per l'ottimo funzionamento del sistema.
Equit: Gestione paritaria dei processi (no starvation, morte di fame)
Priorit Interne:
Uso delle Risorse: Inteso come bilanciamento complessivo e non solo
CPU, quindi anche RAM, GPU, HDD ecc...
4.5.1 - Algoritmi
FIFO/FCFS (First In First Out/First Come First Served)
Questo algoritmo prevede che i l primo che arriva occupa la CPU per tutto il
tempo necessario alle sue esigenze, e se nel frattempo arriva qualcun altro,
non importa chi sia e che priorit abbia, aspetta pazientemente i l suo turno. un algoritmo senza prelazione cio chi prende il controllo della CPU
non pu essere costretto a rilasciarla; pu solo farlo spontaneamente.
Esempio:

Come si pu notare la percentuale di uso della CPU non ottimale (ci sono
dei momenti in cui non fa nulla), inoltre P2 (principalmente I/O Bound) viene spesso scavalcato da P1 dando una sensazione di lentezza all'utente. In
generale il FIFO abbatte tutti i parametri presi in considerazione ed inoltre ha
il grosso svantaggio che se un programma si blocca, si incioda l'intero sistema senza possibilit di recupero (senza spegnerlo), tuttavia esiste un solo
caso i n cui l'algoritmo FIFO risulta ragionevole: quando tutti i processi
sono CPU Bound:

Round Robin con Time Slice


Il principio base il FIFO, ma la CPU viene assegnata ad ogni processo, a turno, dopo lo scadere di un quanto di tempo. Questo un algoritmo con prelazione.
Esempio:

Migliora la percentuale di uso della CPU migliore rispetto al FIFO puro, inoltre i processi interattivi aspettano, in media, meno rispetto a prima, e i CPU
Bound non vengono penalizzati t r o p p o .
In questo caso diventa per fondamentale la durata del quanto di tempo:
se troppo lungo cado nel caso FIFO, se troppo corto la percentuale di
tempo usata per i Process switch ed i mode switch aumenta (tempo sprecato perch non faccio calcoli utili, ma solo spostamenti di registri).
Il problema nasce nel caso di processi molto interattivi (es servizio di messaggistica istantanea), dato che il tempo complessivo speso in coda sar
molto maggiore di quello in cui processo viene eseguito, quindi, per ristabilire
un po' di equit tra i processi, si sono introdotti meccanismi a priorit (sia
esterne, assegnate dall'utente, che interne).
Le priorit possono essere:
Non Preemptive ( senza prelazione). Il processo pi prioritario, quando torna pronto, viene messo in testa alla coda ma non interrompe il
processo in esecuzione.
Preeemptive: ( con Prelazione). Il processo pi prioritario ruba la
CPU a quello che sta girando.
Shortest Job First
Immaginiamo un bambino con un pacchetto di caramelle dietro a un signore
con il carrello pieno, entrambi in coda alla cassa di un negozio, usualmente,

per gentilezza, il bambino viene lasciato passare.


Questo algoritmo tenta di fare la stessa cosa: il processo che occupa meno
la CPU viene fatto passare prima di un altro.
Esistono due possibili versioni:

Senza prelazione (SPN)


Con Prelazione (SPT)

Il problema interpretare la frase "ha meno da fare" visto che in un normale funzionamento impossibile stabilire a priori, a meno di conoscere il futuro, per quanto tempo i processi in coda devono usare la CPU singolarmente.
Nella realt si usa un algoritmo per stimare il tempo del prossimo CPU Burst
in base all'utilizzo attuale e pregresso della CPU:

Tale formula ricorsiva quindi espandendo e facendo i calcoli si ottenono una


serie di esponenziali. Nella formula compare un coefficiente alfa, che pu
assumere valori tra 0 e 1, il quale indica il peso dato al presente (misurato)
e al passato (stima precedente).
Di solito ha valori vicino a 0,9, infatti le precedenti stime sono meno importanti di quella attuale (il comportamento dinamico di un processo pu variare
molto nel tempo).
Il problema nasce quando si hanno troppi processi molto interattivi, perch si
rischia la starvation (morte di fame), per quelli CPU Bound, perch non progrediscono mai (si pu verificare anche con le priorit per i processi meno
prioritari).
Per evitare ci si introduce il concetto di Aging (letteralmente "invecchiamento", nonnismo): dopo un po' che un processo viene scavalcato, esso viene promosso e posto in esecuzione. Il SO premia i processi che hanno aspettato e li fa avanzare anche se hanno un CPU Brust alto.
Tale meccanismo implementato con l'HRRN (Higest Response Ratio Next), il
quale fornisce il livello di priorit di ogni processo:

HRRN=

W +S
S

dove W il tempo passato in coda ed S il prossimo CPU Brust stimato.


Finora abbiamo ragionato con una coda sola ma in realt le code sono
pi di una, e nel caso dell'utilizzo di priorit, solitamente si assegna ad
ogni coda un livello e si utilizzano sistemi a prelazione per assegnare il processo alla giusta coda (in combinazione con l'aging per evitare starvation).
Inizialmente tutti i processi sono nella prima
coda ed passeranno nelle altre se utilizzano
tutto il loro quanto di tempo.
possibile assegnare ad ogni coda un valore di quanto di tempo diverso (solitamente
si incrementa all'aumentare dei livelli).
Nel caso che un processo venga promosso
alla coda pi prioritaria, tramite il meccanismo di aging, il quando di tempo assegnatogli rimane invariato.

Esistono diversi possibili criteri per costruire le code multiple, in base a vari
criteri (priorit, con o senza prelazione ecc...), inoltre sono stati ideati anche
algoritmi basati sull'estrazione a sorte ( Lottery Scheduling), dove ogni
processo ha un range d i numeri associati, e viene eseguito il processo che
possiede, nel suo range, il numero estratto dal SO.

4.6 - Sistemi Real-Time


Il termine Real Time, usualmente usato come sinonimo di in tempo reale/ in diretta", tuttavia nel caso di SO si parla di problema Real Time per indicare sistemi in cui la correttezza dell'output dipende dal momento
in cui sono prodotti.
In un sistema RT (Real Time), spesso di fondamentale importanza, il momento
in cui sono prodotti i dati di uscita (es quando e come azionare i freni della macchina da parte dell'ABS).
I problemi possono essere:
Soft real time: la perdita di qualche scadenza accettabile
Hard real Time: la perdita di una sola scadenza inaccettabile
La distinzione si basa sulla gravit delle conseguenze, sulla funzione di
costo (continua per i soft, la perdita aumenta perlopi gradualmente con il
numero di miss, discontinua per gli hard, dove il costo aumenta drasticamente dopo un certo numero di miss) e sulla probabilit di miss accettata.
Una seconda classificazione in base al tipo di compito/ambiente in cui il sistema si ritrova ad operare:
Purely Cyclic: Completamente Ciclici (tipicamente monitoraggio/controllo es telecamere)
Mostly Cyclic: Quasi completamente ciclico,con qualche evento asincrono (es. Allarmi, comandi, PLC)
Asincronous and somewath predictable: Sistema con eventi asincroni, ma generalmente prevedibili (es. controllore aereo)
Asyncronous and unpredictable: Sistema predominato da eventi
asincroni e non predicibili ( es. sistema di guida automatico di un'automobile).
Una definizione di SO real time: sistema capace di garantire deterministicamente il rispetto dei requisiti HARD REAL TIME dei processi utente", in
pratica ho la garanzia di risposta a certi interrupt in un tempo prefissato.

Il SO pu garantire l'ampiezza massima del periodo di latenza compreso tra


il rilevamento dell'interrupt e l'inizio della routine di gestione specifica.
Nascono, perci una serie di problematiche specifiche riguardo allo scheduling e all'architettura interna.
4.6.1 - Caratteristiche dei sistemi Real Time
La maggiore caratteristica di un SO Real Time la certezza della latenza
tra riconoscimento dell'interrupt e l'avvio della routine di gestione.
P er contenere il pi possibile anche il valore temporale di questa latenza occorre progettare un sistema con alcune caratteristiche specifiche:

Deve lavorare quasi mai ad interrupt disabilitati;


Le sezioni del Kernel ad interrupt disabilitati devono essere "brevi" e poche,
Il Kernel scritto in modo prelazionabile dagli interrupt dei problemi
real time (se viene sollevata un istanza real time essa ruba la CPU al
kernel).
Implementa lo split interrupt handling, la gestione divisa degli interrupt in parte urgente e meno urgente.

Per determinare in modo preciso le scadenze delle applicazioni Real Time si


ha bisogno di orologi ad alta risoluzione (hight resolution clock, clock di
millisecondi sono insufficienti in alcuni casi). Questo comporta interrupt di
clock pi frequenti (che aumentano il costo in termini di mode switch
ecc...), inoltre le normali system call sono imprecise per questi contesti (tra la lettura dell'ora e l'uso del dato da parte dell'applicazione pu
passare un tempo non trascurabile), quindi si sono implementati meccanismi che portano a scavalcare il SO nella lettura del ora. Questa una palese violazione delle regole generali dei SO resa obbligatoria dalle esigenza
implementative.
Oltre all'HW specifico, vengono anche fatte aggiunte di tipo software come
le real time API (POSIX Extensions) che aggiungono i timers e i relativi watchdog software, le quali sono s p e c i fi c h e syscall che controllano che una
certa operazione sia eseguita entro un determinato lasso di tempo. Se ci
non avviene (solitamente il timer non viene spento in tempo) sollevato un
evento che avvia delle routine specifiche (usualmente scatta la routine che
porta alla posizione di massima sicurezza).
Le RTAPI aggiungono anche la gestione di priorit a 32 livelli, delle apposite chiamate per la sincronizzazione dei processi real time (cio
messaggi tra processi con ricezione e latenza garantiti).
Altra caratteristica il Lock della Memoria dei processi RT, la quale non permette di spostarli su disco fisso (swap).
Per risolvere i tappi dovuti a processi a bassa priorit che bloccano una risorsa necessaria ad un processo a priorit pi alta, si fa ereditare da
quest'ultimo, temporaneamente, lo stesso livello di priorit (fenomeno
dell'inversione di priorit o priority inheritance).
4.6.2 - Scheduling Real Time
Date le criticit del problema real time lo scheduling deve implementare
per forza la gestione delle priorit e della prelazione.
Per gli eventi periodici va fatta un'analisi di schedulabilit dati: Se N il
numero di eventi periodici e Ti il periodo dell'evento i-esimo e C i il costo (in
tempo) della routine specificata dell'evento i-esimo la frazione di tempo di
CPU definita come:

Ci
Ti

In particolare se N maggiore di 1 deve essere:


N

T i 1
i=1

Una volta che l'analisi ha dato risultati positivi esistono vari algoritmi che vengono implementati per gestire problemi real time:
Rate Monolitic

La priorit maggiore viene data alla routine dell'evento con frequenza maggiore.
N

Il tasso di utilizzo di questo scheduler di

1
Ci
T n (2 n 1) ; inoltre possibile
i=1
i

che non garantisce assolutamente le dead-line.


EDF: Erliest Deadline First:
Viene assegnata la priorit in base alla vicinanza con la scadenza (pi vicina la
scadenza pi la priorit maggiore).
In questo caso possibile ottenere tassi di utilizzo del sistema vicini al limite
teorico di 1.

4.6.3 - Struttura generale interna dei SO Real Time


In generale un SO Real Time ha un'architettura a
microkernel in quanto essendo usualmente SO
embedded le caratteristiche di modularit sono
molto pi importanti degli svantaggi in termini di prestazioni.
Per tali sistemi si predilige la stabilit e la velocit del
sistema, inoltre, molte componenti di un kernel normale non sono necessarie per questi sistemi, quindi
vengono eliminate per ridurre al minimo la possibilit
di avere bug nel kernel.
Generalmente sono sistemi dedicati e molto ottimizzati.
L' architettura doppio kernel, prevede l'uso di un
SO tradizionale (Windows/Linux) il cui kernel non si
appoggia direttamente sull'HW ma su una patch
d e l ke r n e l real time permettendo l'estensione
d e l le funzionalit del sistema anche alle applicazioni utente.
Il SO tradizionale si trova cos a girare come se
fosse un'applicazione utente a priorit pi bas-

sa.
Se arriva un interrupt real-time il SO usa i criteri ad essi assegnati, nel
caso opposto (per es. una lettura su disco) viene degradato a pi bassa
priorit al SO tradizionale (Linux/Win).
In questo modo possono funzionare alcune garanzie real-time anche su sistemi tradizionali.

4.7 - Comunicazione tra Processi


Esistono due modi possibili per ottenere la comunicazione tra processi:
Memoria condivisa: si basa sulla modifica degli stessi dati. Comunicazione implicita semplice ed efficiente. Funziona solo per processi che risiedono sulla stessa macchina ed esistono alcune complicazioni implementative dovute alle race condition.
Scambio di messaggi: un meccanismo esplicito per cui esistono
strutture dati apposite, protocolli ecc. Introduce dei costi di elaborazione maggiori ma semplifica il controllo della correttezza della trasmissione dei dati.
4.7.1 - Comunicazione con memoria condivisa.
Cominciamo l'analisi introducendo il
problema del produttore/consumatore dove una sorgente di dati (produttore) deve scrivere all'interno di
un buffer (memoria, spesso intesa
come circolare) mentre un processo
(consumatore) deve leggere i dati inseriti dal produttore.
I vincoli di questa struttura sono due:
il produttore non deve sovrascrivere i
dati prima che il consumatore li abbia
usati (altrimenti vengono persi) e il
consumatore non deve leggere i dati
prima che il produttore li abbia aggiornati (altrimenti legge dati non validi).
In pratica i due puntatori, mostrati in figura (IN e OUT) non devono mai superarsi.
Un primo esempio di codice relativo alla gestione di tale memoria il seguente:
produttore
consumatore
...
...
WHILE(TRUE)
WHILE(TRUE)
{
{
WHILE(counter == 0)
produce il dato;
{no-operazioni};
WHILE(contatore == dimArray)
lettura=buffer[out];
{ no-operazioni; }
out=(out +1);
buffer[in]=DATO;
in=(in+1)%dimArray; //fa il giro
counter ;
counter++; }
usalettura();}
La variabile "counter" tiene conto dei dati da leggere, se 0 non c' nessun dato da leggere e non si deve leggere nulla.
Questo codice sbagliato perch contiene un problema non immediatamente individuabile.
Dobbiamo ricordarci che il produttore e il consumatore sono due processi
diversi, quindi l'interruzione avviene in un'istante casuale che dipende

dall'algoritmo di scheduling, inoltre accade sulle istruzioni in linguaggio


macchina non su quelle ad alto livello scritte di sopra.
Il problema si concentra sulle istruzioni "counter ++" e "counter ", le quali
potrebbero non essere aggiornate in tempo (ci prima che venga eseguito il processo duale), questo tipo di problema si chiama race contition consiste in errori che
compaiono durante l'esecuzione del codice (secondo problema, si possono non presentare per 1000 esecuzioni diverse dello stesso tipo e comparire alla 1001, quindi
sono molto difficili da trovare e correggere).
Ognuna delle istruzioni ad alto livello in realt una sequenza istruzioni a basso
livello, ad esempio nel caso delle intruzioni count ++ e count -- si hanno tre
istruzioni a basso livello.
Immaginiamo di lavorare su un calcolatore con architettura "load/store" in
assembler le due operazioni critiche risultano:
COUNTER ++

COUNTER --

LOAD RX COUNTER
INC RX
STORE RX COUNTER

LOAD RY COUNTER
DEC RY
STORE RY COUNTER

Si prenda come esempio questo codice assembly, con valore iniziale di counter
pari a 3 (P1 indica valore della variabile counter nei registri della CPU; mem il
valore scritto in memoria RAM).
P1 LOAD RX COUNTER
P1 INC RX
interrupt
P2 LOAD RY COUNTER
P2 DEC RY
P2 STORE RY
P1 STORE RX

//Carica counter i n RX; P1=3 mem=3


//Incrementa RX; P1=4 mem=3
//Si passa a P2 e legge mem=3 (dovrebbe essere 4)
//Decrementa RY, P2=2 mem=3
//Carica counter i n RX; P2=2 mem=2
//Finito P2, conclude P1. P1=4 mem=4

In memoria il valore di counter 3+1-1=4.


Questi casi vengono chiamati race conditions e sono situazioni condizionate da fattori esterni.
Per eliminarli, gi in fase di progetto bisogna individuare nei due processi
d e l le sezioni critiche, cio parti di codice dove possono incorrere race conditions, in cui vado a garantire l'atomicit estesa.
La propriet dell'atomicit estesa consiste nel rendere non interrompibili delle parti di codice (a livello macchina) alto livello.
In questo modo gli incrementi si concludono ed il problema risolto.
Per risolvere il problema delle race conditions dobbiamo fare alcune ipotesi:
Qualunque processo ha velocit maggiore di 0 (prima o poi verr eseguito.
Non si sa nulla sui "valori" di velocit (hanno priorit diverse).
SI considera qualunque combinazione di eventi (anche quelle pi improbabili)
Inoltre la gestione delle race condition, per essere corretta, deve soddisfare
alcuni requisiti:
Mutua esclusione. Se P1 viene interrotto nella sezione critica, P2
viene eseguito fino a subito fuori la sua sezione critica.
Progresso: Se la sezione critica libera posso eseguire quella di un

qualunque processo in coda.


Attesa limitata: non ci deve essere Starvation di nessun processo,
e non devono esserci attese circolari (Deadlock); necessario quindi
un meccanismo di sincronizzazione tra processi.

Esempi di meccanismi errati per la gestione delle race condition.


Gli esempi di seguito sono errati a l l o scopo d i sottolineare meglio le difficolt esistenti nella gestione delle sezioni critiche. Si Considera ancora il caso
produttore/consumatore.
Esempio 1:
Var condivisa (in memoria): Int Turn=0;
P0
while (turn!=0)
{no operazioni;}
Sezione critica
Turn=1;

P1
while (turn!=1)
{no operazioni;}
Sezione critica
Turn=0;

Questo esempio viola il requisito di progresso; s e un processo utilizza la sezione critica deve per forza aspettare che l'altro finisca, e non importa in che
punto della coda di scheduling esso si trovi. Questo porta alla possibilit di
dover aspettare un tempo potenzialmente infinito per la conclusione dell'elaborazione.
Esempio 2:
Var condivisa (in memoria): Int critica[]=0; //Vettore
P0
critica[0]=1;
while (critica[1])
{no operazioni;}
Sezione critica
critica[0]=0;

P1
critica[1]=1;
while (critica[0])
{no operazioni;}
Sezione critica
critica[1]=0;

In questo caso la propriet di progresso garantita, ma se dovesse occorrere un interrupt tra la prima e a seconda istruzione:
critica[0]=1
interrupt
critica[1]=1
se critica[O]==1 allora non fare nulla
interrupt
(riprende PO) se critica[1]==1 allora non far nulla
In questo caso si ha una deadlock, dove P0 aspetta che P1 esca dalla sua
sezione critica mentre P1 aspetta P0. Se scambiamo l'ordine delle prime
due istruzioni risolviamo il problema della deadlock ma violiamo il principio di
mutua esclusione: superato il while P 1 entra nella sezione critica e P0
pure.
Esempio 3:
Var condivisa (in memoria): Int critica[]=0; //Vettore

P0
while (critica[1])
critica[0]=1;
{no operazioni;}
Sezione critica
critica[0]=0;

P1
critica[1]=1;
while (critica[0])
{no operazioni;}
Sezione critica
critica[1]=0;

Se avviene un interrupt come nel caso precedente (tra la prima e la seconda istruzione di P1) il "gioco" sembra funzionare, l'unico problema che si
pu riscontrare che, in questo caso, uno dei due processi viene trattato
con poca equit, c' il rischio di starvation.
Esiste una soluzione ottimale ed :
4.7.2 - L'algoritmo di Peterson
Var condivise (in memoria): Int turn=0; Int critica[]=0; //Vettore
P0
P1
critica[0]=1;
critica[1]=1;
turn=1;
turn=0;
while (critica[1] & turn==1)
while (critica[0] & turn==0)
{no operazioni;}
{no operazioni;}
Sezione critica
Sezione critica
critica[0]=0;
critica[1]=0;
Tutti i requisisti sono soddisfatti in quanto se esiste solo P0 il requisito di
progressione garantito.
Se esistono PO e P1 ed entrambi arrivano alla sezione critica, inizialmente
P0 resta in attesa, poi P1 si ferma, e solo allora P0 entra nella sezione,
quindi garantita la mutua esclusione, cosi come il requisito di attesa limitata (prima o poi verranno eseguiti tutti e due, non c' rischio di starvation).
Esiste comunque un limite: non estendibile a pi di due processi.
Il problema sembra non risolvibile a livello puramente software, quindi sono
state trovate delle soluzioni che sfruttano l'HW per aggirare il problema.
4.7.3 - Istruzioni HW ad atomicit estesa
Test and Set: legge e setta (mette a 1) la variabile indicata; ci elimina
una race condition molto frequente.
Esempio:
Int Lock=0; //Assumer solo due valori 0 e 1
while (T&S(Lock))
{no operazioni;}
Sezione critica
Lock=0;
La garanzia di atomicit ci viene data dal processore, quindi tale implementazione rende la soluzione valida per un numero indefinito di processi ma resta invariato il problema della mancanza di equit (starvation) in quanto
non possibile prenotare l'uso della sezione critica (meccanismo di code).
Tutti questi ultimi algoritmi, se pur funzionanti, hanno un piccolo difetto in comune: quando un processo aspetta che si liberi la risorsa, occupano la CPU,

con delle operazioni di vuote, creando un fenomeno chiamato busy waitinig;


inoltre non sono algoritmi immediati, perci i programmatori fanno fatica ad
inserirli nei loro progetti.
La soluzione pi semplice quella di avere un costrutto a livello di SO che
permetta di "mettere a dormire i processi in attesa.
4.7.3 - I Semafori
Un semaforo, a livello logico, un dispositivo con stato rappresentato da un
numero (da O a n) e una coda:

se 0 il semaforo rosso, in questo stato il semaforo pu avere una


coda piena (processi in attesa).
se 0 il semaforo verde, in questo stato il semaforo non ha una
coda.

Esistono due operazioni di base per i semafori:


Wait: se il semaforo verde decrementa la variabile e il processo entra nella
sezione critica, se rosso il processo viene accodato e passa dallo stato ready a blocked.
Signal: se verde incrementa lo stato, se rosso o fa tornare ready il primo della coda o incrementa lo stato (e il semaforo diventa verde).
Quando il processo che chiama un wait legge lo stato del semaforo, nel
caso che sia verde (la variabile 0) continua l'esecuzione altrimenti aspetta un signal che lo svegli.
Se arriva un signal senza nessuno in coda resta in attesa di qualcuno.
Esistono tre implementazioni alternative.
Semaforo inizializzato ad 1: serve per gestire la mutua esclusione in
accesso ad una sezione critica (semaforo mutex)
wait(MUTEX)
Sezione Critica
signal(MUTEX)
Semaforo inizializzato a 0: serve quando si vuole una relazione di precedenza stretta tra due eventi (uno deve avvenire prima dell'altro)

P0

P1

wait (sync)

signal (sync)
...

In questo esempio ci sono due processi, in cui una parte del codice di P0 non
pu essere eseguita prima che P1 arrivi ad un punto stabilito (parte sopra il
signal).
In questo modo P0 si ferma se prima dell'istruzione wait P1 non ha eseguito
l'istruzione signal liberando il semaforo.
Semaforo inizializzato ad n (n=>2): serve per gestire n risorse equivalenti (per esempio viene utilizzato nel caso di pi produttori e consumatori
che accedono alla stessa memoria condivisa).
Esempio: Buffer circolare con i semafori
Int empty=n; full=0; mutex=1; //Semafori.

PRODUTTORE

CONSUMATORE

Produci dato();
wait(empty);
wait (mutex);
Sezione Critica (Buffer)
signal (mutex);
signal (full);

wait(full);
wait(mutex);
Sezione Critica (Buffer)
signal(mutex);
signal(empty);

I semafori vengono gestiti dal SO (sono infatti costrutti di sistema), tuttavia


nelle applicazioni utente viene utilizzato il concetto di Monitor.
4.7.4- I Monitor

Un monitor un tipo di dato


astratto, un insieme di variabili e
di funzioni che operano sulle variabili secondo le convenzioni della
OOP (Object Orientig Programming).
Nei monitor, i metodi/funzioni che
scrivono/leggono le variabili sono
condivisi; in questo modo si garantisce che un solo processo alla volta
possa entrare nel monitor. Permette
di bloccare tutto il monitor con una
coppia wait-signal senza doverne
usare molte in tutto il codice (mutua esclusione).

Questa struttura da sola non sufficiente, perci si aggiungono all'oggetto


monitor delle variabili condition sulle quali agiscono due metodi:

wait(): fa si che un processo si "addormenti" dentro il monitor in attesa del verificarsi di una condizione liberando il monitor per l'accesso di
un altro processo
signal(): sveglia un processo "addormentato" nel monitor. Nel caso ci
fosse un altro processo mentre lo svegliato dormiva, uno dei due
deve essere "sbattuto fuori" immediatamente per non causare race
conditions. La precedenza dei processi questo caso non a priori.

Esistono due tipi di monitor, differenziati sulla gestione delle priorit:


Monitor di Hoare: La precedenza va allo svegliato, lo svegliante viene
"sbattuto fuori" in una coda d'attesa.
Monitor di Lampson-Redell: La precedenza va allo svegliante ( lo sveglaito viene messo in coda).
Generalmente implementato il monitor di Hoare, tuttavia possibile ottenere
l'altra tipologia inserendo un signal() alla fine (analogo nel caso opposto).
In Java ogni oggetto un potenziale monitor e si utilizza la parola chiave syncronized per sincronizzare gli oggetti.
4.7.5 - Comunicazione con scambio di messaggi
Lo scambio di messaggi offre meno problemi rispetto alla memoria condivisa
grazie alla propriet di sincronizzazione implicita:"Un messaggio non pu
essere ricevuto prima di essere inviato".

Esistono vari meccanismi per implementare la comunicazione con scambio di


messaggi, ciascuno dei quali definisce:
chi e come crea il canale (logico) di comunicazione;
definisce se il canale per due o pi processi;
definisce se il canale bufferizzato (qual' la capacit del canale).
Comunicazione diretta
Le primitive per la comunicazione saranno:

Send(processo, messaggio), dove processo il destinatario e messaggio i l messaggio da inviare;


Recive(processo2, messaggio), dove, processo2 indica il mittente autorizzato.

un tipo di comunicazione simmetrica che pu coinvolgere solo due processi, in alternativa, la comunicazione asimmetrica, con primitive simili
a:

Send(processo, messaggio), come nel caso simmetrico


Recive(processo2,messaggio), dove processo2 indica qualunque processo.

Il canale creato dal SO e deve essere per forza tra due processi (un
canale per ogni coppia processo/processo2).
La comunicazione diretta non permette la definizione di priorit (c' un solo
canale e non si pu sorpassare) inoltre non posso mandare messaggi a
qualcuno che deve ancora nascere (il destinatario deve essere conosciuto).
Per questi motivi i meccanismi pi usati si basano su una comunicazione indiretta.
Comunicazione indiretta
In questo caso esiste una struttura dati che fa da tramite per i messaggi
(una casella postale, mailbox che non c'entra nulla con la posta elettronica).
Le primitive diventano.

Send(casella, messaggio)
Recive(casella, messaggio)

Quindi esistono syscall per creare la casella, inoltre il rapporto tra utenti
deve essere n:n (n caselle per n utenti).
Con questo metodo possiamo incanalare un messaggio a priorit pi alta in
determinate caselle.
In questo caso si hanno tre tipologie di canali:

a capacit O: il messaggio non pu rimanere nel canale, ci implica


che non posso fare send se non ho il recived del destinatario (esempio
telefono). La send una chiamata bloccante.
canale a capacit n: (esempio casetta della posta) la send non
una chiamata bloccante finch la casella non piena, lo diventa con la
cassetta piena.
canale a capacit infinita: (non esiste in realt ma posso comportarmi come se lo fosse semplicemente accettando di perdere i messaggi o sovrascriverli una volta piena la memoria messa a disposizione).

Con lo scambio di messaggi il problema produttore/consumatore, grazie alla


propriet di sincronizzazione implicita ha una soluzione banale:
recived(mutex)
sezione critica
send(mutex)
La casella inizializzata con un messaggio, alla prima recived si svuota;
tutti i processi che tentano una recived per entrare nella loro sezione critica devono fermarsi e aspettare che chi occupa la propria sezione critica la
liberi con un send. A questo punto la casella conterr almeno un messaggio da
essere ritirato e sbloccher il prossimo processo.
Esistono anche dei messaggi specifici di interruzione e segnalazione
(che in Linux si chiamano signals),
Uno dei sistemi pi usati per scambiare messaggi il socket.
Tra due processi si crea un canale di comunicazione, le chiamate send e recive fatte dai processi vengono fatte verso il socket il quale viene visto
come un flusso di byte. Questo modo di interpretare il socket necessario
in quanto i dati vengono letti nell'ordine in cui sono stati inviati ma non necessariamente con gli stessi blocchi con cui sono stati inviati.
Un altro sistema sono le pipe, che sono utilizzabili solo su processi che risiedono sulla stessa macchina ma possono essere gestite via codice in
un'applicazione perch basta creare un processo padre che le condivide per
ereditariet.
Un altro metodo usato in UNIX sono i signal, che sono dei messaggi specifici di interruzione e segnalazione che il processo pu ricevere e riconoscere agendo di conseguenza (in caso contrario verr ucciso). Altri metodi
sono le mailbox, la shared memory e i semafori.
4.7.6 Deadlock
La deadlock una attesa illimitata da parte dei processi a seguito di un blocco complessivo del sistema.
Un esempio lo spooling di stampa, dove presente una stampante con
100MB di spazio per la coda, e voglio lanciare 2 stampe da 6OMB contemporaneamente, tuttavia quando il trasferimento ha raggiunto i 50MB per documento, infatti si ferma tutto perch la memoria esaurita.
Le condizioni necessarie affinch si possa verificare la deadlock sono:

Mutua esclusione: un processo esclude l'altro e non vengono mai ese

guiti
Hold & wait
No preemption (prelazione)
attesa circolare

Esiste un algoritmo che permette di evitare i deadlock:


Algoritmo dello Struzzo
Consiste nell'idea generale della speranza che non ci siano e nel contempo

si cerca di renderle poco probabili.


Siccome a volte succedono, per evitarli si pu:
renderli impossibili: bisogna fare in modo di gestire bene le 4 condizioni per il deadlock (si cerca di eliminare l'attesa circolare).
renderli impossibili in corso d'opera: il SO esegue dei controlli preventivi sull'accesso alle risorse ed eventualmente aumenta i tempi d'attesa.
Questo metodo per gravoso in termini di utilizzo delle risorse e ha il solito problema della necessit di conoscere in anticipo quello che il sistema dovr fare.
rilevamento automatico dei deadlock: (onerosa da un punto di vista
di risorse richieste)

In conclusione la soluzione migliore ancora usare l'algoritmo dello struzzo


(letteralmente far finta di nulla) effettuando ogni tanto dei controlli.

5 Gestione della memoria (RAM)


Durante il normale funzionamento di un sistema di elaborazione risiedono in
memoria centrale pi programmi contemporaneamente e il meccanismo che
permette a ogni programma di non confondere i suoi pezzi con altri abbastanza articolato.

5.1 - Binding degli indirizzi


Se consideriamo un frammento di codice in cui, ad esempio, si definisce una
variabile, durante l'esecuzione ci si trover nella condizione di dover passare
dall'entit astratta, ad una cella di memoria concreta, nella quale memorizzata l'informazione che desideriamo.
Questa procedura (i passaggi di label di una variabile a indirizzo fisico)
detto: Binding degli indirizzi.
Il Binding degli indirizzi pu essere implementato in modi diversi:
Bindig in compilazione: Il limite evidente, la rigidit di questo
tipo di binding impedisce l'esecuzione di pi programmi diversi in contemporanea.
Binding in fase di caricamento: Un programma C viene compilato
generando un file .out. A questo punto, durante il caricamento il linker si preoccupa di trasformare i riferimenti mnemonici in riferimenti numerici. Questo tipo di binding consiste, nel riscrivere parte dell'eseguibile sostituendo i numeri. Non ci sarebbero controindicazioni, se tutti i
programmi fossero sempre caricati in memoria centrale, purtroppo
per non cos, perch spesso vengono ributtati sul disco quando
sono inattivi. Con questo metodo, per risvegliarli si dovrebbe attendere che sia libera una quantit di memoria (contigua) pari alla dimensione del programma compilato, pi quella dei dati.
Binding in runtime (esecuzione): Il metodo utilizzato in pratica.
Esso sfrutta il concetto di Memoria Virtuale.

5.2 - La memoria virtuale


Ogni processo vede una sua memoria dedicata che si estende dal byte 0 al
byte n, ma solo un'illusione. In questo modo la CPU esegue le operazioni facendo riferimento alla memoria virtuale, che verranno successivamente tra-

dotti in indirizzi fisici.


Praticamente esistono diversi modi per allocare la memoria su un calcolatore:
5.2.1 - Allocazione contigua
Consiste nel caricare tutto il programma in celle di memoria adiacenti.
Poniamo il caso di avere un programma P1, che ha una memoria virtuale che
si estende dal byte 0 a D1.
In memoria centrale, dopo una parte dedicata al SO, che termina per
esempio al byte 3000, avremo una corrispondente area di memoria (da
3001 a 3001+D1) contenente le informazioni di P1.
Durante la vita di P1 probabile che verr tolto dalla memoria centrale
(RAM) e poi ricaricato. Per ricaricarlo, per esempio, nella locazione 5000
devo allocare in memoria centrale le celle dalla 5000 alla 5000+D1.

Questo meccanismo ha dei grossi problemi: il processo o tutto in memoria


centrale o su disco; non posso caricarlo solo in parte.
Quindi anche se ho della memoria libera, non detto che possa utilizzarla.
Questo genera il fenomeno della frammentazione interna: si creano degli
spazi liberi non contigui e non sfruttabili al meglio (se troppo piccoli per qualunque processo sono addirittura inutilizzabili).
Una possibile soluzione sarebbe quella di accorpare gli spazi liberi, ma per
fare ci devo spostare i dati contenuti nella RAM; operazione che richiede calcoli anche onerosi con un forte spreco di risorse.
Un'analisi del fenomeno ha portato alla creazione della regola del 50% secondo la quale, dopo un po' che il sistema in funzione con un numero n di
processi si hanno n/2 buchi.
Questi sono i risultati a cui si giunti sfruttando l'algoritmo First-Fit. Visti i
deludenti risultati si provveduto allo studio di un altro algoritmo chiamato
Best-Fit il quale agisce scegliendo, tra tutti gli spazi disponibili, il pi picco-

lo utile. Cos per, oltre a buttare un sacco di tempo facendo passare tutti
gli spazi, s i creano dei rimasugli piccolissimi che non verranno mai usati.
5.2.2 - La Paginazione
In questo caso si suddivide sia la
memoria virtuale che quella fisica in pagine di uguale dimensione (con pagine chiamate frames). Qualunque pagina libera
pu essere utilizzata, inoltre la
dimensione della memoria fisica
svincolata da quella della
memoria virtuale; la memoria
virtuale pu essere anche maggiore di quella fisica.
Inoltre possono esistere processi in parte in memoria centrale e in parte sul disco.
L'unico prezzo da pagare per
una cos alta flessibilit la
necessit di tabelle che mappino in questo modo la memoria
virtuale in quella fisica.
Per ogni processo esiste in memoria una tabella detta tabella
delle pagine nelle cui righe
vengono memorizzate l'indirizzo
fisico di ogni pagina.
La traduzione dall'indirizzo virtuale a quello fisico, viene effettuato sommando l'indirizzo della pagina fisica (che corrisponde al primo byte della pagina)
all'indirizzo virtuale fornito (in questo modo modifico solo la parte pi significativa dell'indirizzo mentre il resto passa inalterato o quasi).
Questo metodo viene
chiamato indirizzamento per direct
mapping.
Il problema della
frammentazione non
completamente risolto perch allocare
solo spazi multipli
della dimensione della pagina, perci
molto probabile che
qualche pagina non
sar completamente
riempita ma l'entit
del fenomeno comunque minore rispetto al caso con allocazione contigua.
Inoltre sorgono anche
problemi di prestazioni dovuti alla serie di
calcoli necessari al mapping, ridotti attraverso un'opportuna circuiteria hardware, che scarica tale mansione dalla CPU.
Il problema risiede nel fatto che qualunque accesso in memoria necessita di

un altro accesso per la tabella delle pagine e quindi si osserva il raddoppio


degli accessi in memoria (e il conseguente dimezzamento della velocit della RAM dal punto di vista della CPU).
Per limitare i danni degli accessi in sovra numero stato introdotto un livello di cache (una memoria aggiuntiva di piccole dimensioni ma molto veloce)
chiamata TLB (Translation Lookaside Buffer) che memorizza le associazioni
gi fatte. Il doppio accesso si ha solo quando si verifica un miss nel TLB.
Il costo medio di accesso alla RAM viene misurato con hr (hit radio, quante corrispondenze ho nella TLB in media) hr(tTLB+tMEM)+(1-hr)(tTLB+2tMEM) che in
media maggiore del 90% (considerando tTLB molto minore di tMEM, che sono i
tempi di accesso alle due memorie).
Bisogna tenere conto della memoria occupata dalle tabelle delle pagine,
che non sono altro degli
array, e perci risentono
di tutti i problemi di allocazione contigua, inoltre
molte delle pagine non
sono utilizzate.
L'unica soluzione trovata
per gestire meglio la tabella quella della reiterazione dell'algoritmo (si pagina la tabella delle pagine).
Il costo in questo caso diventa: hr(tTLB+tMEM)+(1-hr)(tTLB+(n+1)tMEM), dove n livello di paginazione (inteso come numeri di accessi necessari per trovare
l'indirizzo fisico della pagina cercata).
Una soluzione alternativa stata quella di creare una tabella delle pagine
invertita, dove si passa da una situazione con una tabella per ogni processo
a una di dimensione fissa (data dalla dimensione della RAM) dove ogni riga
contiene il corrispondete numero della pagina memorizzata .
La tabella risulta cos molto pi piccola ma con il problema di avere una rappresentazione diversa da quella che naturalmente si vuole leggere.

La traduzione dell'indirizzo virtuale - PID (Pagina virtuale), avviene cercando


la giusta riga (che indica il n di pagina fisica corrispondente) e perci si
deve far passare tutta la tabella, con un infinit di calcoli; inoltre questa tabella contiene solo le pagine della memoria centrale.
La soluzione resta quindi la paginazione della tabella.
Il salvataggio di tutte le tabelle avviene durante il process switch.

5.3 - Segmentazione
Ogni processo posto in memoria centrale costituito da diverse parti (vedi
capito 4.1) non omogenee tra di loro, a cui opportuno applicare dei permessi
di lettura/scrittura, cosa difficile da implementare se utilizzo un unico blocco di
memoria virtuale.
Consideriamo, per esempio, un blocco di memoria virtuale come quello della figura seguente, allocato con la tecnica della paginazione.

Ad esempio il campo text contiene solo il codice


eseguibile, quindi avr permessi di sola lettura,
inoltre la sua dimensione nel tempo rimarr fissa,
all'opposto il campo data contiene i dati (contenuto delle variabili) di cui necessita il programma
per funzionare, la cui dimensione spesso dinamica e pu cambiare nel tempo.
Quindi oltre al fatto che non possibile mettere dei
permessi, si ha anche il problema che cambia nel
tempo il contenuto delle singole pagine a causa del
allargamento o del restringimento di alcuni campi.
Con la segmentazione, si assegna ad ogni campo
una propria memoria virtuale indipendente da
quella assegnata agli altri segmenti.
Lo spazio di indirizzamento viene quindi scomposto
in segmenti a livello logico, quindi anche l'indirizzo logico, sar diviso in due parti: il numero del
segmento a cui fa riferimento e l'offset, cio la posizione del dato all'interno della memoria virtuale
del segmento.
In questo modo possibile applicare dei controlli, ad esempio, se un segmento dichiarato read-only non possibile modificarne il contenuto con operazioni di scrittura errate.

Un altro vantaggio si apprezza durante la fase di collegamento (linking) con


allocazione paginata, infatti se devo fare linking in un'unica sequenza di indirizzi e ho necessit di modificare una struttura dati, devo ripetere i collegamenti su tutto lo spazio di indirizzamento. Questo avviene a causa del fatto
che le pagine inferiori si sono spostate (fig sx), mentre in una gestione a
segmenti (fig dx) se allungo un segmento l'inizio degli altri non viene spostato l'indirizzo logico mantenendo validi i calcoli fatti precedentemente.

I vari segmenti possono essere allocati con le tecniche viste precedentemente.

5.3.1 Segmentazione ad allocazione contigua


In questo caso i vari segmenti sono allocati in blocco nella memoria RAM e
l'indirizzo logico viene tradotto in fisico, individuando prima il segmento a cui
fa parte, tramite la tabella dei segmenti. Successivamente si utilizzano i
valori di limite e base (contenuti nella tabella dei segmenti). Il valore di limite viene confrontato con il numero del segmento: se minore vengono
affiancati offset e base trovando l'indirizzo del segmento fisico, altrimenti viene generato un errore di indirizzamento.

Con questo metodo per si problemi dovuti alla frammentazione.


5.3.2 Segmentazione Paginata
Si combinano le due tecniche applicando la paginazione ai segmenti. In questo caso la tabella dei segmenti di un processo punta a l le relative tabelle
delle pagine.

Quindi per risolvere l'indirizzo fisico si procede come gi visto: prima si controlla nel TLB se gi risolto questo indirizzo, in caso contrario il numero del
segmento mi permette di trovare, nella tabella dei segmenti, l'indirizzo della tabella delle pagine a lui relativa. Fatto ci, con il numero di pagina virtuale ottengo la relativa fisica che affiancato all'offset mi da l'indirizzo reale cercato.

Anche qui si possono essere pi livelli per i motivi gi visti.


La condivisione delle librerie diviene ancora pi vantaggiosa in questo caso
perch due segmenti diversi usano la stessa tabella delle pagine per accedere alla libreria condivisa.
Questo metodo pu avere un supporto HW per facilitarlo.
Esempio 386
Ogni processo pu avere al massimo 16000 segmenti, di cui 8000 privati
(LDT Local D escrptor Table) e 8000 condivisi tra tutti i processi e che contengono il SO a livello virtuale (GDT Global Descriptor Table). In questo modo
ogni processo vede in modo separato, nelle sue tabelle lo stesso SO, e questo
fa si che i processi non debbano cambiare le proprie tabelle di riferimento nei
mode-switch (tranne durante i process-switch perch cambia il processo).

5.3 - Gestione dei page fault


Accade spesso che un processo, durante la sua esecuzione, salti, ad esempio
dalla pagina 0 alla 5. Il salto perfettamente legale nella memoria virtuale
ma non detto che lo sia in memoria centrale, infatti, visto che la memoria
virtuale pu essere anche molto maggiore della memoria fisica, possibile
che la pagina richiesta non si trova nella memoria centrale: si ha cosi un
page fault.
A questo punto avvengono una serie di operazioni:
1) Il SO, con l'aiuto del processore, rileva il page fault (interrupt) e interviene (esegue un process e mode switch).
2) Recupera dalla tabella delle pagine la sua posizione sul disco (o verifica
che l'indirizzo richiesto e illegale e si comporta di conseguenza).
3) Decide dove mettere la nuova pagina in RAM (questo significa che se la
memoria centrale piena sceglie chi spostare su disco al posto suo) e
avvia il trasferimento dal disco.
4) Mette il processo nello stato di bloccato finch non termina il trasferimento dal disco.
5) Finito l'I/O il processo torna pronto e verr messo in coda per poter essere
eseguito dalla CPU.
Tutto questo necessario per una accesso in memoria con page fault, quindi il tempo medio di accesso in memoria diventa: PPFtPF+tMEM)+(1-PPF)tNoPF
dove PPF indica la probabilit di page fault, tPF il tempo necessario a risolvere
un page fault (di solito nell'ordine dei 10ms) e tNoPF il tempo per accede alla
RAM in condizioni normali (calcolato dalla formula precedente e di solito
nell'ordine dei 10ns).
Da ci risulta evidente che per non degradare troppo le prestazione del sistema i page fault devono essere i meno possibili quindi le scelte delle pagine da spostare sul disco e quelle caricate in RAM in media a ogni processo
risultano essere critiche.
Gi in fase di caricamento si possono delle scelte su cosa caricare per un
processo per limitare i page fault e non riempire inutilmente la RAM:
Demand Paging pura: Si carica, sempre, solo la pagina con la prima
istruzione e le altre solo a richiesta da parte del processo, quindi dopo
una prima serie di page fault il tutto si stabilizza. Non avviene nessun
caricamento anticipato (viene caricato solo quel che serve ma produce
molti page fault).
Prefetching di alcune pagine: Il SO decide, in base al comportamento in passato del programma, quali pagine caricare all'inizio e il resto per richiesta.

Come detto prima, per limitare i page fault importante scegliere bene
chi spostare eventualmente sul disco.
Il criterio ottimale sarebbe quello di scegliere la pagina che verr usata pi
tardi nel futuro, ma ci implica una conoscenza del futuro da parte del SO, e
ci non possibile, tuttavia esistono degli algoritmi che permettono di approssimare tale criterio.
5.3.1 Algoritmi per la scelta della vittima
FIFO: Elimino la pagina pi vecchia nella memoria.
Non ha nessuna relazione con l'algoritmo ottimo, perch non detto che la
pagina caricata prima sia per forza quella pi inutile. Inoltre soffre anche
dell'anomalia di Belady, cio quel fenomeno che porta ad avere pi page
fault (in media) se si aumenta la capacit della RAM (Vedasi figura sottostante).

Nella prima tabella il numero totale di page fault pari a 9, a fronte di una capacit della RAM di tre pagine, tuttavia se si passa a 4 pagine i page fault dello stesso processo sono diventati 10 (sono addirittura aumentati).
Il fenomeno dovuto al fatto che l'algoritmo FIFO non gode della propriet
caratteristica degli algoritmi a pila, cio che il contenuto di una memoria di
dimensione d e distanza t incluso nel contenuto di una memoria di dimensione d+1 e distanza t [ M(Dim, i)M(Dim+1, i) ].
Quindi, come gi fatto, se non posso prevedere il futuro cerco di dedurlo dal
passato.
Least Recently Used (RLU): Scarto la pagina usata meno di recente.
Si utilizza una stringa delle distanze per sapere se la pagina utilizzata gi
stata utilizzata in passato e quantificare il tempo trascorso da allora.
Dalla stringa delle distanze si pu dedurre se utile o meno aggiungere RAM:
Distanza infinita: page fault inevitabile (primo accesso alla pagina).

Distanza finita: la dimensione che dovrebbe avere la RAM per non


avere page fault con RLU.
Tale algoritmo non implementabile completamente perch i calcoli richiesti
per tenere aggiornata la stringa sarebbero troppo onerosi in un sistema reale (la stringa va aggiornata ad ogni accesso alla RAM, e ci implica un intervento dell' SO).

Inoltre la stringa delle distanze risiederebbe anch'essa in ram quindi per


ogni accesso in memoria avrei altri accessi per aggiornare la pila.
Per eliminare la stringa si potrebbe mettere un contatore associato ad ogni pagina che ne memorizza la distanza. A nche cos per ho bassa efficienza perch, devo far passare tutte le pagine per sapere qual la pi vecchia e
devo sempre aggiornare qualcosa.
Algoritmo seconda chance (o algoritmo dell'orologio a 1 bit)
Ad ogni pagina associo un bit. Quando il SO, deve liberare RAM, inizia a cercare delle "vittime" da spostare su disco, e per fare ci scorre la lista dei
flag. Se il bit a 1 la pagina viene scartata e il bit viene resettato, se invece
gi a 0 la pagina viene eletta a "vittima" messa su disco (e si ferma).
Il tutto viene ripetuto periodicamente a seconda delle esigenze del momento
(se la RAM vuota non ho bisogno di cercare vittime).
Esiste una variante che non tiene conto solo se la pagina stata solo acceduta ma distingue il caso lettura da quello scrittura.
Infatti se la pagina stata acceduta solo in lettura non devo aggiornare,
eventualmente, il contenuto sul disco, e quindi risulta essere una scelta migliore di una modificata.
Algoritmo seconda chance a 2 bit (orologio a 2 lancette)
Per ogni pagina abbiamo due bit di flag: uno che memorizza, come prima,
se la pagina stata acceduta o meno (rispettivamente 1 e 0), e l'altro che
indica la modalit di accesso (lettura, 0, o scrittura, 1).

I possibili stati di una pagina sono 4:


Accesso Modifica
0

vittima ideale

vittima papabile

Piuttosto che 11

vittima pessima

La lancetta pu fare pi passate (in caso che i controlli falliscano):


1) Primo giro: cerca 00, e senza fare altro
2) Secondo giro: cerca 01 e mentre scorre resetta i bit di accesso
3) Terzo giro: cerca un 00 (precedentemente 10) e resetta i bit di accesso
4) Quarto giro:si ha sicuramente la scelta della vittima ( quando la trova si ferma) cercando 01 (precedentemente 11).
Un SO reale non aspetta di avere la memoria piena prima di cercare una potenziale vittima, ma hanno un'attivit periodica, detta demone di paginazione, che individua le potenziali vittime, in modo da averle gi pronte in caso
di necessit.
5.3.2 Allocazione locale e globale
In un sistema reale per di solito si hanno pi processi che girano contemporaneamente, quindi nasce la domanda se pi utile cercare le vittime nell'intera
RAM o se ricercare le vittime solo nelle pagine del processo che ha causato i
page fault:

Allocazione Locale: Ogni processo che genera molti page faul penalizza solo se stesso perch le pagine swappate possono essere solo le
sue. Pu essere applicato sia in regime di allocazione statica (alla
nascita del processo gli viene assegnato un numero massimo di pagine che pu disporre in RAM; diverso dal concetto di working set)
e sia in regime di allocazione dinamica (alla nascita del processo gli
viene assegnato un numero massimo di pagine, tuttavia tale numero
pu essere riadatto a seconda delle esigenze). L'esperienza sottolinea
che questa la soluzione migliore.

Allocazione Globale: Ad ogni page fault pu essere swappata una


qualunque pagina a prescindere dal processo proprietario. Questo permette, si, una gestione pi ottimizzata delle risorse (stima automatica
del working set), ma un meccanismo troppo delicato perch basta un
solo processo che si "comporta male" che le prestazioni si degradino di
molto.
5.3.3 - Il Working Set
Il working set il numero di pagine necessarie affinch il processo "lavori
bene".
La stima di tale parametro indica il giusto numero di pagine da assegnare, ad un
processo in RAM, in modo da limitarne (o se possibile eliminare) i page fault.
Prove eseguite dimostrano che tale valore cambia nel tempo e quindi da privilegiare un'allocazione dinamica a una statica.
La stima del working set pu avvenire in tre modi:
Stima diretta del working set: Il SO osserva il comportamento del
processo (numero di pagine utilizzate e page fault) durante una fine-

stra di tempo. Il problema maggiore nel determinare la giusta durata


della finestra.
Stima indiretta del working set: Il SO osserva il tasso dei page
fault: se sono molti il processo "sta male", viceversa, il processo sta
"troppo bene". Individua cos una fascia ideale di page fault sotto la quale tolgo le pagine al processo, se va sopra le si fornisce.

Page Stealing: Il SO ogni tanto toglie delle pagine e osserva il comportamento del processo, se avvengono page fault, le restituisce, se invece non ci sono le assegna ad altri processi.
Il problema nasce sempre dalla limitazione delle risorse, quindi possibile
accontentare un numero limitato di processi altrimenti avviene il fenomeno del Trashing in cui, a causa dei troppi page fault, le prestazioni del sistema decadono vistosamente ( ad esempio il mouse va a scatti).
Il rischio di trashing aumenta proporzionalmente al grado di multiprogrammazione.

Uso
CPU

Grado di multiprogrammazione
5.4 - Principio di localit e dimensionamento delle pagine
Il principio di localit una affermazione empirica che si basa su due punti
di vista:

Spaziale: se si accede ad una cella di memoria probabilmente si acceder anche a quelle adiacenti.

Temporale: se acceder alle adiacenti, lo far in un lasso di tempo relativamente breve.


Quindi la dimensione della pagina fisica deve essere studiata in modo da garantire, nel modo pi efficiente, la gestione della memoria secondo questo
principio.
Di conseguenza la pagina fisica deve essere pi grande possibile in modo
tale che la tabella delle pagine sia piccola, gestibile e aumenti il pi possibile
l'efficienza degli accessi su disco; d'altra parte deve essere sufficientemente piccola da limitare la frammentazione interna e lo spreco di risorse.
La pagina viene sempre caricata completamente, sia che serva tutta e sia
che ne serva una piccola parte.
5.5 - Gestione dello spazio Libero
Si visto come viene gestito lo spazio allocato nella memoria, ma ho anche il
problema opposto, cio quello di gestire lo spazio libero.
Per questo esiste un meccanismo di gestione anche per lo spazio libero che
pu avvenire in pi modi:

tabella di bit: Assegno bit per pagina, il cui valore indica se la pagi-

na scritta o meno (es 0 vuota, 1 piena). Di facilissima implementazione e gestione lo svantaggio che lento a causa dell'elevato numero
di pagine normalmente presenti su un sistema (devo passare ogni volta
tutte le pagine per sapere quali sono quelle vuote).
lista concatenata: Una lista concatenata
di oggetti formati da: indicazione libero/occupato - cella inizio - n celle libere - riferimento
prox elemento. Esempio in figura, dalla cella 0
alla 5 celle libere; dalla 6 alla 4 occupate e
cos via. Struttura libera dalle limitazioni di
una grossa struttura unica e centralizzata,
ma che ha bisogno di calcoli di gestione
molto pi complessi.

Buddy System: Il sistema prevede l'allocazione della memoria a blocchi multipli di due. SI prende un blocco di memoria che divido met,
ogni met la divido in due met e reitero il procedimento fino ad ottenere un blocco di dimensione sufficiente per contenere il dato della richiesta. Tale metodo per sprecone, tuttavia tale svantaggio viene compensato dalla possibilit dell'applicazione di algoritmi per la gestione degli alberi, nei quali la ricerca molto veloce.

6 - Il File System e gestione della memoria periferica


Un File un tipo di dato astratto con alcune propriet e metodi/funzioni che
virtualizza l'accesso a risorse molto varie.
La propriet fondamentale di un file che raggruppa sia i dati che le funzioni che
operano su di essi.

Propriet:
Nome: (Essenziale).
Dimensione: (Opzionale, quindi pu non esserci, ad esempio per la
tastiera non ha senso).
Data: (Opzionale)
Ultima modifica: (Opzionale)
Permessi: (Opzionale)
Proprietario: (Opzionale)
ecc ma sempre opzionali
Metodi/funzioni
Apri/Chiudi (opzionali, per es
vedi l'NFS: Netwok file system)
Lettura/Scrittura (opzionali, ma
almeno uno due due essenziale, ad esempio esempio un CD-R
gi scritto, tuttavia lo devo
poter leggere).
Un file quindi ha almeno un nome e
pu essere almeno o letto o scritto.
Esistono due sotto-moduli del SO fondamentali per la gestione dei file:
Files services
Directory services
In generale possiamo individuare anche qui un modello a livelli riportato
nella figura a fianco.
Analizzeremo l'argomento seguendo
questo schema partendo dal livello
pi fino ad arrivare alla gestione
dell'hardware.
Il nome di un file non altro che un riferimento logico che raggruppa una
struttura dati, la quale pu contenere qualsiasi cosa (come dati o dispositivi).
Ci che si abituati a distinguere come periferiche e file sono strutturalmente uguali a livello software.
Lo spazio dei nomi gestito dal directory services, il quale ha il compito di collegare ad ogni nome la sua corrispondente risorsa fisica. I nomi vengono organizzati con strutture ad albero, la cui radice (root) il punto di partenza per
analizzarlo (o costruirlo).
Dalla root partono i rami, che contengono le risorse e i dati.
Esistono due filosofie per organizzare i dati: ad unico albero (sistemi UNIX) e
a foresta (sistemi windows).
Nei sistemi ad unico albero (come quello
in figura) si ha sempre un unico punto da
cui partire (punto di mount, indicato con
/) e in si montano gli altri dispositivi (o
alberi) come rami di quello principale.
In questo modo possibile gestire infiniti
dispositivi diversi (in teoria), ma pone il
problema di riconoscere e montare i dispositivi esterni e di riconoscerne il diverso

percorso (anche hardware) per accedere


alla risorsa da parte del file system.

Nei sistemi Windows la struttura a foresta (un insieme di alberi), dove ogni dispositivo viene contrassegnato con una lettera
(es C:/ o D:/) o una parola (di solito il
nome del dispositivo).
In passato, con la sola gestione a lettera
si poteva avere un numero massimo di dispositivi esterni (alcune lettere erano riservate a dispositivi specifici come i floppy disk).
Questo genere di organizzazione ha ragioni storiche pi che tecniche, dove i vari alberi sono in realt rami visti singolarmente.
La struttura prevede che si possano creare dei collegamenti (link) arbitrari tra elementi dello stesso albero (o tra alberi diversi). Con i link abbiamo
un oggetto che pu essere puntato da due nomi differenti. Un collegamento
pu essere di due tipi:
Hard link: i due nomi condividono la stessa struttura a basso livello;

Soft link: un solo nome punta alla struttura a basso livello, l'altro punta al primo nome.
Di quest'ultimo tipo sono i collegamenti pi frequenti.
La differenza principale fra i due tipi di collegamenti risiede nell'accessibilit del
dato, che sempre possibile con l'hard, non risulta pi possibile nel caso del soft
se cancello il primo riferimento (quello che contiene il riferimento a basso livello).

6.1 - Implementazione: da nome a riferimento fisico


Quando si vuole accedere ad un dato per prima cosa viene identificata la sua
posizione fisica (il dispositivo che pu essere una partizione, un CD o una
pennetta USB).
All'avvio del sistema si identifica il mount point, dove si trova la radice
dell'albero. Il procedimento a valanga di caricamento del SO dopo il bootstrap (cft paragrafo 2.3.1) monta a partire da root tutto il File System fino
ad arrivare a definire il dispositivo (partizione primaria) a livello logico.
Tutti i dispositivi devono essere montati da parte del File System affinch sia
possibile accedervi, infatti l'operazione di caricamento di un dispositivo consta nella preparazione delle strutture dati necessarie per l'accesso ai file.

All'interno del processo di file descrpitor eseguo la lettura della relativa


struttura, la quale contiene le informazioni necessarie per sapere il tipo di file
sul quale si sta lavorando.
P ermette, inoltre la traduzione della chiamata di lettura nella corretta funzione, determinata dallo specifico dispositivo, detta puntatore a runtime, il
quale punta a sua volta al directory service, che un' informazione a basso livello necessaria per raggiungere i dati.

Il tipo del file (estensione) ha senso solo per le applicazioni, mentre, per il
SO sono solo sequenze di dati (riesce solo a distinguere se il file eseguibile o meno).
Il record (l'oggetto) l'unit d'accesso a livello di applicazione ed molto
diversa dall'unit d'accesso logica a livello di system call (usualmente il
byte).

6.2 Organizzazione del disco: File system

I file a livello HW non sono altro che delle sequenze di bit, e spetta al SO riconoscere dove finisce un file e ne inizia uno nuovo.
Inoltre compito del SO tradurre gli indirizzi logici in locazioni sul disco ben specifiche (i bit sono raggruppati in blocchi logici per una migliore gestione del disco).
I file possono essere allocati in vari modi (strutture dati per l'allocazione
dei file):
Allocazione contigua: I dati di uno stesso file vengono posizionati in
blocchi contigui, in questo modo si riducono sia i movimenti del braccio
meccanico (estremamente lento se paragonato alla CPU), tuttavia si hanno problemi di frammentazione e di modifica del file nel caso che questo
aumenti le sue dimensioni nel tempo (come per la RAM). la soluzione
ideale per supporti read only, come i CD o i DVD (perch li scrivo una
volta sola) in cui ogni directory non altro che una tabella nelle cui righe
sono contenuti il nome del file e le relative informazioni.
Allocazione concatenata: I dati di uno stesso file possono essere anche in blocchi non contigui, infatti ogni blocco contiene, oltre ai dati, anche la posizione del successivo. Con questo metodo in ogni riga della tabella della directory vengono scritti solo il nome del file e il blocco iniziale. Il problema di questa gestione che se un blocco viene corrotto, per un
qualche motivo, non pi possibile accede ai successivi (con la contigua
questo non accade perch il SO copia le sue strutture in vari punti del disco, ma non fa lo stesso con i dati). Inoltre la lettura molto pi lenta perch la testina, in media, dovr fare molti spostamenti per leggere i vari
blocchi ( possibile che per motivi di spazio siano dispersi sulla superficie
del disco). Vedi FAT per la versione migliorata.
Allocazione indicizzata: Ogni file ha un blocco indice che lo descrive, il quale conterr informazioni sia sul file e sia sulle posizioni
dei vari blocchi. In questo caso possibile anche la lettura casuale
(leggo un blocco a caso) oltre che quella sequenziale (l'unica
permessa nei due metodi precedenti). Vedi Inode.
6.2.1 FAT (File allocation table)
Prevede un allocazione concatenata con
l'aggiunta di una tabella (chiamata FAT), che
contiene i riferimenti dei prossimi blocchi, in
modo da evitare di spargerli su tutto il disco.
Questo aumenta la robustezza del sistema e diminuisce gli spostamenti della testina; inoltre in
caso di accesso sequenziale sfrutta il principio di
localit.
Ogni elemento della directory formato da 8
campi:
Nome del File:
Estensione:
Flags: indicano se l'elemento una directory, o un eseguibile ecc.

Ora:
Data:
Primo Blocco:
Dimensione del File:

Il principale difetto di questa struttura la dimensione del puntatore, perch


questo limita il numero di blocchi in cui possibile dividere la memoria (si
utilizza spesso anche per memorie solide, come le chiavi USB) e ci influisce anche sulla dimensione degli stessi (es con la FAT16 possibile avere
blocchi da 4KB per memorie da 256MB, i quali per diventano 32KB nel caso
di memorie da 2048MB, con evidenti problemi di spreco di memoria, come
per la RAM).
Con la FAT16 possibile gestire memorie fino ad un massimo di 2GB, quindi
ad un certo punto si reso necessario aumentare la dimensione del puntatore (a 32 bit) ed nata la FAT32 che permette di gestire memorie fino a
2TB.
6.2.2 - Allocazione in UNIX: gli Inode
I sistemi UNIX prevedono file system ad un'allocazione indicizzata (Inode), dove
ogni file esiste un blocco indice.
All'interno di una directory c' una tabella che associa ad ogni file un blocco
indice che contiene gli indirizzi degli altri blocchi di quel file.
Tale metodo semplice, efficiente e compatto, dato che le informazioni sono
memorizzate file per file il limite consiste nella dimensione del file non sul numero di partizioni presenti sul disco.
In particolare il blocco indice (chiamato Inode) contiene tutte le informazioni sul file escluso il nome (mode, owner, time stamp, sizeblok, direct block,
ecc..).
L'Inode quindi una struttura a basso livello, esso contiene anche il numero degli hard link che riguardano quel file.
Con un hard link i collegamenti condividono le stesse strutture a basso livello, quindi se esistono N hard link e ne viene cancellato uno l'Inode deve
essere preservato. Esiste a tale proposito un contatore che memorizza il numero di hard link che fanno riferimento a quel file, e se raggiunge il valore
zero verr cancellato Inode.
I nomi degli hard link devono risiedere sulla stessa partizione perch
ogni partizione ha i propri Inode, all'opposto i soft link, essendo invece nomi
che puntano a nomi, funzionano anche a cavallo delle partizioni.
L'Inode non per una normale lista concatenata, se cos fosse al crescere della dimensione del file diventerebbe enorme, quindi separato in
due parti:
righe dirette (prime righe)
righe indirette (righe che puntano un blocco che un altro Inode)
In questo modo l'Inode non cresce a dismisura ma si crea una struttura ad albero con pi Inode collegati.
Esempio: con un Inode da 1KB e tripla
indirezione (tre livelli di blocchi che indicano altri Inode) posso gestire un file
da al massimo 16GB.
Gli Inode sono alla base dei moderni file
system per sistemi UNIX (come ext3,
ext4, brfs, ecc...), che sono transazionali,
cio vedono come atomiche operazioni

fisicamente separate per garantire una


migliore affidabilit del sistema.
6.2.3 - NTFS: NT File System (Solo per sistemi Windows)
Di tipo transazionale (Journalizied), quindi non compie operazioni ma transazioni.
Una transazione un'insieme di operazioni per modificare la struttura del
file system che devono essere svolte (e completate) tutte prima di poter dichiarare la transazione effettuata con successo.
Quando si eseguono operazioni di scrittura dei dati, per evitare disguidi/guai
dovuti ad una interruzione non prevista dell'operazione, si tiene nota delle
operazioni eseguite sul Journal, il quale un file che contiene l'elenco delle
transazioni in corso.
In questo modo in caso di interruzione si sa sempre in che punto avvenuta.
Il SO gestisce in questo modo solo le sue strutture dati, a cuasa dell'elevato
numero di operazioni richieste, altrimenti si avrebbe un enorme calo delle
prestazioni.
In NTFS la gestione dei dati avviene tramite una grossa struttura centrale, la MFT (Master File Table), la quale contiene una riga per ogni file o directory.
La MFT non altro che una tabella di descrittori di file in cui ogni descrittore
una sequenza di coppie (attributo, valore); gli attributi sono descritti un
file speciale (es data, bitmap).
Gli attributi possono essere: il nome, le informazioni standard, dati e altri
contenuti speciali. L'attributo dati pu essere ripetuto (pi campi dati con
valori diversi).
Se il file (molto) piccolo contenuto direttamente nella MFT, altrimenti vengono allocati in altre parti tentando di farlo in modo contiguo a tratti, in
modo tale che se un file aumenta le proprie dimensioni, posso allocare i nuovi
dati altri blocchi, senza dover spostare l'intero file.
Nella directory c' scritto in quale riga della MFT si trova la posizione del file in
memoria.

Una riga della MFT (record) contiene, in ordine: informazioni varie, il nome
del file, una sequenza di coppie che identifica il numero del blocco e la sua
dimensione in allocazione contigua.
Se il file grosso e le indicazioni sforano la dimensione massima di un record della MFT se ne utilizza un altro mettendo come ultimo parametro del
primo il riferimento al record successivo.

6.3 Organizzazione del disco: Hardware

A livello fisico, il disco (pi in generale, qualsiasi memoria periferica per


l'archiviazione dei dati) gestita in partizioni, che contengono le varie strutture dati. Esistono principalmente tre tipi di partizioni:
Area di swap: non viene gestita da un file system vero e proprio.
organizzata in una serie di slot, grandi come una pagina RAM, e mappati attraverso una tabella. Vengono allocati in modo contiguo.
RAW I/O: Indica una partizione non gestita da un file system (e di conseguenza dal SO), dove certe applicazioni possono accedervi (gestendo
direttamente il disco) per scopi particolari (come ad esempio i database).
Normale: Gestita dal SO attraverso il file system.
La creazione di una partizione prende il nome di formattazione ad alto livel-

lo, tuttavia esistono altre strutture dati per la gestione delle partizioni.
Oltre a queste partizioni c' ne sono altre due di servizio, indicate nella figura
sottostante con i nomi MBR e Partition Table.
MBR un software di Windows per il caricamento iniziale del SO (in fase di boo tstrap), al pari del GRUB (sistemi GNU/Linux).
La Partition Table, contiene invece le informazioni varie sulle partizioni presenti
sul disco (nome, file system, dimensione ecc...).

Altre strutture dati che definiscono una partizione dipendono dal FS scelto,
per esempio in UNIX si ha la struttura rappresentata dalla figura sottostante.

Per l'NTFS si ha invece:

Queste ultime strutture dati vengono create durante la formattazione ad


alto livello (creazione delle partizioni), e sono strutture nascoste all'utente o alle applicazioni, ma di dimensioni notevoli.
I SO si differenziano anche sul modo di riempimento della partizione (come
dispongono fisicamente i dati sul disco), ad esempio:
FAT: Per qualunque accesso prima si legge la FAT e poi si passa ai blocchi dei
file. Pi essi sono vicini meno movimenti fa la testina, ma ci possibile finch
il file contiguo la testina si muove una sola volta. Quando la frammentazione del file System diventa eccessiva le sue prestazioni possono calare anche di molto; quindi, ogni tanto, bene raggruppare i file in modo continuo,
tramite degli appositi programmi. I dati, inoltre, erano memorizzati partendo
dal centro del disco per poi spostarsi verso l'esterno, e ci provoca un trattamento non omogeneo della superficie del disco e dell'usura.
UNIX: Le strutture dati sono distribuite, con gli Inode sparpagliati sul disco,
in questo modo la probabilit di trovare uno spazio libero il pi contiguo possibile al resto del file (utile se si aggiunge un pezzo) molto alta e la superficie del disco trattata in modo uniforme.
6.3.1 - Accorgimenti fisici per migliorare le prestazioni
Per aumentare le prestazioni si utilizzano delle memorie apposite, dette chache (come per la RAM), che memorizzano temporaneamente i dati da e per il
disco.
In questo modo possibile fare:
ricordarsi i blocchi appena letti/scritti;
letture anticipate (secondo il principio di localit)
scritture ritardate (pericolose perch posso perdere i dati).
Da qui il grosso guadagno in prestazioni ma la necessit di un file system di

tipo transazionali.
Tale memoria pu essere sia su RAM (in questo caso gestita dal SO) e si a livello
HW, integrata nel dispositivo stesso.
Esistono inoltre forme di chaching gestite dall'utente come i:
RAM disk: In cui utilizzo la RAM come disco fittizio (a tale scopo viene
creato un File System apparente in RAM ) sul quale mappo pi file.
File mapping: In cui tutto il file viene mappato in RAM e solo alla fine
verr salvato sul disco.
Esistono anche forme di chacing usate dal SO all'insaputa dell'utente, il quale pu sempre decidere di disabilitarle usando il SO in modalit
sincrona, usate per delle info di sistema poco critiche (metadati) e per i dati
utente.
6.3.2 Consistenza del file system
Una perdita dell'alimentazione quando una parte dei dati si trova in un qualunque livello di chache (che sono usualmente in RAM) pu causare gravi danni creando il problema della coerenza del file system (ridotto in quelli
transazionali perch tengono traccia delle operazioni svolte).
In caso di perdita dell'alimentazione in modo improvviso, il sistema in grado di accorgersene (non ho distrutto certe strutture dati, normalmente eliminte in fase di shutdown) e di avviare un controllo della coerenza del file system.
Un file system coerente quando un blocco o libero o occupato da un
solo file:
blocco li- blocco con
commenti
bero
file
1

coerente

coerente

>1

blocco libero pi volte

>0

>1

sia libero pi volte che occupato da pi file (lo stavo


occupando o liberando?)

>1

blocco libero e occupato da pi file

L'unica operazione da fare nel caso che non sia coerente, quella di assegnare
il blocco ai liberi, con la conseguente perdita dei dati (in realt erano gi persi
perch non so pi a cosa si riferissero), tranne nel caso che ci sia il journal che
mi indica quale operazione stavo svolgendo su di essi prima di perdere l'alimentazione.
6.3.3 Struttura fisica del disco
Un hard disk in realt una pila di dischi a doppia faccia che girano solidalmente ad una certa velocit angolare indicata in RPM (Round Per Minute: giri
al minuto).
Ogni faccia ha una testina che lo legge, individuando cos delle superfici tridimensionali chiamate chiamate cilindri (vedi figura sottostante).
Ogni faccia divisa in tracce (serie di anelli concentrici), a loro volta divise in settori.
Tali strutture vengono di solito create o in fase di creazione del disco in fabbrica, o durante l'operazione di formattazione di basso livello, in cui si porta il
disco alle condizioni originali (si cancellano definitivamente tutti i dati).
L'operazione di seek corrisponde al posizionamento della testina sul cilindro
voluto, ed l'operazione pi lenta perch coinvolge diversi organi meccanici.

Oltre al tempo di posizionamento della testina, esistono altri fattori che allungano l'operazione di lettura (aumentano il tempo di accesso):
Latency time: tempo impiegato dalla testina a raggiungere il settore
voluto all'interno di una traccia.
Transmission time: tempo che occorre per leggere il settore voluto
(variabile perch dipende dalla posizione del disco dal momento che la testina in posizione sulla traccia, ed massimo se il settore cercato appena
passato, perch si deve aspettare un intero giro del disco per poter di nuovo
leggere le informazioni).
Per individuare la traccia cercata esistono sul disco informazioni che ne dichiarano la geometria. Queste informazioni sono generate dal costruttore
del disco o tramite l'operazione di formattazione a basso livello (si creano tracce e settori numerati).
Non detto che tutte le tracce abbiano lo stesso numero di settori (pi ci
sia allontana dal centro e pi aumenta il raggio della circonferenza che descrive la traccia) perch oggigiorno si preferisce avere settori tutti della
stessa capacit, inoltre non detto che i settori siano numerati in sequenza.
Ci viene fatto per aumentare le prestazioni del disco perch, nel caso che
sia stata letto il settore 1 e si abbia la richiesta di leggere anche il 2, se i
settori fossero numerati in modo sequenziale non si potrebbe avere abbastanza tempo per elaborare la richiesta e si dovrebbe aspettare che il disco
faccia un intero giro prima di potrebbe leggere il secondo settore (problema presente nei vecchi dischi senza buffer).
Numerando in modo non sequenziale invece si aspetta un tempo minore
perch il settore cercato un po pi in la dando al sistema il tempo per elaborare la richiesta (prende il nome di interleaving).

Nella figura sopra riportata, si evidenzia il fatto che nei vecchi dischi ogni
traccia aveva lo stesso numero di settori, con il problema che essi hanno
diversa capacit man mano che ci si sposta verso l'esterno (maggiori problemi nella scelta di dove posizionare il file).
Nonostante i raffinati processi di produzione moderni, resta comunque impossibile creare superfici perfette mentre si produce un hard disk (disco rigido o
HDD), i bad sector (settori danneggiati) sono praticamente inevitabili.
Per evitare che il sistema li impieghi per il salvataggio dei dati, vengono individuati durante la formattazione a basso livello e non vengono numerati.
Considerando i possibili danneggiamenti dovuti alla normale usura dei dispositivi, vengono creati dei blocchi in sovra numero, come "scorta", che verranno usati al posto dei danneggiati (la numerazione fissa vengono accodati), il SO in grado di individuare i blocchi danneggiati e ricordarli per evitare
di usarli e perdere cos i dati dei suoi utenti.
6.3.3 - Scheduling del disco
Dato che il tempo di accesso di un disco determinato in larga parte dal
tempo impiegato dalla testina per posizionarsi correttamente esistono degli algoritmi atti a ottimizzare al massimo lo spostamento della testina, in relazione alle richieste di lettura/scrittura.

FIFO: La testina legge i blocchi cos come gli arrivano dal sistema cen

trale. il peggiore di tutti perch la testina continua a zigzagare sulla


superficie del disco.
SSTF: Shortest Seek Time First: Le richieste di lettura vengono
messe in una coda e si sceglie l'ordine migliore per la lettura: analizzo
la coda scelgo la richiesta pi vicina leggo e reitero il procedimento. Il problema analogo a quando accadeva per la PCU con il job first,
dove se arrivano tante richieste vicine sul disco, quelle pi lontane
verranno analizzate con molto ritardo o addirittura mai (rischio di starvation).
SCAN: La testina serve tutte le richieste della coda in una direzione seguendo SSTF, poi fa lo stesso stesso nella direzione opposta. Elimina
completamente il pericolo di Starvation in quanto esegue sicuramente
tutte le letture in una direzione. Per come definito deve arrivare a
fine corsa anche se non necessario.
LOOK: uno SCAN che non va che pu invertire la direzione prima di arrivare a 0 se non ci sono richieste per quell'area. Si il LOOK che lo SCAN
hanno il problema che non trattano equamente tutta la superficie
del disco (le aeree centrali sono pi passate rispetto a quelle periferiche).
CSCAN-CLOOK: la C sta per circolare. Sono i due algoritmi SCAN e
LOOK nei quali la testina si muove solo nella direzione dalle richieste pi
basse alle pi alte, quando dovrebbe cambiare direzione torna alla cella
pi piccola (lo 0 per lo SCAN) rapidamente e ricomincia ad avanzare
con SSTF verso l'indice pi alto.

7 - Protezione e Sicurezza
Un moderno SO deve garantire la protezione del sistema e la sua sicurezza.
Per protezione ci si riferisce alle regole e meccanismi per garantire la convivenza tra utenti (anche indesiderati) nel sistema (es utente A non pu eliminare per errore i dati di B), necessarie per evitare danni derivanti dalla distrazione (tento di far salvare i dati ad un programma dove non pu), male-

ducazione (voglio leggere la posta degli altri) malafede, ecc.


Con sicurezza si vuole garantire immunit da attacchi volontari e specifici
atti a creare danno e/o a trafugare e/o alterare informazioni contenute nel
sistema.
Alcuni sistemi di protezione implementati dall'hardware sono stati trattati in
precedenza come :

dual mode
protezione della memoria
protezione dei file
Spesso tali meccanismi non bastano ad impedire che un soggetto (a maggior ragione se fortemente motivato) possa eseguire operazioni non consentite. La politica basilare che si deve tenere presente quando si parla di protezione e sicurezza il principio del minimo privilegio, dove ad ogni entit ha accesso solo alle risorse a lui strettamente necessarie per svolgere le
sue attivit (non applicabile completamente sempre perch necessita della
conoscenza del futuro per stabilire quali risorse user l'utente nel tempo).
Inoltre esistono molteplici violazioni a questo principio, proprio a causa del
fatto che non si conosce con esattezza ci di cui l'utente avr bisogno in futuro (es utente Root nei sistemi UNIX, il quale pu svolgere qualsiasi operazione
sulla sua macchina).
Un metodo per attuare, almeno in parte questo principio quello di stabilire
delle regole che permettano o meno l'accesso alle risorse da parte dei soggetti.
Gli obbiettivi generali da seguire nell'implementare meccanismi di protezione
e sicurezza sono raggruppati nella CIA:
Confidentaly (C): Le informazioni devono essere disponibili solo a
utenti autorizzati (esempio il tema esame deve essere visto solo dal
professore prima della prova).
Integry (I): Le informazioni non vengono modificate da utenti non autorizzate (esempio lo studente che modifica il proprio voto nel sistema).
Avallability (A): Le informazioni devono essere sempre disponibili
quando serve (esempio i vecchi temi esami devono essere disponibili
agli studenti).
Si segue perci uno schema a tra fasi per autenticare il soggetto che vuole
usare la risorsa:
Identificazione: (fase di Login)
Autenticazione: Verifica dell'identit (ad esempio tramite password)
Autorizzazione: Racchiude sia le regole di accesso e sia le azioni vietate o permesse.

7.1 La Matrice di accesso


Le informazioni su cosa permesso accedere ad un utente (o cosa no) sono
raggruppate (teoricamente) nella matrice di accesso la quale una tabella
che ha sulle sue righe l'elenco dei domini d'accesso e sulle sue colonne gli
oggetti da proteggere:
Dominio/oggetti

F1

D1

Read

F2

Stampante
print

D2

read
Write

execute

Un dominio (soggetto attivo) un insieme di utenti, gruppi di utenti o altre


entit che possiedono i permessi per eseguire operazioni sugli oggetti (soggetti passivi).
In alcuni s istemi i domini compaiono anche sulle colonne per permettere ai
domini di poter acquisire i privilegi di un altro, previa esplicita autenticazione (es superuser i n UNIX).
Quando si cambia dominio per possibile perdere alcuni permessi che si avevano nel precedente (detti non ereditabili), tali politiche cambiano da sistema
a sistema a seconda della configurazione possibile comunque avere una
copia dei diritti quando si cambia dominio, la matrice d'accesso diventa cos
una matrice di accesso con diritti di copy.
Il problema nasce dal fatto che devo anche rappresentare il proprietario
dell'oggetto con la conseguente possibilit di conflitti tra proprietario e dominio.
Ci sono tre diverse filosofie per gestire i permessi (assegnazione e cambio):
DAC (Discretional Access Control): I permessi sono dati dagli utenti
come proprietari degli oggetti (UNIX).
MAC (Mandatary Access Control): I permessi sono dati da un'unica
autorit centrale (amministratore).
RBAC (Role Based Access Control): I permessi sono associati a ruoli
e non a utenti (si presume che gli utenti possano assumere ruoli diversi
nel tempo).

7.2 - Politiche di accesso: ACL e Capability List


La matrice di accesso cos creata non implementata in nessun sistema reale
perch risulterebbe difficile da gestire (il numero di risorse presenti in un sistema pu essere molto elevato, come anche quello degli utenti) e occuperebbe troppo spazio.
Una implementazione alternativa arriva dalle ACL e dalle capability List.
Qualunque oggetto possiede un proprietario/responsabile (anche i domini, in
questo caso si parla di c ontrol) che prende le decisioni riguardo al suo oggetto.
In generale:
L'Owner (proprietario) stabilisce i valori massimi dei permessi relativi al
suo oggetto.
Il Control libero di ridurre a piacimento i permessi dati dall'Owner.
7.2.1 - ACL e Capability List
I concetti espressi fin qui, di solito vengono implementati nei SO direttamente
cio la matrice degli accessi non viene usata come tabella ma implementata
secondo due politiche:

ACL (Access Control List): Ad ogni oggetto vengono assegnanti i per

messi relativi a ogni singolo dominio (es bit di protezione in UNIX).


Equivale all'uso della matrice d'accesso letta per colonne.
Capability List: Ogni dominio possiede una serie di coppie oggetto-permessi. Equivale alla lettura della matrice per righe.

Tutto questo non solo un esercizio di geometria, ma i controlli dati dai due
metodi sono concettualmente diversi.

Nelle ACL ad ogni accesso di un oggetto il richiedente viene identificato, e


successivamente, gli vengono assegnati gli specifici permessi; con le capability list, invece, si assegna al richiedente una sessione (dominio) che contiene gi i permessi di accesso agli oggetti senza verificare l'identit (come un
biglietto per entrare al concerto).
Questa semplice differenza porta ad una gestione completamente diversa
della revoca dei permessi.
7.2.2 - Revoca dei permessi
La revoca dei permessi pu avvenire per i pi svariati motivi ma con caratteristiche combinabili a piacere.
La revoca pu essere
Immediata o ritardata

Parziale o totale sui diritti


Selettiva o generale sugli utenti
ACL e le Capability list si basano su meccanismi di revoca (da parte di una
autorit come ad esempio l'amministratore del sistema) differenti
Con le ACL la revoca dei permessi immediata, selettiva sugli utenti e
parziale sui diritti. Caratteristiche che permettono una facile gestione
dei permessi.
Con le Capability List la gestione della revoca dei permessi risulta
pi efficiente ma non immediata. Se un utente ha una sessione attiva,
l'assunzione delle nuove capability avverr solo dopo la "morte" della
sessione corrente (che pu avvenire anche per cause non naturali,
l'individuazione di una sessione, per, un compito abbastanza gravoso).
Nelle implementazioni reali si usa un meccanismo ibrido per cui le informazioni memorizzate sono protette con ACL, quindi si ha un controllo dell'identit
dell'utente solo la prima volta in modo da assegnarli una sessione con le capability adatte.
Nei SO moderni esistono soluzioni anche pi sofisticate.
Un esmpio di ci dato dal WinNT, in cui quando un utente si collega ottiene
un token di accesso (gettone di accesso) che contiene tutte le informazioni
necessarie per passare i controlli d'accesso sugli oggetti.
I controlli avvengono confrontando il token con un Security Descriptor di
propriet dell'oggetto.

Un altro modo sono i bit mask: una maschera di bit che indicano se

l'oggetto ha, o meno, una certa lista di permessi. Alcuni di essi possono essere
uguali per qualunque oggetto, altri variano a seconda del tipo dell'oggetto.

7.3 Livelli di sicurezza fisici


Tutti i concetti fin qui espressi non hanno nessun valore se non si pu garantire la sicurezza fisica della macchina (il meccanismo di estrema difesa pu essere lo spegnimento immediato della macchina), si deve essere sicuri che nessuno, non autorizzato, possa smontare/rovinare o modificare il sistema di elaborazione.
Un sistema con permessi di accesso molto rigorosi inutile se chiunque abbia pu accedere alla macchina e avviarla oppure smontare il disco dei dati
( per rimontarlo su una macchina di cui possiede il pieno controllo).
Il livello successivo garantito dal SO, il quale deve avere un cuore sicuro sul
quale possa appoggiarmi, senza il timore di falle (esempio cancella le pagine
virtuali in RAM prima di dichiarala libera, in modo che nessun altro possa leggerne il precedente contenuto).
Bisogna porre anche attenzione ai software di terze parti (come i driver per
esempio) di cui non posso fidarmi ciecamente, perch spesso non sono stati
progettati a tale scopo.
L'ultimo livello (il primo in realt) costituito dalle applicazioni (integrit delle
stesse) e del controllo dei linguaggi interpretati (come il Java), spesso in passato veicoli di attacco.
In questo caso esistono particolari sistemi di protezioni garantiti dal codice
stesso (o meglio dalla macchina virtuale sottostante, come JVM), chiamati
sand boxing, le quali confinano a loro volta tali applicazioni in ambienti protetti.
Il principale metodo di protezione rimane comunque quello di non far conoscere le politiche e i sistemi di sicurezza agli attaccanti e questo pu essere ridotto chiudendo tutti i possibili cover channel (canali coperti).
Uno dei maggiori rischi di fuga di informazioni costituito dall'esigenza
di elaborare dei dati riservati ma non si ha la capacit tecnica per farlo e ci
si rivolge a terzi per farlo.
In questo modo si costretti a dare il premesso di lettura a persone esterne, cercando di assicurarsi che non escano dall'ambito autorizzato, quindi si
studieranno procedure che vietino alcune operazioni. Spesso si assiste all'utilizzo di cose autorizzate ed apparentemente innocue (come la creazione di
un file temporaneo) con fini diversi e pericolosi (creazione/cancellazione ad
intervalli regolari permettendo la trasmissione di informazioni).
Ovviamente posso implementare tutti i meccanismi di difesa possibili e immaginabili, ma i principali responsabili di fuga di informazione sono gli utenti stessi.

7.4 Livelli di sicurezza (informazioni)


Per ridurre la fuga di notizie posso lavorare sulle Sensibility Labels (etichette di
sensibilit), che posso applicare sia agli utenti e sia ai dati, dandone una classificazione in termini riservatezza/sicurezza.
I dati che sono accessibili a tutti sono detti unclassified, viceversa, quelli non
accessibili a chiunque sono classified.
In questo modo possibile stabilire con dei livelli di sicurezza e a quali tipologie
di dati pu accedere ogni singolo utente in base al suo livello.
Esistono due modelli che lavorano su tali livelli per stabilire delle regole per garantire sicurezza e sono: il modello di Bell La Padula e il BIBA.

Il modello di Bell La Padula prevede due regole:


Segretezza: (Simple Security Property), un utente con livello pi basso
non pu leggere file a livello maggiore
*: un utente a livello pi alto non pu scrivere file a priorit minore.
Il modello di BIBA ( mirato all'integrit):
Simple Integrity property: un utente con livello pi basso non pu scrivere file a livello maggiore
*: un utente a livello pi alto non pu leggere dati a priorit minore.
Se applico in contemporanea tutte e quattro le regole non ho comunicazione tra i
vari livelli.

7.5 Classificazione di SO sulla sicurezza


Esistono due metodi per classificare i vari SO in base alla sicurezza:
Orange Book: Classificazione di base ed ormai osoleta
Common Criteria: vengono stipulati dei criteri sul quale fare la valutazione in base alle esigenze e ai nuovi metodi di attacco.
L'Orange Book, anche se desueta da un'idea su come viene fatta una classificazione di un SO in base alla sicurezza.
Essa prevede una divisione in classi (quelle inferiori racchiudono quelle superiori,
come sicurezza adottate):
D: Nessuna sicurezza (es MSDOS o i primi SO).
C1: Protezioni di base come (proprietario, permessi) il UNIX di base.
C2: Protezioni su base individuale, controllo sui dati rilasciati dal sistema e
forme di protezione base (es cancellazione della pagina della RAM liberata
in modo fisico). Di questa classe fanno parte moderni SO.
B1: Implementazione delle etichette di sensibilit.
B2: Etichette di sensibilit anche su periferiche (per il controllo dei cover
cannels) e audit degli eventi (elaborazione degli eventi).
B3: Implementa meccanismi di divieti espliciti e fa un monitoraggio in tempo reale degli attacchi (tramite magari una macchina-esca vulnerabile costruita appositamente allo scopo).
A: Tutto accompagnato da documentazione formale (non basta il codice
per autenticarsi ma servono anche altre credenziali di accesso).

7.6 L'autenticazione
Serve alla macchina per conoscere e verificare l'autenticit del soggetto
che vuole accedervi. Di solito un processo reciproco per evitare che
qualche malintenzionato possa rubare le credenziali di accesso attraverso
macchine civetta.
Gli strumenti di verifica sono di tre tipi, indicati con SY- (Something You):
SYHAVE (Own): hai o possiedi, come una chiavetta, un sigillo o
una carta
SYKNOW: qualcosa che tu conosci, come una password
SYKARE: Indica le caratteristiche biomediche di un soggetto (come
l'impronta digitale).
Nessuno di questi metodi da la certezza assoluta di non subire i furti di

identit, tuttavia, se combinati assieme, rendono molto pi difficile il furto


e proteggono meglio il sistema.
Nessun sistema adeguatamente protetto se tutti gli utenti non prendono
adeguate precauzioni nel custodire le chiavi di accesso (di tutti i tre i tipi).
I vantaggi del SYHAVE sono dovuti all'univocit spaziale (se lo perdo la
chiave, in teoria, me ne posso accorgere prima che il malfattore possa
fare danni), tuttavia sono molto vulnerabili al furto della stessa.
Gli SYARE sono di facile utilizzo, in teoria difficile da falsificare e non re versibile. I problemi nascono sempre dal possibile furto delle stesse
(esempio le impronte digitali) ed al meccanismo di rilevamento che su
base statistica e non deterministica (c' una piccolissima probabilit che
un soggetto terzo abbia le impronte tanto simili alle mie da essere ricono sciute dal sistema come valide).
Gli SYKNOW sono il metodo pi utilizzato perch semplice da implementa re e relativamente sicuro.
I problemi che nascono sono dovuti, anche in questo caso, all'utente, per ch una buona gestione delle Password prevede che esse siano: robuste
(formate da caratteri speciali, numeri, e lettere e che non siano riconduci bili a effetti personali del soggetto come la data di nascita o il nome del
cane o della moglie/marito), che vengano conservate nel modo corretto
sia dall'utente (meglio nella mente o su un taccuino in modo cifrato) e dal
sistema (devono essere cifrate in file appositi), devono essere cambiate
spesso (il sistema pu fornire password mono-uso) e non devono essere
condivise tra pi sistemi/servizi (come Facebook, SO, mail, ecc.).