SISTEMI OPERATIVI
Il sistema operativo serve come interfaccia tra livello applicativo ed hardware, non compie
operazioni apparentemente utili ma fornisce un ambiente per il corretto funzionamento dei
programmi.
Funzioni e servizi base
- Controllo flusso ed esecuzione dei programmi
- Allocazione risorse e gestione
memoria
- Accesso a dispositivi I/O
- Comunicazione tra processi
- Gestione errori e malfunzionamenti
- Protezione
- Gestione statistiche e raccolta dati
Componenti base SO
- Gestore dei processi
- Gestore della memoria
- Gestore della memoria secondaria
- Gestore dei sistemi di I/O
- Gestore dei file
- Gestore della comunicazione di rete
- Gestore della sicurezza
- Interprete dei comandi
Tipi di sistema
- sistemi paralleli => massimizzano throughput (multiprocessore / multicore / distribuiti)
1
si può simulare con sistemi time-sharing 2
- Sistemi real-time => forniscono risultati entro il tempo prestabilito, possono essere
HARD real time o SOFT real time (embedded / sistemi di controllo)
SISTEMI MULTIPROCESSORE SISTEMI MULTICORE
Nel caso di processori con multithread fisico si ha un unico chip con una sola ALU ma 2
set di registri separati che permettono l'esecuzione quasi simultanea.
throughput = (produttività) capacità elaborativa in un tempo determinato
1 time-sharing = possiedono scheduler che assegna quanti di tempo limitati ad ogni processo
2 1
SISTEMI CLOUSTER Tipi di connessione
- Client-server => ruoli dispositivi
ben definiti, un server ed n clients
che si connettono ad esso
- Peer-to-peer => ogni dispositivo
è sia server che client
Architetture di base
- Architettura a strati => ogni strato fornisce funzionalità allo strato superiore
nascondendo quelle del livello inferiore
- Architettura microkernel => kernel comprende solo funzioni essenziali (scheduling /
gestione interruzioni / gestione memoria / comunicazione tra processi), tutte le altre
funzionalità sono trattate come processi
OSS Sia UNIX che windows NT usano architetture intermedie incentrate su un kernel 3
monolitico che implementa gli altri moduli dinamicamente.
Unix ha architettura base a strati.
Windows ha architettura base microkernel, inoltre è multithread nativo.
Standards
- Standard di linguaggio (API ) => insieme delle funzioni di libreria che rimangono
4
invariate al variare del sistema, permettono portabilità su piattaforme differenti
(ex. ANSI-C)
- Standard di sistema (ABI ) => insieme di servizi per la programmazione su una
5
determinata famiglia di sistemi (POSIX per UNIX-LIKE , WINAPI per WINDOWS)
6
Essendo POSIX gestito da open-group, che vende le licenze, linux usa uno
standard simile chiamato LSB (linux standard base)
Gli standard forniscono anche le funzioni preambolo per le System call.
kernel = nucleo del sistema operativo contente tutte le funzioni essenziali
3 API = application programming interface, librerie tra app e OS
4 ABI = application binary interface, convenzioni di sistema, interazione tra OS e Hardware
5 Certificazione POSIX appartiene a OPEN-GROUP, linux non è certificato posix
6 2
Flusso di controllo eccezionale
Il flusso normale di un programma può essere alterato da:
- Interrupt (asincrone)=> eventi generati all’esterno della cpu (I/O / segnali )
- Exception (sincrone)=> eventi generati dalla cpu (trap / fault / abort )
7 8 9
Al verificarsi di uno di questi si utilizza il codice del sistema operativo, quindi cambia
modalità utente modalità
anche il modo di esecuzione, si passa da (mode bit = 1) a
kernel (mode bit = 0) che permette di usare tutte le istruzioni di cui è dotato il sistema.
Per sapere quale programma del kernel bisogna utilizzare il sistema operativo fa
vector”,
riferimento al suo “interrupt un array contenuto nel kernel con tutti i descriptor
(linux) o handle (Windows) alle routine di sistema.
Implicano un context switch che utilizza una IRET per il ritorno. (vedere più avanti)
System call
Sono chiamate ad una routine di sistema operativo tramite il supporto delle trap.
Possono essere utilizzate direttamente tramite programmazione con Assembly mediante
$0x80”
l'istruzione “int che permette di accedere al "interrupt vector”, verrà eseguita la
routine corrispondente al numero contenuto nel registro %?a? utilizzando come parametri
le informazioni contenute nei registri subito dopo.
Altrimenti possono essere implementate in c (o altro linguaggi di alto livello) tramite uno o
wrapper
più (involucri), ovvero funzioni che avvolgono a loro volta altre funzioni fino ad
arrivare al codice Assembly per la syscall. (ex. printf())
I wrapper fanno sembrare le System call normali funzioni di libreria e combinati con
l'utilizzo dell'ambiente di esecuzione permettono la portabilità del codice, la syscall in se
è però machine dependent.
Ambiente di esecuzione
Insieme dei moduli aggiuntivi (import librerie e simili) inseriti nel codice dal linker in fase di
compilazione del programma, cambia da un sistema operativo all’altro.
Serve come raccordo tra il kernel ed il main del programma, permette la portabilità del
codice ricompilando sul nuovo sistema compatibile.
Ex: nei sistemi linux il main richiama la funzione “_start()” che effettua il setup e ne
permette l’esecuzione.
trap = istruzione macchina per il passaggio del controllo al kernel (usato per system call)
7 fault = evento generato in condizione di errore, recuperabile (ex. Page fault)
8 abort = evento raro non recuperabile, provoca terminazione del processo (ex. Prob hardware)
9 3
I processi
Entità attiva che rappresenta il programma in esecuzione, 2 o più processi possono
condividere lo steso programma.
I processi vengono creati da altri processi come loro copie, il processo che crea il nuovo
processo viene detto padre, il processo creato figlio.
Si crea quindi una struttura ad albero dei processi (in linux il primo processo è init).
Stati di un processo
New
1. => fase di creazione del processo
Ready
2. => il processo è pronto ed aspetta di essere assegnato alla CPU dallo
scheduler per entrare in esecuzione
Running
3. (USER / KERNEL) => il processo è in esecuzione nella CPU
OSS il numero massimo dei processi in stato running è dato dal numero delle CPU
Waiting
4. => il processo rimane in attesa di un evento di I/O
Terminated
5. => il processo termina e rilascia le risorse utilizzate (funzione exit())
OSS Quando un processo termina, se lo stato di terminazione (dato dalla funzione exit())
non può essere raccolto dal padre, entra
Zombie,
nello stato di il suo stato di
terminazione verrà quindi salvato in un
record del kernel finché un altro
programma lo raccoglierà e chiuderà. <- sp (stack pointer)
Immagine in memoria
Ad ogni nuovo processo che entra in
esecuzione viene associata una immagine
di memoria virtuale corrispondente al suo <- brk
spazio di indirizzamento.
Lo spazio di memoria si divide in:
- text => codice del programma
(execute and read only)
- Data => variabili globali, static e
stringhe (stringhe read only)
- Heap => memoria allocata
dinamicamente
- Stack => dati delle funzioni, … 4
PCB (process control block)
Contiene tutte le informazioni per la gestione del processo contenuta nel kernel, differente
dall'immagine di memoria ma contiene riferimenti ad essa.
È composto da:
- PID (process ID) e PPID (parent process ID)
- Puntatori ad immagine di memoria
- Privilegi del processo
- Stato del programma
- PC (program counter)
- priorità ed informazioni sullo scheduling
- Tabella file processo ed I/O
- Area salvataggio registri
Creazione ed esecuzione processo in unix (versione base)
#include <unistd.h >
10
#include <stdio.h>
int main() {
pid_t pid = fork() ;
11
if (pid < 0) { /* -1 => error occurred */
printf("Fork Failed");
exit(1);
}
else if (pid == 0) { /* child process */
execlp("/bin/ls", "ls", NULL) ; /* execute program “ls" */
12
_exit(0) ;
13
}
else { /* parent process */
wait (NULL) ;
14
printf ("Child Complete");
}
return 0; /* in main equivalente a exit()*/ pid_t getpid() => restituisce pid processo
} vfork() => figlio usa stesso Address space del padre
WARNING se si inserisce fork() in ciclo
per generare più processi è essenziale status);”
terminare il codice figlio con “break;” o “_exit( per evitare generazione infinita di
processi (data dal “goto” alla fine del ciclo)
<unistd.h> => libreria contente funzioni fork(), wait(), exec()…
10 pid_t fork( ) => permette di creare nuovo processo come copia del padre ma con pid e
11 immagine di memoria differenti, tutte le operazioni effettuate da adesso in avanti nella
sezione del figlio non influenzeranno il padre (e viceversa)
int exec => famiglia di funzioni che permettono al processo di eseguire un nuovo programma
12 passato come argomento, possibile consultare famiglia exec su OPEN-GROUP
void _exit( int status) => versione con trattino permette di mantenere canali I/O aperti siccome
13 processo padre e figlio usano gli stessi, status = stato di ritorno
pid_t wait( int *stat_loc) => costringe processo padre ad aspettare terminazione figlio evitando
14 generazione processi zombie. Possibile salvare stato di ritorno del figlio su una variabile
passandola come argomento stat_loc.
Se processo ha più figli si usa funzione “waitpid()“ per selezionare processo desiderato 5
Threads process).
Un thread è definito come un processo leggero (lightweight
I thread appartenenti allo stesso processo, pur eseguendo operazioni differenti, utilizzano
lo stesso spazio di memoria.
Rendono quindi possibile:
- Condivisione della memoria dentro lo stesso task
15
- Multiprogrammazione e scalabilità
16
- Esecuzione indipendente dei singoli thread, un thread può non essere bloccante per
altro thread (Ex. grafica separata da elaborazione dati / richieste multiple su server)
Programmazione singolcore vs multicore
La programmazione multithread permette quindi di sfruttare la concorrenza nei
programmi, si può implementare in due modi a seconda del tipo di architettura :
- Esecuzione concorrente => si utilizza in sistemi singolo core, illusione di parallelismo
data dall’esecuzione alternata dei thread in quanti di tempo (interleaved)
- Esecuzione parallela => utilizzabile in sistemi multicore, permette di assegnare ad
ogni core uno o più threads ed eseguirli contemporaneamente
2 tipi di parallelismo
- Data parallelism =>
suddivisione sottoinsiemi dei
dati su più core per eseguire
più velocemente stesse
operazioni
- Task parallelism =>
suddivisione dei threads su
ogni core, ognuno esegue le
sue operazioni
task = insieme di threads appartenenti allo stesso processo
15 scalabilità = esecuzione parallela su più processori (o core)
16 6
Strategie creazione threads
- Threading sincrono => (fork-join) genitore dopo aver creato uno o più figli aspetta la
loro terminazione e conseguente join prima di continuare la sua esecuzione.
(Ex. somma vettore)
- Threading asincrono => dopo aver creato thread figlio, genitore riprende la sua
normale esecuzione e non ha bisogno di conoscere quando termina il figlio.
(Ex. sever)
Categorie di thread
- kernel level thread (KLT) => implementazione a livello kernel, scheduling più lento a
causa del passaggio per il kernel ma i threads sono completamente indipendenti ed il
blocco di uno non implica in blocco degli altri.
- User level thread (ULT) => implementazione a livello di applicazione, il kernel non è a
conoscenza della loro esistenza, quindi scheduling più veloce ma nel caso di syscall
bloccante su un thread si bloccano tutti.
Modelli di multithreading
da uno a molti
1. => più user threads mappati su un solo kernel thread, gestione
efficiente risorse, non è però possibile usare parallelismo ed il blocco di uno provoca
provoca il blocco di tutti. Usato su pochi sistemi.
Da uno ad uno
2. => ogni user thread è mappato su un kernel thread, permette
parallelismo e chiamate non sono bloccanti su altri threads, ha lo svantaggio di dover
creare un kernel thread per ogni user thread (threads limitati per evitare overhead ).
17
Usato in linux e windows.
Da molti a molti
3. => più user threads vengono mappati su un numero minore o uguale
di kernel threads, permette di creare un numero maggiore di user threads rispetto al
modello uno ad uno potendo rimappare gli ULT (necessita di LWP)
1 3
2
overhead = richiesta risorse maggiore di quella realmente necessaria
17 7
Pthreads POSIX I sistemi unix non fanno
#include <stdio.h>
#include <stdlib.h> distinzione tra processi e threads.
#include <pthread.h> Infatti posso anche usare syscall
clone() invece di pthread_create()
void *print_message_function( void *ptr ) {
char *message;
message = (char *) ptr;
printf("%s \n", message);
}
main() {
pthread_t thread1, thread2;
const char *message1 = "Thread 1";
const char *message2 = "Thread 2";
int iret1, iret2; /*permettono di controllare risultato, 0 in caso di successo*/
iret1 = pthread_create( &thread1, NULL, print_message_function, (void*) message1) ;
18
if(iret1)
{ fprintf(stderr,"Error - pthread_create() return code: %d\n",iret1);
exit(EXIT_FAILURE);
}
iret2 = pthread_create( &thread2, NULL, print_message_function, (void*) message2);
if(iret2)
{ fprintf(stderr,"Error - pthread_create() return code: %d\n",iret2);
exit(EXIT_FAILURE);
}
printf("pthread_create() for thread 1 returns: %d\n",iret1);
printf("pthread_create() for thread 2 returns: %d\n”,iret2);
pthread_join( thread1, NULL) ;
19
pthread_join( thread2, NULL);
exit(EXIT_SUCCESS);
} int pthread_detach( phtread_t thread) => utilizzabile al posto
di join, notifica che non verranno eseguite
funzioni ritornano come operazioni di join sul thread
valore 0 in caso di
successo, altrimenti la void pthread_exit (void* value_ptr) => termina thread corrente
causa di errore.
OSS se uso funzione famiglia fork in processo multithread in molti sistemi unix posso
scegliere se replicare tutti i thread nel nuovo processore oppure no, se uso exec elimina
tutti i thread già presenti. DA EVITARE ENTRAMBE
int pthread_create( * ,
18 phtread_t* const pthread_attr_t void* (* )(void*) void*
thread, attr, start_routine arg);
- thread => puntatore variabile pthread_t dove memorizzare id
- Attr => attributo di creazione (spesso NULL)
- start_routine => funzione da eseguire (deve essere void)
- Arg => puntatore ad argomenti della funzione, spesso vettore allocato dinamicamente
int pthread_join(phtread_t thread, void** value_ptr) => attende terminazione esplicita
19 thread con id passato come argomento 8
Extra threads
Threading implicito
Consiste nel delegare creazione e gestione dei thread a compilatori e librerie di routine.
- thread pools (gruppi) => Si crea un gruppo di threads durante la creazione del
processo che attendono di eseguire un determinato servizio.
Finito il lavoro il thread torna in attesa, se non ci sono threads disponibili il servizio
aspetta.
Non bisogna attendere creazione threads durante esecuzione processo e si evita
overhead dato dalla creazione di troppi threads
OSS numero threads potrebbe cambiare durante esecuzione
- OpenMP => insieme di direttive per il compilatore che si inseriscono nel codice e
permettono esecuzione parallela tramite librerie runtime openMP
(Ex. #pragma omp parallel => crea più thread possibili su architettura ed esegue)
- grand central dispatch => versione proprietaria per sistemi Apple simile a openMP e
basata su pthread POSIX…
Thread-local storage (TLS)
Permette di allocare variabili visibili solo al thread, uniche per ogni thread.
Differiscono da variabili locali poiché visibili in tutto il thread e non solo nella funzione in
cui sono invocate.
Processori virtuali (LWP) e scheduling threads
Necessari per implementazione da molti a molti dinamica, il sistema operativo crea
processori virtuali intermedi che dialogano tra kernel threads e user threads.
Ad ogni LWP sono associati vari threads, al blocco di uno si bloccano anche gli altri .
Sono essenziali per lo scheduling in sistemi multithread che può avvenire in 2 modi:
- process-contention scope (PCS) => ambito della cortesia rispetto al processo,
ovvero la mappatura su LWP libero viene fatta in ambito utente tra i threads dello
stesso processo.
- System-contention scope (SCS) => ambito della cortesia allargato al sistema, ovvero
la mappatura su LWP viene fatta dal kernel e comprende tutti i threads presenti nel
sistema
OSS È possibile personalizzare le priorità di scheduling a livello utente
OSS linux e mac OS X permettono solo SCS 9
Lo scheduler
Si occupa della commutazione della CPU da un processo all’altro, funzione alla base dei
sistemi operativi multiprogrammati, questi permettono infatti di mantenere in memoria più
processi rispetto al numero di core disponibili.
Quando un processo entra in stato di wait (I/O) lo scheduler gli sottrae il controllo della
CPU e la assegna ad un altro processo in stato ready.
Fasi cicliche di un processo
- CPU burst => fasi di elaborazione della CPU
- I/O burst => fasi di attesa dell’I/O
I programmi si possono dividere un due tipologie a secondo del
tempo impiegato in ogni burst, è importante osservare la durata
dei cic
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
-
Sistemi operativi - Appunti
-
Appunti di Sistemi operativi
-
Appunti Fondamenti di Informatica teoria - sistemi di operativi
-
Appunti Sistemi operativi