Docsity
Docsity

Prepara i tuoi esami
Prepara i tuoi esami

Studia grazie alle numerose risorse presenti su Docsity


Ottieni i punti per scaricare
Ottieni i punti per scaricare

Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium


Guide e consigli

Sintesi Sistemi Operativi modulo 1, prof. De Gaspari, Schemi e mappe concettuali di Sistemi Operativi

All'interno del documento si può trovare una sintesi schematizzata dell'intero programma svolto dal professor De Gaspari riguardante il primo modulo di Sistemi Operativi

Tipologia: Schemi e mappe concettuali

2022/2023

In vendita dal 19/08/2023

i-m-bonny
i-m-bonny 🇮🇹

3 documenti

4 / 20

Toggle sidebar

Questa pagina non è visibile nell’anteprima

Non perderti parti importanti!

bg1
Componenti di un
sistema operativo
Processori: cervello del computer
RAM: volatile
Moduli I/O: comprende i dischi, i dispositivi di comunicazione e altri dispositivi come mouse e tastiera
Bus di sistema: mezzo per permettere la comunicazione tra le parti interne del computer
Registri I registri sono delle sequenze di celle di memoria, in cui si possono leggere e scrivere dati, in particolare risultati di operazioni.
Sono molto più veloci rispetto alla memoria primaria.
- Registri visibili all'utente: usati da chi programma in assembler o da compilatori di linguaggi non interpretati
- Registri interni: gestiti dal firmware e permettono la comunicazione tra memoria e I/O
- Registri di controllo e di stato: usati dal processore per controllare l'uso dello stesso
Esecuzione di
un'istruzione Ci sono due fasi: fetch della memoria principale ed esecuzione delle istruzioni.
Il PC mantiene l'indirizzo della prossima istruzione mentre l'IR contiene l'istruzione prelevata.
Esistono vari tipi di istruzioni, ma in generale fuzionano tutte allo stesso modo
Interruzioni Le interruzioni bloccano l'esecuzione sequenziale del programma in modo inaspettato e attivano l'esecuzione del
software di sistema, che p parte del sistema operativo
Possono avvenire per varie ragioni e si dividono in due classi principali:
- Interruzioni sincrone, ossia causarte da errori interni al programma, che vengono sollevate immediatamente:
- I faults sono errori corregibili
- Gli aborts non sono corregibili
- Nelle traps/syscall si riparte dall'istruzione successiva
- Interruzioni asincrone, generalmente causate da un evento esterno. In generale, dopo l'handler si riparte
dall'istruzione successiva, a meno che la computazione non sia stata abortita. Si dividono anche queste in
sottocategorie:
- Le interruzioni da I/O, generate dal controllore di un dispositivo input/output
- Le interruzioni da fallimento hardware, causate da un'improvvisa mancanza di potenza
- Le interruzioni da timer, generate da un timer all'interno del processore e servono al sistema operativo
per eseguire alcune operazioni a intervalli regolari
Interrupt handler Funzione in cui sistema operativo e hardware collaborano per salvare le informazioni importanti, come registro
di stato e program counter
La chiamata di funzione non è scritta dal programmatore ma avviene in moido inaspettato, per questo
bisogna salvare queste informazioni
Le interruzioni possono essere sequenziali o annidate, inoltre gli interrupt possono essere disabilitati, quindi le
interruzioni si generano e non sono gestite
Metodi per la
gestione I/O I/O programmato: tutto è gestito dalla CPU
I/O da interruzione: la CPU manda l'istruzione e si mette a fare altro. Quando l'I/O è pronto a scambiare dati, il
processore viene interrotto
Nei computer moderni si accede direttamente alla memoria: quando l'operazione è completata, la CPU non si deve
più preoccupare di trasferire i dati perche i dispositivi I/O vanno in memoria
Multiprogrammazione: Capacità di un processore di eseguire più programmi contemporaneamente
Gerarchia della memoria: La memoria è composta da vari livelli: man mano che si scende, diminuiscono velocità di accesso, costo
al bit e frequenza di accesso, emntre aumenta la capacità
bg2
Cache La cache contiene copie di porzioni di memoria principale e viene gestita dall'hardware. Il processore controlla se il dato è
nella cache: se non c?è, il corrispondente blocco di memoria viene caricato. Tutto questo viene fatto perché probabilmente
questo dato potrà servire ancora nell?immediato futuro, e così è meno costoso accedervi
Naturalmente, più grande è il blocco di memoria maggiori sono gli accessi riusciti, ma è bene non aumentare troppo le
dimensioni, poiché a quel punto verranno rimossi più dati.
- Funzione di mappatura ? determina la locazione della cache dove va messo il blocco.
- Algoritmo di rimpiazzamento ? sceglie quale blocco va rimpiazzato. Ne esistono di diversi, per esempio LRU rimuove
il blocco più vecchio.
- Politica di scrittura ? determina quando si scrive in memoria. Ad esempio ogni volta che un blocco viene modificato
(write through) o quando il blocco è rimpiazzato (write back). In teoria cerchiamo di minimizzare la scrittura, quindi è
probabile trovare la memoria in uno stato obsoleto.
Sistema Operativo Il sistema operativo offre una serie di servizi come esecuzioni di programmi, accesso ai dispositivi I\O, accesso al
sistema operativo stesso, sviluppo di programmi, rilevamento di e reazione ad errori, accounting
Il sistema operativo è un programma che controlla l?esecuzione di programmi applicativi: è un?interfaccia tra le
applicazioni e l?hardware, e i suoi obiettivi sono convenienza, efficienza e capacità di evolvere.
Inoltre, il sistema operativo gestisce le risorse (come un software, ma con privilegi più alti) e concede il controllo
del processore ad altri programmi.
Il kernel è la parte del sistema operativo che si trova sempre in memoria principale: contiene le funzioni più usate.
Elementi del
sistema operativo
Viene visto come una serie di livelli: ogni livello si occupa di un sottoinsieme delle funzioni del sistema e si basa sul
livello immediatamente inferiore. In questo modo i problemi vengono decomposti in problemi più semplici.
- Livello 1 ? circuiti elettrici, qui si resettano registri e si leggono locazioni di memoria.
- Livello 2 ? insieme di istruzioni macchina, per esempio add, subtract?
- Livello 3 ? concetto di subroutine (procedura) con operazioni di chiamata e ritorno.
- Livello 4 ? interruzioni.
- Livello 5 ? processo come programma in esecuzione, sospensione e ripresa.
- Livello 6 ? dispositivi di memorizzazione secondaria, trasferimento di blocchi dati.
- Livello 7 ? spazio logico degli indirizzi, organizza spazio degli indirizzi virtuali in blocchi.
- Livello 8 ? comunicazioni tra processi.
- Livello 9 ? salvataggio di file con nome.
- Livello 10 ? accesso a dispositivi esterni con interfacce standard.
- Livello 11 ? associazione tra identificatori interni ed esterni.
- Livello 12 ? supporto di alto livello,
- Livello 13 ? interfaccia utente.
Architettura UNIX
Il kernel Linux è un mix tra kernel monolitico (kernel tutto in memoria da boot a spegnimento) e microkernel (minima
parte del kernel in memoria, il resto caricato all?occorrenza): il monolitico infatti è più veloce, ma occupa più spazio,
nonostante ciò è usato dalla maggior parte dei sistemi operativi moderni. Linux possiede i moduli, ovvero alcune
parti possono essere tolte o aggiunte a richiesta dall?immagine in memoria del kernel.
Processi Il sistema operativo si occupa principalmente di gestire i processi, ovvero le computazioni che si vuole eseguire
Il sistema operativo permette l?esecuzione alternata di processi multipli, assegna le risorse ai processi e le protegge da altri
processi; inoltre permette ai processi di scambiarsi informazioni e la sincronizzazione.
Un processo è un?istanza di un programma in esecuzione su un computer, che viene assegnata a un processore per la sua
esecuzione
È composto da una serie di istruzioni, da uno stato e da un insieme di risorse, ovvero da codice (istruzioni da
eseguire), insieme di dati e attributi che ne descrivono lo stato
pf3
pf4
pf5
bg6
Short Term
Scheduling
Quello eseguito più frequentemente, viene invocato sulla base di eventi: il suo scopo è allocare
tempo di esecuzione su un processore per ottimizzare il comportamento del sistema, in base a
indici prestazionali.
i criteri utente prestazionali sono:
- turnaround time (tempo di ritorno) ? per i batch;
- response time ? tempo tra sottomissione richiesta e inizio della risposta all?interazione, si
usa nei processi interattivi. Qui lo scheduler deve minimizzare il tempo di risposta medio e
massimizzare gli utenti con un buon tempo di risposta;
- deadline ? quando si possono specificare, lo scheduler deve massimizzare il numero di
scadenze rispettate, quindi poca variabilità nei tempi di risposta e ritorno.
L?unico criterio utente non prestazionale è la predictability: legato alla deadline, si vuole che
lanciando lo stesso processo più volte ci metta più o meno lo stesso tempo
Invece per quanto riguarda i criteri di sistema prestazionali abbiamo:
- throughput (volume di lavoro) ? dobbiamo massimizzare il numero di processi completati
per unità di tempo: infatti il throughput è una misura di quanto lavoro viene effettuato e
dipende anche da quanto tempo richiede un processo in media.
- processor utilization ? percentuale di tempo in cui il processore viene usato, dev?essere il
più grande possibile. Utile per sistemi costosi e condivisi tra più utenti.
I criteri di sistema non prestazionali sono:
- fairness ed enforcing priorities ? tutti i processi devono essere trattati allo stesso modo
senza favoritismi. Se ci sono priorità, però, lo scheduler favorisce quelli a più alta priorità,
quindi bisogna creare una coda per ogni livello di priorità. Attenzione alla starvation: man
mano che l?età del processo aumenta\le volte in cui è andato in esecuzione sono poche la
priorità cresce.
- balancing resources ? le risorse del sistema devono essere usate il più possibile, quindi
processi che usano meno le risorse più usate sono favoriti.
- turn-around time ? tempo tra creazione di un processo e suo completamento, comprende
tempi di attesa: in un processo batch è minimo.
Funzione di
selezione Sceglie il processo da mandare in esecuzione, dipende da w (tempo trascorso in attesa), e (tempo trascorso in
esecuzione) e s (tempo totale richiesto, incluso e). All?inizio s va stimato, visto che e=0.
Abbiamo due modalità di decisione:
- non-preemptive ? se un processo è in esecuzione, attendo la fine o una richiesta bloccante;
- preemptive ? il sistema può interrompere un processo in esecuzione, che in tal caso diventa ready. Il
processo può essere bloccato per l?arrivo di nuovi processi o per un interrupt.
FCFS First Come First Served, tutti i processi sono messi nella coda ready e quando un processo è terminato si passa a quello
che ha aspettato più tempo. Si favoriscono i processi che usano più CPU ed è non-preemptive. Molto equo, ma un
processo corto deve attendere molto.
Round
Robin
Ad intervalli periodici si genera un?interruzione di clock: il processo in esecuzione allora va nella coda dei ready. Se poi
esso arriva ad un?istruzione di I\O prima dell?interruzione, viene spostato nella coda dei blocked e si seleziona il prossimo
processo ready (FIFO).
Quindi c?è preemption basata su un clock, e se il quanto di tempo è maggiore (ma non troppo, o si finisce in FCFS) del
tipico tempo di interazione allora è ottimale: il tipico tempo di interazione è solo stimato.
Vengono favoriti i processi CPU-bound poiché usano tutto il loro quanto di tempo; invece gli I\O bound usano solo una
porzione del loro quanto di tempo fino alla richiesta, il che non è equo ed è inefficiente
La soluzione dunque è round-robin virtuale: dopo un completamento di I\O il processo va in una nuova coda
che ha priorità sui ready, ma solo per la porzione del quanto che gli restava da completare. In questo modo
migliora la fairness.
SRT Shortest Remaining Time, è come SPN, ma preemptive: stima il tempo rimanente richiesto per l?esecuzione e prende
quello più breve, perciò un processo può essere interrotto solo quando ne arriva uno appena creato. Ci può essere
starvation.
HRRN Highest Response Ratio Next, massimizza il rapporto (w+s)/s, con w=tempo trascorso in attesa e s=tempo totale richiesto:
non guardo solo il tempo che ci mette, ma anche quello che ha passato in attesa. No starvation.
Scheduling
in UNIX Combina priorità e round robin: un processo resta in esecuzione al massimo un secondo, ci sono diverse code a seconda
della priorità, e all?interno di ciascuna coda si usa il round robin.
bg7
Architetture
multiprocessore
Sono caratterizzati da cluster (quindi ogni processore ha la sua RAM), processori specializzati (per esempio, ogni
dispositivo di I\O ha un suo processore) e multi-core che condividono la RAM (ovvero, un solo SO controlla tutto).
Lo scheduler su architetture multiprocessore può avere assegnamento statico o dinamico.
Nell?assegnamento statico, quando viene creato un processo, gli viene assegnato un processore, e per
tutta la sua durata resterà lì. Questo metodo è semplice da realizzare, c?è poco overhead, ma un
processore può rimanere idle.
Nell?assegnamento dinamico, un processo può essere eseguito su diversi processori. Stabiliamo per
semplificazione che solo i processi utente possono spostarsi, poiché il SO deve restare su un processore
fisso.
Scheduling in
Linux
Lo scheduling in Linux è preemptive a priorità dinamica, come quello di UNIX; la differenza sta nel consentire
maggiore velocità e servire in modo appropriato i processi real-time. Per fare ciò, l?hardware manda un timer
interrupt ogni 1 ms per consentire la revisione delle priorità.
Gestione della
memoria
Il SO si occupa anche di porre dei limiti di memoria a ciascun processo: se ogni processo gestisse la propria
memoria, ogni processo userebbe tutta la memoria a disposizione, e non ci sarebbe multiprogrammazione. Gestire
la memoria include lo swap di blocchi di dati dalla memoria secondaria: questa gestione è più lenta del processore,
perciò sta al SO pianificare lo swap in modo intelligente. Inoltre, bisogna far sì che ci siano sempre un numero
ragionevole di processi pronti all?esecuzione, in modo che il processore sia sempre al lavoro.
Abbiamo cinque requisiti fondamentali per la gestione della memoria: rilocazione, protezione, condivisione,
organizzazione logica e fisica.
Rilocazione Il programmatore non deve sapere dove il programma verrà caricato: i riferimenti alla memoria
devono essere tradotti nell?indirizzo fisico ?vero?, in preprocessing o run-time (in questo caso
occorre supporto hardware).
Quando viene generato codice eseguibile, il linker mette tutto insieme: il risultato è un load
module, perché può essere tradotto dal loader. Vengono escluse dal linker le librerie
dinamiche, mentre quelle statiche passano per esso, e il load module viene caricato dal loader
in memoria principale con le librerie dinamiche.
Entrambi i tipi di librerie sono collezioni di funzioni che implementano funzionalità utili a diversi
programmi: quelle statiche sono messe in una parte di codice di un programma, mentre nelle
dinamiche la stessa copia può essere utilizzata tra più programmi diversi, quindi il loader le
recupera e le carica in un?area di memoria, così ce n?è solo una copia caricata in RAM. Così si
risparmiano risorse e, se ci sono nuove versioni delle librerie, basta aggiornare quella presente
nel SO.
Protezione I processi non devono accedere a locazioni di memoria di un altro processo, a meno che non
siano autorizzati: a causa della rilocazione, si può fare solo a tempo di esecuzione, pertanto
serve l?aiuto dell?hardware.
Condivisione
Deve essere possibile permettere a più processi di accedere alla stessa zona di memoria
dai processi: per esempio quando più processi vengono creati eseguendo più volte lo stesso
codice sorgente, quindi è più utile che lo condividano. Ci sono anche casi in cui processi
diversi vengono esplicitamente programmati per accedere a sezioni di memoria comuni
usando chiamate di sistema (thread di uno stesso processo).
Organizzazione
Logica
A livello hardware, la memoria è organizzata in modo lineare; a livello software, i programmi
sono scritti in moduli, che possono essere compilati separatamente. A ciascun modulo
possono essere dati diversi permessi, e possono essere condivisi tra processi. Il SO deve
offrire tali caratteristiche, facendo da ponte tra la prima visuale e la seconda.
Organizzazione
fisica
Sarebbe la gestione del flusso tra RAM e memoria secondaria: non può essere lasciata al
programmatore, poiché la memoria potrebbe non essere sufficiente a contenere il
programma ed i suoi dati. La tecnica dell?overlaying permette a più moduli di essere
posizionati nella stessa zona di memoria in tempi diversi, ma è difficile da programmare,
poiché il programmatore non sa quanta memoria avrà a disposizione, quindi ci deve pensare
il SO.
Partizionamento
Uno dei primi metodi per la gestione della memoria prima che venisse introdotta quella virtuale: è utile per capire
quest?ultima. Abbiamo diverse tecniche di partizionamento: fisso, dinamico, paginazione semplice e con memoria
virtuale, segmentazione semplice e con memoria virtuale.
pf8
pf9
pfa
bg11
Gestione dell'I/O
Definiamo tre categorie di dispositivi:
- Leggibili dall?utente ? stampanti, mouse, usati per la comunicazione diretta con l?utente.
- Leggibili dalla macchina ? chiavi USB, usati per la comunicazione con materiale elettronico.
- Di comunicazione ? modem, Wi-Fi, per comunicare con dispositivi remoti.
Un dispositivo di input prevede di essere interrogato sul valore di una certa grandezza fisica al suo interno
(esempio: la tastiera identifica il codice Unicode).
Un processo che effettua una syscall read su un dispositivo del genere vuole conoscere questo
dato per poterlo elaborare (esempio: un editor di testo rappresenta il codice con il simbolo
corrispondente).
Un dispositivo di output prevede di poter cambiare il valore di una certa grandezza fisica al suo interno
(esempio: nel monitor il valore RGB dei pixel).
Un processo che effettua una syscall write su un dispositivo del genere vuole cambiare qualcosa:
spesso l?effetto è visibile dall?utente.
I dispositivi differiscono in:
- Data rate ? frequenza di accettazione\emissione di dati, velocità di trasferimento.
- Applicazione ? i dischi sono usati per memorizzare file, quindi hanno bisogno di un software per la loro
gestione. Un terminale usato da un amministratore ha priorità più alta.
- Complessità nel controllo ? molte cose vengono controllate da hardware dedicato. Mouse e tastiera sono
molto semplici, il più difficile è il disco. Molte cose sono controllate da hardware dedicato poiché col software
sarebbe molto difficile.
- Unità di trasferimento dati ? può essere blocco di byte di lunghezza fissa (chiavi USB, DVD etc), stream di
byte\caratteri, cose che non siano memoria secondaria (stampanti, terminali).
- Rappresentazione dei dati ? i dati possono essere rappresentati con codifiche diverse.
- Condizioni di errore ? la natura degli errori varia, ad esempio per come vengono notificati, gestiti, e per le
loro conseguenze.
Effettuare l'I/O
Ci sono 3 metodi:
- programmato
- guidato dagli interrupt
- accesso diretto in memoria
Il processore delega le operazioni di I\O al modulo DMA, direct memory access, che trasferisce i dati direttamente
da\verso la memoria principale; quando finisce il modulo genera un interrupt per il processore.
Il secondo obiettivo è la generalità: cerchiamo di gestire i dispositivi in modo uniforme, nascondendo i loro dettagli
nelle procedure di basso livello e progettare le funzioni in modo modulare e gerarchico. Ad esempio, le funzionalità
base sono read, write, unlock, etc.
Gerarchia di un
dispositivo di I/O
- Dispositivi locali ? in cima i processi utente, poi i logical I\O dove il sistema viene visto come una risorsa logica
(open, close, read). Poi device I\O, dove le richieste logiche diventano una sequenza di comandi di I\O. Poi
scheduling and control, che sceglie l?ordine di esecuzione dei processi. Infine l?hardware traduce le richieste in
input.
- Dispositivi di comunicazione ? simile, ma c?è l'architettura di comunicazione al posto del logical, poiché ci
possono essere diverse architetture di comunicazione.
- File system ? si accede a dischi meccanici. Usiamo moduli diversi perché i problemi sono legati alla gerarchia
del tipo di sistema: sotto la directory management, che racchiude le operazioni utente che hanno a che fare con i
file (crea, cancella, sposta), abbiamo il file system, che dà struttura logica alle operazioni. Sotto ancora,
l?organizzazione fisica, ossia da identificatori a indirizzi. Sotto ci sono device I\O, scheduling and control e
hardware.
Sistemi di
buffering
Senza buffer, il SO accede al dispositivo nel momento in cui ne ha necessità.
- Con un buffer singolo, esso viene creato in memoria principale per una data richiesta di I\O; lettura e scrittura
sono separate e sequenziali. I buffer singoli si dividono a loro volta in orientati ai blocchi e agli stream: in quello
orientato ai blocchi i trasferimenti sono fatti al buffer in system memory, quindi il blocco viene mandato nello
spazio utente quando necessario, ma il prossimo blocco viene comunque letto nel buffer. Vi è quindi un input o
lettura anticipata, e un output posticipato. I dati solitamente sono acceduti sequenzialmente, quindi c?è buona
possibilità che servirà e sarà già stato letto.
In quello orientato agli stream i terminali tipicamente hanno a che fare con linee, quindi si bufferizza una linea
intera di input\output, o un byte alla volta per i device in cui un singolo carattere premuto va gestito.
- Con un buffer doppio, un processo può trasferire dati da\a uno dei buffer, mentre il SO svuota\riempie l?altro.
Lettura e scrittura sono parallele.
- Nel buffer circolare, ho più di due buffer e ciascuno è un?unità. Viene usato quando l?operazione di I\O deve
tenere il passo del processo.
bg12
Pro e contro
dei buffer
Il buffering smussa i picchi di richieste di I\O, ma se la domanda è molta i buffer si riempiono e si perde il vantaggio.
Serve quando ci sono molti e vari dispositivi, perché migliora l?efficienza dei processi e del SO. Inoltre il buffer
introduce overhead a causa della copia intermedia in kernel memory
Come
funzionano i
dischi Gli HDD hanno all?interno una serie di dischi suddivisi in track e settori
Ciascuna track è divisa in settori, separati da un intersector gap
Sul disco i dati vengono scritti per settori, quindi l?unità di accesso atomico su un disco è il settore (di solito 512
byte). La lettura dei dischi è gestita dal controller dei dischi stessi, così da togliere lavoro al SO
I dati si trovano sulle tracce (corone concentriche) su un certo insieme di settori, quindi per leggere\scrivere occorre
sapere su quale traccia si trovano i dati, e in quale settore.
Per selezionare una traccia bisogna spostare una testina se mobili, o selezionare una testina, se fisse; per
selezionare un settore bisogna aspettare che il disco ruoti. Un?operazione su disco dipende da molti dettagli
Il tempo di accesso è la somma di tempo di posizionamento (seek) della testina sulla traccia e ritardo di rotazione
(rotational delay), ovvero il tempo che l?inizio del settore impiega a raggiungere la testina. Invece il tempo di
trasferimento è il tempo necessario a trasferire i dati che scorrono sotto la testina. A parte si considerano wait for
device (attesa che il dispositivo venga assegnato alla richiesta) e wait for channel (se più dischi condividono lo
stesso canale, attesa che il sottodispositivo sia assegnato alla richiesta).
Politiche di
scheduling
del disco
Consideriamo solo il seek time e il confronto viene fatto con la politica random (la peggiore).
- FIFO ? le richieste sono servite sequenzialmente. Se ci sono molti processi, come il random.
- Priorità ? l?obiettivo non è ottimizzare il disco. Non va bene per i DBMS, i processi batch corti hanno priorità
più alta, quelli più lunghi potrebbero dover aspettare troppo.
- LIFO ? si riferisce all?utente, non alla transazione. Ottimo per DBMS con transazioni, possibile la starvation
se lo stesso utente fa le richieste: probabilmente vuol dire che sta accedendo sequenzialmente a un file.
- Minimo tempo di servizio ? la richiesta che minimizza il movimento del braccio, quindi sceglie il tempo di
posizionamento minore relativo. Possibile la starvation.
- SCAN ? si scelgono le richieste in modo che il braccio si muova sempre in un verso e poi torni indietro. No
starvation ma poco fair.
- C-SCAN ? il braccio quando torna indietro non sceglie richieste, quindi i bordi non sono visitati due volte in
poco tempo.
- FSCAN ? quando SCAN inizia tutte le richieste sono nella coda F; le richieste che arrivano mentre SCAN
scorre vengono aggiunte a R. Quando SCAN finisce si scambiano le code.
- N-step-SCAN ? generalizza FSCAN a N>2 code: si accodano le richieste nella coda i-esima finché non si
riempie, poi si passa a quella (i+1) mod N. Se N è alto, le prestazioni sono quelle di SCAN con fairness; se
N=1 si usa il FIFO.
Cache del
disco Si tratta di un buffer in memoria principale, usato per i settori del disco
Contiene la copia di alcuni settori: quando si fa una richiesta di I\O per dati che si trovano su un certo settore, si
vede prima se tale settore è nella cache, se non c?è il settore letto viene anche copiato
Ci sono vari metodi per gestire la page cache, che è hardware e quindi trasparente per il SO.
- LRU ? prendo i settori nella cache da più tempo senza referenze. La cache viene puntata da uno stack di
puntatori: quello riferito più di frequente sta in cima allo stack, quindi ogni volta che viene referenziato torna
in cima allo stack.
- LFU ? prendo il settore con meno referenze: serve un contatore per ogni settore. Se un settore viene
acceduto più volte di fila e poi mai più, andrebbe rimosso, ma qui non succede.
- Frequenza ? stack di puntatori, quando un blocco viene referenziato si sposta in cima e viene spezzato in
due. Poi c'è anche qui un contatore, ma si incrementa solo se è nella parte vecchia, e si sceglie il blocco con
il contatore minimo che è nella parte vecchia; in caso di parità, quello più recente. Quando un blocco vecchio
viene riferito e diventa nuovo, spinge l?ultimo dei nuovi a diventare vecchio. Per evitare che un blocco sia
rimosso solo perché è appena arrivato nella parte vecchia, creiamo una sostituzione a tre segmenti: nuovo
(qui non si incrementano contatori né si rimpiazza), medio (contatori incrementati, ma non rimpiazzati) e
vecchio (contatori incrementati e rimpiazzati).
pfd
pfe
pff
bg16
Requisiti per la
mutua esclusione
- Solo un processo alla volta può essere nella sezione critica per una risorsa.
- Niente deadlock né starvation.
- Nessuna assunzione su scheduling dei processi, né sul loro numero.
- Un processo deve entrare subito nella sezione critica se nessun altro processo usa la risorsa.
- Un processo che si trova nella sezione non critica non può essere bloccato.
- Un processo che si trova nella sezione critica prima o poi deve uscire.
I sistemi con un solo processore permettono solo l?interleaving: un processo viene eseguito finché non
invoca il SO o viene interrotto.
Disabilitare le interruzioni garantisce la mutua esclusione: se il processo può decidere di non essere
interrotto, allora nessun altro lo può interrompere mentre si trova nella sezione critica
Istruzioni macchina
speciali
- Compare and swap: compara due parametri e, se sono uguali, assegna il nuovo valore alla variabile e ritorna il
vecchio valore, altrimenti ritorna il vecchio valore.
- Exchange: scambia due valori in maniera atomica
Semaforo
Valore intero usato dai processi per scambiarsi segnali, ha solo tre operazioni atomiche definite: initialize,
decrement (o semWait, può mettere il processo in blocked così non spreca CPU) e increment (o semSignal, mette
un processo blocked in ready). Sono eseguite in kernel mode e agiscono direttamente sui processi.
La struttura del semaforo contiene un contatore e una coda, usata per mettere i processi in blocked; la wait prende
in input un semaforo, che diminuisce il count e poi verifica se è minore di zero, e in caso positivo mette il processo
nella coda del semaforo e lo blocca; il signal prende in input un semaforo e incrementa il count, se è minore o
uguale a zero rimuove un processo P dalla coda e lo mette a ready.
Al posto del count possiamo mettere una enumerazione con valori 0 e 1: se il valore del semaforo è 1 continuiamo
a eseguire mettendolo a 0, altrimenti si mette un processo nella coda e si blocca. Nel signal invece si controlla la
coda: se è vuota il valore del semaforo va a 1, altrimenti si rimuove un processo dalla coda e si mette nella lista dei
processi ready.
Problema del
produttore /
consumatore
La premessa è che uno o più produttori creano dati e li mettono in un buffer: un consumatore prende dati dal buffer
uno alla volta, e al buffer può accedere un solo processo alla volta. Il problema è assicurare che i produttori non
inseriscano i dati quando il buffer è pieno e assicurare che il consumatore non prenda dati quando il buffer è vuoto,
oltre alla mutua esclusione sull?intero buffer.
Problema di
trastevere
I blocchetti sono macchine, il tubo è una strada che si restringe a senso unico alternato: possono passare massimo
4 auto alla volta. Vince chi arriva prima: assumendo semafori strong, le macchine dovrebbero impegnare la strettoia
nell?ordine con cui vi arrivano (o si accodano dalla propria parte).
Problema del
negozio del
barbiere
La dimensione massima del negozio è max cust; prima ci si siede sul divano, poi da lì si accede a una delle sedie,
si compete per entrambe le risorse. Un certo numero va sul divano, le sedie effettive poi sono di meno; tra tutti
quelli nel negozio si compete per il divano, mentre tra tutti quelli sul divano si compete per le sedie. Il barbiere
dorme o taglia capelli o prende soldi dopo il taglio.
Algoritmo
di Dekker
Riferito alla soluzione del negozio del barbiere: vogliamo implementare il quarto modo
senza livelock: verifichiamo con turn che sia il mio turno, e finché non lo è metto il flag
a false e c?è un busy waiting. Quando sarà il mio turno, rimetto il flag a true, vado in
sezione critica e faccio il flip del turno.
Algoritmo
di Peterson
Simile a Dekker ma dalla struttura diversa: ciclo while infinito e quando entriamo il flag
dice se vogliamo accedere. Poi cambio turno e verifico che, se l?altro processo vuole
entrare ed è il suo turno, non faccio niente; altrimenti vado avanti e alla fine metto il
flag a 0. Così non ho momenti in cui due processi vogliono accedere alla sezione
critica
bg17
Interazione dei
processi Dobbiamo soddisfare due requisiti: sincronizzazione e comunicazione.
Lo scambio di messaggi è una soluzione al secondo requisito, poiché funziona sia con memoria condivisa
che distribuita e può essere usata anche per la sincronizzazione. La funzionalità è fornita tramite
send(destination, message) e receive(source, message): message è un input per la send ed un output per
la receive.
La comunicazione però richiede anche la sincronizzazione: il mittente deve inviare prima che il
ricevente riceva.
Nelle operazioni bloccanti sia mittente che destinatario sono bloccati finché il messaggio non è
completamente ricevuto: si chiama rendezvous, e la sincronizzazione è molto stretta.
Il send non bloccante (nbsend) è il più naturale; nella ricezione bloccante il mittente non viene
messo in blocked e il destinatario rimane bloccato finché non riceve il messaggio. Nella ricezione
non bloccante (nbreceive), se c?è il messaggio, esso viene ricevuto, altrimenti va avanti; può
settare un bit nel messaggio per dire se la ricezione è avvenuta. In questo caso, tipicamente non
è bloccante nemmeno l?invio. Queste operazioni sono sempre atomiche.
Il mittente deve poter dire a quale processo vuole mandare il messaggio, idem per il destinatario:
si possono usare indirizzamento diretto o indiretto. Nel diretto la primitiva di send include uno
specifico ID per il destinatario o per un gruppo di destinatari; per la receive, non è detto che ci
sia: con receive(sender, msg) ricevi solo se il mittente coincide con sender; altrimenti
receive(null, msg) ricevi da chiunque. Dentro msg c?è anche il mittente, e ogni processo ha una
coda: una volta piena il messaggio si perde oppure viene ritrasmesso.
Nell?indiretto, i messaggi sono inviati ad una casella di posta condivisa: il mittente manda
messaggi alla mailbox, da dove li prende il destinatario. Se la ricezione è bloccante e ci sono più
processi in attesa su una ricezione, un solo processo viene svegliato. Ci sono similitudini con
produttore\consumatore (se la mailbox è piena? ).
Struttura dei
messaggi
Deadlock Il blocco permanente di un insieme di processi, che competono per delle risorse di sistema o comunicano tra loro:
vengono richieste in contemporanea le stesse risorse da parte di due o più processi.
Con il Joint Progress Diagram analizziamo i diversi percorsi che possono fare i processi in base alle risorse che
richiedono: qui abbiamo un processo su x e uno su y, e vediamo le diverse situazioni in cui i processi si trovano in
caso decidano di accedere in diversi momenti temporali alle risorse. In questo esempio il deadlock è inevitabile
Reusable vs
Consumable
resource
Le reusable sono usabili da un solo processo alla volta, ma non si consumano, ad esempio i file
Le consumable vengono prodotrte e consumate, ad esempio le interruzioni
pf12
pf13
pf14

