Studia grazie alle numerose risorse presenti su Docsity
Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium
Prepara i tuoi esami
Studia grazie alle numerose risorse presenti su Docsity
Prepara i tuoi esami con i documenti condivisi da studenti come te su Docsity
Trova i documenti specifici per gli esami della tua università
Preparati con lezioni e prove svolte basate sui programmi universitari!
Rispondi a reali domande d’esame e scopri la tua preparazione
Riassumi i tuoi documenti, fagli domande, convertili in quiz e mappe concettuali
Studia con prove svolte, tesine e consigli utili
Togliti ogni dubbio leggendo le risposte alle domande fatte da altri studenti come te
Esplora i documenti più scaricati per gli argomenti di studio più popolari
Ottieni i punti per scaricare
Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium
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
4 / 20
Questa pagina non è visibile nell’anteprima
Non perderti parti importanti!
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.
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:
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
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.
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
Contiene informazioni di cui il SO ha bisogno per controllare e coordinare i vari processi attivi.
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:
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
Per creare un processo in UNIX, prima viene effettuata la chiamata di sistema fork(), poi il SO in kernel mode:
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.
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:
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.
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:
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
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
Gestione dell'I/O
Definiamo tre categorie di dispositivi:
I dispositivi differiscono in:
Effettuare l'I/O
Ci sono 3 metodi:
Gerarchia di un dispositivo di I/O
Sistemi di buffering
Senza buffer, il SO accede al dispositivo nel momento in cui ne ha necessità.
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:
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.
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:
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.
Requisiti per la mutua esclusione
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
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.
Controllo di accesso
Determina quali tipi di accesso sono ammessi, sotto quali circostanze e da chi.
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
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: