Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Sistemi Operativi
Linee guida formattazione 3
Processi 11
Gestione dei processi 11
Composizione di un processo 12
Stati di un processo 12
Process Control Block PCB 12
Creazione di un processo 12
Terminazione di un processo 13
Alcune delle system call più importanti 13
Context switch 14
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 1/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
Tipi di scheduler 14
Politiche di scheduling 14
Punti di scheduling 14
Cambio di contesto 15
Context switch overhead 15
Kernel preemption 15
Codice rientrante 15
Kernel rientrante e preemptive 16
Threads vs Processi 16
Perchè si utilizzano i thread 16
Programmazione multi-thread 17
Terminazione di un thread 17
Up-Calls 17
Thread in Windows, Linux e Java 17
Comunicazione tra processi 18
Comunicazione tra processi 18
Paradigma produttore-consumatore 18
Comunicazione tramite message passing 19
Architettura client-server 19
Sincronizzazione 20
La sezione critica 20
Soluzioni al problema della sezione critica 20
Soluzione di Peterson 20
Sincronizzazione Hardware 20
Semafori 21
Deadlock e Starvation 21
Algoritmo del banchiere 22
Problemi di sincronizzazione e deadlock 22
Buffer limitato 22
Problema dei lettori-scrittori 22
La cena dei 5 filosofi 23
Barbiere che dorme 23
Strumenti di sincronizzazione dei SO 24
Scheduling 25
Concetti fondamentali 25
Criteri di scheduling 25
Politiche di scheduling 26
FCFS (First Come First Serve) 26
SJF (Shortest Job First) 26
Scheduler prioritario 27
Round Robin (RR) 27
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 2/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
Scheduling in Linux 30
Linux scheduler 2.4.x 30
Coda dei processi 30
Stato dei processi 30
Principali caratteristiche dello scheduler 30
Quanto di tempo 30
Priorità dei processi 31
Process Descriptor 31
Funzione schedule() 31
Linux Scheduler 2.6.x 32
Scheduler SMP 32
CFS (Completely Fair Scheduler) 33
Implementazione 33
Memoria principale 34
Organizzazione della memoria principale 34
Binding 34
MMU (Memory Management Unit) 35
Dynamic Loading e Dynamic Linking 35
Allocazione contigua e frammentata 35
Paginazione 36
Implementazione della tabella di paging 36
Pagine condivise 36
Struttura della tabella di paging 37
Struttura della tabella delle pagine 38
Tabella delle pagine hash 38
Tabella delle pagine invertita 38
Segmentazione 38
Architettura di segmentazione 38
Differenze tra pagine e segmenti 39
Segmentazione Intel Pentium 39
Segmentazione AMD64 39
Memoria virtuale 40
Paginazione su richiesta 40
Sostituzione delle pagine 40
Allocazione dei frame 40
File mappati in memoria 40
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 3/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 4/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
Prima evoluzione
Il sistema operativo è in grado di eseguire una sola applicazione alla volta.
L’utilizzo di CPU è massimo ma appena l’elaborazione necessita di un evento di I/O la
CPU entra in fase di stallo che viene terminata solo quando l’evento viene generato.
Tuttavia quando un programma viene eseguito diventa padrone totale della macchina e
potrebbe portare a corruzione della memoria dello stesso SO o a un ciclo infinito che
sarebbe risolvibile soltanto con lo spegnimento della macchina.
Per risolvere tali problemi è necessario che il sistema operativo fornisca
● preemption: la possibilità di fermare l’esecuzione di un processo quando non sta
attivamente utilizzando la CPU
● protezione della memoria: la possibilità di assegnare porzioni di memoria al
sistema che non possono essere accedute dalle applicazioni
Un esempio di SO di prima evoluzione è MS-DOS che viene creato da Microsoft con l’intento
di raggruppare funzioni comuni nello sviluppo di applicazioni nel più piccolo spazio possibile.
Nonostante MS-DOS presenti una struttura e non sia totalmente a cazzo la separazione di
funzionalità non è netta e porta a confusione e difficoltà nello stabilire il ruolo di ogni
funzione: un’applicazione utente accede ai dispositivi hardware tramite funzione del sistema
operativo, ma è anche in grado di farlo direttamente bypassando il sistema operativo.
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 5/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
Seconda evoluzione
Nasce dal tentativo di sfruttare a pieno le potenzialità della CPU implementando la
multiutenza.
La CPU viene utilizzate da più utenti diversi che sono in grado di eseguire programmi
diversi, in questo modo la CPU viene sfruttata maggiormente poiché nei momenti di stallo
dell’applicazione dell utente A viene eseguita l’applicazione dell’utente B.
Tuttavia si possono ancora presentare momenti in cui un processo potrebbe monopolizzare
la CPU o corrompere l’area di memoria utilizzata dalle applicazioni degli altri utenti.
Per risolvere tali problemi il SO deve fornire:
● protezione dagli utenti che eseguono ad esempio un ciclo infinito
● protezioni della memoria di un’applicazione dalle altre applicazioni
A strati
Il SO è diviso a strati in cui lo strato più interno rappresente l’hardware e quello più esterno
l’interfaccia per l’utente.
Ogni strato può accedere alle funzioni degli strati sottostanti.
Così facendo si garantisce modularità e separazione dei compiti all’interno del SO.
UNIX
Viene detto approccio monolitico ed è diviso in due parti principali:
● kernel
● applicazioni di sistema
Il kernel offre un’interfaccia tramite quale le applicazioni di sistema sono in grado di
comunicare con l’hardware e si occupa di comunicare con l’hardware, gestire la memoria,
effettuare lo scheduling della CPU, gestire i driver, gestire il filesystem ecc.
Esso risulta essere una componente piuttosto piena del SO, ma tale implementazione è
dovuta alle risorse hardware limitate su cui i primi sistemi UNIX vennero installati.
Microkernel
Viene SO a microkernel un SO il cui kernel svolge soltanto le funzionalità essenziali per far
sì che la macchina sia utilizzabile.
Ogni funzionalità aggiuntiva che non è strettamente necessaria (filesystem, driver per l’I/O
ecc.) viene implementata come moduli che comunicano con il kernel. Le applicazione
dell’utente comunicheranno tramite messaggi con i moduli che a loro volta comunicheranno
con il kernel.
Tale struttura è più sicura, affidabile, facile da estendere e da adattare a strutture
hardware diverse poiché le parti fondamentali che consentono l’utilizzo della macchina
risiedono in una piccola parte del SO rispetto ad un approccio monolitico.
Tuttavia tale approccio potrebbe risultare in un overhead per via dei moduli e del kernel che
si interpongono tra un’applicazione dell’utente e l’hardware.
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 6/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
Kernel modulare
Utilizzato nei SO più moderni (Solaris) utilizza i concetti della programmazione ad oggetti:
ogni funzionalità fondamentale è separata dalle altre e rappresenta un modulo che
comunica tramite un’interfaccia con gli altri moduli.
Tramite kernel modulare è possibile caricare moduli solo quando è necessario.
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 7/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
Protezione e Sicurezza
Il SO deve garantire:
- protezione → controllo degli accessi al sistema;
- sicurezza → controllo dell’integrità dei dati, non permettendone la violazione.
Protezione
Il SO deve proteggere se stesso, i suoi dati, i suoi programmi, i suoi utenti e le sue risorse da
quelli che vengono definiti bad users and programs.
Per garantire la protezione di tali risorse, vengono utilizzate tre diverse tecnologie:
- preemption → consente di arrestare un processo contro la sua volontà;
- modalità privilegiata → individua uno stato in cui un insieme di istruzioni, che
possono essere svolte solo quando la CPU si trova in un particolare stato, hanno il
controllo completo della macchina;
- registri speciali → utilizzati per memorizzare informazioni riguardo alla memoria dei
processi.
Preemption
Tale tecnica protegge la CPU tramite l’utilizzo dell’interrupt. L’utilizzo dell’interrupt permette
al SO di negare l’utilizzo della CPU ad un processo contro la sua volontà, ed assegnarla ad
un altro processo. Senza la preemption, un programma maligno potrebbe monopolizzare
l’utilizzo della CPU.
Per l‘implementazione della preemption viene adottata la tecnica del time-sharing, ovvero
viene individuata una piccola fetta di tempo (ex. 25/40 ms) in cui ogni processo può far uso
della CPU. Al termine di questo slice di tempo, viene generato un interrupt che segnala al
SO che è arrivato il momento di negare la CPU al processo corrente e allocarla per un altro
processo.
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 8/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
Modalità privilegiata
Consente di proteggere l’I/O e la RAM.
Esistono due modalità in cui la CPU può eseguire istruzioni:
- Modalità PRIVILEGIATA → consente di eseguire tutte le istruzioni dell’instruction set
- Modalità UTENTE.
Vi sono diverse situazioni in cui il SO operativo interviene per eseguire in modalità
PRIVILEGIATA, tra queste:
- L’accesso a routine che devono essere eseguite in caso di errore: tali routine sono
definite in un’area di memoria protetta (EVT - Exception Vector Table) che può
essere acceduta solo in modalità PRIVILEGIATA
- L’accesso a dispositivi di I/O: gli accessi ai dispositivi di I/O in modalità UTENTE
vengono effettuati tramite l’utilizzo di system call.
Una system call fa entrare in modalità privilegiata la CPU e viene invocata tramite
apposite istruzioni di sistema (che generano una trappola) alle quali si dovranno
passare opportuni parametri tramite l’uso di registri particolari.
L’utilizzo di system call permette al SO di standardizzare e controllare l’accesso ai
dispositivi di I/O da parte delle applicazioni.
La CPU entra in modalità PRIVILEGIATA modificando un particolare bit interno (che funge
da flag), tale bit viene modificato soltanto dal SO e ogni tentativo di modifica da parte di un
programma esterno viene bloccata dallo stesso SO.
All’accensione, il boot loader (ossia la routine che inizializza la macchina e avvia il sistema
operativo) viene eseguito in modalità privilegiata, e quindi il SO è molto vulnerabile in quanto
nel caso in cui venisse preso il controllo del pc in questo momento, l’attacker potrebbe
eseguire qualsiasi istruzione, accedere ad ogni cella di memoria e utilizzare ogni periferiche.
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 9/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
Registri speciali
L’utilizzo di registri speciali è una tecnica che contribuisce alla protezione del sistema
operativo.
In particolare protegge lo stesso SO (il codice di esecuzione, EVT, ecc.) e la memoria
utilizzata dai diversi processi in esecuzione nella macchina.
Vengono introdotti i concetti di base e limite, che identificano una precisa area all’interno
della memoria (base da dove parte, limite fin dove arriva).
Il contenuto di tale area è modificabile solo dal SO ossia ogni processo dovrà richiedere al
SO di modificare aree di memoria di interesse.
Ciò consente integrità dei dati per ogni processo: la memoria utilizzata da un processo non
può essere sporcata da un altro processo poiché il SO si occupa di bloccare e prevenire
accessi non consentiti.
System Call
Una system call è un’interfaccia di programmazione ai servizi offerti dal sistema operativo.
Sono funzioni tipicamente scritte in un linguaggio ad alto livello (C o C++) e sono utilizzate
dai programmi tramite le API (Application Program Interface) che le rendono però non
portatili cioè ogni SO implementa le proprie system call (Windows32 API, POSIX API, Java
API).
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 10/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
10
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 11/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
Processi
E’ impossibile però per il SO catalogare a priori un processo prima della sua esecuzione. Il
SO infatti etichetta il processo solamente quando esso è in esecuzione, infatti un processo
potrebbe potenzialmente essere CPU intensive per un periodo di tempo e per il restante
tempo di esecuzione potrebbe comportarsi come I/O intensive.
L'obiettivo principale che viene perseguito con l’utilizzo di processi è quello di sfruttare al
meglio la CPU: eseguendo ad esempio un processo I/O intensive potrebbe esserci un lasso
di tempo piuttosto lungo in cui la CPU non esegue nessuna operazione (in quanto attendo la
risposta dell’I/O). In tal caso è lecito pensare di occupare la CPU con un altro processo
mentre si attende l’evento dell’I/O.
Tale obiettivo viene centrato tramite l’uso della multiprogrammazione disponendo il
computer in modo che sia in grado di eseguire più processi contemporaneamente.
Esistono due tipi principali di multiprogrammazione
- Virtuale: l’esecuzione di processi viene sovrapposta (un processo utilizza CPU
mentre un altro attende I/O).
- Reale: sono disponibili più di 1 CPU per cui diversi processi vengono effettivamente
eseguiti contemporaneamente.
Nel caso della multiprogrammazione virtuale viene introdotto il concetto di time burst ossia
lo slice di tempo che viene garantito a un processo per eseguire codice sulla CPU.
Tale burst viene inizialmente definito dal SO e può essere modificato dinamicamente a
seconda della frequenza di utilizzo della CPU (la maggior parte dei processi usano la cpu
per Xms poi attendono per I/O).
11
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 12/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
Composizione di un processo
Un processo in esecuzione è rappresentato in memoria da:
- sezione CODE/TEXT: contiene le istruzioni macchina che vengono eseguite sulla
CPU. Dimensione fissa.
- sezione DATA: contiene le variabili globali del programma. Dimensione fissa.
- sezione STACK: contiene le variabili utilizzate per gestire le chiamate a funzioni del
processo consentendo l’utilizzo di funzioni ricorsive. Dimensione variabile.
- sezione HEAP: contiene le strutture dati (viene utilizzata la funzione MALLOC per
allocare memoria). Dimensione variabile.
Stati di un processo
- NEW: processo appena creato e allocato in memoria.
- RUNNING: il processo è in esecuzione nella CPU (ciclo fetch-decode-execute).
- WAITING: in attesa di I/O dopo una richiesta o di un’informazione da un altro
processo.
- READY: pronto per essere eseguito, in attesa della CPU.
- TERMINATED: processo terminato → terminazione deferita, cioè il SO non termina
bruscamente il processo, ma avviene prima un controllo (circa 50ms).
- ZOMBIE: particolare stato presente in Linux, che indica un processo il cui padre è
terminato senza attendere la terminazione del figlio.
Creazione di un processo
Ogni processo è creato da un altro processo. Il processo creatore viene chiamato processo
padre (init su linux), e rispettivamente il processo creato si chiamerà processo figlio, che a
sua volta creerà altri figli, dando vita ad una struttura ad albero.
Il padre ha la responsabilità di tutto ciò che che esegue il figlio (cercare di evitare la
creazione di processi zombie), e dovrà aspettare la terminazione di ogni processo figlio.
Padre e figlio possono condividere o meno risorse: nel caso in cui non le condividano si
verifica il paradigma concorrente.
Appena creato, il processo figlio è una copia del processo padre (due istanze dello stesso
programma), il figlio poi potrà decidere se caricare o meno nel suo spazio di memoria un
12
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 13/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
nuovo programma. Dopo la creazione del processo figlio, il processo padre continuerà la sua
esecuzione, aspettando la terminazione di tutti i suoi processi figli.
La creazione di un figlio, su linux, avviene tramite la system call fork() il cui valore di ritorno
è il pid (process id) del nuovo processo creato. Inizialmente il pid di un processo figlio
appena creato è 0, poi in seguito è assegnato automaticamente dal SO. Il processo padre
può continuare la sua esecuzione parallelamente o aspettare la terminazione del figlio e poi
riprendere l’esecuzione.
Nel caso il processo padre termini prima del processo figlio, dando vita ad un processo
zombie, sarà il processo init (su linux) ad assumere il ruolo di padre e attendere la
terminazione del figlio raccogliendo il suo valore di ritorno.
Terminazione di un processo
La terminazione di un processo può avvenire:
1. dopo che segue la sua ultima istruzione
2. quando viene bloccato e terminato direttamente dal SO per utilizzo di troppe risorse,
o inutilità, ecc.ecc.
3. quando il padre ne richiede la terminazione
4. quando la RAM è satura e ci sono troppe risorse allocate e viene interrotto
direttamente dal SO
5. quando il processo padre termina, e di conseguenza vengono terminati tutti i
processi figli a cascata (solo in alcuni vecchi SO che non consentono altrimenti di
gestire processi zombie).
13
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 14/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
Context switch
I processi sono organizzati in code i cui elementi sono i PCB di ogni processo o un puntatore
ad essi. Il processo attualmente in esecuzione viene indicato con current.
Esiste una coda per ogni ogni stato di processo (coda per i processi in stato ready, per quelli
in I/O waiting, ecc.ecc.).
Nella coda READY vengono messi tutti i processi pronti per essere eseguiti e che sono in
attesa della CPU, nella coda I/O waiting tutti i processi in attesa di una risorsa di I/O (esiste
una coda per ogni risorsa) e così via.
Ogni processo può migrare da una coda all’altra a seconda del suo stato.
Tipi di scheduler
Esistono due tipi di scheduler:
- long-term scheduler → modulo del SO che viene invocato ogni 10 secondi (in
alcuni SO anche ogni minuto) che controlla i processi e seleziona quelli che possono
essere trasferiti dalla memoria virtuale a quella principale, ovvero li trasferisce e li
distribuisce nelle varie code viste in precedenza. Controlla il grado della
multiprogrammazione.
- short-term scheduler → chiamato anche CPU-scheduler, è un modulo del SO che
decide a quale dei processi nella coda dei processi ready assegnare la CPU. La
decisione del processo deve avvenire in tempo brevissimo, circa 1ms.
Politiche di scheduling
1. Non dimenticare nessun processo.
2. Eseguire per primi i processi con priorità maggiore.
3. Assegnare la CPU entro le deadline, cioè entro tempi ragionevoli: alcuni processi
(ad esempio quelli realtime) potrebbero necessitare di prendere spesso il controllo
della CPU pena il malfunzionamento del programma (un player video deve poter
aggiornare l’immagine abbastanza spesso che l’utente non si accorga di niente).
4. Ottimizzare il più possibile il meccanismo di esecuzione dei processi (utilizzo della
CPU, tempo di risposta, ecc.ecc.)
Punti di scheduling
I punti di scheduling sono quei particolari punti durante l’esecuzione di un processo in cui è
viene effettuata un’operazione di scheduling da parte del SO. In questi particolari punti il SO
ottiene il controllo, scegliendo le azioni da eseguire.
Questi punti di scheduling sono:
- trappole → system calls, errori, page faults, ecc.ecc.
- interrupts → I/O interrupt, timer interrupt, ecc.ecc.
- particolari punti esplicitati del processo → sleep, yield, syscalls, ecc.ecc.
Nei punti di scheduling, il processo corrente può essere privato dell’utilizzo della CPU e
rimpiazzato da un altro processo dando vita ad un context switch, ovvero ad un cambio di
contesto.
14
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 15/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
Cambio di contesto
Il processo corrente, ovvero quello che viene eseguito in questo momento dalla CPU, viene
rimpiazzato da un altro processo. Il SO salva lo stato del processo che verrà rimpiazzato
(save), e carica lo stato del nuovo processo da eseguire (load).
I dati che vengono salvati sono generalmente gli indirizzi dei registri di memoria, i dati,
ecc.ecc. ma questi dipendono dal tipo di architettura. I dati vengono salvati con singole
istruzioni (CISC e RISC) riducendo al minimo l’impatto sulle prestazione, oppure vengono
salvati tramite apposite tecniche software che salvano un singolo registro alla volta
(puramente RISC).
Kernel preemption
Preemption: interruzione forzata dell’esecuzione di un processo.
Esistono due tipi di preemption:
- livello user → viene sospeso un processo eseguito in modalità user
- livello kernel → viene sospeso un processo eseguito in modalità kernel
Un SO che gestisce la preemption a livello kernel si dice che un preemptive kernel, ed ogni
processo kernel è gestito tramite un kernel control path (KCP), ognuno dei quali è gestito
come un normale processo, con il proprio spazio di memoria, ecc.ecc.
Codice rientrante
Una funzione si dice rientrante se mentre è in esecuzione può essere invocata da un’altra
funzione mantenendo l’integrità dei dati, cioè se può essere interrotta durante la sua
esecuzione (da un segnale o da un interrupt per esempio) e successivamente ripresa
nuovamente prima che l’esecuzione interrotta finisca. Un codice rientrante deve evitare
15
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 16/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
variabili globali e non deve cambiare il suo codice durante l’esecuzione, oltre a dover evitare
di chiamare funzioni non rientranti.
Threads vs Processi
I thread sono anche chiamati processi leggeri, poiché condividono la parte di data, file e
codice. Ogni singolo thread invece è composto da un program counter, dai suoi registri, dal
suo stack e heap. Anche i thread, così come i processi, hanno degli stati predefiniti (ready,
running, blocked, terminated). Esistono due tipi di thread, gli user thread e i kernel thread.
16
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 17/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
Svantaggi:
- la concorrenza deve essere controllata dall’utente programmatore, mentre i processi
erano controllati dal SO.
Tutti i moderni sistemi operativi consentono l’utilizzo di threads tramite apposite librerie
(POSIX Pthreads, Win32 threads, Java threads).
Programmazione multi-thread
Quando si utilizza la programmazione multi-thread, è importante capire come il SO interpreta
i thread. Esistono 3 politiche adottate dal SO:
- MANY TO ONE → i thread creati dall’utente vengono trattati dal SO come un unico
kernel thread (ex. se ho 4 thread, il SO li vede come un unico thread. Nel caso in cui
il SO decida di dare la CPU al kernel thread che contiene i 4 user thread, interverrà lo
scheduler della libreria thread che deciderà quale dei 4 thread eseguire).
- ONE TO ONE → ogni user thread viene tradotto in un kernel thread, con il SO che
decide quale thread mandare in esecuzione. E’ una tecnica considerata pericolosa
poichè se l’utente crea 200 thread potrebbe bloccare il SO (deny of service).
- MANY TO MANY → consente al SO di gestire un numero m di user thread in un
numero n di kernel thread, con n ≤ m. Questa tecnica è del tutto dinamica ed è
gestita dal SO tramite le up-call, utilizzate per la mappatura dinamica dei thread.
Terminazione di un thread
La cancellazione dei thread può avvenire in due modi:
- terminazione asincrona → non appena il thread ha finito le istruzioni da eseguire,
elimino il thread. Non viene utilizzata.
- terminazione deferita → appena finite le operazioni da eseguire, il thread si mette in
condizione di terminazione (sarà quindi in grado di ricevere eventuali valori di ritorno,
ecc.ecc.) e verrà periodicamente controllato dal thread principale che deciderà se
dovrà essere cancellato o meno.
Up-Calls
Le up-calls sono delle funzioni di un sistema operativo utilizzate nel modello
MANY-TO-MANY della programmazione multi-thread. Sono delle chiamate che il SO effettua
per comunicare con la libreria dei thread per notificare il cambio di mappatura dei thread.
17
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 18/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
Linux
In Linux, i thread vengono chiamati tasks, e ogni nuovo task viene creato tramite la funzione
clone(). Se alla funzione non passo nessun parametro, il funzionamento sarà uguale a quello
di fork() (crea due processi che operano in maniera del tutto separata), tramite invece
l’apposito passaggio di parametri potrò copiare e creare un nuovo thread condividendo con il
vecchio thread le aree di memoria, dei particolari registri, ecc.ecc.
Java
I thread in Java sono gestiti dalla Java Virtual Machine (JVM). I thread possono essere creati
tramite due metodi:
- estendendo la classe Thread
- implementando l’interfaccia Runnable.
Paradigma produttore-consumatore
Quando si parla di processi cooperanti, viene introdotto il paradigma del
produttore-consumatore: ci sono due thread, uno che produce informazioni chiamato
produttore, e uno che legge ed elabora le informazioni, chiamato consumatore.
Questo tipo di funzionamento utilizza un buffer, ovvero una zona di memoria condivisa alla
quale possono accedere sia il produttore che il consumatore dove andranno a scrivere e
leggere le informazioni. Il buffer può essere:
- limitato → una volta esaurito lo spazio, il produttore si ferma
- illimitato → una volta esaurito lo spazio, il produttore ricomincia a scrivere dal primo
indirizzo di memoria a scrivere sovrascrivendo le informazioni.
18
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 19/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
Architettura client-server
E’ un tipo di architettura utilizzata quando si intende effettuare una comunicazione tra due
processi che risiedono però in due macchine diverse. Si utilizzano i socket, le RPC (Remote
Procedure Calls) e le RMI (Remote Method Invocation) utilizzate da Java.
1. Socket
Utilizzano la concatenazione di indirizzo ip e porta della macchina in cui è attivo il processo
2. RPC
3. RMI
19
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 20/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
Sincronizzazione
La sezione critica
La sezione critica è la parte di codice che inizia nel momento in cui faccio il primo accesso
(in lettura o scrittura) ad una variabile condivisa, e finisce quando viene effettuato l’ultimo
accesso a tale variabile condivisa. E’ chiamata sezione critica poichè tale variabile è
condivisa tra più processi, e di conseguenza potrebbe essere modificata da un processo
mentre un altro processo ne sta effettuando la lettura, portando quindi ad una potenziale
violazione dell’integrità del dato contenuto nella variabile.
Soluzione di Peterson
La soluzione di Peterson è un algoritmo che garantisce la mutua esclusione nell’esecuzione
della sezione critica di due processi. I due processi condividono due variabili, una che indica
quale sarà dei due il processo che entrerà nella sezione critica, mentre l’altro indica lo stato
di un processo, ovvero se è pronto o meno ad entrare nella sezione critica.
Sincronizzazione Hardware
Questo tipo di sincronizzazione viene introdotto per risolvere le falle della soluzione
puramente software presentata da Peterson.
Vengono introdotte le istruzioni atomiche, ovvero quelle operazioni che non possono
essere divise, ma una volta che iniziano ad essere eseguite dalla CPU, la loro esecuzione
viene finita dalla CPU senza che essa venga utilizzata per altri processi.
Esistono due tipi di istruzioni atomiche:
- TEST and SET → legge il valore di un indirizzo di memoria e lo setta a “true”
- SWAP → scambia il contenuto di due celle di memoria individuate del loro indirizzo
20
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 21/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
Semafori
Un semaforo è una variabile o una struttura dati astratta che contiene al suo interno
un’istanza di una variabile intera. La variabile interna al semaforo può essere modificata
tramite due operazioni:
- wait()
- signal()
Deadlock e Starvation
Deadlock: situazione che si verifica quando un insieme di processi che cooperano in un task
sono in attesa di un evento che può essere generato solo da loro stessi (i processi sono in
attesa, quindi non eseguono nulla in attesa di un evento che può essere generato solo da
loro stessi). Una situazione di deadlock può essere generata da:
- mutua esclusione: una risorsa può essere utilizzata solo da un processo alla volta
- hold and wait: un processo può “bloccare” una risorsa allocata mentre è in attesa di
un assegnamento di altre risorse
- no preemption: nessuna risorsa può essere rimossa forzatamente da un processo
che la sta utilizzando
- attesa circolare: ogni processo utilizza una risorsa che è richiesta anche dal processo
successivo nella catena.
Starvation: la starvation è una condizione che si verifica quando alcuni processi non
entrano mai nella loro sezione critica. La soluzione a tale condizione è l’inserimento della
politica di aging, ovvero di ogni processo viene calcolato il tempo in cui tale processo non
esegue nessuna operazione perchè è in attesa di un evento o di una risorsa.
21
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 22/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
Buffer limitato
Si prende in considerazione un buffer contenente N elementi
Soluzione
- semaforo binario inizializzato a 1
- semaforo contatore full inizializzato a 1
- semaforo contatore empty inizializzato ad N
Quindi il problema consiste nel dover consentire a più lettori di poter leggere la risorsa
condivisa contemporaneamente, ed invece consentire ad un solo scrittore per volta di poter
scrivere sulla risorsa condivisa. All’interno della base di dati, potranno quindi esserci o più
scrittori o solamente uno scrittore.
Soluzione
- semaforo binario mutex inizializzato a 1
- semaforo binario wrt inizializzato a 1
22
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 23/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
Il codice dello scrittore sarà molto semplice, esso infatti acquisirà i permessi sul semaforo
binario wrt e scriverà all’interno della base di dati. Il codice del lettore sarà invece
leggermente più complicato: ogni volta che un lettore vuole entrare nella base dati, aumenta
il numero di lettori (cioè la variabile intera readcount). Il primo lettore che entra nella base di
dati acquista la mutua esclusione del semaforo wrt, così tutti gli altri lettori si accoderanno
sull’altro semaforo mutex ed entreranno direttamente nella loro sezione critica.
Soluzione
- array di 5 semafori binari inizializzati ad 1
Questa soluzione tutta via può portare ad una condizione di deadlock quando tutti i filosofi
riescono ad ottenere la loro bacchetta sinistra e non riescono ad ottenere la bacchetta destra
necessaria per mangiare. Si sono quindi individuate ulteriori soluzioni.
Ulteriori soluzioni
- Limitare a 4 il numero di filosofi che arrivano al tavolo allo stesso tempo
- Numerare le bacchette e stabilire una convenzione in base a cui ogni filosofo chiede
la bacchetta con il numero più bassa
- Implementazione tramite Monitor JAVA.
23
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 24/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
Soluzione
Si implementano 3 semafori :
- un semaforo contatore costumers inizializzato a 0 che blocca il barbiere che è in
attesa dei clienti
- un semaforo contatore barbers inizializzato a 0 che blocca i clienti in attesa che si
liberi il barbiere
- un semaforo binario mutex per la mutua esclusione
- una variabile intera inizializzata ad N.
- Windows
1. Interrupt per sistemi a singolo processore
2. Spin lock per sistemi multiprocessore
3. Oggetti del dispatcher che si comportano come mutex e semafori
4. Eventi generati dal dispatcher
- UNIX
1. Mutex
2. Variabili condizionate
3. Lock lettore-scrittore (estensione non portabile)
4. Spin lock (estensione non portabile)
24
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 25/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
Scheduling
Concetti fondamentali
L’obiettivo della multiprogrammazione è quello di ottimizzare al massimo l’utilizzo della CPU,
evitando che essa resti in attesa di processi senza eseguire alcuna operazione.
Ogni processo, durante la sua esecuzione, alterna fasi di CPU Burst e I/O Burst, cioè due
fasi in cui il processo, rispettivamente, utilizza la CPU e resta in attesa di risorse di I/O.
Durante la fase di I/O Burst, la CPU non viene utilizzata dal processo, e quindi la CPU passa
da un processo ad un altro.
Il nuovo processo che viene eseguito dalla CPU viene scelto dallo scheduler. Lo scheduler
è un modulo del SO che sceglie tra i processi pronti per essere eseguiti, quale sia il migliore
da mandare in esecuzione, e una volta scelto, gli alloca la CPU. L’intervento dello scheduler,
e quindi il cambio di processo nella CPU, è richiesto quando:
- il processo in CPU passa dallo stato di running a waiting
- il processo in CPU passa dallo stato di running a ready
- il processo in CPU passa dallo stato di waiting a ready
- il processo in CPU termina.
Le politiche di scheduling si dividono in:
- non preemptive → la CPU esegue lo stesso processo finchè esso è in stato di
running e non si sospende autonomamente (termina, fa una richiesta di I/O, …)
- preemptive → la CPU può essere assegnata ad un nuovo processo anche se il
processo è ancora in stato di running. Di questa operazione si occupa il SO, e questa
tecnica è nota come time-sharing, ovvero si condivide la CPU, che viene assegnata
ad un processo diverso in base ad un quanto di tempo stabilito.
Dopo aver deciso il processo migliore dalla coda dei processi pronti per essere eseguiti in
base ad una precisa politica di scheduling, il modulo del SO che dà il controllo effettivo della
CPU al nuovo processo scelto è chiamato dispatcher. Questa operazione comprende la
fase di cambio di contesto. Ogni dispatcher ha la sua latenza, ovvero il tempo che esso
impiega per stoppare un processo in esecuzione e allocare la CPU ad un altro processo.
Criteri di scheduling
- Massimizzare l’utilizzazione della CPU → tenere la CPU in esecuzione il più
possibile, evitando periodi di tempo in cui essa non esegue nessuna operazione.
- Massimizzare il throughput → cercare di completare il maggior numero possibile di
processi nell’unità di tempo.
- Minimizzare il turnaround time → minimizzare il tempo di esecuzione totale di un
particolare processo.
- Minimizzare il tempo di attesa → minimizzare il tempo di attesa di un processo nella
coda dei processi in stato ready.
- Minimizzare il tempo di risposta → minimizzare il tempo tra una richiesta e la prima
risposta prodotta.
25
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 26/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
Politiche di scheduling
26
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 27/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
Scheduler prioritario
E’ una politica secondo la quale il processo da eseguire è quello con priorità più alta. Tale
priorità viene assegnata ad un processo tramite un numero intero (la priorità più alta è
indicata con il numero più basso ex.1,2,3…). Tale priorità può essere fissa o variare
dinamicamente. Anche in questo caso, l’implementazione di questa politica può avvenire in
maniera preemptive e non preemptive. Il problema di questa politica è sempre la starvation,
infatti un processo con priorità bassa potrebbe non riuscire mai ad ottenere l’allocazione
della CPU. Anche in questo caso si utilizza la politica di aging, secondo la quale in questo
caso, la priorità di un processo aumenta dinamicamente col passare del tempo di attesa di
un processo.
27
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 28/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
1. Shared Memory
Tutte le CPU elaborano informazioni ottenute da una memoria RAM condivisa tra tutte le
CPU. Ci sono due possibili implementazioni:
- Asimmetrica (ASMP) → le CPU non sono trattate tutte nello stesso modo, ma
esistono master e slave, con i primi che eseguono solamente il SO e le funzioni di
scheduling, mentre gli slave eseguono i processi secondari di minor importanza che
gli son stati assegnati dal master. Il problema di questo tipo di architettura è che un
eventuale guasto del master, porterebbe ad una condizione di deadlock.
- Simmetrica (SMP) → tutte le CPU sono trattate allo stesso modo, per cui ogni CPU
eseguirà lo scheduling dei propri processi in maniera autonoma. Il SO viene eseguito
da tutte le CPU, quindi sarà organizzato in due possibili implementazioni:
1. Global Ready Queue → le CPU ottengono i processi da eseguire da una
coda globale condivisa. Il carico per ogni CPU sarà bilanciato, a patto che la
coda di processi sia sincronizzata poichè deve gestire gli accessi da più CPU
diverse.
2. Private Ready Queue → ogni CPU ha una propria coda di processi. Queste
code non dovranno essere sincronizzate, poichè sono proprietarie di ogni
CPU, ma viene introdotto il problema del bilanciamento del carico, in
quanto una CPU avrebbe potuto eseguire molti processi a discapito di CPU
che ne avrebbero eseguiti pochissimi.
28
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 29/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
Non sempre per un processo è però conveniente cambiare CPU dalla quale viene eseguito.
Infatti, un processo dovrebbe ripopolare di nuovo la cache in un’altra CPU, richiedendo così
maggior tempo di esecuzione. Pertanto, i sistemi operativi SMP cercano di evitare il più
possibile la migrazione di un processo da una CPU ad un’altra.
Un processo, in questo caso, al momento della creazione da parte del SO, può essere
catalogato come un processo che può essere eseguito su CPU diverse (soft affinity), o
come un processo che dovrà essere eseguito sempre dallo stesso processore (hard
affinity).
29
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 30/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
Scheduling in Linux
Quanto di tempo
Il quanto di tempo è un intervallo di tempo assegnato ad ogni processo in cui tale processo
viene eseguito dalla CPU. Tale intervallo di tempo è sempre un multiplo del timer interrupt
hardware di sistema. Man mano che un processo esegue sulla CPU, il suo quanto di tempo
viene consumato fino a quando non finisce. Il quanto di tempo viene riassegnato alla fine di
un’epoca, ovvero il momento in cui tutti i processi finiscono il loro quanto di tempo. Quando
un processo finisce il suo quanto di tempo, può verificarsi però che il processo stesso non
abbia finito la sua esecuzione, quindi necessita di essere eseguito ulteriormente dalla CPU.
30
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 31/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
Nel momento in cui finisce un’epoca e viene riassegnato il quanto di tempo, ai nuovi
processi viene assegnato il quanto di tempo base (ex. 210 ms), mentre a questi processi che
non hanno ancora finito la loro esecuzione, viene assegnato il quanto di tempo base più la
metà del suo tempo residuo che gli era rimasto alla fine della scorsa epoca. Questa
assegnazione di un quanto di tempo variabile consente l’implementazione di una priorità
dinamica (i processi con più tempo residuo saranno quelli con priorità maggiore).
Le due system call per cambiare il quanto di tempo base (solitamente di 210 ms) sono nice()
e set_priority(), che rispettivamente diminuiscono e aumentano il quanto di tempo.
I processi I/O intensive sono favoriti rispetto a quelli CPU intensive: infatti i primi, alla
scadenza di un epoca, avranno sempre un tempo residuo molto alto e di conseguenza
otterranno una priorità maggiore rispetto ai CPU intensive che finiscono subito il loro quanto
di tempo e devono aspettare la fine di un’epoca per ottenere un nuovo assegnamento.
Process Descriptor
Il process descriptor è una struttura dati che all’interno contiene le informazioni di un
processo di cui il SO deve essere a conoscenza per rendere la gestione dei processi più
efficiente.
Al suo interno contiene diversi campi:
- politica di scheduling → che può essere FIFO (solo per processi real time), RR (solo
per processi real time), OTHER che indica un processo convenzionale, e YIELD che
è un flag che indica se il processo ha rilasciato la CPU
- priorità → utilizzerà rt_priority per i processi real-time e priority per i processi
convenzionali
- counter → è il contatore del quanto di tempo rimasto, che diminuisce man mano che
il processo esegue in CPU
Funzione schedule()
E’ una funzione che confronta tra loro i vari candidati pronti ad essere eseguiti dalla CPU
con l’ultimo processo che è stato eseguito proprio dalla CPU. Nel caso in cui l’ultimo
processo eseguito dalla CPU abbia esaurito il suo quanto di tempo, la funzione schedule()
gli assegna un nuovo quanto e lo mette in fondo alla coda dei processi pronti per essere
eseguiti. Nel caso in cui il processo eseguito dalla CPU non abbia finito ancora la sua
esecuzione, gli viene data la possibilità tramite la funzione schedule() di essere nella lista dei
possibili candidati tra i processi che potranno essere scelti per essere eseguiti. Nel caso in
31
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 32/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
cui il processo eseguito dalla CPU non sia in stato STAK-RUNNING, la funzione rimuove il
processo dalla coda dei processi pronti per essere eseguiti. Una volta analizzato e
collocato l’ultimo processo eseguito dalla CPU, la funzione schedule() invoca la funzione
goodness() che esamina tutti i possibili processi che possono essere eseguiti dalla CPU, e
ne determina il migliore da mandare in esecuzione. La funzione schedule() può essere
invocata in due diverse modalità:
- invocazione diretta → tramite system call, semafori o richiesta di I/O
- invocazione “pigra” → eseguita da un processo che si trova nella coda dei processi
con need_resched = 1.
Scheduler SMP
Acronimo di Symmetric Multi Processor, è un’architettura nella quale ogni processore è
uguale agli altri, ciascuno dei quali ha una sua cache proprietaria e condividono memoria.
Ogni processore invoca la funzione schedule(), e in questa fase i diversi processori si
scambiano informazioni riguardo al proprio stato (se stanno eseguendo processi, o se sono
in stato di busy, …). Nel caso in cui ci sia un processo con alta priorità nella coda dei
processi del processore 1, ma il processore 1 sia impiegato nell’esecuzione di un altro
processo, tramite questo scambio di informazioni il processo con questa alta priorità
potrebbe venir eseguito da un altro processore. E’ buona norma comunque eseguire un
processo nello stesso processore in cui esso si trova in coda, per ridurre i problemi riguardo
la ripopolazione della cache: più è grande la cache, più tale tecnica è conveniente, in quanto
ripopolare la cache richiederebbe più tempo.
32
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 33/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
Implementazione
Viene introdotta nel PCB (Process Control Block) un nuovo campo vruntime che indica (in
nanosecondi) il tempo che tale processo ha passato in esecuzione sulla CPU. Un processo
con vruntime molto basso indica che ha più bisogno di essere eseguito dalla CPU. Per
raccogliere i processi che sono pronti ad essere utilizzati, non vengono utilizzate più code,
ma alberi rosso-neri, che hanno complessità di ricerca, inserimento e cancellazione pari a
O(logn), piuttosto che complessità lineare come nelle precedenti versioni. La chiave su cui si
basa l’ordinamento in questi alberi è il vruntime, e quindi il prossimo task da eseguire sarà
quello più a sinistra e più in basso dell’albero rosso-nero, cioè quello con vruntime minore.
Un processo con priorità più alta, accumulerà vruntime in maniera più lenta, mentre un
processo con priorità più bassa accumulerà vruntime in maniera più veloce. Ogni task viene
interrotto dal SO (preemption) non ogni quanto di tempo stabilito, ma ad intervalli variabili:
infatti, il processo che si trova più in basso e più a sinistra dell’albero rosso-nero viene
eseguito finchè un altro processo prende il suo posto nell’albero, e andrà quindi a
rimpiazzarlo sulla CPU.
33
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 34/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
Memoria principale
Binding
L’operazione di binding può avvenire in 3 diversi momenti:
- a tempo di compilazione → viene eseguita a tempo di compilazione dal compilatore.
E’ una tecnica utilizzata quando il compilatore conosce l’architettura hardware e
associa ogni variabile ad un preciso registro nella RAM. Il codice viene chiamato
assoluto, in quanto può essere eseguito solo nelle celle di memoria selezionate dal
compilatore e il SO non può spostare il processo da queste celle. Questa tecnica
preclude la multi-programmazione.
34
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 35/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
35
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 36/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
- worst-fit → alloco il processo nel buco più grande . E’ la tecnica peggiore perchè si
ha uno grande spreco di memoria.
Paginazione
Molto spesso, un processo viene allocato in memoria in maniera non contigua. Si dividono
gli indirizzi logici in blocchi della stessa dimensione (potenze di 2), chiamati pagine
(frammentazione interna), mentre si dividono gli indirizzi fisici in blocchi della stessa
dimensione chiamati frames. Per allocare ed eseguire un processo di n pagine, occorrerà
trovare n frames liberi nella RAM. Occorrerà quindi costruire una tabella delle pagine per
tradurre da indirizzi logici ad indirizzi fisici. Gli indirizzi logici generati si dividono in page
number e page offset: i primi rappresentano l’indice di una pagina all’interno della tabella
delle pagine che contiene il registro base nella memoria fisica reale, mentre i secondi,
combinati con la base, rappresentano l’indirizzo limite fisico inviato alla memoria centrale per
l’elaborazione.
Pagine condivise
Utilizzando la paginazione si può condividere del codice. Si ottiene quindi:
- codice condiviso → una copia del codice (in sola lettura)viene condivisa tra i processi
(come per esempio per i text editor, i compilatori, le finestre di sistema). Per questa
tecnica però il codice condiviso deve essere memorizzato nella stessa posizione
dello spazio logico per tutti i diversi processi che lo utilizzano.
36
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 37/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
- codice privato e dati privati → ogni processo ha la propria copia del codice e dei dati.
In questo caso, le pagine contenenti il codice possono essere memorizzate ovunque
nello spazio di memoria logico dei processi.
Paging gerarchico: lo spazio degli indirizzi logici viene diviso in più tabelle. Una tecnica
utilizzata per il paging gerarchico è la tabella a due livelli. In questo tipo di implementazione,
ci sono due diversi tipi di tabelle delle pagine: una tabella esterna e una interna. Ciascuna
entry della tabella delle pagine esterna punterà ad una nuova tabella delle pagine interna. Il
vantaggio di questo tipo di implementazione è che solo la tabella delle pagine esterna
necessita di essere completamente allocata, mentre la tabella delle pagine interne può
essere allocata quando c’è un riferimento ad essa.
37
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 38/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
Segmentazione
La segmentazione è uno schema di gestione della memoria che corrisponde alla vista della
memoria che generalmente ha l’utente. Un programma è un insieme di segmenti, con un
segmento che può essere un metodo, una funzione, un oggetto, un array, uno stack, …
Architettura di segmentazione
Un indirizzo logico è composta da una coppia di valori definiti dal compilatore
<segment-number, offset>.
La tabella dei segmenti è una tabella composta da entry, ciascuna composta da una base,
che contiene l’indirizzo fisico in cui il segmento è memorizzato, e un limite che indica la
lunghezza del segmento. La tabella dei segmenti è indicata anch’essa dal registro base
(SegmentTableBaseRegister) STBR che indica la locazione in memoria effettiva della
38
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 39/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
Ciascuna entry della tabella dei segmenti ha un bit di validazione e un campo che indica i
privilegi (lettura/scrittura/esecuzione).
Segmentazione AMD64
Questo tipo di architettura definisce un indirizzo virtuale di 64bit, dei quali solo 48 però sono
utilizzati, in quanto i SO e le applicazioni non necessitano di tale quantità di memoria. I bit
non utilizzati (dal 48 al 67), replicano l’informazione del bit 47. Gli indirizzi che rispettano
questa forma vengono detti in forma canonica. Questa forma permetterebbe di avere uno
spazio di indirizzi logico di 256TB, che è comunque molto maggiore dei 4GB delle macchine
ad architettura a 32 bit. Questa implementazione è utile in quanto può essere facilmente
scalata ad architetture a 64bit effettivi (e non ai soli 48 utilizzati ora) e permette di dividere lo
spazio RAM in una parte alta in cui memorizzerà i processi relativi al kernel e una parte
bassa in cui memorizzerà i processi dell’utente.
39
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 40/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
Memoria virtuale
La memoria virtuale di un processo è la vista logica di come il processo è memorizzato in
memoria. Serve per ampliare la memoria principale (RAM) tramite l’utilizzo di altre memorie
(solitamente le unità disco), in quanto solamente una parte del processo deve essere
memorizzato nella memoria principale per permetterne l’esecuzione. Questa tecnica può
essere implementata tramite due architetture:
- paginazione su richiesta
- segmentazione su richiesta.
Paginazione su richiesta
Questa tecnica consiste nel caricare in memoria principale una pagina solamente quando
viene richiesto un riferimento a tale pagina. In questo caso si risparmia memoria e si ha una
esecuzione più efficiente. Se il riferimento non è valido, la pagina non viene caricata, se il
riferimento risulta essere invece valido e la pagina non è già caricata in memoria, la pagina
richiesta viene caricata. Si utilizza il lazy swapper, chiamato così poichè non carica tutte le
pagine del processo subito in memoria, ma ne carica una ad una quando una determinata
pagina viene richiesta. Ciascuna entry nella tabella ha anche un questo caso un bit di
validazione: i indica che la pagina non è in memoria, v indica invece che la pagina è in
memoria. Il primo riferimento ad una pagina crea una trappola al SO chiamata page fault,
che può indicare diverse situazioni come una pagina non caricata in memoria, o una pagina
vuota. Nel caso in cui la pagina richiesta non risieda in memoria, il sistema operativo cerca
la pagina richiesta nella memoria secondaria (ex. disco ottico), la carica in memoria, ed
effettua il reset della tabella delle pagine e fa ripartire l’istruzione che questa volta troverà
nella memoria virtuale la pagina corrispondente.
In tutto questo, mentre viene cercata la pagina in memoria, la CPU viene assegnata ad altri
processi. Nel momento in cui arriva un interrupt da parte di un dispositivo I/O (in questo caso
dal disco in cui la pagina era memorizzata), la CPU salva lo stato dell’altro processo che
stava eseguendo. Il SO quindi, una volta determinata la natura dell’interrupt, corregge la
tabella delle pagine in modo che il vecchio processo ora richiederà una pagina che è
40
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 41/42
27/5/2019 Rissunto Sistemi Operativi - Documenti Google
presente in memoria. A questo punto, la CPU potrà essere riallocata al vecchio processo
che ripartirà dalla vecchia istruzione che era stata interrotta per mancanza della pagina
richiesta in memoria.
41
https://docs.google.com/document/d/1yG4rCedxEB99KMMpslmbZG03cJzNW8H4sWUtKpLRKp0/edit#heading=h.k2twfbn202ja 42/42