1. Descrivere il meccanismo di realizzazione di una chiamata al supervisore (SVC), evidenziando la differenza che esiste tra il salvataggio dei registri del processore nello stack di sistema e quello nel descrittore di un processo
Una chiamata a supervisore (SVC), che è semplicemente la richiesta da parte di un programma utente di accedere ad una funzione del kernel (implementata tipicamente con una primitiva, cioè con una procedura non interrompibile), non si può realizzare attraverso una semplice chiamata a sottoprogramma, in quanto prevede la variazione della modalità di esecuzione, dallo stato utente a quello supervisore (che consente l’uso di istruzioni privilegiate).
Una SVC si effettua allora attraverso apposite procedure di interfaccia, messe a disposizione dalle librerie runtime dei linguaggi di programmazione che generano un’interruzione con un’apposita istruzione del processore detta Software Interrupt (SWI), questa deve essere in coppia con l’Interrupt Return (IRET) la quale ripristinerà il contenuto dei registri prelevandoli dallo stack e riabilita la possibilità di interruzione; o in alternativa, con un trap, causata da un errore.
In questo caso sarà poi la ISR (interrupt service routine) a dover riconoscere gli errori fortuiti da quelli provocati volontariamente per accedere al kernel. È possibile distinguere le due situazioni attraverso il contenuto di un registro particolare.
Le interruzioni così ottenute producono i seguenti effetti:
- Il processo in esecuzione viene interrotto e il suo stato puntuale viene salvato nello stack di sistema;
- Nel PC viene immesso un elemento del vettore delle interruzioni, che di fatto è l’entry della particolare ISR preposta alla gestione delle interruzioni provocate;
- L’esecuzione passa da modalità utente a modalità supervisore, in modo che la ISR possa sfruttare istruzioni privilegiate. In kernel mode sono disabilitate le interruzioni;
- Lo stesso meccanismo permette anche ad un livello del kernel di chiamare una funzione di livello inferiore.
Una SVC non comporta necessariamente un context switch, cioè il passaggio del processo in esecuzione nello stato waiting, seguito dalla schedulazione di un altro processo, per cui anche il salvataggio dei registri del processore nel PCB del processo corrente non avviene sempre ma solo quando al termine dell’esecuzione dell’ISR, non si torna subito al programma chiamante a causa del cambiamento di contesto.
Indispensabile è invece il salvataggio nello stack di sistema del contesto. Quindi la memorizzazione dei registri del processo nel PCB viene effettuata quando un dato processo deve essere swappato dalla RAM (le sue informazioni verranno memorizzate nel suo descrittore sul disco, per un suo eventuale riutilizzo).
2. Descrivere la struttura di un descrittore di processo e la struttura dati di un S.O. di cui fa parte
Ogni volta che un processo viene creato, si definisce un descrittore di processo o Process Control Block (PCB), ossia un record che tiene traccia dell’evoluzione del processo.
Il PCB contiene tutte le informazioni relative a: stato globale (new, running, waiting, ready, terminated), program counter (indica l’indirizzo della successiva istruzione da eseguire per tale processo), risorse possedute (memoria centrale, unità di I/O assegnate staticamente, file aperti, ecc.), valori correnti dei registri del processore all’atto dell’uscita dallo stato running, parametri di schedulazione per l’assegnazione di alcune risorse, posizione dell’area di swap su disco se privo della risorsa memoria centrale, informazioni di account, ecc.
Il PCB viene assegnato ad un processo quando viene posto nello stato new e viene rilasciato quando esce dallo stato terminated e ne viene persa traccia. Tipicamente i PCB sono disposti in code costituite da liste a puntatori.
Appartengono ad una stessa coda PCB di processi caratterizzati dallo stesso stato globale, ad esempio coda dei processi ready ad uguale priorità di schedulazione, coda dei processi waiting in attesa di avviare una operazione di accesso ad un disco, coda dei processi waiting in attesa di sincronizzarsi con uno stesso processo di servizio, ecc.
L’abbandono del processore da parte di un processo provoca il cosiddetto cambiamento di contesto (context switch) nel senso che cambia il contesto a cui appartengono le informazioni contenute nei registri del processore. Il contenuto dei registri appartenente al processo uscente viene salvato nel suo descrittore e, effettuata la schedulazione del processo entrante, il contenuto dei registri del processore viene ripristinato con le informazioni prelevate dal descrittore di quest’ultimo.
Il cambiamento di contesto è quindi il risultato dell’esecuzione di tre procedure distinte del S.O.
Procedure Context_Switch
begin Salvataggio_stato, Scheduling_CPU, Ripristino_stato
end
Nel caso di UNIX, per consentire una migliore gestione della memoria, le informazioni contenute nel descrittore di processo sono distribuite su tre strutture dati: USER STRUCTURE (può essere trasferita su memoria di massa), PROCESS STRUCTURE e TEXT STRUCTURE (sempre residenti in memoria).
UNIX mette a disposizione un array di process structure la cui lunghezza è definita nella fase di inizializzazione del sistema e che fissa il livello di multiprogrammazione del sistema.
Nella process structure sono contenuti: parametri per lo scheduler (priorità, tempo trascorso in stato bloccato, tempo di CPU usato), informazioni sulla memoria, segnali, varie (stato del processo, process identifier PID, parent process identifier PPID, user id UID, group id GID, puntatore a user structure).
Nella user structure sono contenuti: registri macchina (SP e PC), parametri system call, tabella dei file aperti (dal processo in questione), informazione sull’accounting (ovvero sull’uso delle risorse), stack del kernel.
La differenza tra process e user structure è che la prima definisce quando e come reperire le informazioni di esecuzione di processo, mentre la seconda è il processo vero e proprio.
Quelli di informatica www.quellidiinformatica.tk - Scaricato da www.cplusplus.it
3. Descrivere i concetti di stato puntuale e di stato globale di un processo, i possibili stati globali in cui può trovarsi e per ciascuno stato gli eventi che danno luogo a transizioni
Stato puntuale di un processo
Un processo, in quanto sequenza di eventi prodotti dall’esecuzione di un programma, è caratterizzato dal possedere uno stato che è definito dal valore del contatore di programma e dalla n-pla dei valori memorizzati nei registri che contengono le sue istruzioni e i suoi dati.
Concorrono quindi a definire lo stato di un processo i valori delle sue variabili globali, dello stack dei registri del processore accessibili da programma.
Stato globale di un processo
Definisce la condizione in cui si trova un processo in relazione alla sua possibilità di evolvere, ossia di eseguire istruzioni su di un processore. Può essere uno dei seguenti:
- Running: le sue istruzioni sono eseguite da un processore;
- Ready: il processo è in attesa che le sue istruzioni vengano eseguite da un processore;
- Waiting: il processo è in attesa di un evento dovendosi sincronizzare, implicitamente o esplicitamente, con un altro processo.
Nello stato running lo stato puntuale di un processo cambia mentre è bloccato negli stati ready e waiting. Lo stato ready è anche detto di attesa passiva di un processore e manca se si fa l’ipotesi che il sistema possiede tanti processori quanti sono i processi.
Oltre che negli stati precedenti, un processo può essere negli stati di:
- New: in cui attende che il S.O. gli assegni le risorse necessarie per poterne iniziare l’esecuzione;
- Terminated: in cui attende che il S.O. recuperi tutte le risorse che possiede al termine dell’esecuzione delle sue istruzioni.
Transizioni tra gli stati
- New ready: il S.O. ha assegnato al processo la necessaria memoria centrale e la ha inizializzata, per cui si passa all’esecuzione su di un processore della sua prima istruzione;
- Ready running: lo scheduler della CPU ha assegnato un processore al processo;
- Running ready: il processo ha perso forzatamente l’uso del processore, e.g. per eccesso di utilizzo continuativo;
- Running waiting: il processo ha eseguito una primitiva del S.O.
- Per sincronizzarsi implicitamente o esplicitamente con altri processi e deve attendere;
- Per l’uso di risorse gestite dal S.O. e la risorsa non è disponibile o è stata concessa e trattandosi di risorsa di I/O l’operazione è stata lanciata e deve attenderne la terminazione;
- Waiting ready: la risorsa gestita dal sistema operativo è disponibile e viene concessa o l’operazione di I/O in precedenza lanciata è terminata;
- Running terminated: il processo ha eseguito la primitiva del S.O. di terminazione e attende che le risorse di cui ancora dispone siano recuperate dal S.O., oppure ha commesso un errore fatale per il quale il S.O. lo ha mandato in abort.
Quelli di informatica www.quellidiinformatica.tk - Scaricato da www.cplusplus.it
4. Descrivere la differenza concettuale che esiste tra l’implementazione di una funzionalità del S.O. con una procedura del kernel e quella della stessa funzionalità come una procedura eseguibile nel contesto di un processo del S.O.
Il S.O. non è altro che un insieme di programmi, per cui si può pensare che esso sia assimilabile ad un processo. Come tale esso ha una sua evoluzione.
Secondo lo standard di progettazione tradizionale le procedure del kernel vengono eseguite al di fuori di qualsiasi processo: quando il processo in esecuzione è interrotto o effettua una SVC, il suo contesto viene salvato e il controllo passa al kernel. Quest’ultimo ha una sua regione di memoria e un suo stack di sistema e, dopo aver eseguito la procedura richiesta, può ripristinare il processo interrotto prima o schedulare un altro.
Il concetto di processo non si può, in questo caso, applicare al S.O. perché esso viene visto come un’entità separata operante in modalità privilegiata.
Un’altra possibile scelta è quella di eseguire gran parte del codice del kernel nel contesto dei processi utenti, le cui immagini, oltre ad area codice, area dati, stack, heap, PCB, comprenderanno anche uno stack di sistema (del kernel) per la gestione delle chiamate e dei ritorni dalle primitive del kernel, mentre il codice e i dati appartenenti ad esso si troveranno in uno spazio di indirizzamento condiviso da tutti i processi.
Quando si verifica un’interruzione, o una SVC, la modalità di esecuzione passa da utente a supervisore e il controllo passa al kernel che esegue, però, il proprio codice nel contesto dello stesso processo utente.
Il principale vantaggio di questa soluzione è che l’esecuzione di una primitiva del S.O. non comporta un cambio di contesto ma solo un cambio di modalità di esecuzione e la stessa cosa vale per il ritorno al processo chiamante, a meno che al suo posto non sia stato schedulato un altro.
Si evince, da quanto detto, che all’interno di un solo processo utente si possono eseguire sia programmi utente, sia routine del S.O. La sicurezza di questi metodi è garantita dal cambio di modalità di esecuzione (user mode a kernel mode).
Un terzo modo è quello di “concepire” le procedure del kernel come processi separati, eseguiti ovviamente in kernel mode. Questo approccio incoraggia a strutturare il S.O. in moduli, rende efficace l’implementazione di routines e porta ottime prestazioni in ambiente multiprocessing, in cui una parte del S.O. si può eseguire su CPU dedicate.
5. Descrivere il meccanismo di creazione e di terminazione di un processo in generale e in UNIX. Evidenziare inoltre la differenza concettuale che esiste tra condivisione o meno dell’immagine in memoria dei processi padre e figlio
La creazione di un processo avviene in maniera dinamica ad opera di altri processi per mezzo di meccanismi messi a disposizione del S.O. In particolare, un processo viene “eseguito” dall’interprete di JCL (Job Control Language) al momento del lancio di un programma e da un altro processo utente durante l’esecuzione di un programma concorrente.
Anche la terminazione viene realizzata da meccanismi del S.O. e può essere volontaria, in tal caso il processo esegue una apposita primitiva di terminazione, exit(), o forzata, tipicamente per un errore irrecuperabile.
Al momento della creazione di un processo, gli vengono assegnati un identificatore unico (ID) e delle risorse, costituite principalmente da uno spazio di memoria, in cui vi sono un’area codice, un’area dati e uno stack. Ad ogni processo viene, inoltre, dato un PCB contenente tutte le informazioni necessarie al S.O. per gestire il processo stesso.
Alla terminazione, un processo rilascia tutte le sue risorse che vengono recuperate dal S.O. I meccanismi fondamentali che consentono ad un processo di generare un altro processo sono le primitive di fork e join.
In particolare la fork consente ad un processo padre di creare un processo figlio che può condividere o meno lo spazio di memoria del padre ma che in ogni caso eseguirà istruzioni diverse.
Se i due processi condividono area codice, area dati e stack, pur procedendo su flussi di controllo diversi, si hanno in realtà due thread, cioè due sequenze dinamiche all’interno dello stesso processo e quindi il passaggio dal padre al figlio è poco oneroso, non comportando un cambio di contesto.
Se, viceversa, non c’è condivisione dell’immagine in memoria, padre e figlio sono due processi concorrenti e ad ogni passaggio dell’uno all’altro si avrà un cambiamento di contesto.
La fork esiste in due versioni: quella originale non ha parametri di uscita mentre quella ideata in seguito restituisce il PID del figlio, che viene poi accettato in ingresso dalla join, in modo da permettere al padre di sincronizzarsi con la terminazione di un determinato figlio, realizzata con la primitiva exit.
Un’altra versione della join ha come parametro di ingresso un intero che è il numero di figli generati e permette al padre di attendere la terminazione di tutti i figli per proseguire.
Anche in UNIX un processo ne genera un altro con una fork, ma stavolta padre e figlio condividono l’area codice e non l’area dati, perché le loro istruzioni possono modificare in maniera concorrente gli stessi dati.
La sequenza dinamica percorsa dai due processi si differenzia all’interno della comune sequenza statica grazie al parametro di uscita della fork, che assume il valore zero per il figlio ed è uguale al PID del figlio per il padre.
Il figlio può, poi, differenziarsi ulteriormente dal padre con una system call exec, che gli permette di eseguire un nuovo programma, ricopiandone il codice nella propria area testo.
Il padre, invece, esegue una wait per sincronizzarsi con la terminazione del figlio, che può essere volontaria o forzata, questa informazione è contenuta nel parametro di ingresso della wait.
Il figlio, per terminare, esegue una exit che restituisce zero alla wait del padre in caso di esito positivo (-1 in caso di esito negativo) e gli permette di proseguire.
La exit causa anche la chiusura dei file aperti dal processo chiamante e la terminazione di tutti i processi del gruppo di cui era capostipite, mentre i suoi eventuali figli vengono imparentati col processo 1 (init) che avvia il S.O.
Un figlio terminato prima che il padre possa eseguire la wait diventa <Zombie>, in quanto rilascia tutte le risorse possedute ma non il PCB, in attesa che il padre chieda di ricongiungersi con lui.
Quelli di informatica www.quellidiinformatica.tk - Scaricato da www.cplusplus.it
6. Descrivere la natura dei thread e i vantaggi-svantaggi di una programmazione concorrente che fa uso di thread in luogo di processi cooperanti
In generale, un processo è definito dalle risorse utilizzate e dalla locazione di memoria nella quale è in esecuzione. Risorse e dati sono assegnate ad un solo processo per volta. È utile però ammettere la condivisione delle risorse, consentendo l’accesso in concorrenza.
Alcuni sistemi operativi di nuova concezione hanno provveduto a fornire questo meccanismo attraverso l’utilizzo dei thread.
Un thread è l’unità di base dell’utilizzo della CPU e consiste in un program counter, un insieme di registri ed uno stack. Esso condivide con i thread ad esso associati le sezioni codice e dati, oltre alle risorse fornite dal S.O.
L’insieme dei thread e dell’ambiente da loro condiviso prende il nome di task. L’alto grado di condivisione rende la creazione dei thread e il passaggio da un thread all’altro in uno stesso task operazioni meno costose del context switch tra processi.
Un primo svantaggio, legato all’utilizzo dei thread, è il problema del controllo della concorrenza che richiede l’uso delle sezioni critiche o dei lock.
Un modo per rendere più veloce il passaggio da un thread all’altro è quello di implementare i thread a livello utente, evitando chiamate al S.O. che produrrebbero interrupt.
Altro svantaggio legato ai thread si ha nel caso in cui il kernel sia composto da un singolo thread: in questo caso ogni thread al livello utente che esegua una system call finirà per sospendere l’esecuzione dell’intero task fino al completamento della system call stessa.
Per capire l’efficacia dei thread basta confrontare la gestione del controllo con thread multipli con il modo di operare di più processi.
Nel caso di processi multipli, ciascuno opera in maniera indipendente e possiede i propri registri e i propri dati. Invece nel caso di thread multipli, è possibile la condivisione di registri e dati con un evidente risparmio di risorse e maggiore velocità di esecuzione.
Quelli di informatica www.quellidiinformatica.tk - Scaricato da www.cplusplus.it
Anche i thread, in maniera analoga ai processi possono trovarsi ne
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.
-
Domande Teoria Sistemi operativi
-
Appunti Sistemi Operativi - Seminari sistemi UAV , sistemi operativi NuttX-ROS
-
Domande Sistemi operativi
-
Soluzione teoria Sistemi operativi