Anteprima parziale del testo

Scarica Sintesi Sistemi Operativi modulo 1, prof. De Gaspari e più Schemi e mappe concettuali in PDF di Sistemi Operativi solo su Docsity!

Componenti di un sistema operativo

Processori: cervello del computer RAM: volatile Moduli I/O: comprende i dischi, i dispositivi di comunicazione e altri dispositivi come mouse e tastiera Bus di sistema: mezzo per permettere la comunicazione tra le parti interne del computer

Registri I registri sono delle sequenze di celle di memoria, in cui si possono leggere e scrivere dati, in particolare risultati di operazioni. Sono molto più veloci rispetto alla memoria primaria.

  • Registri visibili all'utente: usati da chi programma in assembler o da compilatori di linguaggi non interpretati
  • Registri interni: gestiti dal firmware e permettono la comunicazione tra memoria e I/O
  • Registri di controllo e di stato: usati dal processore per controllare l'uso dello stesso

Esecuzione di un'istruzione Ci sono due fasi: fetch della memoria principale ed esecuzione delle istruzioni.

Il PC mantiene l'indirizzo della prossima istruzione mentre l'IR contiene l'istruzione prelevata.

Esistono vari tipi di istruzioni, ma in generale fuzionano tutte allo stesso modo

Interruzioni Le interruzioni bloccano l'esecuzione sequenziale del programma in modo inaspettato e attivano l'esecuzione del software di sistema, che p parte del sistema operativo

