...

nucleo - Dipartimento di Ingegneria dell`Informazione

by user

on
Category: Documents
7

views

Report

Comments

Transcript

nucleo - Dipartimento di Ingegneria dell`Informazione
NUCLEO
G. Frosini
Slide 1
NUCLEO MLTIPROGRAMMATO (1)
•
Multiprogrammazione col processore PC:
– un processore reale, più processori virtuali;
– memoria virtuale paginata;
– pagine con livello di privilegio (o stato) utente e sistema.
•
Processo:
– livello di privilegio proprio (o stato) utente o sistema;
– spazio di indirizzamento:
• comune a tutti i processi e privato per ciascun processo.
– processo utente:
• spazio comune e spazio privato parte a livello di privilegio sistema e parte
a livello di privilegio utente;
– processo sistema:
• spazio comune e spazio privato tutti a livello di privilegio sistema;
– spazio comune a livello di privilegio sistema:
• routine e strutture dati del nucleo del sistema operativo.
G. Frosini
Slide 2
NUCLEO MLTIPROGRAMMATO (2)
•
Meccanismo di interruzione:
– un processo utente può portarsi in stato sistema, per poi tornare nello
stato iniziale eseguendo una IRET.
•
Processo caratterizzato da:
– descrittore;
– corpo (programma che ne definisce le elaborazioni), costituito da codice e
area dati privata;
– area dati comune.
•
Descrittore di processo:
– si trova nello spazio comune a livello di privilegio sistema;
– contiene, fra l’altro:
• se trattasi di un processo utente, il valore iniziale del puntatore della pila
sistema;
• il contenuto del registro CR3;
• il contenuto dei registri generali del processore (fra cui il puntatore di
pila).
G. Frosini
Slide 3
NUCLEO MLTIPROGRAMMATO (3)
•
Codice di un processo:
– può essere condiviso da più processi con lo stesso livello di privilegio;
– deve trovarsi nello spazio comune (faremo sempre questa ipotesi).
•
Area dati privata:
– contiene le pile per i due livelli di privilegio se trattasi di un processo
utente, o la pila se trattasi di un processo sistema.
– ipotesi: area dati privata costituita interamente dalle pile.
•
Area dati comune:
– informazioni comuni;
– scambio di informazioni.
G. Frosini
Slide 4
PROPRIETA’ DEL C++
•
Linguaggio C++:
– una funzione, anche con parametri formali, può costituire il modello le cui istanze
specificano le azioni compiute dai vari processi;
– brevemente:
• il codice di una funzione può essere condiviso tra più processi;
• ciascun processo prevede un’istanza della funzione, con i propri parametri attuali e
la propria copia delle variabili locali;
– il codice è memorizzato nella zona testo (.text) comune a tutti i processi;
– i parametri attuali e le variabili locali (variabili automatiche) sono contenuti nella
pila del livello di privilegio proprio di ciascun processo;
– la zona dati (.data) comune a tutti i processi, contiene le variabili comuni (variabili
statiche), definite al di fuori delle funzioni.
•
Zona testo e zona dati:
– comuni a tutti i processi, e differenti per ogni livello di privilegio.
•
Pile:
– differenti da processo a processo;
• un processo ne ha due (se processo utente) o una (se processo sistema).
•
Corpo di un processo:
– istanza della funzione modello;
– per semplicità, funzione void con un argomento di tipo intero .
G. Frosini
Slide 5
OPERAZIONI SUI PROCESSI
•
Processi:
– iniziano e terminano.
•
Processo che inizia:
– vengono creati il suo descrittore e le sue pile (la sua pila se il processo ha
livello di privilegio proprio sistema);
– il codice può venir creato, oppure, se condiviso, può già esistere.
•
Processo che termina:
– le prime due entità vengono distrutte;
– il codice viene distrutto solo se non condiviso.
•
Altre operazioni su un processo:
– coinvolgono il suo descrittore;
– per questo motivo il descrittore assume un ruolo fondamentale, e spesso
viene a confondersi con il processo che rappresenta.
G. Frosini
Slide 6
COMMUTAZIONE DI CONTESTO (1)
•
Evento che produce cambiamento del processo in esecuzione
(commutazione di contesto):
– interruzione (hardware o software).
•
Ipotesi:
– la commutazione avviene attraverso routine con livello di privilegio sistema (via
software);
– il gate coinvolto da queste routine è di tipo interrupt: vengono memorizzati in pila
sistema i contenuti di EFLAG, CPL ed EIP, che non prevedono spazio nel
descrittore di processo.
•
Commutazione di contesto (che avviene a livello di privilegio sistema)
comporta:
– memorizzazione dello stato attuale relativo al processo in esecuzione o processo
uscente;
– la scelta del nuovo processo da mandare in esecuzione (o processo entrante): questo
compito viene svolto da una specifica routine detta schedulatore;
– il caricamento dello stato relativo al processo entrante.
G. Frosini
Slide 7
COMMUTAZIONE DI CONTESTO (2)
•
Commutazione di contesto:
– produce il caricamento di un nuovo valore in CR3;
• cambiamento dello spazio di indirizzamento privato.
•
Nuovo valore caricato nel puntatore di pila:
– si riferisce alla pila sistema realizzata nello spazio privato del processo
entrante.
•
Nuovo valore caricato nel contatore di programma:
– si riferisce al punto di esecuzione del codice del processo entrante, nello
spazio comune (il codice può essere anche lo stesso, nel caso in cui il
vecchio e il nuovo processo abbiano lo abbiano in comune, ma l’indirizzo
di partenza è in genere diverso).
G. Frosini
Slide 8
COMMUTAZIONE DI CONTESTO (3)
•
La memorizzazione dello stato attuale consiste comunemente in
due azioni:
– salvataggio nel descrittore del processo delle informazioni di stato, fra cui
il contenuto dei registri del processore;
– inserimento dell’identificatore del processo in una determinata lista
(qualche volta in nessuna lista).
•
Liste tipiche:
– quella dei processi pronti per l’esecuzione;
– quelle dei processi bloccati in attesa che si verifichi un certo evento;
– quella relativa al processo in esecuzione (particolare lista, costituita da un
solo elemento).
•
Effettuazione della scelta:
– lo schedulatore opera sulla lista dei processi pronti avvalendosi anche di
un’informazione di precedenza.
G. Frosini
Slide 9
COMMUTAZIONE DI CONTESTO (4)
•
Il caricamento del nuovo stato consiste comunemente in due
azioni:
– caricamento nei registri del processore delle informazioni contenute nel
descrittore del processo entrante;
– caricamento nel registro TR dell’identificatore del processo entrante.
•
Processo:
– può trovarsi in ogni istante in uno di questi tre stati:
• in esecuzione, quando ha il controllo del processore;
• pronto, quando è in attesa di essere mandato in esecuzione (tipicamente
dallo schedulatore);
• bloccato, quando è in attesa del verificarsi di una certa condizione di
attivazione.
•
Processo che da bloccato diviene pronto:
– il suo identificatore viene rimosso dalla lista in cui si trova e inserito nella
lista dei processi pronti.
G. Frosini
Slide 10
SISTEMA OPERATIVO PERSONALE
•
Nucleo di un sistema operativo multiprogrammato:
– costituito da un insieme di routine e di strutture dati in grado di gestire la
multiprogrammazione;
– queste si trovano nello spazio comune a tutti i processi, e hanno livello di
privilegio sistema.
•
Sistema personale (monoutente ma multitasking):
– corpi dei processi costituiti dai vari programmi che l’utente attiva;
– quello in esecuzione ha il controllo della tastiera e del video;
– lo schedulatore, quando invocato esplicitamente dall’utente (tipicamente
attraverso il mouse), seleziona il nuovo processo da mandare in esecuzione
in base alle indicazioni fornite dall’utente stesso.
G. Frosini
Slide 11
SISTEMA OPERATIVO TIME-SHARING
•
Sistema time-sharing:
– molti utenti condividono lo stesso elaboratore in maniera interattiva,
tipicamente mediante terminali.
•
Corpi dei processi:
– costituiti dai programmi dei vari utenti.
•
Processore:
– esegue il processo relativo a un certo utente, mentre gli altri utenti stanno
(per esempio) battendo i loro comandi, e i processi corrispondenti non
sono pertanto pronti.
•
Schedulatore:
– per mezzo di un contatore di tempo, va in esecuzione a intervalli regolari
(sfruttando il meccanismo di interruzione);
– se più processi sono pronti per l’esecuzione, assegna il processore a un
utente alla volta in modo ciclico (la durata di ogni intervallo di tempo
(tick) è tipicamente di 50 ms).
G. Frosini
Slide 12
SISTEMA OPERATIVO IN TEMPO REALE
•
Sistema in tempo reale:
– processi mandati in esecuzione al verificarsi di eventi esterni, con i
requisiti temporali imposti dall’applicazione.
•
Alcuni processi sono più importanti di altri:
– ad ogni commutazione di contesto lo schedulatore sceglie il processo a
precedenza più alta.
•
Per il verificarsi di un certo evento, un processo da bloccato
diviene pronto:
– occorre che anche il processo in esecuzione venga inserito, o sia già
inserito, fra i processi pronti;
– deve essere invocato lo schedulatore;
• può accadere che la precedenza del processo sbloccato sia maggiore di
quello in esecuzione, ossia che si abbia preemption (prelazione).
•
Nel seguito, faremo esplicito riferimento ai sistemi in tempo reale.
G. Frosini
Slide 13
PRIMITIVE E DRIVER
•
Routine del nucleo:
– primitive, invocate da un processo utente con interruzioni software, che
fanno passare il processo stesso da stato utente a stato sistema;
– driver, mandati in esecuzione da un’interruzione esterna, che operano in
stato sistema.
•
•
Alcune routine possono determinare una commutazione di
contesto.
Strutture dati più rilevanti:
– liste dei processi;
– costituite da elementi che contengono identificatori di processo;
– ogni lista è individuata da un apposito puntatore (contenuto in una
variabile di nucleo).
G. Frosini
Slide 14
ROUTINE DI NUCLEO E INTERRUZIONI
•
Routine di nucleo:
– molte lavorano sulle liste dei processi.
•
Liste dei processi:
– risorse che si trovano in uno stato consistente solo all’inizio e alla fine di
ogni operazione che le manipola.
•
Interruzione esterna (hardware):
– esecuzione di una routine che può lavorare sulle liste dei processi;
– tutte le routine che lavorano sulle liste dei processi devono essere eseguite
in maniera indivisibile, con le interruzioni (mascherabili) disabilitate;
– vedremo che anche le routine che non lavorano sulle liste dei processi
devono essere eseguite in maniera indivisibile.
•
Meccanismo di interruzione (hardware o software) utilizzato per
eseguire routine di nucleo:
– fa uso di gate di tipo interrupt, che disabilitano automaticamente le
interruzioni mascherabili.
G. Frosini
Slide 15
PROCESSI E INTERRUZIONI
•
Processo in esecuzione può produrre un cambiamento di contesto:
– volontariamente (mediante la chiamata di una primitiva):
• effettua delle richieste che non possono essere immediatamente
soddisfatte;
• il processo diviene bloccato e viene richiamato lo schedulatore;
– involontariamente, perché si verifica un’interruzione esterna:
• va in esecuzione un driver:
– può effettuare le sue elaborazioni e poi terminare (il processo interrotto
ritorna in esecuzione alla fine del driver stesso);
– può forzare l’esecuzione di un altro processo;
– può far divenire pronto un processo bloccato;
– può inserire nella lista dei processi pronti il processo in esecuzione e
richiamare lo schedulatore.
•
Cambiamento di contesto involontario:
– perché sia possibile, i processi, quando non eseguono routine di nucleo,
devono avere le interruzioni (mascherabili) abilitate.
G. Frosini
Slide 16
TIPI USATI NELLE REALIZZAZIONI
•
Si utilizzano le seguenti dichiarazioni typedef (le prime 5 sono già state
introdotte in precedenza):
typedef void* addr;
// indirizzo virtuali e indirizzo fisico nello spazio di memoria
typedef unsigned short ioaddr;
// indirizzo (fisico) nello spazio di I/O
typedef unsigned char natb;
typedef unsigned short natw;
typedef unsigned long natl;
typedef void* str;
typedef const void* cstr;
•
In C++:
– un indirizzo di memoria (di tipo void*) può essere dereferenziato, previa una
conversione di tipo;
– un indirizzo di I/O non può essere dereferenziato (lo può essere solo in Assembler).
G. Frosini
Slide 17
REALIZZAZIONE DELLE PRIMITIVE (1)
•
Descrizione delle primitive di nucleo:
– file scritti in Assembler e file scritti in C++;
– due file utente.s e utente.cpp aventi livello di privilegio utente (modulo
utente) con invocazione delle primitive;
– due file sistema.s e sistema.cpp aventi livello di privilegio sistema (modulo
sistema) con il codice delle primitive.
•
Processo:
– può utilizzare le primitive di nucleo, che si trovano a livello sistema.
– in C++ non sono possibili chiamate dirette di primitive, poiché non sono
previste istruzioni che provocano interruzioni software (INT $tipo);
– la primitiva è in realtà un sottoprogramma di interfaccia (scritto in
Assembler), che esegue un’interruzione software con esecuzione della
primitiva vera e propria (a_primitiva);
– questa deve accedere al record di attivazione per il prelievo dei parametri.
G. Frosini
Slide 18
REALIZZAZIONE DELLE PRIMITIVE (2)
•
Processo utente:
– il record di attivazione della routine di interfaccia primitiva si trova nella
pila utente, dalla quale devono essere prelevati dalla a_primitiva, la cui
pila è a livello sistema.
•
a_primitiva:
– termina con un’istruzione di ritorno da interruzione;
– se non si sono avute commutazioni di contesto, con l’istruzione IRET
restituisce il controllo al sottoprogramma di interfaccia;
– questo a sua volta termina con l’istruzione RET, con il ritorno al
programma che ha chiamato la primitiva.
G. Frosini
Slide 19
REALIZZAZIONE DELLE PRIMITIVE (3)
// utente.cpp
extern "C" void primitiva_i (/* parametri formali */);
void p (int h)
{
// …
primitiva_i (/* parametri attuali */);
// ritp_i …
}
# utente.s
.TEXT
.GLOBAL
primitiva_i: INT
rit_i:
# sistema.s
.TEXT
a_primitiva_i:
primitiva_i
$tipo_i
RET
// per semplicità, di tipo void
// codice di un processo
# sottoprogramma di interfaccia
# routine INT $tipo_i
# ...
IRET
G. Frosini
Slide 20
REALIZZAZIONE DELLE PRIMITIVE (4)
•
Processo utente che chiama una routine di interfaccia (in C++), sia
primitiva_i:
– memorizza in pila utente i parametri attuali e, con l’istruzione di
chiamata, il valore di EIP relativo al punto di ritorno ritp_i.
•
Routine di interfaccia (in assembler) primitiva_i:
– invoca la primitiva effettiva (a_primitiva_i) con l’istruzione INT $tipo_i;
•
Interruzione:
– variazione di livello di privilegio, e quindi cambiamento di pila;
– prelievo del puntatore della pila sistema dal descrittore del processo in
esecuzione (individuato dal contenuto del registro TR);
– memorizzazione nella nuova pila del valore del puntatore di pila utente e
dei valori dei registri EFLAG, CPL ed EIP (quest’ultimo contiene il
valore rit_i).
•
Moduli di livello utente e Moduli di livello sistema:
– non hanno nomi comuni e non richiedono nessuna azione di collegamento.
G. Frosini
Slide 21
REALIZZAZIONE DELLE PRIMITIVE (5)
EIP (ritp_i)
parametri
attuali
ESP
ESP
EIPu (rit_i)
CPLu
EFLAGu
ESPu
spazio riservato
ESPs
Pila utente
Pila sistema
G. Frosini
Slide 22
REALIZZAZIONE DELLE PRIMITIVE (6)
•
Invocazione di una primitiva da parte di un processo sistema:
– non produce nessun cambiamento di livello di privilegio.
ESP
EIP (rit_i)
CPL
EFLAG
EIP (rit_pi)
Parametri
attuali
Pila sistema
G. Frosini
Slide 23
STRUTTURA DI UNA A-PRIMITIVA (1)
•
a_primitiva:
– può provocare o meno una commutazione di contesto.
•
Se la commutazione di contesto è possibile:
– all’inizio, la a_primitiva salva lo stato del processo al momento della
esecuzione dell’istruzione INT (il valore dei registri EFLAG, CPL ed EIP
(quest’ultimo relativo all’indirizzo rit_i) non fanno parte dello stato che
viene salvato, essendo già stati memorizzati nella pila sistema
dall’istruzione INT stessa);
– alla fine, la a_primitiva carica lo stato del nuovo processo (il nuovo stato
può anche coincidere con quello salvato);
– dopo il caricamento del nuovo stato, l’istruzione IRET agisce sulla pila
sistema del nuovo processo.
G. Frosini
Slide 24
STRUTTURA DI UNA A-PRIMITIVA (2)
•
Processi:
– hanno la sommità della pila sistema (tre o cinque parole lunghe) nella
stessa situazione;
– hanno abbandonato l’esecuzione o per effetto di un’interruzione software
(invocazione di una a_primitiva), o per effetto di un’interruzione
hardware.
•
Creazione di un processo:
– occorre predisporre una situazione iniziale appropriata per la pila
sistema.
•
Istruzione IRET della a_primitiva:
– produce il ritorno a un nuovo processo (tipicamente a una nuova routine
di interfaccia).
•
Nota:
– quando un processo che invoca una a_primitiva torna di nuovo in
esecuzione, riparte dal punto successivo rispetto all’invocazione della
a_primitiva stessa (punto successivo rispetto all’istruzione INT $tipo).
G. Frosini
Slide 25
TIPO DESCRITTORE DI PROCESSO
•
Tipo descrittore di processo (struttura des_proc):
• campo id contenente un identificatore di processo ID (tipicamente del
processo che lo ha creato o di quello che lo ha mandato in esecuzione);
• campo punt_nucleo contenente il puntatore (valore iniziale) alla pila di
nucleo;
• campo per il registro CR3;
• campo contesto destinato a contenere la copia del contenuto dei registri
generali del processore, supponiamo siano N_REG.
// sistema.cpp
struct des_proc
{ natl id;
addr punt_nucleo;
addr riservato;
// quattro parole lunghe a disposizione
addr cr3;
natl contesto[N_REG];
};
G. Frosini
Slide 26
DESCRITTORI E PROCESSI
•
Descrittori dei processi:
– variabili definite nel nucleo;
– un descrittore è individuato da un identificatore che, tramite la tabella
GDT, determina la base e il limite del descrittore stesso.
•
Processo:
– costituito da un identificatore, dal corrispondente descrittore, da un corpo
(istanza di una funzione, con pile di dimensioni standard);
– nella pila relativa al livello di privilegio proprio si trovano i parametri
attuali e le variabili locali di una istanza di funzione;
– supponiamo (per semplicità) che il codice di un processo sia una istanza
di una funzione C++ con un solo parametro di tipo intero.
G. Frosini
Slide 27
LISTE DI PROCESSI
•
Processi;
– gestiti attraverso liste, individuate da un puntatore, e costituite da
elementi di tipo proc_elem (informazione: identificatore e precedenza).
•
Puntatori definiti nel nucleo:
– esecuzione: puntatore a un elemento (specifica il processo in esecuzione);
– pronti: puntatore alla lista dei processi pronti.
// sistema.cpp
struct proc_elem { natl id; natl precedenza; proc_elem* puntatore; };
extern proc_elem* esecuzione; extern proc_elem* pronti;
# sistema.s
.DATA
.GLOBAL
esecuzione:
.GLOBAL
pronti:
esecuzione
.LONG
0
pronti
.LONG
0
G. Frosini
Slide 28
ATTIVAZIONE DI UN PROCESSO UTENTE (1)
•
Attivazione (creazione) di un processo utente:
– nel caso più semplice avviene in fase di inizializzazione (in molti sistemi è
consentito che un processo padre crei processi figli);
•
•
•
•
•
selezione di un identificatore;
inizializzazione del corrispondente descrittore e delle corrispondenti pile;
allocazione in memoria dinamica di un nuovo elemento di tipo proc_elem;
inserimento dell’elemento nella lista dei processi pronti.
Primitiva di nucleo activate_p():
natl activate_p(void f(int), int a, natl prio, natl liv);
– parametri: intestazione di funzione, parametro attuale della funzione,
precedenza e livello proprio;
– restituisce l’identificatore del processo ovvero 0xFFFFFFFF se
l’attivazione non è andata a buon fine.
•
Attivazione:
– richiamo della primitiva activate_p() con parametri attuali p, A, PRIO,
LIV;
G. Frosini
Slide 29
ATTIVAZIONE DI UN PROCESSO UTENTE (2)
•
1) Inizializzazione della pila sistema del processo (cinque parole
lunghe):
– dall’alto in basso:
•
•
•
•
•
•
indirizzo della prima istruzione (puntatore a funzione p);
livello di privilegio proprio (LIV);
costante che rappresenta il contenuto del registro EFLAG;
puntatore alla cima della pila utente;
parola lunga riservata.
2) Inizializzazione della pila utente (due parole lunghe):
– dall’alto in basso:
• parola lunga di valore 0;
• parametro attuale della funzione p() (A).
•
3) Inizializzazione del descrittore del processo:
– valore del puntatore della pila sistema relativo a pila vuota e
valore del puntatore della pila sistema con le cinque parole lunghe:
• inseriti nei campi ESPSv ed ESPv del descrittore stesso.
•
4) Allocazione in memoria dinamica di un elemento di tipo proc_elem;
• memorizzazione in esso dell’identificatore e della priorità (PRIO);
•
5) Inserimento nella lista dei processi pronti.
G. Frosini
Slide 30
ATTIVAZIONE DI UN PROCESSO UTENTE (3)
ESPv
EIPu (puntatore p)
CPLu (parametro LIV)
EFLAGu
ESPu
Parametro di p() (A)
Parola lunga riservata
ESPSv
Pila utente
Pila sistema
G. Frosini
Slide 31
ATTIVAZIONE DI UN PROCESSO UTENTE (4)
•
Processo da mandare in esecuzione:
– scelta del processo da parte dello schedulatore:
– caricamento del suo stato;
– esecuzione dell’istruzione IRET (che rappresenta una specie di
istruzione di chiamata).
•
Il processo inizia eseguendo il prologo della funzione puntata
da p:
– salvataggio di EBP, ricopiamento di ESP in EBP, decremento di
ESP per tener conto di variabili locali;
– elaborazioni effettuate dalla funzione puntata da p:
• il parametro attuale corrisponde (correttamente) all’indirizzo
EBP+8.
G. Frosini
Slide 32
TERMINAZIONE DEI PROCESSI UTENTE
•
Terminazione di un processo utente:
– avviene per mezzo della primitiva terminate_p(), che non ha parametri;
– essa richiama semplicemente lo schedulatore.
•
Chiamata della primitiva terminate_p():
– ultima istruzione di ogni processo.
•
Fase di inizializzazione:
– creazione di un processo start_utente, che richiama la funzione main();
•
Mediante tale funzione, il processo start_utente:
– attiva tutti i processi utente;
– …
– termina (come tutti i processi) eseguendo la primitiva terminate_p().
G. Frosini
Slide 33
FILE UTENTE.CPP
// utente.cpp
extern "C" natl activate_p(void f(int), int a, natl prio, natl liv);
extern "C" void terminate_p();
natl processo_i;
// contiene l’identificatore di un processo
void p(int h)
// codice di un processo
{
...
terminate_p();
}
int main()
{
…
processo_i = activate_p(p, A, PRIO, LIV);
...
terminate_p();
}
G. Frosini
Slide 34
FILE UTENTE.S e SISTEMA.S
# utente.s
...
.TEXT
.GLOBAL
activate_p:
.GLOBAL
terminate_p:
# sistema.s
.TEXT
...
a_activate_p:
a_terminate_p:
activate_p
INT
$tipo_a
RET
terminate_p
INT
$tipo_t
RET
# routine INT $tipo_a
# ...
IRET
# routine INT $tipo_t
# ...
IRET
G. Frosini
Slide 35
AREA DATI CONDIVISA
•
Identificatori dei processi:
– variabili condivise (non private dei singoli processi);
– un processo può utilizzare primitive che agiscono esplicitamente su un
altro processo;
– esempio:
•
un processo può provocare la terminazione forzata di un altro processo.
G. Frosini
Slide 36
PROCESSO DUMMY (1)
•
Lista dei processi pronti:
–
–
–
–
–
•
processo dummy;
precedenza inferiore a quella di tutti gli altri processi utente;
va in esecuzione quando tutti i processi utente sono bloccati;
ha livello di privilegio sistema (non può essere modificato da un utente);
come tutti i processi, gira con le interruzioni abilitate.
Processi utente tutti bloccati:
– situazione modificabile solo da un’interruzione esterna.
•
Routine relativa a una tale interruzione esterna:
– può far passare un processo da bloccato a pronto;
• in questo caso deve anche richiamare lo schedulatore e caricare lo stato
del nuovo processo.
•
Processo dummy:
– utilizza la variabile processi, inizializzata a 0, che viene incrementata dalla
primitiva activate_p() e decrementata dalla primitiva terminate_p().
G. Frosini
Slide 37
PROCESSO DUMMY (2)
•
Processo dummy:
– esamina il valore della variabile processi, e se vale 1 (solo il processo dymmy non è
terminato) termina con una primitiva (end_program()) che effettua le operazioni
di chiusura.
// sistema.cpp
extern natl processi;
extern "C" void end_program();
void dd(int i)
{ while(processi !=1) ;
end_program();
}
•
// variabile definita in sistema.s
Ciclo:
– non viene effettuato se la variabile processi vale 1;
– può essere interrotto da un’interruzione esterna, che fa divenire pronto un
processo bloccato e richiama lo schedulatore;
• il processo sboccato, quando esegue la primitiva terminate_p(), produce la modifica
del contenuto della variabile processi.
G. Frosini
Slide 38
STRUTTURA DELLE PRIMITIVE
•
Primitiva:
– produce l’esecuzione di una a_primitiva, che può provocare o meno una
commutazione di contesto;
– nel primo caso, all’inizio deve essere effettuato il salvataggio dello stato
attuale, e alla fine il caricamento del nuovo stato;
– queste due azioni sono espresse utilizzando il linguaggio Assembler, in
quanto coinvolgono i registri del processore;
– le elaborazioni compiute da una a_primitiva possono essere più
comodamente espresse utilizzando il linguaggio C++ (c_primitiva):
• vanno rispettati gli standard di aggancio dei sottoprogrammi ad alto
livello;
• in particolare vanno ricopiati in testa alla pila sistema i parametri che,
per i processi utente, si trovano nella pila utente.
G. Frosini
Slide 39
STRUTTURA DI UNA A_PRIMITIVA
# sistema.s
.TEXT
salva_stato:
carica_stato:
.EXTERN
a_primitiva_i:
# ...
RET
# ...
RET
c_primitiva_i
CALL
salva_stato
# eventuale verifica e ricopiamento dei parametri
CALL
c_primitiva_i
# ripulitura della pila
CALL
carica_stato
IRET
// sistema.cpp
extern "C" void c_primitiva_i(/* parametri formali */)
{ /* ... */ }
G. Frosini
Slide 40
PARAMETRI E PILA
•
Realizzazione di una a_primitiva:
– il ricopiamento dei parametri avviene dalla pila utente o dalla pila sistema
in base al valore del vecchio CPL (memorizzato in pila sistema);
– l’eventuale accesso alla pila utente da livello sistema non deve comportare
alcuna violazione delle regole di privilegio (livello di privilegio delle
pagine contenenti la pila uguale al livello di privilegio del processore):
• da livello sistema si accede alla pila utente come insieme di pagine dati,
senza utilizzare come registri contenenti indirizzi né ESP né EBP.
•
Ripulitura della pila:
– non è indispensabile;
– subito dopo si ha il caricamento di un nuovo stato (precedentemente
salvato), e quindi il trasferimento in ESP di un nuovo valore.
G. Frosini
Slide 41
SOTTOPROGRAMMA SALVA_STATO
•
Sottoprogramma salva_stato:
– memorizza il contenuto dei registri del processore nel descrittore di
processo il cui identificatore è contenuto nell’elemento puntato da
esecuzione;
– i valori dei registri EFLAG, CPL ed EIP si trovano nella pila sistema del
processo che ha invocato la a_primitiva e non prevedono spazio nel
descrittore di processo;
– il salvataggio dei valori di CR3 e di ESP produce il salvataggio dell’intera
pila;
– il valore di ESP che deve essere salvato non corrisponde al valore attuale
(ma questo va opportunamente incrementato), poiché in testa alla pila
sistema è memorizzato l’indirizzo di ritorno del sottoprogramma
salva_stato.
G. Frosini
Slide 42
SOTTOPROGRAMMA CARICA_STATO
•
c_primitiva:
– deve lasciare in esecuzione l’indirizzo dell’elemento contenente
l’identificatore ID del processo da mandare in esecuzione.
•
Sottoprogramma carica_stato:
– tramite il puntatore esecuzione copia in TR il valore di ID;
– tramite la GDT, carica nei registri del processore i contenuti dei
corrispondenti registri virtuali prelevati dal descrittore del processo
identificato da ID (sia nel caso che vi sia stata una commutazione di
contesto come nel caso che non vi sia stata).
– poiché può esservi stata una commutazione di contesto, il sottoprogramma
carica_stato deve provvedere a trasferire dalla vecchia pila sistema nella
nuova pila sistema il suo indirizzo di ritorno, per poter terminare
correttamente.
G. Frosini
Slide 43
PRIMITIVA CHE RESTITUISCE UN VALORE (1)
•
Supponiamo che la primitiva restituisca un intero:
// utente.cpp
...
extern "C" int primitiva_i(/* parametri formali */);
...
void p/(int h)
{
...
int risu = primitiva_i(/* parametri attuali */);
// ritp_i
...
}
// codice di un processo
– il risultato viene prodotto dalla c_primitiva e lasciato in EAX;
– nel caso in cui sia possibile una commutazione di contesto (la c_primitiva
può invocare lo schedulatore, che cambia il valore del puntatore
esecuzione) olccorre:
• preliminarmente salvare l’identificatore del processo che ha invocato la primitiva;
• alla fine della c_primitiva, salvare EAX nel descrittore di tale processo.
...
G. Frosini
Slide 44
PRIMITIVA CHE RESTITUISCE UN VALORE (2)
# sistema.s
...
.TEXT
…
.EXTERN
a_primitiva_i:
c_primitiva_i
CALL
salva_stato
# salvataggio in pila di ID (identificatore del processo in esecuzione)
# eventuale verifica e ricopiamento dei parametri
CALL
c_primitiva_i
# ripulitura della pila
# prelievo dalla pila di ID e salvataggio di EAX nel suo descrittore
CALL
carica_stato
IRET
...
// sistema.cpp
..
extern "C" int c_primitiva_i(/* parametri formali */)
{ /* ... */ }
...
G. Frosini
Slide 45
PRIMITIVA SENZA COMMUTAZIONE
•
Primitiva che non effettua mai commutazione di contesto:
– salvataggio e ripristino dello stato del processo chiamante viene sostituito
con salvataggio e ripristino dei registri utilizzati;
• se produce un risultato, non si deve salvare e ripristinare EAX.
# sistema.s
.TEXT
.EXTERN c_primitiva_j
a_primitiva_j:
# salvataggio dei registri
# eventuale verifica e ricopiamento dei parametri
CALL
c_primitiva_j
# ripulitura della pila
# ripristino dei registri
IRET
// sistema.cpp
extern "C" void c_primitiva_j(/* parametri formali */)
{ /* ... */ }
G. Frosini
Slide 46
FUNZIONI DI UTILITA’
•
Descrizione di una c_primitiva:
– nel file sistema.cpp vengono utilizzate funzioni di utilità:
–
void inserimento_lista(proc_elem*& p_lista, proc_elem* p_elem)
{ /* ... */ }
inserisce nella lista prioritaria (precedenza decrescente) puntata da p_lista
l’ elemento (contenente identificatore di processo e precedenza) puntato
da p_elem ponendolo come ultimo tra quelli aventi uguale precedenza;
–
void rimozione_lista(proc_elem*& p_lista, proc_elem*& p_elem)
{ /* ... */ }
rimuove dalla lista il primo elemento;
–
extern "C" void inspronti()
{ /* ... */ }
// utilizzato anche dal file sistema.s
inserisce nella lista dei processi puntata da pronti l’elemento puntato da
esecuzione.
–
extern "C" void schedulatore()
{ /* ... */ }
// utilizzato anche dal file sistema.s
estrae dalla lista dei processi puntata da pronti il primo elemento
(identifica il processo a precedenza più alta) e lo fa puntare da esecuzione.
G. Frosini
Slide 47
ATOMICITA`
•
Sistema multiprogrammato:
– alcune operazioni, compiute da un processo su una risorsa, debbono
avvenire in modo atomico (non divisibile):
• azione effettuata completamente, dall’inizio alla fine, senza che un altro
processo possa inserirsi e provocare alterazioni dell’azione stessa.
•
Sezione critica:
– operazione (insieme di istruzioni) su una risorsa condivisa che deve essere
eseguita da un processo in maniera atomica.
•
Concetto di atomicità:
– non coincide con quello di non interrompibilità:
• azione non interrompibile è certamente atomica;
• un’azione atomica può anche essere interrompibile: l’interruzione può
mandare in esecuzione un nuovo processo, purché questo non compia
elaborazioni che arrechino danno all’azione iniziata (ma non terminata)
dal processo interrotto.
G. Frosini
Slide 48
MUTUA ESCLUSIONE
•
Concetto di atomicità analogo a quello di mutua esclusione;
– su una risorsa condivisa possono venir effettuate differenti operazioni da
differenti processi;
– ognuna di queste, una volta iniziata, deve essere completata prima che ne
venga iniziata un’altra.
•
Sistema monoprogrammato:
– un’operazione su una risorsa viene sempre effettuata in modo atomico;
– una risorsa è sempre in uno stato consistente.
•
Sistema multiprogrammato:
– un processo può essere interrotto mentre sta compiendo un’operazione su
una risorsa, e il nuovo processo può accedere alla stessa risorsa e trovarla
in uno stato inconsistente;
– le operazioni su una risorsa condivisa possono avvenire in modo non
atomico.
G. Frosini
Slide 49
MUTUA ESCLUSIONE E INTERRUZIONI
•
Caso semplice:
– risorsa protetta agendo direttamente sulle interruzioni (mascherabili):
disabilitazione_interruzioni
sezione_critica
riabilitazione_interruzioni
•
In una sezione critica:
– ipotesi: non c’è sospensione volontaria del processo in esecuzione;
– la soluzione precedente assicura l’indivisibilità della sezione critica.
•
Agendo direttamente sulle interruzioni:
– tutte le sezioni critiche (relative a tutte le risorse) divengono mutuamente
esclusive.
G. Frosini
Slide 50
SEZIONI CRITICHE LUNGHE
•
Agire direttamente sul meccanismo di interruzione:
– tecnica utilizzata per sezioni critiche corte, per le quali non è ragionevole
ricorrere a soluzioni più sofisticate, e quindi più complesse.
•
Routine di nucleo:
– vengono eseguite tutte con le interruzioni disabilitate;
• quelle che lavorano sulle liste dei processi devono comunque avere le
interruzioni disabilitate;
• quelle che non lavorano sulle liste dei processi rientrano nella categoria
delle sezioni critiche corte.
•
Processo utente:
– una sezione critica può essere lunga;
– non è ragionevole mantenere a lungo le interruzioni disabilitate.
– le sezioni critiche devono essere mutuamente esclusive solo a gruppi, in
relazione a una certa risorsa;
– tecniche più flessibili, che consentano di risolvere il problema della mutua
esclusione senza agire sul meccanismo di interruzione.
G. Frosini
Slide 51
SEMAFORI
•
Mutua esclusione:
– può essere gestita mediante un semaforo e due primitive di nucleo
sem_wait() e di sem_signal().
•
Semaforo di mutua esclusione:
– protegge una risorsa;
– possiede un contatore inizializzato a 1.
•
Processo che vuole utilizzare una risorsa:
– intende compiervi un’operazione;
– se trova che la risorsa è occupata perché un altro processo vi sta
compiendo un’altra operazione, si blocca volontariamente dando luogo a
una commutazione di contesto;
– quando la risorsa sarà libera, il processo bloccato diventerà di nuovo
pronto per l’esecuzione.
G. Frosini
Slide 52
PRIMITIVE SEMAFORICHE (1)
•
Primitiva sem_wait() (parametro: indice del semaforo su cui opera):
–
–
viene decrementato il contatore del semaforo e ne viene poi effettuato il test;
se il nuovo valore è minore di 0, il processo che la ha invocata viene bloccato sulla coda
del semaforo, viene richiamato lo schedulatore e mandato in esecuzione un nuovo
processo.
salva_stato
contatore = contatore-1
SI
contatore < 0
inserimento_lista_semaforo
schedulatore
NO
carica_stato
IRET
G. Frosini
Slide 53
PRIMITIVE SEMAFORICHE (2)
•
Primitiva sem_signal() (parametro: indice del semaforo su cui opera):
–
–
viene incrementato il contatore del semaforo e ne viene poi effettuato il test;
se il nuovo valore è minore o uguale a 0 (vi sono processi bloccati sul semaforo), quello a precedenza più
alta viene tolto dalla coda del semaforo e inserito nella coda dei processi pronti, il processo in esecuzione
viene inserito nella lista dei processi pronti, viene richiamato lo schedulatore e mandato in esecuzione il
nuovo processo
salva_stato
• Nota:
contatore = contatore+1
se il nuovo valore del contatore
è minore o uguale a 0, il suo
valore assoluto, aumentato di
1, rappresenta il numero dei
processi bloccati sul semaforo.
NO
SI
contatore <= 0
rimozione_lista_semaforo
inserimento_lista_pronti
processi rimosso e processo in esecuzione
schedulatore
carica_stato
G. Frosini
Slide 54
PREEMPTION
•
Si ha preemption (prelazione):
– un processo da bloccato diviene pronto e viene messo nella lista dei
processi pronti;
– il processo in esecuzione viene messo nella lista dei processi pronti;
– viene richiamato lo schedulatore.
•
Il processo sboccato, se ha precedenza maggiore di quello in
esecuzione, va in esecuzione al posto di quello attualmente in
esecuzione.
G. Frosini
Slide 55
STATI DI UN PROCESSO
•
Transizioni di stato di un processo:
– processo in esecuzione: può bloccarsi perché esegue una sem_wait();
– processo bloccato: può divenire pronto per effetto di una sem_signal()
eseguita da un altro processo;
– transizione tra pronto ed esecuzione (e viceversa): può avvenire o quando
viene invocato (direttamente o indirettamente) lo schedulatore o per
effetto di un’interruzione esterna.
processo in
esecuzione
sem_wait
(effettuata dallo stesso processo)
terminazione
revoca
processore
assegnazione
processore
processo
pronto
attivazione
processo
bloccato
sem_signal
(effettuata da un altro processo
G. Frosini
Slide 56
REALIZZAZIONE DEI SEMAFORI (1)
•
Tipo descrittore di semaforo (des_sem):
– struttura con un campo counter per la gestione del semaforo mediante le
primitive sem_wait() e sem_signal(), e un campo pointer a cui si accodano i
processi che si bloccano sul semaforo.
// sistema.cpp
struct des_sem { int counter; proc_elem* pointer; };
•
Descrittori di semaforo:
– variabili definite nel nucleo, per esempio come un array di descrittori.
# sistema.s
.DATA
.GLOBAL
array_dess:
array_dess
.SPACE BYTE_SEM
# MAX_SEM*sizeof(des_sem)
// sistema.cpp
extern des_sem array_dess[MAX_SEM];
G. Frosini
Slide 57
REALIZZAZIONE DEI SEMAFORI (2)
•
Semaforo:
– indice di un descrittore, con valore iniziale nel campo counter;
•
I semafori vengono inizializzati dal programma main(), utilizzando
la primitiva di nucleo sem_ini():
natl sem_ini(int val);
– essa ha come parametro il valore iniziale del contatore, e restituisce
l’indice selezionato all’interno dell’array dei descrittori di semaforo,
oppure 0xFFFFFFFF se non vi sono più elementi.
// utente.cpp
extern "C" int sem_ini(int val);
natl semaforo_i;
// memorizza l’indice di un descrittore di semaforo,
int main()
{
…
semaforo_i = sem_ini(VALORE);
…
terminate_p();
}
G. Frosini
Slide 58
PRIMITIVA SEM_WAIT() (1)
•
Primitiva sem_wait():
// utente.cpp
extern "C" void sem_wait(natl sem);
natl proc;
natl semaforo;
void p_code(int n)
{
// ...
sem_wait(semaforo);
// ...
}
int main()
{
…
semaforo = sem_ini(1) ;
proc = activate_p(p_code, A, PRIO, LIV);
…
}
G. Frosini
Slide 59
PRIMITIVA SEM_WAIT() (2)
# sistema.s
.TEXT
.EXTERN
a_sem_wait:
c_sem_wait
# routine INT $tipo_w
CALL
salva_stato
# ricopiamento del parametro sem
CALL
c_sem_wait
# ripulitura della pila
CALL
carica_stato
IRET
# sistema.cpp
extern "C" void c_sem_wait(natl sem)
{
des_sem* s; s = &array_dess[sem];
(s -> counter) --;
if ((s ->counter) < 0)
{
inserimento_lista(s -> pointer, esecuzione);
schedulatore();
}
}
G. Frosini
Slide 60
PRIMITIVA SEM_SIGNAL() (1)
•
Primitiva sem_signal():
// utente.cpp
extern "C" void sem_signal(natl sem);
natl proc;
natl semaforo;
void p_code(int n)
{
// ...
sem_signal(semaforo);
// ...
}
int main()
{
…
semaforo = sem_ini(1) ;
proc = activate_p(p_code, A, PRIO, LIV);
…
}
G. Frosini
Slide 61
PRIMITIVA SEM_SIGNAL() (2)
# sistema.s
.TEXT
.EXTERN
a_sem_signal:
c_sem_signal
# routine INT $tipo_s
CALL
salva_stato
# ricopiamento del parametro sem
CALL
c_sem_signal
# ripulitura della pila
CALL
carica_stato
IRET
// sistema.cpp
extern "C" void c_sem_signal(natl sem)
{
des_sem* s; proc_elem* lavoro; s = &array_dess[sem];
(s ->counter) ++;
if ((s ->counter) <= 0)
{ rimozione_lista(s -> pointer, lavoro);
inserimento_lista(pronti, lavoro);
inspronti();
// preemption
schedulatore;
// preemption
}
}
G. Frosini
Slide 62
MUTUA ESCLUSIONE
•
Processi utente:
– risorsa su cui i processi compiono le operazioni oper_1, oper_2, ..., oper_n;
– semaforo di mutua esclusione, sia mutex, inizializzato a 1.
// utente.cpp
natl mutex;
natl procm_a, procm_b;
void pma(int i)
{
sem_wait(mutex);
oper_1;
sem_signal(mutex);
}
void pmb(int i)
{
sem_wait(mutex);
oper_2;
sem_signal(mutex);
}
// identificatori dei processi
// i cui corpi sono istanze di pma() e pmb()
// sezione critica
// sezione critica
G. Frosini
Slide 63
SINCRONIZZAZIONE
•
Semaforo con contatore inizializzato a 0:
–
utilizzato per scambiare segnali temporali tra due processi (sincronizzazione).
// utente.cpp
natl sincr;
natl procs_a, procs_b; // identificatori dei processi i cui corpi sono istanze di pma() e pmb()
void psa(int i)
{
sem_wait(sincr); // ...
}
void psb(int i)
{
sem_signal(sincr); // ...
}
•
•
Il processo procs_a si blocca quando esegue la sem_wait(), e diviene di
nuovo pronto quando il processo procs_b esegue la sem_signal().
Se il processo procs_b esegue la sem_signal() per primo, il processo
procs_a non si blocca quando esegue la sem_wait() (l’evento si è già
verificato). Questa sincronizzazione non è simmetrica.
G. Frosini
Slide 64
MEMORIA DINAMICA (1)
•
Sistema multiprogrammato:
– la memoria dinamica deve essere gestita in mutua esclusione;
– si fa uso di due funzioni, mem_alloc() e mem_free():
void* mem_alloc(natl dim);
• alloca una struttura di dim byte, restituendo il puntatore al primo byte
allocato (nel caso di allocazione di un tipo struttura, il numero di byte
allocati viene determinato applicando l’operatore sizeof al tipo struttura
stesso).
void mem_free(void* pv);
• libera la struttura indirizzata da pv, che deve essere stata allocata
utilizzando la funzione mem_alloc() (e quindi ha associata l’informazione
sul numero di byte da liberare).
•
Mutua esclusione:
– utilizzo di un semaforo mem_mutex, inizializzato dal processo main (con il
campo contatore che vale 1).
G. Frosini
Slide 65
MEMORIA DINAMICA (2)
•
Struttura delle funzioni mem_alloc() e mem_free()):
# utente.cpp
…
natl mem_mutex;
…
void* mem_alloc(natl dim)
{
void* p;
sem_wait(mem_mutex):
…
sem_signal(mem_mutex);
return p;
}
void mem_free(void* pv)
{
sem_wait(mem_mutex):
…
sem_signal(mem_mutex);
}
G. Frosini
Slide 66
MEMORIA DINAMICA (3)
•
Processo utente:
– per poter utilizzare la funzione mem_alloc(), deve convertire il tipo del
risultato, da puntatore a void a puntatore:
// utente.cpp
…
void p(int i)
// codice di un processo
{
struct struttura { /* ... */ };
struttura* ps;
...
ps = static_cast<struttura*>(mem_alloc(sizeof(struttura)));
mem_free(ps);
}
G. Frosini
Slide 67
MEMORIA DINAMICA (4)
•
Memoria dinamica:
– può essere utilizzata da alcune routine di sistema (che girano con le interruzioni
disabilitate, per cui è assicurata la mutua esclusione).
•
Funzioni che allocano e deallocano memoria dinamica di livello
sistema:
// sistema.cpp
...
void* alloca(natl dim)
{
/* ... */
}
void dealloca(void* p)
{
/* ... */
}
•
Non è opportuno utilizzare gli operatori new e delete del C++, in
quanto utilizzano il supporto a run-time del linguaggio (ossia alcune
primitive del sistema operativo su cui il C++ è installato).
G. Frosini
Slide 68
INTERFACCIA DI CONTEGGIO
•
Interfaccia conta-tempi:
– possiede un contatore, inizializzato con il contenuto di CTR (di 16 bit), che
viene decrementato con la frequenza di pilotaggio;
– quando il conteggio arriva a 0, l’interfaccia attiva un piedino di uscita, che
viene utilizzato per generare una richiesta di interruzione, e inizializza
nuovamente il contatore;
– l’interfaccia possiede anche un registro di stato STR che consente di
leggere il valore attuale del conteggio, e un registro di controllo CWR che
specifica le modalità di funzionamento dell’interfaccia.
CTR
STR
CVR
G. Frosini
Slide 69
TIMER DI SISTEMA
•
Interfaccia di conteggio:
– predisposta con una costante di tempo (correlata con la frequenza di
pilotaggio) in modo da avere una richiesta di interruzione ogni 50 ms.
•
Sistema in tempo reale:
– i processi possono sospendersi per un certo numero di intervalli di tempo,
ciascuno lungo 50 ms.
•
Puntatore p_sospesi:
– puntatore a una lista di attesa, composta da elementi che individuano i
processi che si sospendono sul timer.
•
Processo che vuole sospendersi:
– invoca la primitiva delay(), specificando il numero di intervalli di tempo
in cui intende rimanere bloccato. Trascorso questo tempo, il processo
diviene di nuovo pronto.
G. Frosini
Slide 70
PRIMITIVA DELAY() (1)
•
Tipo richiesta:
// sistema.cpp
struct richiesta
{
natl d_attesa;
richiesta* p_rich;
proc_elem* pp;
};
richiesta* p_sospesi;
•
// puntatore alla lista dei processi sospesi
Primitiva delay():
// utente.cpp
extern "C" void delay(natl n);
void procd(int i)
{
// ...
delay(N); //...
}
// codice di un processo
G. Frosini
Slide 71
FUNZIONE C_DELAY()
•
Parte della primitiva delay() scritta in C++:
// sistema.cpp
void inserimento_lista_attesa(richiesta* p);
extern "C" void c_delay(natl n)
{
richiesta* p;
p = static_cast<richiesta*>(alloca(sizeof(richiesta)));
p -> d_attesa = n;
p -> pp = esecuzione;
inserimento_lista_attesa(p);
schedulatore();
}
G. Frosini
Slide 72
FUNZIONE INSERIMENTO_LISTA_ATTESA() (1)
•
Primitiva delay():
– utilizza la funzione di utilità inserimento_lista_attesa();
– questa ha come parametro il puntatore all’elemento (di tipo richiesta) da
inserire.
•
Lista di attesa:
– organizzata in modo tale che il numero di intervalli di attesa di un certo
elemento è dato dalla somma dei campi d_attesa di tutti gli elementi che
precedono e di quello dell’elemento stesso;
– questa organizzazione rende più semplice la gestione della lista da parte
del driver di interruzione, che si limita a decrementare il contenuto del
campo d_attesa del primo elemento in lista e a mettere nella lista dei
processi pronti quelli le cui richieste si trovano in testa alla lista e hanno
il campo d_attesa uguale a 0.
G. Frosini
Slide 73
FUNZIONE INSERIMENTO_LISTA_ATTESA() (2)
d_attesa
Nuovo elemento di tipo
richiesta
p_ric
5
0
4
0
3
0
8
p_sospesi
pp
d_attesa
5
p_ric
1
0
p_sospesi
pp
G. Frosini
Slide 74
FUNZIONE INSERIMENTO_LISTA_ATTESA() (3)
// sistema.cpp
void inserimento_lista_attesa(richiesta* p)
{
richiesta* r; richiesta* precedente; bool ins;
r = p_sospesi; precedente = 0;
ins = false;
while (r != 0 && !ins)
if (p -> d_attesa > r -> d_attesa)
{
p -> d_attesa -= r -> d_attesa;
precedente = r; r = r -> p_rich;
else ins = true;
p -> p_rich = r;
if (precedente != 0) precedente -> p_rich = p;
else p_sospesi = p;
if (r != 0) r -> d_attesa -= p -> d_attesa;
}
}
G. Frosini
Slide 75
INTERRUZIONE DA TIMER
•
Ogni 50 ms arriva un’interruzione dal timer, che manda in
esecuzione uno specifico driver:
– esso salva lo stato del processo che è stato interrotto, utilizzando il
sottoprogramma salva_stato (il meccanismo di interruzione hardware è
uguale a quello software, utilizzato nelle primitive);
– pone il processo interrotto nella lista dei processi pronti, utilizzando il
sottoprogramma inspronti (senza parametri);
– esegue le elaborazioni dovute sulla lista puntata da p_sospesi (decrementa
il contenuto del campo d_attesa del primo elemento in lista e immette nella
lista dei processi pronti quelli relativi alle richieste che si trovano in testa
alla lista e che hanno il campo d_attesa uguale a 0);
– richiama lo schedulatore;
– carica lo stato del nuovo processo.
G. Frosini
Slide 76
DRIVER DEL TIMER
# sistema.s
.TEXT
.EXTERN
driver_td:
c_driver_td
CALL
salva_stato
CALL
c_driver_td
CALL
inviaEOI
CALL
carica_stato
IRET
// sistema.cpp
extern "C" void c_driver_td(void)
{
richiesta* p;
if (p_sospesi != 0) p_sospesi -> d_attesa --;
while (p_sospesi != 0 && p_sospesi -> d_attesa == 0)
{ inserimento_lista(pronti, p_sospesi -> pp);
p = p_sospesi; p_sospesi = p_sospesi -> p_rich;
dealloca(p);
}
inspronti();
// preemption
schedulatore();
// preemption
}
G. Frosini
Slide 77
Fly UP