Il sistema operativo è un programma che si occupa di gestire gli altri programmi creando un’interfaccia tra hardware e software.
Il sistema operativo nel tempo si evolve venendo aggiornato per supportare nuovi tipi di hardware, nuovi servizi e per correggere degli errori.
I primi calcolatori utilizzavano delle valvole per la gestione delle informazioni, non avevano un sistema operativo e il controllo era tramite console. Avevano enormi problemi di schedulazione in quanto ogni cosa doveva essere gestita dal programmatore (non c’era un sistema operativo) e occorreva un lungo tempo di preparazione
Iniziano ad essere utilizzati i transistor e il sistema era gestito da un operatore. I lavori erano organizzati in lotti inseriti da un dispositivo di ingresso (es: schede forate) che vengono caricate da un monitor che ordina il caricamento dei programmi e delega l’esecuzione a un’altro programma. Una volta terminata l’esecuzione il controllo della macchina torna al monitor.
Ci sono nuove funzioni hardware, la memoria non è più solo composta da una serie di istruzioni da eseguire in sequenza, ma ci sono nuove funzioni che generano notevoli cambiamenti all’interno del processore, come l’interruzione dell’esecuzione, il salto di un’istruzione ecc…
L’hardware è composto da circuiti integrati fatti di silicio che connette insieme più dispositivi, rendendo possibile la multiprogrammazione.
Il dispositivo doveva gestire più lavori interattivi, con processore condiviso tra più utenti che accedono attraverso terminali e l’esecuzione del programma di ogni utente è alternata uno alla volta per un breve lasso di tempo.
La multiprogrammazione ottimizza l’utilizzo del processore, a differenza del time sharing il cui obiettivo è minimizzare i tempi di risposta.
I computer vengono integrati su larga scala e viene costruita un’interfaccia amichevole per l’uso da parte di utenti non esperti.
Gli smartphone, dispositivi con autonomia limitata che devono quindi avere una gestione della batteria appurata.
Il sistema di elaborazione è composto da:
Il processore preleva dalla memoria centrale istruzioni e dati, esegue le istruzioni, scrive il risultato in memoria centrale e passa all’istruzione successiva.
Memory address register è dove viene salvata la posizione in memoria dei dati
Memory buffer register è dove viene salvata l’istruzione prima di essere caricata nell’IR
composto da un singolo chip che contiene l’equivalente di numerosi transistor. Nei calcolatori moderni è in realtà un multiprocessore, ovvero sullo stesso chip sono presenti più cores che possono agire l’uno indipendentemente dall’altro.
è dotato di cache, una memoria interna al processore che è molto più veloce anche della memoria centrale.
Esegue le operazioni su un array di più dati con la tecnica SIMD (Single Instruction Multiple Data) e può essere usato anche per applicazioni non grafiche
Si occupa di codificare i segnali audio e video e supporta la crittografia
Tutti i componenti sono su un chip singolo (Es: smartphone)
Nei primi sistemi il processore estrae l’istruzione dalla memoria, la esegue e ripete con la successiva fino a che le istruzioni non finiscono.
Permettono agli altri modulo di interrompere il ciclo fetch-execute, per evitare che la cpu resti inattiva nell’attesa di I/O e permette di sospendere l’esecuzione del programma.
Sono contenuti i programmi da eseguire, composti da istruzioni e dati. è compreso il sistema operativo.
I moduli di input e output forniscono dati per il processo, ma sono molto più lenti della memoria centrale
Possiamo immaginare che il nostro sistema operativo realizzi delle macchine virtuali, ciascuna dotata di propria memoria e sistema operativo.
Il sistema operativo deve mettere a disposizione dei programmi una serie di servizi necessari all’esecuzione.
Sono sistemi non divisi in moduli e quindi era tutto un grosso programma
Ogni stato implementa un oggetto astratto composto da dati e operazioni, i dettagli dell’implementazione vengono nascosti agli strati superiori che richiedono solamente dei servizi in attesa di un risultato. Questo permette di rendere molto facile la programmazione di ogni strato e la sua portabilità, ma bisogna definire quali sono gli strati e potrebbero esserci degli overhead.
2
I processi sono caratterizzati dalle risorse allocate e dallo scheduling:
Ma le due caratteristiche possono essere trattate in maniera indipendente.
In un ambiente multi-thread, il processo è l’unità di allocazione delle risorse e un’unità di protezione.
Sono associati al processore:
Unità che viene assegnata al processore. Sono associati ai thread:
Si dice multithreading la capacità di un sistema operativo di permettere più tracce di esecuzione concorrenti in un singolo processo, ci sono quattro casi:
La creazione e l’eliminazione di un thread sono più rapide rispetto a un processo, così come lo switch tra due thread dello stesso processo rispetto a due processi diversi.
La comunicazione tra programmi diversi è più efficiente (diversi thread nello stesso processo)
In un sistema a utente singolo si permette l’esecuzione sia in foreground che in background, elaborazione asincrona con un’alta velocità di esecuzione.
I programmi sono più modulari.
La schedulazione è gestita principalmente a livello di thread. Ma non la sospensione (memory swap) e la terminazione.
I thread di un processo condividono spazio di indirizzamento e altre risorse, ogni modifica delle risorse influenza gli altri thread.
La modifica simultanea di un dato da parte di due thread può corrompere il dato medesimo. È necessario quindi sincronizzare l’accesso dei thread alle risorse
La gestione dei thread è fatta dall’applicazione stessa, quindi il kernel non sa dell’esistenza dei thread.
La gestione dei thread è fatta dal kernel e l’applicazione utilizza un’API per la gestione dei thread.
Il kernel gestisce quindi tutti i PCB dei processi e dei thread e la schedulazione del processore si basa su tutti i PCB.
Multiprogrammazione: molti processi, un singolo processore
Multiprocessing: molti processi, un multiprocessore
Processi Distribuiti: molti processi, molti processori distribuiti (cluster)
La concorrenza appare in tre differenti contesti
**Elaborazione concorrente **
**Difficoltà **
![[Pasted image 20230321112302.png]]
![[Pasted image 20230321112321.png]]
![[Pasted image 20230321112333.png]]
È necessario proteggere le variabili condivise
SI deve controllare il codice che accede alla variabile
Race condition: Si verifica quando più processi o threads leggono e scrivono dati in modo che il risultato finale dipende dall’ordine di esecuzione delle istruzioni dei processi
I processi $P1$ e $P2$ assegnano rispettivamente $1$ e $2$ alla variabile condivisa a. Il valore finale sarà quello del ‘secondo arrivato’.
I processi P1 e P2 condi.idono le variabili b e c con valori iniziali $b = 1, c = 2$. $P1$ esegue $b = b + c$, $P2$ esegue $c = b + c$. Se arriva primo $P1$, avremo $b = 3, c = 5$. Nel caso opposto, $b = 4, c = 3$.
SOno compiti del OS:
![[Pasted image 20230321112750.png]]
Processi concorrenti sono in conflitto per l’uso delle risorse
Problemi
/* PROCESS 1 */
void P1 {
while (true) {
/* preceding code */;
entercritical (Ra);
/* critical section */;
exitcritical (Ra);
/* following code */;
}
}
/* PROCESS 2 */
void P2 {
while (true) {
/* preceding code */;
entercritical (Ra);
/* critical section */;
exitcritical (Ra);
/* following code */;
}
}
Due o più processi che interagiscono senza conoscersi usano e modificano dati condivisi. Devono cooperare per la corretta gestione dei dati utilizzando un meccanismo di controllo che deve assicurare l’integrità dei dati, data la presenza di problemi di mutua esclusione, stallo, starvation
P1:
a = a + 1;
b = b + 1;
P2:
b = 2 * b;
a = 2 * a;
ordine:
a = a + 1;
b = 2 * b;
b = b + 1;
a = 2 * a;
I processi collaborano per un obiettivo comune, la comunicazione permette di sincronizzarsi e coordinarsi attraverso delle primitive per invio e ricezione di messaggi che sono fornite dal kernel o dal linguaggio di programmazione.
Questo permette di non richiedere la mutua esclusione ma può determinare stallo o starvation.
Su uniprocessore, assicura che la sezione critica sia portata a termine senza interruzioni:
while (true) {
/* disabilita interruzioni */;
/* sezione critica */;
/* abilita interruzioni */;
/* resto del programma */;
}
Un solo processore alla volta può accedere a una locazione di memoria, esistono quindi speciali istruzioni macchina che eseguono due operazioni su un dato in maniera atomica
int compare_and_swap ( int *word, int testval, int newval) {
int oldval;
oldval = *word
if (oldval == testval) *word = newval;
return oldval;
}
/* program mutualexclusion */
const int n = /* number of processes */;
int bolt;
void P( int i) {
while (true) {
while (compare_and_swap(bolt, 0, 1) == 1)
/* do nothing */;
/* critical section */;
bolt = 0;
/* remainder */;
}
}
void main() {
bolt = 0;
parbegin (P(1), P(2), ... ,P(n));
}
/* program mutualexclusion */
int const n = /* number of processes */;
int bolt;
void P( int i) {
int keyi = 1;
while (true) {
do exchange (&keyi, &bolt)
while (keyi != 0);
/* critical section */;
bolt = 0;
/* remainder */;
}
}
void main() {
bolt = 0;
parbegin (P(1), P(2), ..., P(n));
}
Un protocollo che permetta a due processi di assicurare la mutua esclusione
Una variabile con valore intero, una coda e tre operazioni
title: Osservazione
Il processo non ha modo di sapere in anticipo
- se con l’esecuzione di semWait sarà sospeso
- se c’è un processo che attende l’esecuzione di semSignal
- quale fra i processi in attesa sarà riattivato da semSignal
struct semaphore {
int count;
queueType queue;
};
void semWait(semaphore s) {
s.count--;
if (s.count < 0) {
/* place this process in s.queue */;
/* block this process */;
}
}
void semSignal(semaphore s) {
s.count++;
if (s.count<= 0) {
/* remove a process P from s.queue */;
/* place process P on ready list */;
}
}
Una variabile con valore binario (0 o 1), una coda e tre operazioni
struct binary_semaphore {
enum {zero, one} value;
queueType queue;
};
void semWaitB(binary_semaphore s) {
if (s.value == one)
s.value == zero;
else{
/* place this process in s.queue */;
/* block this process */;
}
}
void semSignal(semaphore s) {
if (s.queue is empty())
s.value = one;
else{
/* remove a process P from s.queue */;
/* place process P on ready list */;
}
}