Possono avvenire per varie ragioni e si dividono in due classi principali:

  • Interruzioni sincrone, ossia causarte da errori interni al programma, che vengono sollevate immediatamente:
    • I faults sono errori corregibili
    • Gli aborts non sono corregibili
    • Nelle traps/syscall si riparte dall'istruzione successiva
  • Interruzioni asincrone, generalmente causate da un evento esterno. In generale, dopo l'handler si riparte dall'istruzione successiva, a meno che la computazione non sia stata abortita. Si dividono anche queste in sottocategorie: - Le interruzioni da I/O, generate dal controllore di un dispositivo input/output - Le interruzioni da fallimento hardware, causate da un'improvvisa mancanza di potenza - Le interruzioni da timer, generate da un timer all'interno del processore e servono al sistema operativo per eseguire alcune operazioni a intervalli regolari

Interrupt handler Funzione in cui sistema operativo e hardware collaborano per salvare le informazioni importanti, come registrodi stato e program counter

La chiamata di funzione non è scritta dal programmatore ma avviene in moido inaspettato, per questo bisogna salvare queste informazioni

Le interruzioni possono essere sequenziali o annidate, inoltre gli interrupt possono essere disabilitati, quindi le interruzioni si generano e non sono gestite

Metodi per la gestione I/O I/O programmato: tutto è gestito dalla CPU

I/O da interruzione: la CPU manda l'istruzione e si mette a fare altro. Quando l'I/O è pronto a scambiare dati, il processore viene interrotto

Nei computer moderni si accede direttamente alla memoria: quando l'operazione è completata, la CPU non si deve più preoccupare di trasferire i dati perche i dispositivi I/O vanno in memoria

Multiprogrammazione: Capacità di un processore di eseguire più programmi contemporaneamente

Gerarchia della memoria: La memoria è composta da vari livelli: man mano che si scende, diminuiscono velocità di accesso, costo al bit e frequenza di accesso, emntre aumenta la capacità

Cache

La cache contiene copie di porzioni di memoria principale e viene gestita dall'hardware. Il processore controlla se il dato è

nella cache: se non c?è, il corrispondente blocco di memoria viene caricato. Tutto questo viene fatto perché probabilmente questo dato potrà servire ancora nell?immediato futuro, e così è meno costoso accedervi

Naturalmente, più grande è il blocco di memoria maggiori sono gli accessi riusciti, ma è bene non aumentare troppo le dimensioni, poiché a quel punto verranno rimossi più dati.

  • Funzione di mappatura? determina la locazione della cache dove va messo il blocco.
  • Algoritmo di rimpiazzamento? sceglie quale blocco va rimpiazzato. Ne esistono di diversi, per esempio LRU rimuove il blocco più vecchio.
  • Politica di scrittura? determina quando si scrive in memoria. Ad esempio ogni volta che un blocco viene modificato (write through) o quando il blocco è rimpiazzato (write back). In teoria cerchiamo di minimizzare la scrittura, quindi è probabile trovare la memoria in uno stato obsoleto.

Sistema Operativo Il sistema operativo offre una serie di servizi come esecuzioni di programmi, accesso ai dispositivi I\O, accesso al sistema operativo stesso, sviluppo di programmi, rilevamento di e reazione ad errori, accounting

Il sistema operativo è un programma che controlla l?esecuzione di programmi applicativi: è un?interfaccia tra le applicazioni e l?hardware, e i suoi obiettivi sono convenienza, efficienza e capacità di evolvere.

Inoltre, il sistema operativo gestisce le risorse (come un software, ma con privilegi più alti) e concede il controllo del processore ad altri programmi.

Il kernel è la parte del sistema operativo che si trova sempre in memoria principale: contiene le funzioni più usate.

Elementi del sistema operativo

Viene visto come una serie di livelli: ogni livello si occupa di un sottoinsieme delle funzioni del sistema e si basa sul livello immediatamente inferiore. In questo modo i problemi vengono decomposti in problemi più semplici.

- Livello 1? circuiti elettrici, qui si resettano registri e si leggono locazioni di memoria. - Livello 2? insieme di istruzioni macchina, per esempio add, subtract? - Livello 3? concetto di subroutine (procedura) con operazioni di chiamata e ritorno. - Livello 4? interruzioni. - Livello 5? processo come programma in esecuzione, sospensione e ripresa. - Livello 6? dispositivi di memorizzazione secondaria, trasferimento di blocchi dati. - Livello 7? spazio logico degli indirizzi, organizza spazio degli indirizzi virtuali in blocchi. - Livello 8? comunicazioni tra processi. - Livello 9? salvataggio di file con nome. - Livello 10? accesso a dispositivi esterni con interfacce standard. - Livello 11? associazione tra identificatori interni ed esterni. - Livello 12? supporto di alto livello, - Livello 13? interfaccia utente.

Architettura UNIX

Il kernel Linux è un mix tra kernel monolitico (kernel tutto in memoria da boot a spegnimento) e microkernel (minima parte del kernel in memoria, il resto caricato all?occorrenza): il monolitico infatti è più veloce, ma occupa più spazio, nonostante ciò è usato dalla maggior parte dei sistemi operativi moderni. Linux possiede i moduli, ovvero alcune parti possono essere tolte o aggiunte a richiesta dall?immagine in memoria del kernel.

Processi Il sistema operativo si occupa principalmente di gestire i processi, ovvero le computazioni che si vuole eseguire

Il sistema operativo permette l?esecuzione alternata di processi multipli, assegna le risorse ai processi e le protegge da altri processi; inoltre permette ai processi di scambiarsi informazioni e la sincronizzazione. Un processo è un?istanza di un programma in esecuzione su un computer, che viene assegnata a un processore per la sua esecuzione È composto da una serie di istruzioni, da uno stato e da un insieme di risorse, ovvero da codice (istruzioni da eseguire), insieme di dati e attributi che ne descrivono lo stato

Attributi dei processi Le informazioni in ciascun blocco di controllo possono essere raggruppate in tre categorie: identificazione, stato e controllo

Ad ogni processo è assegnato un PID (process identifier) univoco, lo stato del processore, ossia il contenuto dei registri in un certo momento, e il PSW, che contiene le informazioni di stato

PCB

Contiene informazioni di cui il SO ha bisogno per controllare e coordinare i vari processi attivi.

  • PID del processo padre e dell?utente proprietario;
  • stato del processore;
  • informazioni per il controllo del processo;
  • supporto per strutture dati (per esempio puntatori ad altri processi);
  • comunicazioni tra processi;
  • permessi speciali;
  • gestione della memoria;
  • uso delle risorse (file aperti e uso di risorse finora).

Modalità di esecuzione dei processori

La maggior parte dei processori supporta almeno due modalità di esecuzione: modo sistema o kernel e modo utente

La modalità kernel può accedere a ogni parte di memoria, si occupa della gestione dei processi tramite PCB, di scheduling e dispatching, di process switching, di sincronizzazione, di gestione di memoria principale e dell?I\O

Creazione di un processo

Per creare un processo, il sistema operativo deve assegnargli un PID, allocargli spazio in memoria principale, inizializzare il PCB (puntatori che indicano dove inizia lo spazio+PID), inserirlo nella giusta coda e creare\espandere altre strutture dati (per esempio quando devo definire chi ha creato il processo)

Lo switching fra processi può causare diversi problemi

Per far avvenire il process switch abbiamo sette passaggi eseguiti in kernel mode:

  1. Salvare registri e PC, ovvero scritti sul PCB, cosa che avviene solo a switch ( processo A) ;
  2. Aggiornare il PCB, attualmente in running ( processo A) ;
  3. Spostare il PBC nella coda appropriata ( processo A) ;
  4. Scegliere un altro processo da eseguire ( processo B) ;
  5. Aggiornare il PCB del processo scelto ( processo B) ;
  6. Aggiornare le strutture dati per la gestione della memoria ( processo B) ;
  7. Ripristinare il contesto del processo scelto.

Esecuzione del sistema operativo

Nei sistemi più moderni, il SO viene eseguito all?interno dei processi utente: cambia la modalità di esecuzione, il SO viene eseguito come se fosse parte del processo utente, ma per eseguire le sue funzioni non c?è bisogno di process switch. Lo stack delle chiamate è separato, e il SO è implementato come insieme di processi di sistema che partecipano alla competizione per il processore.

Funzionamento processi in UNIX

  • User running? in esecuzione in modalità utente;
  • Kernel running? in esecuzione in modalità kernel;
  • Ready to run in memory? in memoria, può andare in esecuzione appena selezionato;
  • Asleep in memory? in memoria, non può essere eseguito finché non accade un evento;
  • Ready to run swapped? non in memoria, può andare in esecuzione;
  • Sleeping swapped? non in memoria, non può essere eseguito finché non accade un evento;
  • Preempted? il kernel gli ha appena tolto l?uso del processore anche se non ha finito;
  • Created? appena creato, non pronto all?esecuzione;
  • Zombie? terminato, ma serve il suo valore di ritorno quindi resta solo il PCB.

Per creare un processo in UNIX, prima viene effettuata la chiamata di sistema fork(), poi il SO in kernel mode:

  1. alloca una entry nella tabella dei processi;
  2. assegna un PID unico al processo figlio;
  3. copia l?immagine del padre, escludendo la memoria condivisa;
  4. incrementa i contatori di ogni file aperto dal padre, visto che sono anche del figlio;
  5. assegna al processo figlio lo stato ready to run;
  6. fa ritornare alla fork il PID del figlio al padre e 0 al figlio.
    • Poi il kernel sceglie se continuare con il padre, switchare al figlio o a un altro processo.

Thread I thread sono unità di esecuzione che fanno parte di un processo: un processo può avere quindi multipli flussi di esecuzione I thread di uno stesso processo condividono tutte le risorse tranne lo stack delle chiamate e il processore: se un thread acquisisce un dispositivo di I\O, è come se l?avesse acquisito l?intero processo.

I sistemi possono essere mono thread, con processi multipli, singoli in parallelo o multipli in parallelo.

Alla definizione di un processo servono anche i TCB (thread control block)

È molto più semplice lavorare con i thread che con i processi: è più efficiente crearli, terminarli, switchare e farli comunicare. Inoltre, dato che ogni processo ha almeno un thread, possiamo svolgere queste operazioni: spawn, block, unblock e finish.

Implementare i thread Abbiamo diversi design architetturali: il primo è lo User Level Thread, in cui il sistema operativo conosce il processo e non i thread, per lui è come se i thread non esistessero Con questa architettura è più facile ed efficiente lo switch, si può avere una politica di scheduling diversa per ogni applicazione e possiamo usare i thread anche su SO che non li offrono nativamente Tuttavia i thread sono implementati da librerie molto pesanti, quando un thread fa richiesta di I\O si blocca tutto perché il SO lo vede come una richiesta dell?intero processo Poi abbiamo il Kernel Level Thread (più usato oggi), ovvero il sistema operativo implementa i thread e i programmi utente chiamano funzioni del SO per usarli Stavolta i thread vanno in competizione tra loro e sono gestiti dallo scheduling, e se uno di essi fa un?operazione bloccante gli altri possono continuare.

Infine abbiamo un mix: il SO prevede un certo numero di thread e c?è una mappatura molti a uno tra ULT e KLT

Thread in Linux

In Linux i thread sono l?unità di base (è come se la fork creasse un thread), e sono chiamati LWP (lightweight process), e sono possibili sia KLT (usati dal sistema operativo) che ULT (scrivibili da qualunque utente e poi mappati in KLT).

L?unico problema è l?identificazione, infatti utente e sistema usano termini diversi: ad esempio il PID è unico per tutti i thread di un processo, mentre il TID (task identifier) identifica i singoli thread

Scheduling

Lo scheduling deve assegnare ad ogni processore i processi da eseguire man mano che i processi stessi vengono creati e distrutti: ciò si ottiene ottimizzando vari aspetti come tempo di risposta, throughput (posso eseguire più processi nella stessa unità di tempo) ed efficienza del processore. Un altro obiettivo è distribuire il tempo di esecuzione equamente tra i processi e gestire le priorità dei processi quando necessario Long Term Scheduling

Spesso è un FIFO che tiene conto di alcuni criteri come priorità e tempo di esecuzione atteso, decide quali programmi possono entrare nel sistema per essere eseguiti Inoltre, se ci sono tanti processi, diminuisce la percentuale di tempo per cui ogni processo viene eseguito Abbiamo tre tipiche strategie: i lavori batch vengono presi dal LTS quando questi lo ritiene giusto, i lavori interattivi vengono ammessi fino a saturazione del sistema e mantiene un buon mix tra i processi I\O bound e CPU-bound (quando si sa), oltre a bilanciare le richieste di I\O. Medium Term Scheduling

È parte della funzione di swapping tra processi: il passaggio da memoria secondaria a principale è basato sulla necessità di gestire il grado di multiprogrammazione.

Continua alla prossima

pagina

Architetture multiprocessore

Sono caratterizzati da cluster (quindi ogni processore ha la sua RAM), processori specializzati (per esempio, ogni dispositivo di I\O ha un suo processore) e multi-core che condividono la RAM (ovvero, un solo SO controlla tutto). Lo scheduler su architetture multiprocessore può avere assegnamento statico o dinamico. Nell?assegnamento statico, quando viene creato un processo, gli viene assegnato un processore, e per tutta la sua durata resterà lì. Questo metodo è semplice da realizzare, c?è poco overhead, ma un processore può rimanere idle. Nell?assegnamento dinamico, un processo può essere eseguito su diversi processori. Stabiliamo per semplificazione che solo i processi utente possono spostarsi, poiché il SO deve restare su un processore fisso.

Scheduling in Linux

Lo scheduling in Linux è preemptive a priorità dinamica, come quello di UNIX; la differenza sta nel consentire maggiore velocità e servire in modo appropriato i processi real-time. Per fare ciò, l?hardware manda un timer interrupt ogni 1 ms per consentire la revisione delle priorità.

Gestione della memoria

Il SO si occupa anche di porre dei limiti di memoria a ciascun processo: se ogni processo gestisse la propria memoria, ogni processo userebbe tutta la memoria a disposizione, e non ci sarebbe multiprogrammazione. Gestire la memoria include lo swap di blocchi di dati dalla memoria secondaria: questa gestione è più lenta del processore, perciò sta al SO pianificare lo swap in modo intelligente. Inoltre, bisogna far sì che ci siano sempre un numero ragionevole di processi pronti all?esecuzione, in modo che il processore sia sempre al lavoro. Abbiamo cinque requisiti fondamentali per la gestione della memoria: rilocazione, protezione, condivisione, organizzazione logica e fisica.

Rilocazione

Il programmatore non deve sapere dove il programma verrà caricato: i riferimenti alla memoria devono essere tradotti nell?indirizzo fisico ?vero?, in preprocessing o run-time (in questo caso occorre supporto hardware). Quando viene generato codice eseguibile, il linker mette tutto insieme: il risultato è un load module, perché può essere tradotto dal loader. Vengono escluse dal linker le librerie dinamiche, mentre quelle statiche passano per esso, e il load module viene caricato dal loader in memoria principale con le librerie dinamiche. Entrambi i tipi di librerie sono collezioni di funzioni che implementano funzionalità utili a diversi programmi: quelle statiche sono messe in una parte di codice di un programma, mentre nelle dinamiche la stessa copia può essere utilizzata tra più programmi diversi, quindi il loader le recupera e le carica in un?area di memoria, così ce n?è solo una copia caricata in RAM. Così si risparmiano risorse e, se ci sono nuove versioni delle librerie, basta aggiornare quella presente nel SO.

Protezione

I processi non devono accedere a locazioni di memoria di un altro processo, a meno che non siano autorizzati: a causa della rilocazione, si può fare solo a tempo di esecuzione, pertanto serve l?aiuto dell?hardware.

Condivisione

Deve essere possibile permettere a più processi di accedere alla stessa zona di memoria dai processi: per esempio quando più processi vengono creati eseguendo più volte lo stesso codice sorgente, quindi è più utile che lo condividano. Ci sono anche casi in cui processi diversi vengono esplicitamente programmati per accedere a sezioni di memoria comuni usando chiamate di sistema (thread di uno stesso processo).

Organizzazione Logica

A livello hardware, la memoria è organizzata in modo lineare; a livello software, i programmi sono scritti in moduli, che possono essere compilati separatamente. A ciascun modulo possono essere dati diversi permessi, e possono essere condivisi tra processi. Il SO deve offrire tali caratteristiche, facendo da ponte tra la prima visuale e la seconda.

Organizzazione fisica

Sarebbe la gestione del flusso tra RAM e memoria secondaria: non può essere lasciata al programmatore, poiché la memoria potrebbe non essere sufficiente a contenere il programma ed i suoi dati. La tecnica dell?overlaying permette a più moduli di essere posizionati nella stessa zona di memoria in tempi diversi, ma è difficile da programmare, poiché il programmatore non sa quanta memoria avrà a disposizione, quindi ci deve pensare il SO.

Partizionamento

Uno dei primi metodi per la gestione della memoria prima che venisse introdotta quella virtuale: è utile per capire quest?ultima. Abbiamo diverse tecniche di partizionamento: fisso, dinamico, paginazione semplice e con memoria virtuale, segmentazione semplice e con memoria virtuale.

Partizionamento fisso

Un partizionamento uniforme con le partizioni di uguale lunghezza. Il sistema operativo può rimuovere un processo da una partizione se nessuno dei processi in memoria è ready.

Qui c'è il pericolo che un programma potrebbe non entrare in una partizione perché troppo grande, quindi sta al programmatore dividerlo in overlays Un altro problema è quello dell'uso inefficiente della memoria L'algoritmo di posizionamento è banale

Partizionamento dinamico

Le partizioni variano sia in misura che in quantità: ad ogni processo viene allocata esattamente la quantità di memoria che serve. Rischiamo di lasciare dei buchi quando i processi escono visto che la memoria è una partizione unica (frammentazione esterna), ma questo si risolve con la compattazione: il SO sposta i processi in modo che siano contigui, con però un elevato overhead Abbiamo 3 algoritmi di posizionamento:

  • Best fit? sceglie il blocco la cui misura è la più vicina a quella del processo. Lascia frammenti molto piccoli, quindi spesso si deve compattare.
  • First-fit? il primo blocco con abbastanza memoria viene scelto. Molto veloce, riempie solo la prima parte della memoria, ma è il migliore.
  • Next-fit? come il first, ma parte dall?ultima posizione assegnata a un processo. Più spesso si usa il blocco alla fine della memoria, che è il più grande e viene spezzettato in blocchi piccoli.

Buddy System Compromesso tra partizionamento fisso e dinamico, ancora usato nei SO moderni

Sia 2U la dimensione dello user space ed s la dimensione di un processo da mettere in RAM; si dimezza lo spazio fino a trovare un X tale che 2X?1 < s? 2X , con L? X? U. Una delle porzioni è usata per il processo, con L che dà un lower bound. Quando il processo finisce, se la porzione ?compagna?è rimasta libera, posso unirle di nuovo insieme per formare la partizione iniziale.

Paginazione

In quella semplice, la memoria e i processi vengono partizionati in pezzi di grandezza uguale e piccola. I pezzi di processi sono chiamati pagine, quelli di memoria sono chiamati frame Ogni pagina deve essere collocata in un frame, che può essere uno qualsiasi, basta che abbia le stesse dimensioni. Il SO mantiene una tabella delle pagine per ogni numero di pagina, e lo spiazzamento è relativo all'inizio della pagina, non dell'intero processo Solitamente la pagina ha una dimensione di 2N bytes : in generale possiamo dire che, modulando un numero per 2 N, ci basta prendere il numero in binario e considerare solo gli N bit meno significativi. Questo vale anche quando devo calcolare l?offset degli indirizzi di paginazione. Si realizza la rilocazione aggiornando lo schema con il solo base register. Quando c'è process switch, la tabella delle pagine del nuovo processo deve essere ricaricata

Segmentazione (^) Un programma può essere diviso in segmenti che hanno lunghezza variabile e un limite massimo alla dimensione.

Un indirizzo di memoria è un numero di segmento e uno spiazzamento al suoo interno, quindi è simile al partizionamento dinamico, solo che stavolta il programmatore deve gestire esplicitamente la segmentazione dicendo quanti segmenti ci sono e qual è la loro dimensione, mentre a metterli in RAM ci pensa il SO.

Memoria virtuale

Schema di allocazione di memoria in cui la memoria secondaria può essere usata come principale. Gli indirizzi usati sono diversi, quindi c?è una fase di traduzione automatica; la dimensione è limitata dallo schema di indirizzamento e dalla dimensione della memoria secondaria.

  • Indirizzo virtuale? indirizzo associato ad una locazione della memoria virtuale, permette di accedervi come se fosse parte della memoria principale.
  • Spazio degli indirizzi virtuali? quantità di memoria virtuale assegnata a un processo.
  • Spazio degli indirizzi? quantità di memoria assegnata a un processo.
  • Indirizzo reale? indirizzo di una locazione di memoria principale.
  • Memoria reale? la RAM, memoria principale.
  • Memoria virtuale? su disco, memoria secondaria. Permette multiprogrammazione.

Ricapitolazione terminologia

Come progettiamo la memoria?

Gli elementi centrali per progettare il SO sono la politica di prelievo (fetch), quella di posizionamento (placement), quella di sostituzione (replacement) e altre piccole cose. Il tutto va stabilito minimizzando i page fault.

Fetch Policy

Decide quando una pagina data debba essere portata in memoria principale. Le due politiche principali sono:

  • Demand paging: una pagina viene portata in memoria quando un processo la richiede.
  • Prepaging: porta in memoria più pagine di quelle richieste, vicine ad essa.

Placement Policy

Decide dove mettere una pagina in memoria principale quando c?è almeno un frame libero; se non ce ne sono, usiamo la replacement. Visto che l?hardware traduce gli indirizzi, possiamo mettere la pagina ovunque: di solito va nel primo frame libero (con indirizzo più basso).

Replacement Policy

Decide quale frame sostituire, minimizzando la probabilità che la pagina appena rimossa venga richiesta immediatamente, usando il principio di località. Se un frame è bloccato, non si può sostituire: di solito si bloccano i frame del sistema operativo, e quelli di altri processi se si usa la politica locale per il rimpiazzamento.

Gestione dei resident set

Ci chiediamo, per ogni processo in esecuzione, quanti frame vanno allocati (RSM) e, quando si rimpiazza un frame, se possiamo scegliere un frame qualsiasi o se scegliamo solo quelli del processo corrente (replacement scope).

Per il RSM, se abbiamo allocazione fissa, il numero di frame è deciso a tempo di creazione di un processo; se abbiamo allocazione dinamica, il numero di frame varia durante la vita del processo.

Per il replacement scope, abbiamo una politica locale o globale. In tutto abbiamo tre possibili strategie, poiché con l?allocazione fissa non possiamo adottare la politica globale, poiché si potrebbe ampliare il numero di frames per processo

Politica di pulitura Quando modifichiamo un frame, quando riportiamo la modifica sulla pagina? Di solito, intrecciandosi con il page buffering, raccogliamo un po?di richieste di frame da modificare e le eseguiamo.

Controllo del carico Il controllo del carico decide il grado di multiprogrammazione del sistema, ovvero quanti processi vogliamo caricare in RAM: l?uso del processore diminuisce se c?è troppa multiprogrammazione.

Il medium-term scheduler ha la capacità di sospendere processi (il suo resident set è 0), in modo da diminuire la multiprogrammazione; se vogliamo invece aumentarla, svegliamo dalla sospensione, senza arrivare però al thrashing. Scegliamo quale processo sospendere in base a quale ha priorità minore, quale ha causato l?ultimo page fault, quale è stato attivato per ultimo, quale ha il working set più piccolo (pochi frame allocati), quale ha immagine più grande (alto numero di pagine su disco), quale ha il più alto tempo rimanente di esecuzione.

Algoritmi di sostituzione

  • Ottimale? si rimuove la pagina che verrà richiesta più in là nel futuro (non implementabile).
  • LRU? si rimuove la pagina cui non sia stato fatto riferimento per il tempo più lungo, ovvero quella che ha meno probabilità di essere usata nel prossimo futuro. Però va etichettato ogni frame col tempo dell?ultimo accesso, quindi è troppo costoso (nella cache lo fa l?hardware).
  • FIFO? i frame sono trattati come una coda circolare, e le pagine sono rimosse a turno in base a quanto tempo sono state in memoria. Non tiene conto del numero di accessi.
  • Orologio? ogni frame ha uno ?use bit?che indica se la pagina è stata riferita: diventa 1 quando la pagina viene caricata e ogni volta che si fa un accesso. Poi si procede come nel FIFO, ma si sceglie la prima pagina che ha 0 e cambia gli 1 con 0.

Buffering delle pagine

Un?altra cache, stavolta per le pagine: se occorre rimpiazzare una pagina, essa viene messa in questa cache, così se viene nuovamente referenziata si può portare subito in memoria. Di solito si divide tra pagine lette e scritte, e si cerca di scrivere le pagine modificate tutte insieme quando la lista diventa quasi piena.

Gestione della memoria in LINUX

  • Fetch policy? paging on demand.
  • Placement policy? il primo frame libero.
  • Gestione del resident set? dinamica, replacement scope globale (compresa cache del disco).
  • Politica di pulitura? scrittura quando la page cache è troppo piena con molte richieste pending, pagine sporche o pagina sporca da troppo tempo.
  • Controllo del carico? no.
  • Replacement policy? algoritmo dell?orologio corretto.

Gestione dell'I/O

Definiamo tre categorie di dispositivi:

  • Leggibili dall?utente? stampanti, mouse, usati per la comunicazione diretta con l?utente.
  • Leggibili dalla macchina? chiavi USB, usati per la comunicazione con materiale elettronico.
  • Di comunicazione? modem, Wi-Fi, per comunicare con dispositivi remoti. Un dispositivo di input prevede di essere interrogato sul valore di una certa grandezza fisica al suo interno (esempio: la tastiera identifica il codice Unicode). Un processo che effettua una syscall read su un dispositivo del genere vuole conoscere questo dato per poterlo elaborare (esempio: un editor di testo rappresenta il codice con il simbolo corrispondente). Un dispositivo di output prevede di poter cambiare il valore di una certa grandezza fisica al suo interno (esempio: nel monitor il valore RGB dei pixel). Un processo che effettua una syscall write su un dispositivo del genere vuole cambiare qualcosa: spesso l?effetto è visibile dall?utente.

I dispositivi differiscono in:

  • Data rate? frequenza di accettazione\emissione di dati, velocità di trasferimento.
  • Applicazione? i dischi sono usati per memorizzare file, quindi hanno bisogno di un software per la loro gestione. Un terminale usato da un amministratore ha priorità più alta.
  • Complessità nel controllo? molte cose vengono controllate da hardware dedicato. Mouse e tastiera sono molto semplici, il più difficile è il disco. Molte cose sono controllate da hardware dedicato poiché col software sarebbe molto difficile.
  • Unità di trasferimento dati? può essere blocco di byte di lunghezza fissa (chiavi USB, DVD etc), stream di byte\caratteri, cose che non siano memoria secondaria (stampanti, terminali).
  • Rappresentazione dei dati? i dati possono essere rappresentati con codifiche diverse.
  • Condizioni di errore? la natura degli errori varia, ad esempio per come vengono notificati, gestiti, e per le loro conseguenze.

Effettuare l'I/O

Ci sono 3 metodi:

  • programmato
  • guidato dagli interrupt
  • accesso diretto in memoria Il processore delega le operazioni di I\O al modulo DMA, direct memory access, che trasferisce i dati direttamente da\verso la memoria principale; quando finisce il modulo genera un interrupt per il processore. Il secondo obiettivo è la generalità: cerchiamo di gestire i dispositivi in modo uniforme, nascondendo i loro dettagli nelle procedure di basso livello e progettare le funzioni in modo modulare e gerarchico. Ad esempio, le funzionalità base sono read, write, unlock, etc.

Gerarchia di un dispositivo di I/O

  • Dispositivi locali? in cima i processi utente, poi i logical I\O dove il sistema viene visto come una risorsa logica (open, close, read). Poi device I\O, dove le richieste logiche diventano una sequenza di comandi di I\O. Poi scheduling and control, che sceglie l?ordine di esecuzione dei processi. Infine l?hardware traduce le richieste in input.
  • Dispositivi di comunicazione? simile, ma c?è l'architettura di comunicazione al posto del logical, poiché ci possono essere diverse architetture di comunicazione.
  • File system? si accede a dischi meccanici. Usiamo moduli diversi perché i problemi sono legati alla gerarchia del tipo di sistema: sotto la directory management, che racchiude le operazioni utente che hanno a che fare con i file (crea, cancella, sposta), abbiamo il file system, che dà struttura logica alle operazioni. Sotto ancora, l?organizzazione fisica, ossia da identificatori a indirizzi. Sotto ci sono device I\O, scheduling and control e hardware.

Sistemi di buffering

Senza buffer, il SO accede al dispositivo nel momento in cui ne ha necessità.

  • Con un buffer singolo, esso viene creato in memoria principale per una data richiesta di I\O; lettura e scrittura sono separate e sequenziali. I buffer singoli si dividono a loro volta in orientati ai blocchi e agli stream: in quello orientato ai blocchi i trasferimenti sono fatti al buffer in system memory, quindi il blocco viene mandato nello spazio utente quando necessario, ma il prossimo blocco viene comunque letto nel buffer. Vi è quindi un input o lettura anticipata, e un output posticipato. I dati solitamente sono acceduti sequenzialmente, quindi c?è buona possibilità che servirà e sarà già stato letto. In quello orientato agli stream i terminali tipicamente hanno a che fare con linee, quindi si bufferizza una linea intera di input\output, o un byte alla volta per i device in cui un singolo carattere premuto va gestito.
  • Con un buffer doppio, un processo può trasferire dati da\a uno dei buffer, mentre il SO svuota\riempie l?altro. Lettura e scrittura sono parallele.
  • Nel buffer circolare, ho più di due buffer e ciascuno è un?unità. Viene usato quando l?operazione di I\O deve tenere il passo del processo.

RAID Redundant Array of Independent Disks: se si hanno più dischi fisici, possiamo trattarli separatament

Esistono devices da più dischi fisici gestiti da un RAID direttamente a livello di dispositivo: il SO fa solo read e write, il dispositivo gestisce internamente il RAID. Gerarchie di RAID:

  • RAID0? dati distribuiti per maggiore efficienza nell'accesso. Ogni disco è diviso in strip; ogni strip contiene un numero di settori; un insieme di strips su vari dischi si chiama stripe.
  • RAID1? ogni dato viene duplicato. Si hanno 2N dischi fisicamente, ma la capacità di memorizzazione è quella di N dischi: se se ne rompe uno, posso recuperare, ma se se ne rompono due posso perdere dati.
  • RAID2? Nei casi rari in cui l?errore è dovuto al flip di un bit, posso usare un codice che non replica l?intera informazione. Il codice di Hamming corregge errori su singoli bit e li rileva su due bit: non più di N dischi di overhead.
  • RAID3? non usato, un solo disco di overhead che memorizza per ogni bit la parità di quelli che hanno la stessa posizione. Nonostante la semplicità, posso recuperare i dati quando fallisce un unico disco. Se muore il disco di parità siamo fregati.
  • RAID4? non usato, è come RAID3 ma ogni strip è un blocco potenzialmente grande e recuperabile in caso di fallimento di un unico disco. Complicato gestire piccole scritture.
  • RAID5? le informazioni di parità non sono su un unico disco, ma distribuite sui vari dischi. Non c?è un disco privilegiato per la scrittura.
  • RAID6? due dischi di parità indipendenti, permette di recuperare anche due fallimenti di disco. 30% di penalità in più rispetto a RAID5 per la scrittura, per lettura sono uguali.

Page cache in Linux

Unica per tutti i trasferimenti tra disco e memoria, compresi quelli dovuti alla gestione della memoria virtuale. Ci sono due vantaggi: scritture condensate e si risparmiano accessi a disco sfruttando la località dei riferimenti.

File system Il file system è l'elemento principale per la maggior parte delle applicazioni, poiché spesso l'input e l'output sono proprio dei files.

Le proprietà che deve avere sono esistenza a lungo termine, condivisibilità con altri processi e strutturabilità

I file sono gestiti da un insieme di programmi e librerie: tali programmi costituiscono i FMS (file management systems), e vengono eseguiti in kernel mode. Invece le librerie vengono invocate come syscall e hanno a che fare con la memoria secondaria. Forniscono un?astrazione sotto forma di operazioni tipiche: per ogni file vengono mantenuti degli attributi, o metadati. Le operazioni tipiche sui file sono creazione, cancellazione, apertura, lettura, scrittura, chiusura.

  • Campo? dati di base, contengono valori singoli caratterizzati da lunghezza e tipo.
  • Record? insiemi di campi correlati, ognuno trattato come un?unità.
  • File? hanno nome e insiemi di record correlati. Possono implementare meccanismi di controllo dell?accesso.
  • Database? collezioni di dati correlati, mantengono relazioni tra gli elementi memorizzati.

Sistemi per la gestione di file

Abbiamo detto che i file sono gestiti dai FMS, che forniscono servizi agli utenti e alle applicazioni per l?uso di file e definiscono anche il modo in cui i file sono usati. Sollevano i programmatori dal dover scrivere codice per gestire i file. Gli obiettivi sono rispondere alle necessità degli utenti riguardo alla gestione dei dati, garantire che i dati nei file siano validi, ottimizzare le prestazioni sia dal punto di vista del SO che dell?utente, fornire supporto per diversi tipi di memoria secondaria, minimizzare i dati persi o distrutti, fornire un insieme di interfacce standard per i processi utente, fornire supporto per l?I\O effettuato da più utenti in contemporanea. Invece i requisiti sono i seguenti:

  1. Ogni utente deve poter creare, cancellare, leggere, scrivere e modificare un file.
  2. Ogni utente deve poter accedere in modo controllato ai file di un altro utente.
  3. Ogni utente deve poter leggere e modificare i permessi di accesso ai propri file.
  4. Ogni utente deve poter ristrutturare i propri file in modo attinente al problema affrontato.
  5. Ogni utente deve poter muovere dati da un file all?altro.
  6. Ogni utente deve poter mantenere una copia di backup dei propri file.
  7. Ogni utente deve poter accedere ai propri file tramite nomi simbolici.

Directory

Le directory contengono informazioni sui file, come attributi (o metadati), posizione, proprietario. Una directory è un file speciale che fornisce il mapping tra nomi dei file e file stessi. Le operazioni su directory sono ricerca, creazione file, cancellazione file, lista del contenuto, modifica directory.

Il nome del file è quello scelto dal creatore ed è unico in una directory data; il tipo del file può essere eseguibile, di testo, binario, eccetera; l?organizzazione del file viene specificata per sistemi che supportano diverse organizzazioni.

Nell?indirizzo troviamo alcune informazioni: volume (dispositivo su cui il file è memorizzato), indirizzo di partenza, dimensione attuale, dimensione allocata (ovvero quella massima).

Il proprietario può concedere, negare o cambiare i permessi di altri utenti.

Le informazioni sull?accesso potrebbero contenere username e password per ogni utente autorizzato, e le azioni permesse servono per controllare lettura, scrittura, esecuzione e spedizione tramite rete.

Le informazioni sull?uso sono data di creazione, identità del creatore, data dell?ultimo accesso in lettura, data dell?ultimo accesso in scrittura, identità dell?ultimo lettore, identità dell?ultimo scrittore, data dell?ultimo backup e uso attuale. Il metodo utilizzato per memorizzare le informazioni di cui sopra varia molto da sistema a sistema: quello più semplice è fare una lista di entry per ogni file. Con uno schema a due livelli si ha una directory per ogni utente più la master accessibile solo dal sistema che le contiene, insieme all?indirizzo e le informazioni per il controllo dell?accesso. Ogni directory utente è solo una lista dei file di quell?utente, non offre struttura per insieme di file. Nello schema ad albero abbiamo una directory master che contiene le directory utente: ognuna può contenere file oppure altri directory utente.

Gestione della memoria secondaria

Il SO è responsabile dell?assegnamento di blocchi a file, dunque abbiamo due problemi correlati: occorre allocare spazio per i file, e mantenere traccia una volta allocati, oltre a tener traccia dello spazio allocabile.

  • Preallocazione o allocazione dinamica? Nella preallocazione occorre che la massima dimensione sia dichiarata a tempo di creazione: questa è stimabile in alcune applicazioni, ma non in tutte. Quindi l?allocazione dinamica è preferibile, in quanto viene aggiustata in base ad append o truncate.
  • Porzioni di dimensione fissa o dinamica? Allocare una porzione larga a sufficienza per l?intero file è efficiente per il processo che vuole creare il file, e l?accesso sequenziale è più veloce. Allocare un blocco alla volta è efficiente per il SO: ciascun blocco è una sequenza di n settori contigui, con n fisso e piccolo. Alla fine abbiamo due opzioni: porzioni grandi a dimensione variabile (ogni allocazione è contigua, la tabella di allocazione è contenuta ma la gestione dello spazio libero è complicata), oppure porzioni fisse e piccole (un blocco per una porzione, meno contiguo ma per lo spazio libero basta guardare una tabella di bit).
  • Metodo di allocazione contiguo, indicizzato o concatenato? Nel primo, un insieme di blocchi viene allocato per il file quando quest?ultimo viene creato. La preallocazione è necessaria, poiché il file potrebbe andare oltre il limite massimo. Necessaria una sola entry nella tabella di allocazione, ovvero blocco di partenza e lunghezza dei file. C?è frammentazione esterna. Nel secondo c?è allocazione di un blocco alla volta: ogni blocco ha un puntatore al successivo, la prima parte del blocco sono dati del file, l?ultima è il puntatore. Necessaria una sola entry nella tabella di allocazione, ovvero blocco di partenza e lunghezza del file. Non c?è frammentazione esterna, e quella interna è trascurabile. Va bene per accessi sequenziali. Nel terzo abbiamo una via di mezzo: la tabella di allocazione contiene l?indirizzo di un blocco, che in realtà ha una entry per ogni porzione allocata al file, quindi fa parte della tabella, pur trovandosi in un blocco separato. Se il file è troppo grande si fanno più livelli. Ovviamente c?è un bit che indica se il blocco è composto da dati o è un indice. Se i blocchi sono a lunghezza fissa, non c?è frammentazione esterna; se sono a lunghezza variabile, migliora la località e va specificata la lunghezza dei blocchi.
  • Lo spazio libero è altrettanto importante di quello occupato: per allocare spazio, occorre sapere dov?è lo spazio libero. Ci serve una tabella di allocazione di disco oltre a quella dei file.
  • Il volume è un disco logico: più dischi messi insieme e visti come un solo disco. In sostanza si comporta come un insieme di settori in memoria secondaria, che possono essere usati dal SO o dalle applicazioni. I settori di un volume non devono necessariamente essere contigui, ma appariranno come tali al SO e alle applicazioni. Un volume potrebbe essere il risultato dell?unione di volumi piccoli.

Requisiti per la mutua esclusione

  • Solo un processo alla volta può essere nella sezione critica per una risorsa.
  • Niente deadlock né starvation.
  • Nessuna assunzione su scheduling dei processi, né sul loro numero.
  • Un processo deve entrare subito nella sezione critica se nessun altro processo usa la risorsa.
  • Un processo che si trova nella sezione non critica non può essere bloccato.
  • Un processo che si trova nella sezione critica prima o poi deve uscire.

I sistemi con un solo processore permettono solo l?interleaving: un processo viene eseguito finché non invoca il SO o viene interrotto. Disabilitare le interruzioni garantisce la mutua esclusione: se il processo può decidere di non essere interrotto, allora nessun altro lo può interrompere mentre si trova nella sezione critica

Istruzioni macchina speciali

  • Compare and swap: compara due parametri e, se sono uguali, assegna il nuovo valore alla variabile e ritorna il vecchio valore, altrimenti ritorna il vecchio valore.
  • Exchange: scambia due valori in maniera atomica

Semaforo

Valore intero usato dai processi per scambiarsi segnali, ha solo tre operazioni atomiche definite: initialize, decrement (o semWait, può mettere il processo in blocked così non spreca CPU) e increment (o semSignal, mette un processo blocked in ready). Sono eseguite in kernel mode e agiscono direttamente sui processi. La struttura del semaforo contiene un contatore e una coda, usata per mettere i processi in blocked; la wait prende in input un semaforo, che diminuisce il count e poi verifica se è minore di zero, e in caso positivo mette il processo nella coda del semaforo e lo blocca; il signal prende in input un semaforo e incrementa il count, se è minore o uguale a zero rimuove un processo P dalla coda e lo mette a ready. Al posto del count possiamo mettere una enumerazione con valori 0 e 1: se il valore del semaforo è 1 continuiamo a eseguire mettendolo a 0, altrimenti si mette un processo nella coda e si blocca. Nel signal invece si controlla la coda: se è vuota il valore del semaforo va a 1, altrimenti si rimuove un processo dalla coda e si mette nella lista dei processi ready.

Problema del produttore / consumatore

La premessa è che uno o più produttori creano dati e li mettono in un buffer: un consumatore prende dati dal buffer uno alla volta, e al buffer può accedere un solo processo alla volta. Il problema è assicurare che i produttori non inseriscano i dati quando il buffer è pieno e assicurare che il consumatore non prenda dati quando il buffer è vuoto, oltre alla mutua esclusione sull?intero buffer.

Problema di trastevere

I blocchetti sono macchine, il tubo è una strada che si restringe a senso unico alternato: possono passare massimo 4 auto alla volta. Vince chi arriva prima: assumendo semafori strong, le macchine dovrebbero impegnare la strettoia nell?ordine con cui vi arrivano (o si accodano dalla propria parte).

Problema del negozio del barbiere

La dimensione massima del negozio è max cust; prima ci si siede sul divano, poi da lì si accede a una delle sedie, si compete per entrambe le risorse. Un certo numero va sul divano, le sedie effettive poi sono di meno; tra tutti quelli nel negozio si compete per il divano, mentre tra tutti quelli sul divano si compete per le sedie. Il barbiere dorme o taglia capelli o prende soldi dopo il taglio.

Algoritmo di Dekker

Riferito alla soluzione del negozio del barbiere: vogliamo implementare il quarto modo senza livelock: verifichiamo con turn che sia il mio turno, e finché non lo è metto il flag a false e c?è un busy waiting. Quando sarà il mio turno, rimetto il flag a true, vado in sezione critica e faccio il flip del turno.

Algoritmo di Peterson

Simile a Dekker ma dalla struttura diversa: ciclo while infinito e quando entriamo il flag dice se vogliamo accedere. Poi cambio turno e verifico che, se l?altro processo vuole entrare ed è il suo turno, non faccio niente; altrimenti vado avanti e alla fine metto il flag a 0. Così non ho momenti in cui due processi vogliono accedere alla sezione critica

Interazione dei processi Dobbiamo soddisfare due requisiti: sincronizzazione e comunicazione.

Lo scambio di messaggi è una soluzione al secondo requisito, poiché funziona sia con memoria condivisa che distribuita e può essere usata anche per la sincronizzazione. La funzionalità è fornita tramite send(destination, message) e receive(source, message): message è un input per la send ed un output per la receive.

La comunicazione però richiede anche la sincronizzazione: il mittente deve inviare prima che il ricevente riceva. Nelle operazioni bloccanti sia mittente che destinatario sono bloccati finché il messaggio non è completamente ricevuto: si chiama rendezvous, e la sincronizzazione è molto stretta. Il send non bloccante (nbsend) è il più naturale; nella ricezione bloccante il mittente non viene messo in blocked e il destinatario rimane bloccato finché non riceve il messaggio. Nella ricezione non bloccante (nbreceive), se c?è il messaggio, esso viene ricevuto, altrimenti va avanti; può settare un bit nel messaggio per dire se la ricezione è avvenuta. In questo caso, tipicamente non è bloccante nemmeno l?invio. Queste operazioni sono sempre atomiche. Il mittente deve poter dire a quale processo vuole mandare il messaggio, idem per il destinatario: si possono usare indirizzamento diretto o indiretto. Nel diretto la primitiva di send include uno specifico ID per il destinatario o per un gruppo di destinatari; per la receive, non è detto che ci sia: con receive(sender, msg) ricevi solo se il mittente coincide con sender; altrimenti receive(null, msg) ricevi da chiunque. Dentro msg c?è anche il mittente, e ogni processo ha una coda: una volta piena il messaggio si perde oppure viene ritrasmesso.

Nell?indiretto, i messaggi sono inviati ad una casella di posta condivisa: il mittente manda messaggi alla mailbox, da dove li prende il destinatario. Se la ricezione è bloccante e ci sono più processi in attesa su una ricezione, un solo processo viene svegliato. Ci sono similitudini con produttore\consumatore (se la mailbox è piena? ).

Struttura dei messaggi

Deadlock Il blocco permanente di un insieme di processi, che competono per delle risorse di sistema o comunicano tra loro: vengono richieste in contemporanea le stesse risorse da parte di due o più processi.

Con il Joint Progress Diagram analizziamo i diversi percorsi che possono fare i processi in base alle risorse che richiedono: qui abbiamo un processo su x e uno su y, e vediamo le diverse situazioni in cui i processi si trovano in caso decidano di accedere in diversi momenti temporali alle risorse. In questo esempio il deadlock è inevitabile

Reusable vs Consumable resource

Le reusable sono usabili da un solo processo alla volta, ma non si consumano, ad esempio i file Le consumable vengono prodotrte e consumate, ad esempio le interruzioni

Gli asset (^) ?Risorse?, possono essere categorizzati come hardware, software, dati, linee di comunicazioni e reti.

Autenticazione Base per la maggior parte dei tipi di controllo e tracciabilità, consiste in identificazione e verifica e ne esistono di diversi tipi.

  • L?autenticazione con password è la più nota e usata; meglio che siano memorizzate in chiaro.
  • L?autenticazione con token consiste in oggetti fisici posseduti da un utente, per esempio memory card e smartcard. Le memory card possono memorizzare dati ma non elaborarli; spesso sono usate insieme a password o PIN. Gli svantaggi sono che serve un lettore apposito e si potrebbe perdere il token. Le smartcard hanno un microprocessore, memoria e porte I\O: ne esistono di diversi tipi, a seconda di caratteristiche fisiche ed interfaccia. Hanno un lettore apposito, ma alcune anche il tastierino; il protocollo di autenticazione genera password in modo statico o dinamico.
  • Recentemente, la biometrica è stata espansa con qualcosa che sei (biometrica statica), qualcosa che fai (biometrica dinamica). L?autenticazione biometrica statica include caratteristiche facciali, impronte digitali etc: si basa sul riconoscimento di pattern, ed è complesso e costoso. Nell?autenticazione biometrica dinamica i pattern possono cambiare, include firma e voce.

Controllo di accesso

Determina quali tipi di accesso sono ammessi, sotto quali circostanze e da chi.

  • Discrezionale? un utente può concedere i suoi stessi privilegi ad altri.
  • Obbligatorio? un utente non può concedere i suoi stessi privilegi ad altri.
  • Ruoli? un utente riceve un ruolo che lo abilita ad effettuare le operazioni concesse a quel ruolo, ma solo mentre si agisce sotto di esso.

Meccanismi di protezione UNIX

Autenticazione dell?utente (User-Oriented Access Control) e diritti o permessi di accesso ai dati (Data-Oriented Access Control).

Ogni utente ha uno username alfanumerico e un UID numerico, usato ogni volta che occorre dare un proprietario ad una risorsa; ogni utente appartiene a un gruppo, identificato da groupname e GID

Il login può essere fatto su un terminale della macchina (processo getty) o tramite rete (telnet, ssh): richiede una coppia username+password, se corrisponde a una entry di /etc/passwd, viene eseguita la shell, che, quando arriva ad exit, ritorna al getty o chiude la connessione rete. All?interno di una shell si può cambiare identità.

Per ogni file ci sono tre terne di permessi: lettura, scrittura, esecuzione. La prima è per il proprietario, la seconda per il gruppo cui appartiene il proprietario, la terza per tutti gli altri. Il proprietario è colui che ha creato il file, ma si può cambiare con chown; i diritti si cambiano con chmod.

Possibili attacchi

  • Attacco dizionario? anche se ci sono un numero infinito di password che possono risultare nello stesso hash, gli utenti sono pigri, quindi non creano password troppo complesse (quindi password brevi e semplici) e non si ricordano troppe password (quindi stessa password riusata molte volte). Quindi è possibile compilare una lista di password comunemente usate e fare bruteforce: dato un hash d che voglio invertire, per ogni x nella mia lista di password, calcolo dx = h(x), e ripeto finché dx==d. Questo attacco è molto semplice da effettuare, è versatile e moltissimi tool disponibili per automatizzare il tutto; tuttavia può essere molto lento e la password potrebbe non esserci.
  • Attacco Rainbow Table? la Rainbow Table è un dizionario delle coppie (valore hash, plaintext password), e viene usato per trovare velocemente quale password corrisponde a un hash. Creato offline e riutilizzato più volte per molti attacchi, in realtà usa un sistema complesso di funzioni di riduzione per mantenere trattabili le dimensioni della tabella. Questo attacco è molto semplice e molto più veloce rispetto al dizionario, ma è rigido: funziona solo per la funzione di hash per la quale è stata creata la Rainbow Table.

Come ci proteggiamo?

Con il salt: un valore randomico generato quando un utente sceglie la password, che viene aggiunto alla computazione dell?hash. Il salt viene poi salvato in chiaro assieme all?hash calcolato: rende impossibile l?uso di Rainbow Table, poiché non si può precomputare l?hash. Inoltre due utenti con la stessa password hanno due hash diversi perché probabilmente hanno salt diversi. L?attacco dizionario funziona ancora.

Buffer overflow

Stiamo inserendo troppi dati rispetto alla dimensione del buffer, ma il computer non la sa, quindi continua a copiare a partire dal primo indirizzo di memoria di buf[], finché non ha occupato tutti gli indirizzi, e poi sovrascrive ciò che trova finché non completa l?operazione. Di solito fare overflow di un buffer in questo modo porta a segmentation fault; tuttavia se i dati usati nell?overflow sono preparati accuratamente, è possibile eseguire codice arbitrario

Shellcode

Un piccolo pezzo di codice che viene eseguito quando si sfrutta una vulnerabilità per attaccare un sistema: deve rientrare nelle dimensioni del buffer.

Si chiama shellcode perché solitamente avvia command shell, dalla quale l?attaccante può prendere il controllo della macchina

Return-to-libc

Non sempre possiamo inserire shellcode arbitrario, ma esiste del codice che è sempre presente in RAM ed è sempre raggiungibile dal processo: le librerie dinamiche e di sistema. Possiamo quindi mettere come indirizzo di ritorno quello di una funzione di sistema utile per un attacco: si chiama così perché modifica l?indirizzo di ritorno solitamente con l?indirizzo di una funzione standard.

Contromisure

Due tipi:

  • difese a tempo di compilazione
  • difese a tempo di esecuzione. Durante la compilazione faccio uso di linguaggi di programmazione e di funzioni sicure: l?overflow è possibile perché in C ci sono alcune funzioni che spostano dati senza limiti di dimensione. Nello Stack Smashing Protection il compilatore inserisce del codice per generare un valore casuale a runtime: questo valore viene inserito tra il frame pointer e l?indirizzo di ritorno, e, se il valore viene modificato prima che la funzione ritorni, interrompe l?esecuzione perché è stato sovrascritto da un possibile attacco. Durante l?esecuzione, uso Executable Space Protection: stack e heap non contengono codice eseguibile, quindi il SO marca le pagine\segmenti dello stack ed heap come non eseguibili. Se un attaccante cerca di eseguire del codice nello stack, il sistema termina il processo con un errore. Altrimenti Uso Address Space Layout Randomization: ad ogni esecuzione randomizza gli indirizzi dove sono caricati i diversi segmenti del programma. Se l?attaccante non sa dove inizia lo stack è più difficile indovinare l?indirizzo del buffer contenente lo shellcode e quello delle librerie.