Che materia stai cercando?

Anteprima

ESTRATTO DOCUMENTO

<copia mess in un buffer e collegalo a buca_post.primo>

else flag = false;

end.

Il tipo mailbox incontrato all’inizio del codice è implementabile attraverso un record con due campi:

contatore che è un intero che esprime il numero di elementi in coda alla mailbox e un campo primo di

tipo coda che esprime la coda dei msg nella mailbox o dei processi in attesa di msg nella stessa. La send

asincrona effettuata dal mittente consta di due parametri: l’indirizzo del buffer della propria area di

memoria il cui contenuto deve essere inviato al destinatario, e l’identificatore del destinatario. A questo

punto, il kernel accede all’area di memoria del mittente, copia il buffer indicato in un buffer della

propria area dati, e restituisce il controllo al mittente, il quale riscontra che il proprio è stato liberato. In

questa versione di send, il mittente invia il suo msg senza preoccuparsi dello stato del destinatario; a

tempo debito, il destinatario riceverà il msg senza interessarsi dello stato attuale del mittente. Il suo

vantaggio evidente è che ottimizza il parallelismo; tuttavia presenta anche degli svantaggi: trattandosi di

un meccanismo a basso livello, il cercare di realizzare schemi complessi è complicato. Inoltre, il kernel

dovrebbe avere un buffer illimitato, dato che il mittente potrebbe fare infinite send senza attendere

alcuna receive.

22. Illustrare le modalità di sincronizzazione possibili tra mittente e destinatario nel modello a

scambio di messaggio e l’algoritmo di una receive bloccante.

Quando dei processi interagiscono fra loro, devono essere sincronizzati per garantire la mutua

esclusione, e hanno bisogno di scambiarsi info per cooperare. Un modo per soddisfare entrambi è lo

scambio di messaggi, che in più ha il vantaggio di un’implementazione semplice sia su sistemi

multiprocessore che monoprocessore. In questo modello ciascun processo dispone della propria

memoria locale, evolve in un ambiente proprio che non è comune ad altri processi. Ne segue che non è

possibile che i processi entrino in competizione su strutture dati condivise. Ne consegue, anche, che se

due processi devono interagire, non possono farlo tramite la memoria comune, ma solo tramite lo

scambio di messaggi attraverso il kernel del S.O.: ogni volta che un processo desidera ricevere

informazioni riguardo un altro, deve fare una richiesta al kernel. Le modalità di sincronizzazione che i

due processi adottano sono: send e receive nelle loro varie sfaccettature. La send è la primitiva di invio,

mentre la receive è la primitiva di ricezione. Le varie primitive send, receive si distinguono fra loro per

due motivi fondamentali: 1) il modo in cui i meccanismi di trasmissione e ricezione si sincronizzano tra

loro; 2) il modo in cui vengono designati il processo destinatario della send e il processo mittente della

receive. Tali primitive possono essere classificate così:

send asincrona: il processo mittente non attende la consegna del msg e continua nella propria

esecuzione; send bloccante: il processo mittente attende la trasmissione del msg sulla rete; send

sincrona: il processo mittente attende la consegna del msg al destinatario; receive bloccante: il

destinatario si blocca fino a quando non gli viene inviato il msg; receive non bloccante: il destinatario

prosegue comunque e gli viene segnalato se il msg è stato recapitato.

Una possibile implementazione della receive bloccante è la seguente:

procedure receive_blocc(buca_post,mess);

begin

if buca_post.contatore > 0 then

<preleva un msg da buca_post.primo, copialo in mess e collega il buffer liberato a

buffer_liberi.primo>;

else begin

<poni il processo running in attesa in buca_post.primo>;

context_switch;

end;

end.

Quelli di informatica www.quellidiinformatica.tk - Scaricato da www.cplusplus.it

La receive bloccante è una system call caratterizzata da due parametri: l’indirizzo del buffer del

destinatario in cui il msg deve essere memorizzato, nome della mailbox. Poiché più mittenti possono

voler inviare messaggi ad uno stesso destinatario, può essere previsto uno spazio apposito nell’area di

memoria del kernel, che è la mailbox del destinatario in questione, nella quale i msg in arrivo vengono

sistemati in una coda. Il vantaggio di questa soluzione è l’attesa passiva del processo che attende un

messaggio, lo svantaggio consiste nell’impossibilità di memorizzare messaggi di tipo differente in una

stessa mailbox.

23. Illustrare le modalità di sincronizzazione possibili tra mittente e destinatario nel modello a

scambio di messaggio e l’algoritmo di una send sincrona.

Quando dei processi interagiscono fra loro, devono essere sincronizzati per garantire la mutua

esclusione, e hanno bisogno di scambiarsi info per cooperare. Un modo per soddisfare entrambi è lo

scambio di messaggi, che in più ha il vantaggio di un’implementazione semplice sia su sistemi

multiprocessore che monoprocessore. In questo modello ciascun processo dispone della propria

memoria locale, evolve in un ambiente proprio che non è comune ad altri processi. Ne segue che non è

possibile che i processi entrino in competizione su strutture dati condivise. Ne consegue, anche, che se

due processi devono interagire, non possono farlo tramite la memoria comune, ma solo tramite lo

scambio di messaggi attraverso il kernel del S.O.: ogni volta che un processo desidera ricevere

informazioni riguardo un altro, deve fare una richiesta al kernel. Le modalità di sincronizzazione che i

due processi adottano sono: send e receive nelle loro varie sfaccettature. La send è la primitiva di invio,

mentre la receive è la primitiva di ricezione. Le varie primitive send, receive si distinguono fra loro per

due motivi fondamentali: 1) il modo in cui i meccanismi di trasmissione e ricezione si sincronizzano tra

loro; 2) il modo in cui vengono designati il processo destinatario della send e il processo mittente della

receive. Tali primitive possono essere classificate così:

send asincrona: il processo mittente non attende la consegna del msg e continua nella propria

esecuzione; send bloccante: il processo mittente attende la trasmissione del msg sulla rete; send

sincrona: il processo mittente attende la consegna del msg al destinatario; receive bloccante: il

destinatario si blocca fino a quando non gli viene inviato il msg; receive non bloccante: il destinatario

prosegue comunque e gli viene segnalato se il msg è stato recapitato.

Var buca_post: mailbox;

procedure send_sinc(bica_post, mess, flag);

begin

if buca_post.contatore < 0 then

begin

<copia mess nel buffer di ricezione mes1 del primo processo proc in attesa di un msg>

<poni proc nella coda dei processi Ready>

end;

else

begin <poni il processo running in attesa in buca_post.primo>;

context_switch;

end;

end.

I parametri della send sincrona sono due: indirizzo del buffer della propria area di memoria il cui

contenuto deve essere inviato al destinatario e l’identificatore del destinatario. Quando il mittente

esegue una send viene effettuata una system call e interviene il kernel del S.O. A questo punto, si

presentano due alternative: il destinatario ha già eseguito una receive e quindi il mittente viene

temporaneamente sospeso attendendo il msg di avvenuta ricezione, ma poiché non vi è alcun ostacolo

l’operazione viene portata rapidamente a compimento con la liberazione dei due processi, oppure il

Quelli di informatica www.quellidiinformatica.tk - Scaricato da www.cplusplus.it

destinatario non ha ancora eseguito la receive e, quindi, il mittente viene sospeso finchè il destinatario

non effettua la receive. Uno dei vantaggi di questa soluzione è che non si pone il problema della

lunghezza del buffer del kernel e non vi è possibilità di perdita di msg. Lo svantaggio più evidente

consiste nella perdita di parallelismo, poiché il mittente deve aspettare che il destinatario abbia ricevuto

il msg prima di continuare la sua esecuzione.

24. Illustrare le modalità di sincronizzazione possibili tra mittente e destinatario nel modello a

scambio di messaggio e l’algoritmo di una receive non bloccante.

Quando dei processi interagiscono fra loro, devono essere sincronizzati per garantire la mutua

esclusione, e hanno bisogno di scambiarsi info per cooperare. Un modo per soddisfare entrambi è lo

scambio di messaggi, che in più ha il vantaggio di un’implementazione semplice sia su sistemi

multiprocessore che monoprocessore. In questo modello ciascun processo dispone della propria

memoria locale, evolve in un ambiente proprio che non è comune ad altri processi. Ne segue che non è

possibile che i processi entrino in competizione su strutture dati condivise. Ne consegue, anche, che se

due processi devono interagire, non possono farlo tramite la memoria comune, ma solo tramite lo

scambio di messaggi attraverso il kernel del S.O.: ogni volta che un processo desidera ricevere

informazioni riguardo un altro, deve fare una richiesta al kernel. Le modalità di sincronizzazione che i

due processi adottano sono: send e receive nelle loro varie sfaccettature. La send è la primitiva di invio,

mentre la receive è la primitiva di ricezione. Le varie primitive send, receive si distinguono fra loro per

due motivi fondamentali: 1) il modo in cui i meccanismi di trasmissione e ricezione si sincronizzano tra

loro; 2) il modo in cui vengono designati il processo destinatario della send e il processo mittente della

receive. Tali primitive possono essere classificate così:

send asincrona: il processo mittente non attende la consegna del msg e continua nella propria

esecuzione; send bloccante: il processo mittente attende la trasmissione del msg sulla rete; send

sincrona: il processo mittente attende la consegna del msg al destinatario; receive bloccante: il

destinatario si blocca fino a quando non gli viene inviato il msg; receive non bloccante: il destinatario

prosegue comunque e gli viene segnalato se il msg è stato recapitato.

Var buca_post, buffer_liberi:mailbox, flag: boolean;

procedure receive_non_blocc(buca_post,mess,flag);

begin

if buca_post.contatore > 0 then

begin

flag = true;

<preleva un msg da buca_post.primo, copialo in mess e collega il buffer liberato a

buffer_liberi.primo>;

end;

else flag = false;

end;

end.

Quando si esegue questa system call il destinatario non viene bloccato anche se non c’è nessun msg

disponibile. Questo tipo di receive serve per realizzare un polling di mailbox. Supponiamo che un

processo abbia la possibilità di attingere da più mailbox, è evidente che l’uso di una receive non

bloccante è consigliato poiché è inutile rimanere bloccato il destinatario su di una mailbox quando il

msg potrebbe arrivare in un’altra mailbox. Per esigenze di programmazione, potrebbe essere necessario

fare in modo che un processo ricevente si blocchi in seguito ad una receive e fino a quando giunge un

msg; per rendere possibile questo in S.O. che implementano la receive non bloccante, si fa così: il

processo effettua la receive e se il risultato è negativo si blocca su di un semaforo temporale (un

semaforo che automaticamente diventa verde dopo un certo intervallo di tempo). Quando viene

Quelli di informatica www.quellidiinformatica.tk - Scaricato da www.cplusplus.it

risvegliato, il processo riesegue la receive non bloccante, la quale restituirà ogni volta un parametro che

indica se il buffer è pieno o vuoto.

25. Illustrare le modalità di sincronizzazione possibili tra mittente e destinatario nel modello a

scambio di messaggio e l’algoritmo di una send asincrona che impiega una delle modalità di

identificazione del destinatario illustrate.

Quando dei processi interagiscono fra loro, devono essere sincronizzati per garantire la mutua

esclusione, e hanno bisogno di scambiarsi info per cooperare. Un modo per soddisfare entrambi è lo

scambio di messaggi, che in più ha il vantaggio di un’implementazione semplice sia su sistemi

multiprocessore che monoprocessore. In questo modello ciascun processo dispone della propria

memoria locale, evolve in un ambiente proprio che non è comune ad altri processi. Ne segue che non è

possibile che i processi entrino in competizione su strutture dati condivise. Ne consegue, anche, che se

due processi devono interagire, non possono farlo tramite la memoria comune, ma solo tramite lo

scambio di messaggi attraverso il kernel del S.O.: ogni volta che un processo desidera ricevere

informazioni riguardo un altro, deve fare una richiesta al kernel. Le modalità di sincronizzazione che i

due processi adottano sono: send e receive nelle loro varie sfaccettature. La send è la primitiva di invio,

mentre la receive è la primitiva di ricezione. Le varie primitive send, receive si distinguono fra loro per

due motivi fondamentali: 1) il modo in cui i meccanismi di trasmissione e ricezione si sincronizzano tra

loro; 2) il modo in cui vengono designati il processo destinatario della send e il processo mittente della

receive. La primitiva send richiede di specificare quale processo deve ricevere il msg; analogamente

molte implementazioni permettono al processo ricevente di specificare il mittente del msg da ricevere.

Gli schemi per la realizzazione delle primitive send e receive sono due: indirizzamento diretto e

indiretto. Con l’indirizzamento diretto, la primitiva send contiene il pid del destinatario. La receive si

può gestire in due modi: si richiede di indicare esplicitamente il pid del mittente (comunicazione diretta

simmetrica), oppure di esplicitare nella receive come parametro di uscita il pid del mittente

(comunicazione diretta asimmetrica). L’altro approccio è l’indirizzamento indiretto, nel quale, i msg non

viaggiano direttamente dal mittente al destinatario ma sono inviati ad una struttura dati condivisa

(mailbox) che si compone di code che contengono temporaneamente i msg, finché un processo non ne

preleva qualcuno. Utilizzando il metodo di indirizzamento indiretto, l’algoritmo di una send asincrona

può essere il seguente:

var pid: integer /* pid del destinatario */, buca_post, buffer_liberi: mailbox, flag: boolean;

procedure send_asinc(buca_post, mess, pid, flag);

begin

flag = true;

if buca_post.contatore < 0 then

begin

< copia mess e pid nel buffer di ricezione mess1 del primo processo proc in attesa di un msg>;

<poni proc nella coda dei processi Ready>;

end;

else if buffer_liberi.contatore > 0 then

<copia mess e pid in un buffer e collegalo a buca_post.primo>;

else flag =false;

end;

end.

26. Illustrare le modalità di sincronizzazione possibili tra mittente e destinatario nel modello a

scambio di messaggio e l’algoritmo di una send sincrona che impiega una delle modalità di

identificazione del destinatario illustrate.

Quelli di informatica www.quellidiinformatica.tk - Scaricato da www.cplusplus.it

Quando dei processi interagiscono fra loro, devono essere sincronizzati per garantire la mutua

esclusione, e hanno bisogno di scambiarsi info per cooperare. Un modo per soddisfare entrambi è lo

scambio di messaggi, che in più ha il vantaggio di un’implementazione semplice sia su sistemi

multiprocessore che monoprocessore. In questo modello ciascun processo dispone della propria

memoria locale, evolve in un ambiente proprio che non è comune ad altri processi. Ne segue che non è

possibile che i processi entrino in competizione su strutture dati condivise. Ne consegue, anche, che se

due processi devono interagire, non possono farlo tramite la memoria comune, ma solo tramite lo

scambio di messaggi attraverso il kernel del S.O.: ogni volta che un processo desidera ricevere

informazioni riguardo un altro, deve fare una richiesta al kernel. Le modalità di sincronizzazione che i

due processi adottano sono: send e receive nelle loro varie sfaccettature. La send è la primitiva di invio,

mentre la receive è la primitiva di ricezione. Le varie primitive send, receive si distinguono fra loro per

due motivi fondamentali: 1) il modo in cui i meccanismi di trasmissione e ricezione si sincronizzano tra

loro; 2) il modo in cui vengono designati il processo destinatario della send e il processo mittente della

receive. La primitiva send richiede di specificare quale processo deve ricevere il msg; analogamente

molte implementazioni permettono al processo ricevente di specificare il mittente del msg da ricevere.

Gli schemi per la realizzazione delle primitive send e receive sono due: indirizzamento diretto e

indiretto. Con l’indirizzamento diretto, la primitiva send contiene il pid del destinatario. La receive si

può gestire in due modi: si richiede di indicare esplicitamente il pid del mittente (comunicazione diretta

simmetrica), oppure di esplicitare nella receive come parametro di uscita il pid del mittente

(comunicazione diretta asimmetrica). L’altro approccio è l’indirizzamento indiretto, nel quale, i msg non

viaggiano direttamente dal mittente al destinatario ma sono inviati ad una struttura dati condivisa

(mailbox) che si compone di code che contengono temporaneamente i msg, finché un processo non ne

preleva qualcuno. Utilizzando il metodo di indirizzamento indiretto, l’algoritmo di una send sincrona

può essere il seguente:

var pid: integer /* pid del destinatario */, buca_post: mailbox;

procedure send_sinc(buca_post, mess, pid, flag);

begin

if buca_post.contatore < 0 then

begin

< copia mess e pid nel buffer di ricezione mess1 del primo processo proc in attesa di un msg>;

<poni proc nella coda dei processi Ready>;

end;

else

begin

<metti il processo running in attesa in buca_post.primo>;

context_switch;

end;

end.

27. Mettere a confronto, elencandone vantaggi e svantaggi, la tecnica di scambio di messaggio

mediante mailbox e quella che prevede che il destinatario riceva con il messaggio

l’identificatore del mittente.

La differenza sostanziale tra lo scambio di messaggi tra due processi tramite una mailbox e tramite il

passaggio del pid del mittente insieme al messaggio sta nel fatto che nella prima non c’è una interazione

vera e propria tra i due processi mentre nella seconda soluzione i due processi “lavorano” a stretto

contatto. Il vantaggio più evidente dell’uso dell’indirizzamento indiretto (scambio di messaggi tramite

mailbox) è che, separando il mittente e il ricevente, permette una maggiore flessibilità nell’uso dei

messaggi. Inoltre si ha un incremento di parallelismo dato che più processi possono inviare più

messaggi alla mailbox senza creare problemi di ricezione per i destinatari. Uno dei svantaggi più

evidenti riguarda il possesso della mailbox stessa: nella maggior parte dei casi la mailbox per un sistema

Quelli di informatica www.quellidiinformatica.tk - Scaricato da www.cplusplus.it

client/server, detta nello specifico “porta”, appartiene al processo ricevente ed è, quindi, facile capire

che se si termina questo processo, viene distrutta anche la porta (mailbox) impossibilitando l’invio e il

recapito dei messaggi. Tale inconveniente, nella seconda soluzione che prevede che il destinatario riceva

con il messaggio il pid del mittente, è del tutto assente visto che i due processi si sincronizzano per

scambiarsi i messaggi in modo autonomo senza l’utilizzo di terze strutture.

28. Descrivere l’implementazione delle procedure send e receive di tipo sincrono mediante

primitive asincrone.

Delle cinque primitive fondamentali (send asincrona, send sincrona, send bloccante, receive bloccante,

receive non bloccante), le due tipicamente messe a disposizione dai S.O. sono la send asincrona e la

receive bloccante. Volendo comunque disporre anche di send e receive sincrone, si può pensare di

poterle realizzare mediante primitive asincrone. In realtà il risultato da ottenere è che, una volta che il

messaggio sia passato dal mittente al destinatario, i due processi siano subito pronti a partire insieme,

ossia siano resi ready dal kernel. In presenza di una send asincrona e di una receive bloccante ciò può

avvenire solo se la receive precede sempre la send; in tal modo il destinatario si blocca sulla receive e

nel momento in cui il mittente esegue la send asincrona, il kernel (vedendo il destinatario in attesa)

manda il messaggio dal buffer del mittente a quello del destinatario e subito sblocca i due. A tale scopo

è fondamentale assicurarsi che, quando il mittente esegue una send, il destinatario abbia a sua volta già

eseguito una receive. Il problema viene risolto con l’introduzione di due messaggi: “pronto ad inviare” e

“pronto a ricevere”. In particolare il “pronto ad inviare” viene effettuato dal ricevente, mentre il

“pronto a ricevere” viene effettuato dal mittente. Gli algoritmi che realizzano la send e la receive

sincrone, mediante primitive asincrone possono essere implementate come di seguito:

procedure send_sincrona(buca_post, mess)

begin

send_asincrona(buca1_post, mess1);

/* mess1 è un msg di “pronto ad inviare” */

receive_bloccante(buca2_post, mess2);

/* mess2 è un msg di “pronto a ricevere” */

send_asincrona(buca_post, mess);

end.

Procedure receive_bloccante_sincrona(buca_post, mess)

Begin

receive_bloccante(buca1_post, mess1);

send_asincrona(buca2_post, mess2);

receive_bloccante(buca_post, mess);

end.

29. Illustrare l’algoritmo di scheduling della CPU Shortest Process First e quello usato da

Unix.

L’entità del S.O. che si occupa di eseguire gli algoritmi di scheduling della CPU è lo schedulatore a

breve termine (CPU scheduler). Questo componente interviene nel momento in cui un processo passa

fra gli stati running e waiting, oppure quando termina definitivamente la propria esecuzione, o ancora

nei sistemi time-sharing in caso di prerilascio. Gli algoritmi di scheduling della CPU hanno lo scopo di

migliorare il throughput (numero di lavori per unità di tempo), turnaround (tempo di attesa relativo ai

lavori batch), tempo di attesa (la quantità di tempo che un processo deve aspettare nella coda di una

certa risorsa) e tempo di risposta (tempo che passa tra l’input e il primo carattere di output). Lo SJF è

un algoritmo di scheduling della CPU basato sul concetto di priorità, legato alla quantità di tempo di

utilizzo della risorsa da parte del processo. Nella fattispecie, il processo che usufruirà della CPU sarà

Quelli di informatica www.quellidiinformatica.tk - Scaricato da www.cplusplus.it

quello che impegnerà per il minor tempo la CPU stessa. Tale soluzione presenta, però, un notevole

problema: quello di dover preconoscere il tempo di utilizzo di una risorsa per ciascun processo in attesa

sulla medesima. Di questo tempo, di solito, se ne fa una stima tramite la media esponenziale:

Tn+1 = A* tn + (1-A)*Tn , dove tn è il tempo effettivo di utilizzo della CPU (CPU burst), Tn è una

stima di tale CPU burst; A è un parametro che varia tra 0 e 1. Quando un processo va in esecuzione,

occupa la CPU per qualche istante, dopodiché se mette in attesa sul semaforo di una risorsa. Se il

processo è I/O bound, sarà caratterizzato da CPU burst brevi. Viceversa se il processo è CPU burst,

monopolizzerà la CPU per parecchio tempo e potrà essere interrotto solo grazie ad un timer. Per

esempio, geniale è la soluzione di UNIX: nel descrittore di ogni processo è presente un contatore,

incaricato di contare il tempo durante il quale il processo è running (tale contatore viene incrementato

ogni 20 ms da un Daemon). Un secondo contatore, situato sempre nel medesimo descrittore, viene

incrementato ogni secondo, ma periodicamente viene dimezzato, indipendentemente dal fatto che il

processo sia running o meno. Ne deriva che avere un valore piccolo di questo secondo contatore

significa che ultimamente il processo ha usato poco la CPU e quindi cicli di CPU burst molto brevi,

diventando I/O bound. A tali processi si assegna una priorità elevata; se invece il valore del secondo

contatore è alto, significa che gli ultimi CPU burst sono stati lunghi e quindi a tali processi va attribuita

una priorità bassa.

30. Illustrare le modalità di sincronizzazione del processo che esegue un driver con l’attività

svolta dalla relativa periferica.

Un Driver è una particolare unità di programma che dirige a basso livello le operazione di I/O. Si può

pensare che esso sia un processo del kernel del S.O.; di conseguenza, il processo driver può essere

eseguito solo attraverso una SVC. Un’altra possibilità, è che il driver sia eseguito nel contesto di un

processo specifico, detto processo Driver, al quale un processo utente, che desidera effettuare

un’operazione di I/O, invia un messaggio mediante apposite procedure del kernel del S.O.

Successivamente il processo utente si metterà in attesa che il driver esegua il proprio compito, il quale

spedirà un messaggio al processo sospeso. Su di una macchina nella quale il processo driver è parte del

kernel del S.O. esiste uno stretto legame tra le istruzioni del driver stesso e del kernel. Nel momento in

cui arriva un’interruzione al driver sospeso, parte una ISR contenente la routine del driver specifica per

quella richiesta di I/O. I S.O. per i quali si adotta questa tecnica devono essere rigenerati ad ogni

aggiunta di nuove periferiche (questo è il caso d UNIX). In S.O., in cui un’operazione di I/O lancia un

processo driver, quest’ultimo deve attendere che venga iinviato un segnale di interrupt dalla periferica

da esso controllata, mettendosi in attesa mediante una wait su di un semaforo inizializzato a zero.

All’arrivo della interruzione parte una ISR che, a sua volta, effettua una signal sbloccando il driver.

Questa seconda soluzione è a volte preferita per il motivo della sua maggiore leggerezza: infatti, nel

caso di aggiunta di una periferica nuova bisogna soltanto “caricare” il driver nella fase di boot, senza

modificare il S.O. (è il caso della famiglia MS Windows). Ogni driver, a prescindere dall’ambiente in cui

si trova (utente o Kernel), ha sempre bisogno di una procedura del Kernel mediante la quale possa

sospendersi una volta che sia iniziata l’operazione di I/O. Ad esempio, il driver chiede un dato alla

periferica e poi si mette in attesa fin quando non lo riceve. La particolare procedura del Kernel che

viene eseguita in questo caso è detta WAIT FOR INTERRUPT. Nel modello in cui il processo driver è

direttamente accessibile agli altri processi, essa viene implementata a mezzo di una wait su un semaforo

privato del driver. Per uscire da questa wait, occorre trasformare l’interrupt HW, che arriva dalla

periferica, in una SWI che sblocca il driver. A tal fine, basta inserire una signal nel relativo ramo della

ISR. Anche nel caso di un driver gestito a livello Kernel, la ISR avrà tanti rami quanti sono i driver, ma

questa volta ogni ramo contiene direttamente la routine del driver: tale routine comincia le operazioni

di I/O e poi si autosospende, per riprendere dopo che è arrivato l’interrupt dalla periferica.

Quelli di informatica www.quellidiinformatica.tk - Scaricato da www.cplusplus.it

31. Descrivere il concetto di virtualizzazione di una periferica e come la virtualizzazione si

implementata da un S.O.

La virtualizzazione di una risorsa consiste nel fare apparire privata una risorsa comune. Data una risorsa

e più processi che la condividono, è immediato comprendere che per una corretta competizione è

necessario che le procedure di accesso vengano eseguite in mutua esclusione. Si indica, a tal proposito,

col termine sezione critica una sequenza di istruzioni che devono essere eseguite in modo mutuamente

esclusivo con altre sezioni critiche. A tal fine basta associare alla struttura dati che rappresenta la risorsa

un semaforo inizializzato a 1, e far precedere e seguire ogni sezione critica da una wait e una signal su

tale semaforo; in questo modo un solo processo per volta potrà trovarsi all’interno della sezione critica.

Detti Richiesta e Rilascio le procedure della risorsa gestore, è possibile virtualizzare per i processi utenti

la risorsa gestita definendo, per ogni possibile procedura d’uso proc_uso della risorsa, una procedura

proc_uso_virtuale in questo modo:

procedure proc_uso_virtuale

begin

richiesta;

proc_uso; /* procedura d’uso reale */

rilascio;

end.

32. Descrivere la funzione dello swapper o schedulatore a medio termine in un S.O.

Tra i vari tipi di scheduling esiste lo schedulatore a medio termine, il cui nome si riferisce alla frequenza

relativa con cui le funzioni sono eseguite. Lo scheduling a medio termine è una parte della funzione di

trasferimento sul disco dei processi, infatti si decide di aggiungere un processo a quelli che sono

parzialmente nella memoria principale e perciò pronti ad essere eseguiti. Generalmente, la decisione di

introdurre un processo in memoria (swap in) è basata sulla necessità di gestire il gradi di

multiprogrammazione. La decisione di trasferire un processo in memoria considererà lo spazio di

memoria che un processo può occupare. Tale tecnica consiste nel lavorare con un quantitativo di

processi attivi maggiore di quello che potrebbe essere consentito con le tradizionali tecniche dì gestione

della memoria. Se il kernel ha un certo numero di processi aperti, ma non c’è abbastanza spazio in

memoria centrale, alcuni di essi vengono spostati nella memoria di massa; tale spostamento viene

chiamato swapping. Ad esempio, un processo può essere svuotato in memoria di massa quando si

mette in attesa del completamento di un’operazione di I/O: questa è l’operazione di swap-out. I

processi ready dovrebbero trovarsi tutti in memoria centrale per velocizzare al massimo il loro

passaggio a running. Ovviamente se deve essere eseguito un processo residente in memoria di massa

occorre dapprima effettuare lo swap-in di tale processo in memoria centrale e contemporaneamente lo

swap-out di un processo residente in memoria centrale, affinché quest’ultima si liberi. Anche Unix

utilizza lo swapping per consentire che i processi non risiedano in memori di massa in alcune fasi: un

processo viene swappato su disco se chiede memoria dopo una fork, se cerca di espandere la propria

area dati oppure se il suo stack supera lo spazio riservato. Tale swapping viene effettuato dal Daemon

Swapper; quest’ultimo interviene ogni pochi secondi per effettuare lo swap-in dei processi pronti ad

essere inseriti nella cosa dei Ready cominciando da quello residente da più tempo. Lo swap-out dei

processi viene invece effettuato a partire da quelli meno prioritari e quindi residenti da più tempo su

disco. Lo Swapper si arresta in due casi: 1) Non ci sono più processi pronti su disco (Ready); 2)Non è

possibile effettuare swap-out.

Quelli di informatica www.quellidiinformatica.tk - Scaricato da www.cplusplus.it

33. Descrivere la tecnica della gestione della memoria a partizioni variabili evidenziandone i

difetti e motivando perché tale tecnica è idonea alla gestione di una partizione di disco

dedicata a fungere da area swap.

La gestione della memoria a partizioni variabili rientra in quelle tipologie di gestione della memoria che

prevedono che l’immagine di un processo in stato running sia caricata completamente in memoria

centrale (nel qual caso corrisponde soltanto alla RAM). La tecnica a partizioni variabili prevede che il

numero delle partizioni (fette di memoria) venga determinato dinamicamente a seconda delle esigenze

dell’utente. In tal caso questo numero sarà legato alla quantità di processi che è necessario mantenere

nello stesso tempo in memoria e dalle esigenze in termini di spazio di memoria di ciascuno di essi. Un

caso banale di questa tecnica è l’allocazione con partizione singola. In questo modello il S.O. risiede

nella “memoria bassa” ed un singolo processo utente è in esecuzione, all’interno della propria

partizione, nella “memoria alta”. Il problema più immediato di questa tecnica è che si potrebbe avere

una sovrapposizione dell’area di memoria occupata dal S.O. da parte della partizione del processo

utente, ma si può facilmente risolvere mediante l’uso di un opportuno registro base e di un registro

limite di memoria. Invece, la tecnica più usata oggi è quella a partizioni variabili multiple: ovvero, a

ciascun processo viene associata una partizione di memoria ad indirizzi consecutivi, in modo che vi

possano essere contenuti codice, stack, e dati del processo stesso. La traduzione degli indirizzi logici in

quelli fisici viene fatta tramite un registro base in questo modo: se ind. Logico < Reg. Limite allora ind.

Fisico = Reg. base + ind. Logico, altrimenti il processo va in ABORT. Dato che non c’è ordine di

terminazione dei processi, in memoria possono rimanere dei “buchi” (partizioni vuote), lasciate dai

processi che terminano aleatoriamente. Le tecniche di gestione delle partizioni vuote sono tre:

FIRST FIT – alloca il primo buco sufficientemente grande. Viene allocata al processo la prima

partizione che può contenerlo; la ricerca non onerosa dato che non si scandiscono tutte le partizioni

vuote. BEST FIT – alloca il più piccolo buco capace di contenere il processo. La ricerca va effettuata su

tutta la lista e poi si sceglie quella più piccola. WORST FIT – alloca la fetta più grande, ricercandola in

tutta la lista delle partizioni. Tale strategia produce le partizioni più grandi del processo stesso con la

speranza di lasciare spazio abile per “arruolare” l’immagine di un altro processo. Il primo criterio tende

a far perdere meno tempo, il secondo a ridurre gli spazi vuoti, mentre l’ultimo tende a lasciare più

spazio libero per altri eventuali processi. La tecnica a partizioni variabili soffre di frammentazione

interna, dovuta al fatto che un processo potrebbe occupare un buco che è di poco più grande rispetto al

processo stesso, e lo spazio che rimane è troppo piccolo per essere gestito dato che la sua gestione

richiederebbe più spazio del buco stesso. Inoltre soffre di frammentazione esterna, ovvero della

frantumazione dello spazio libero di memoria in piccole fette inutilizzabili. Può succedere cioè che non

sia possibile allocare memoria ad un processo, dato che non c’è al momento un “buco” abbastanza

grande, nonostante il fatto che il totale della memoria libera sia sufficiente per le esigenze del processo

in questione. Per risolvere il problema della frammentazione esterna si può utilizzare un sistema di

compattazione (Garbage Collector) che recupera lo spazio riorganizzando la disposizione dei processi

in memoria. In pratica questo sistema accorpa i vari “buchi” liberi in un unico spazio vuoto. Questa

tecnica è adottata alla gestione di un’area di swap, poiché mediante un canale DMA (direct memory

access) si può gestire, in modo indipendente dalla CPU, il passaggio dei processi da RAM a HDD e Da

HDD a RAM ad indirizzi diversi: in poche parole i processi vengono presi dalla memoria e swappati su

disco e poi da disco vengono portati in memoria in altre locazioni, risistemando la dislocazione delle

immagini dei processi nella RAM. Questa tecnica si può applicare solo se il programma in questione è

rilocabile, ovvero espresso mediante indirizzi relativi, in pratica è impossibile applicare la compattazione

se l’allocazione del programma viene fatta in fase di compilazione, assemblaggio o caricamento.

Un’alternativa alla compattazione consiste nel disporre di più registri base, in modo che uno stesso

programma possa essere suddiviso in memoria in più parti, ad esempio allocando l’area di programma,

l’area stack, e l’area dati in punti differenti della RAM. In questo caso, però, bisogna fare uso dei registri

speciali, detti registri Fence, che servono ad impedire che nella fase di mapping un indirizzo possa

“fuoriuscire” dall’area associata al processo.

Quelli di informatica www.quellidiinformatica.tk - Scaricato da www.cplusplus.it

34. Illustrare la tecnica della gestione della memoria a pagine, i possibili supporti HW che un

processore deve rendere disponibili per la sua realizzazione ed il meccanismo di protezione

utilizzato.

La tecnica della gestione a pagine consiste nel suddividere la memoria fisica in blocchi di dimensioni

fisse, detti FRAME, e la memoria logica in blocchi di uguale dimensione detti pagine. Quando un

processo deve essere eseguito le sue pagine vengono caricate nei frame della memoria. Ogni indirizzo

generato dalla CPU è diviso in due parti: un numero di pagina (p) e un offset di pagina (d). Il primo

serve da indice per la tabella delle pagine, che contiene l’indirizzo di ogni pagina nella memoria fisica.

L’insieme di questo numero di pagina e dell’offset di pagina permette di definire l’indirizzo

dell’informazione desiderata nella memoria fisica, mediante il seguente supporto HW: si legge il numero

di pagina dell’indirizzo logico, si accede al corrispondente indirizzo nella tabella delle pagine, si preleva

l’indirizzo fisico, il quale viene giustapposto all’offset per formare l’indirizzo vero e proprio. Dal punto

di vista pratico, il mapping viene eseguito a tempo di esecuzione (dinamicamente) mediante un

dispositivo HW che ricava la pagina fisica da quella logica. Questa tecnica è possibile, però, sole se le

pagine fisiche sono disgiunte (non sovrapposte). In sistemi meno recenti, il componente incaricato di

sostituire l’indice di pagina logica con l’indirizzo di pagina fisica era realizzato da una batteria di registri

contenenti le corrispondenze fra pagine logiche e fisiche; esso era detto MMU, il quale ad ogni

context_switch veniva caricato con dei nuovi valori. I registri della batteria sono tanti quanto deve

essere lunga la tabella delle pagine. Se le pagine non sono disgiunte il problema va risolto all’interno

della CPU; in questa soluzione la tabella delle pagine è contenuta nella memoria stessa, e l’HW è ridotto

ad un solo registro speciale del processore che punta in memoria alla posizione di tale tabella. Per

ottenere quindi l’indirizzo di un frame occorre sommare al contenuto di questo registro l’indirizzo della

pagina richiesta e accedere in memoria all’indirizzo che ne consegue. Ogni processo possiede una

propria tabella delle pagine, quindi al cambiare del processo running cambia dinamicamente il

contenuto del registro, il quale punterà così ad un’altra tabella. Lo svantaggio evidente di questa

alternativa sta nel suo richiedere degli accessi supplementari in memoria, la quale, come è noto,

comporta tempi di accesso più lunghi rispetto ai registri HW dedicati. D’altro si ha il vantaggio di

eliminare il limite fisico per quanto riguarda il numero di pagine occupabili, eccetto ovviamente quello

della dimensione fisica della memoria. Una soluzione intermedia è quella della memoria associativa: una

memoria nella quale l’accesso avviene per chiave e non per indirizzo. Quando ai registri associativi si

presenta un elemento, esso viene confrontato contemporaneamente con tutte le chiavi; nella colonna

“chiave” figureranno le pagine logiche e nella colonna “dato” quelle fisiche. La ricerca è estremamente

veloce ma il costo è elevato. Tuttavia la memoria associativa può essere utilizzata per contenere solo

una parte della tabella delle pagine; nel migliore dei casi la chiave richiesta sarà presente in uno dei

registri associativi e verrà restituito l’indirizzo del corrispondente frame. In caso contrario non resta che

rivolgersi alla tabella completa situata in memoria centrale. Il massimo della velocità si ha costruendo un

dispositivo HW che ricerca contemporaneamente sia nella memoria associativa che nella memoria

centrale: se la chiave viene trovata nella memoria associativa si manda un segnale di abort alla ricerca in

memoria centrale, se non viene trovata si continua con la ricerca (più lenta) che accede alla memoria

principale. La protezione della memoria è garantita dal metodo in sé; infatti non può verificarsi

un’invasione verso altra pagine, poiché le dimensioni di pagina logica e fisica sono uguali. L’unico

registro Fence di protezione occorrente è quello che impedisce all’indice di pagina logica di fuoriuscire

dalla tabella delle pagine presente in memoria centrale. Inoltre questa tecnica non presenta

frammentazione esterna (le pagine sono più piccole dell’immagine di processo), ma denuncia una forte

frammentazione interna (le pagine sono più grandi dell’immagine di processo), in quanto mediamente

l’ultima pagina di ogni processo è occupata per metà.

Quelli di informatica www.quellidiinformatica.tk - Scaricato da www.cplusplus.it

35. Illustrare la tecnica della gestione della memoria a segmenti, i possibili supporti HW che

un processore deve rendere disponibili per la sua realizzazione e il meccanismo di protezione

utilizzato.

La tecnica della segmentazione consiste nel dividere la memoria in segmenti, cioè pagine che hanno una

lunghezza variabile a seconda delle esigenze. Analogamente allo spazio fisico, anche lo spazio logico

viene visto come suddiviso in segmenti: esso è costituito dalla coppia indirizzo del segmento e offset

all’interno del segmento. La fase di mapping, che si occupa di sostituire all’indirizzo di segmento logico

quello fisico, richiede necessariamente un’operazione di addizione invece di una semplice sostituzione

come avviene nel caso della memoria a partizioni variabili. La tabella che realizza la corrispondenza fra

spazio fisico e logico è detta TABELLA DEI SEGMENTI, ed è necessariamente abbastanza ampia. In

tale tabella ad ogni segmento è associata una doppia informazione: l’indirizzo base del segmento nella

memoria fisica e la lunghezza del segmento stesso. Quando viene rilevato un indirizzo logico, in cui

l’indice di segmento serve ad accedere alla tabella dei segmenti dalla quale si ricava la base e il limite del

corrispondente segmento fisico; il displacement viene confrontato con quest’ultimo allo scopo di

impedire accessi illegali. Se il displacement è minore, viene sommato alla base per ottenere l’indirizzo

fisico. L’HW necessario per determinare l’indirizzo fisico include dunque un comparatore, un

addizionatore ed un array di coppie (base, limite). Nei fatti, si ricorre sempre al sistema di utilizzare

l’insieme di una memoria associativa e di un registro speciale che punta alla tabella completa, residente

in memoria. La memoria associativa contiene solo una parte di questa tabella. Se si tenta l’accesso ad un

segmento il cui indice è assente all’interno della memoria associativa, si prosegue la ricerca in parallelo

nella tabella dei segmenti. A ciascuno di questi si associano i diritti di accesso, che permettono di gestire

i meccanismi di protezione relativi ad un processo. In tal modo un programma, anche breve, può essere

disposto su più segmenti, ognuno dei quali con un proprio criterio di accesso. Con la tecnica della

segmentazione sparisce la frammentazione interna (le pagine sono più grandi del processo), perché i

segmenti possono essere definiti della lunghezza necessaria. Rimane, invece, il problema della

frammentazione esterna (le pagine sono più piccole del processo), infatti dopo aver allocato i vari

segmenti in memoria, potremmo ritrovarci con dei buchi di memoria troppi piccoli da essere gestiti. Per

eliminare questo inconveniente, si usa a tal proposito la tecnica della segmentazione paginata nella quale

ogni segmento è organizzato a pagine.

36. Mettere a confronto le tecniche di gestione de una memoria a pagine e a segmenti

evidenziando vantaggi e svantaggi delle due tecniche.

Nella tecnica a segmentazione un programma utente viene suddiviso in segmenti di lunghezza variabile.

L’indirizzo logico che ne deriva è composto da due parti, numero del segmento e un offset, come tra

l’altro avviene nella tecnica della paginazione. La differenza che intercorre tra le due è che, con la

segmentazione, un programma può utilizzare più di un segmento che possono essere anche non

contigui. La segmentazione elimina quindi la frammentazione interna ma, come la paginazione, presenta

il problema di quella esterna. Mentre la paginazione è invisibile al programmatore, la segmentazione è

solitamente visibile, ed è conveniente per organizzare programmi e dati. Generalmente i segmenti

assegnati ad un programma sono diversi, ma lo svantaggio di questo meccanismo è che il

programmatore deve prestare attenzione alla dimensione massima del segmento. Uno dei vantaggi della

tecnica della paginazione è che, se il codice di un processo è separato dai dati, esso può essere condiviso

con altri processi; questo è possibile solo se non ci sono pagine che contengono sia codice che dati,

anche se la tendenza ad accodare i dati al codice in uso nello stesso frame è di solito favorita, perché ha

come effetto positivo quello di minimizzare la frammentazione interna. Inoltre la paginazione permette

un efficace meccanismo di protezione: è possibile definire il genere di accesso consentito sulla pagina.

Uno svantaggio della paginazione, seppur minimo, consiste nel fatto che se la suddivisione in pagine

della memoria diviene elevata, il calcolo computazionale degli indirizzi diventa oneroso.

Quelli di informatica www.quellidiinformatica.tk - Scaricato da www.cplusplus.it

37. Descrivere la tecnica di gestione della memoria a memoria virtuale.

Nella tecnica della gestione a memoria virtuale, la memoria non coincide più con la RAM ma risulta

essere fisicamente allocata sulla memoria di massa; solo un sottoinsieme di questa memoria rimane in

RAM. Durante l’esecuzione di un processo, può accadere che lo stesso abbia bisogno di accedere ad

una particolare pagina residente non nella RAM ma su disco; in questo caso il S.O. prevede un

meccanismo automatico che effettua l’arresto del processo, l’ingresso della pagina mancante in memoria

e la ripresa del processo, il tutto in maniera completamente trasparente rispetto all’utente. Perché un

programma possa essere eseguito è indispensabile che le sue istruzioni siano caricate sempre in RAM, è

questo il motivo fondamentale per cui un sottoinsieme della memoria deve trovarsi sempre in RAM. In

teoria, sarebbe sufficiente che in RAM fosse presente solo l’istruzione corrente e i dati su cui opera. Il

procedimento che permette il passaggio dei dati da disco a RAM è detto swap-in, mentre quello da

RAM a disco è detto swap-out. Se consideriamo per esempio il modello a pagine, alcune macchine

lavorano a “pagine libere” e altre a “pagine piene”; il secondo sistema prevede che un frame passi dalla

RAM al disco ogni volta che le esigenze del programma richiedono che un’altra pagina effettui il

trasferimento opposto. Nel primo caso, invece, c’è sempre un’altissima probabilità di trovare dello

spazio libero, perché il sistema opera lo swap-out non all’atto dello swap-in, ma periodicamente. E’ da

notare che lo swap-out delle pagine fisiche può essere evitato: si sovrascrive un frame già occupato da

un altro processo grazie al criterio dell’avvenuta modifica. Ad ogni frame si associa un bit di modifica

che, inizializzato a zero, viene posto ad 1 quando si scrive nella pagina un’informazione. Se il bit di

modifica è zero, vuol dire che quella pagina non è mai stata modificata, quindi è inutile effettuare lo

swap-out, dato che è uguale a quello su disco.

38. Descrivere le modalità di realizzazione della gestione a memoria virtuale nel S.O. Unix.

La tecnica di gestione della memoria virtuale in Unix è a pagine libere, cioè ogni volta che si verifica un

“page fault” ci sia sempre qualche pagina disponibile. L’algoritmo di sostituzione utilizzato da UNIX è

un ASG (algoritmo di sostituzione globale), cioè la pagina da swappare viene ricercata tra tutte le

pagine. Il “Page Daemon” viene attivato ogni 250 ms e crea un pool di pagine libere. Ricordiamo che

quando una pagina viene swappata, essa viene messa in questo pool di pagine libere ma il sistema

ricorda a quale processo appartiene, in modo che se quel processo acquisisce la CPU e crea un page

fault, il sistema riconosce che nel pool di pagine libere è rimasta la pagina che serviva e la ripristina,

ottimizzando così i tempi. Il Page Daemon in realtà non esamina soltanto il reference bit, cioè

nell’applicare l’algoritmo della seconda chance non scandisce un semplice vettore di bit, ma ha la

possibilità di accedere ad un vettore di record, detto Core Map, che contiene altre informazioni

aggiuntive riguardo la pagina. La Core Map si trova negli indirizzi bassi della memoria, subito dopo

l’area occupata dal kernel, e occupa una pagina di 1K. Ogni elemento di questo vettore descrive una

pagina di 16 byte. Un’altra informazione è il tipo di segmento che si trova in quella pagina (codice, dati

o stack) e l’offset che quella pagina contiene, cioè a partire da quale offset del segmento la pagina ne

contiene le informazioni. In effetti lo swapping interviene nel momento in cui l’attività di paginazione è

troppo sostenuta. L’algoritmo di sostituzione usato da Unix per effettuare lo swapping delle pagine è

detto Algoritmo della Seconda Chance; per realizzare questo algoritmo si deve disporre di una coda

circolare fatta di bit di riferimento, settati ogni volta che si fa un accesso ad una pagina ed azzerati

periodicamente, e di un puntatore del demone che punta a questi bits. Il daemon ha il suo stato

rappresentato dalla posizione di questo puntatore ai bit di riferimento. La pagina puntata è quella

candidata all’eliminazione; quando il daemon entra in funzione, se trova un 1 nel bit di riferimento

puntato significa che si è acceduti a quella pagina di recente; allora dà una seconda possibilità a quella

pagina azzerando il suo reference bit e avanzando il puntatore al bit di riferimento successivo. Ciò va

avanti fin quando non viene trovato un bit a zero, questo significa che dall’ultima volta che il daemon

ha settato il bit non si sono avuti accessi a quella pagina in questione, e quindi può essere sostituita.

Questo procedimento va avanti finchè il limite delle pagine minime non è stato portato al massimo

prestabilito. Questo algoritmo presenta un inconveniente estremo: se le pagine da sostituire sono

numerose così come il numero di bit settati a 1, per cui il puntatore fa un giro completo dell’array

Quelli di informatica www.quellidiinformatica.tk - Scaricato da www.cplusplus.it

ritornando ad una pagina alla quale aveva dato una seconda chance nel giro precedente, di conseguenza

sostituendola, in questo caso la seconda possibilità della pagina è durata niente. Per questo motivo si è

stabilito che una pagina deve rimanere in memoria per almeno 2 secondi.

39. Illustrare gli algoritmi di sostituzione delle pagine nella gestione a memoria virtuale ed in

particolare l’algoritmo implementato da Unix.

Gli algoritmi di sostituzione delle pagine sono i seguenti:

1) ALGORITMO LIFO: Si ha a disposizione un’informazione che dice da quando tempo la pagina è

presente in memoria centrale. Tale algoritmo, basandosi su questa informazione, sostituisce la pagina

che è presente in memoria da più tempo. Il difetto di questo algoritmo è che non tiene conto del fatto

che una pagina caricata da molto tempo potrebbe ancora servire.

2) ALGORITMO OTTIMALE: Esso prevede che nel momento in cui si deve effettuare una

sostituzione, venga scelta la pagina che da quel momento in avanti non sarà referenziata per maggior

tempo. Questo algoritmo è impossibile da realizzare in quanto non si possono avere informazioni sul

tempo di vita di un processo.

3) ALGORITMO LRU (Last Recently Used): questo algoritmo fa lo stesso ragionamento di quello

ottimale, ma invece di sostituire la pagina che è presente in memoria da più tempo (FIFO), sostituisce

quella inutilizzata da maggior tempo. Il concetto su cui si basa questo algoritmo è che se una pagina è

servita di recente, servirà ancora. Per realizzare questo algoritmo c’è bisogno di un supporto HW che

permetta di ordinare le pagine da quella meno usata a quella più usata nel tempo. Vi possono essere 4

realizzazioni: 1) la CPU ha un counter che viene incrementato ad ogni accesso in memoria il cui valore

viene copiato nella tabella delle pagine; la pagina sostituita è quella col valore del counter più basso. 2) Il

modo più semplice per realizzare LRU sarebbe quello di avere un campo nella tabella delle pagine, in

cui ogni volta che viene fatto riferimento ad una determinata pagina viene scaricato il valore di un

timer; nel momento della sostituzione viene scelta la pagina che ha il valore di questo timer più basso.

3) Si usa un dispositivo HW che realizza una lista di pagine, ordinata in base all’accesso, cioè ogni volta

che si fa riferimento ad una pagina, essa viene messa in testa alla lista. Ne segue che la pagina candidata

alla sostituzione è quella in coda alla lista. 4) Si può effettuare una discretizzazione del tempo in

intervalli, registrando se in ognuno di questi intervalli si è effettuato almeno un accesso alle pagine.

Nella tabella delle pagine, ad ogni accesso ad una di queste si setta il bit di riferimento corrispondente

alla pagina. Inoltre si dispone di un timer tarato ad un intervallo generico I di discretizzazione. Si

dispone anche di una batteria di shift register, uno per ogni pagina della tabella delle pagine, il cui bit

più significativo è proprio il bit di riferimento. Il numero di bit di questi shift register dà il numero di

periodi passati (ognuno di lunghezza I) di cui si vuole tenere conto. Ogni volta che scatta il timer viene

fatto lo shift di tutti gli shift register con bit entranti pari a zero. La pagina candidata alla sostituzione è

quella che in definitiva ha il numero binario più piccolo nel proprio shift register perché è quella che è

stata referenziata meno frequentemente di recente.

4) ALGORITMO LFU (Last frequently used): questo algoritmo invece di sostituire la pagina che non

viene usata da più tempo, sostituisce quella che tiene la più bassa frequenza di uso. Per realizzare ciò

basta avere un contatore che si incrementa ogni volta che si fa accesso ad una pagina (cioè si conta il

numero degli accesi alla pagina). Il problema a cui si può andare incontro in questo modo e che le

pagine che stanno in memoria da più tempo hanno molto probabilmente un valore elevato del

contatore rispetto alle pagine che sono state immesse da poco tempo. Per risolvere questo problema, si

potrebbe pensare ad esempio di dimezzare il contatore periodicamente in modo da ridurre il valore

delle pagine che stanno da molto tempo in memoria e che in passato hanno avuto grande uso ma

recentemente vengono utilizzate di meno. Tuttavia in questo caso no sarebbe più un LFU ma un

algoritmo ibrido. 5) ALGORITMO DELLA SECONDA CHANCE (UNIX): L’algoritmo di

sostituzione usato da Unix per effettuare lo swapping delle pagine è detto Algoritmo della Seconda

Chance; per realizzare questo algoritmo si deve disporre di una coda circolare fatta di bit di riferimento,

settati ogni volta che si fa un accesso ad una pagina ed azzerati periodicamente, e di un puntatore del

demone che punta a questi bits. Il daemon ha il suo stato rappresentato dalla posizione di questo

Quelli di informatica www.quellidiinformatica.tk - Scaricato da www.cplusplus.it

puntatore ai bit di riferimento. La pagina puntata è quella candidata all’eliminazione; quando il daemon

entra in funzione, se trova un 1 nel bit di riferimento puntato significa che si è acceduti a quella pagina

di recente; allora dà una seconda possibilità a quella pagina azzerando il suo reference bit e avanzando il

puntatore al bit di riferimento successivo. Ciò va avanti fin quando non viene trovato un bit a zero,

questo significa che dall’ultima volta che il daemon ha settato il bit non si sono avuti accessi a quella

pagina in questione, e quindi può essere sostituita. Questo procedimento va avanti finchè il limite delle

pagine minime non è stato portato al massimo prestabilito. Questo algoritmo presenta un

inconveniente estremo: se le pagine da sostituire sono numerose così come il numero di bit settati a 1,

per cui il puntatore fa un giro completo dell’array ritornando ad una pagina alla quale aveva dato una

seconda chance nel giro precedente, di conseguenza sostituendola, in questo caso la seconda possibilità

della pagina è durata niente. Per questo motivo si è stabilito che una pagina deve rimanere in memoria

per almeno 2 secondi.

40. Illustrare il meccanismo di protezione utilizzato nella gestione della memoria con la

tecnica della memoria virtuale.

Un problema rilevante che affligge la tecnica della memoria virtuale è quello del “Trashing”. Quando in

un sistema ci sono molti page fault significa che esso sta lavorando male, in quanto molte risorse sono

spese per la sostituzione delle pagine. Il fenomeno del Trashing si verifica quando un processo o un

insieme di processi richiedono più risorse per fare lo swapping delle pagine che per effettuare i loro

compiti. Chiaramente un processo genera un gran numero di fault quando ha un numero di pagine

assegnato che non è sufficiente alle sue esigenze. Supponiamo di avere una strategia di gestione a

“pagine piene”, e di un ASL (algoritmo di sostituzione locale). In caso di un processo che svilupperà

molti fault, questo non uscirà mai ad evolvere. Gli effetti del Trashing possono essere attenuati usando

appunto un ASL, in quanto con questo tipo di algoritmo il processo non può “rubare” le pagine ad altri

processi e la sostituzione avviene tenendo conto soltanto delle sue pagine. In questo modo si ha un

aumento del tempo medio di servizio. Se invece si usa un ASG, non si avrà Trashing locale, in quanto

nel momento in cui si verifica un page fault, la pagina viene recuperata dal pool libero (strategia a

pagine libere) oppure è probabile che sia presa dalle pagine di un latro processo (strategia a pagine

piene), mettendo in crisi la paginazione di quest’ultimo, il quale “ruberà” a sua volta, creando così un

Trashing globale di grande fastidio. Per proteggere un sistema dal fenomeno del Trashing sarebbe

l’ideale fornire ad ogni processo un numero adeguato di pagine. E’ possibile usare la strategia del

working-set, cioè l’insieme delle pagine usate da un processo almeno una volta in un certo numero di

riferimenti più recenti. A tal proposito, se un processo, nella sua evoluzione comincia a lavorare su

pagine differenti da quelle su cui lavorava in precedenza, accadrà che qualcuna delle pagine del working-

set non verrà mai referenziata e quindi si potrà sostituire; viceversa se il processo, negli ultimi

riferimenti, ha referenziato tutte le pagine del suo working-set e la frequenza di paginazione è alta,

significa che ha bisogno di molte pagine e che necessita di un working-set più ampio. Una tecnica di

prevenzione dal trashing più semplice consiste nel contare il numero di page fault di un processo

nell’ultimo lasso di tempo; se questi hanno superato un certo limite è sintomo che il suo working-set è

insufficiente e quindi bisogna ampliarlo nei prossimi page fault. Viceversa se la sua frequenza di page

fault è costante vuol dire che il suo working-set è sufficiente, se la frequenza diminuisce, significa che il

uso working-set è più ampio e che quindi può cedere pagine ad altri processi.

41. Descrivere la struttura di un descrittore di file e, in particolare, la sua realizzazione nel S.O.

UNIX

Un descrittore di file non è altro che un record contenente tutte le informazioni che permettono di

gestire un file generico di un S.O. Tipicamente la struttura dei campi di un descrittore di file cambia a

seconda del S.O. Le informazioni memorizzate in un descrittore di file sono:

Nome: nome simbolico del file;

Tipo di file: informazione necessaria ai sistemi che supportano più tipi di file diversi (e.g. Windows);

Quelli di informatica www.quellidiinformatica.tk - Scaricato da www.cplusplus.it

Locazione: puntatore al dispositivo ed alla locazione del file su quel dispositivo;

Posizione corrente: puntatore alla posizione corrente di lettura/scrittura del blocco;

Dimensione: dimensione attuale di un file;

Protezione: controlli per stabilire chi può leggere, scrivere o eseguire il file,

Contatore di utilizzo: numero di utenti che condividono il file;

Ora, data e identificazione dell’utente: informazioni che possono essere relative alla creazione, ultima

modifica e ultimo utilizzo.

La soluzione adottata in Unix consiste nel comporre i descrittore di due soli campi: nome del file

(simbolico) e un puntatore che punta alla rimanente parte del descrittore (Inode). La dimensione del

descrittore di file è di 16 byte (14 byte per il nome del file e 2 byte per il puntatore). La parte restante,

detta inode (index node), è un record memorizzato nei primi blocchi del disco. Un Inode è di 64 byte e

contiene i seguenti campi: 1) Identificatore del proprietario (UID); 2) Identificatore del gruppo

associato al file (GID); 3) tipo di accesso consentito (9 bit, tre terne ERW, rispettivamente per

proprietario, gruppo del proprietario, altri utenti); 4)Dimensione del file; 5) Ora, data di ultima modifica

e ultimo accesso; 6) numero di Hard Link per gestire la cancellazione del file quando questo è

condiviso; 7) tipo del file (fiel dati, file dir, link simbolico, file di dispositivo); 8) allocazione del file (ci

sono 15 puntatori di cui 12 sono puntatori di livello zero, cioè puntano direttamente ai dati, poi ci sono

un puntatore di livello 1 che contiene indirizzi di blocchi che contengono dati, puntatore di livello 2 che

contiene indirizzi di blocchi che contengono indirizzi di blocchi che puntano a dati e un puntatore di

livello 3 che contiene indirizzi che puntano ad indirizzi che puntano ad indirizzi puntanti dati); 9)

dimensione della I-list.

42. Illustrare le operazioni che un file system rende disponibili ai processi distinguendo tra

operazioni relative alle directory e operazioni relative ai file.

Un file è un tipo di dati astratto. Il sistema operativo mette a disposizione delle system call per eseguire

operazioni sui file. Di queste le più importanti sono:

Creazione di un file: è necessario compiere due passaggi. In primo luogo bisogna trovare lo spazio

per il file nel file system, secondariamente bisogna creare nella directory un nuovo elemento, in cui

registrare il nome del file e la sua posizione nel file system.

Scrittura di un file: è indispensabile una system call che specifichi il nome del file e le informazioni

che si vogliono scrivere su di esso. Grazie al nome il sistema cerca nella directory la posizione del file. Il

file system deve mantenere un puntatore di scrittura alla locazione nel file in cui deve aver luogo la

successiva scrittura. Il puntatore deve essere aggiornato ogniqualvolta si esegue una scrittura.

Lettura di un file: è necessaria una system call che specifichi il nome del file e la posizione in memoria

dove porre il successivo blocco del file. Viene ricercato il file nella directory e il sistema deve mantenere

un puntatore di lettura alla locazione nel file in cui deve aver luogo la successiva operazione di lettura.

Completata la lettura il puntatore viene aggiornato. Generalmente la maggior parte dei sistemi mantiene

solamente un puntatore alla posizione corrente del file, così le operazioni di lettura e scrittura

adoperano lo stesso puntatore risparmiando spazio e riducendo la complessità del sistema.

Riposizionamento in un file: si ricerca il file nella director e si assegna un nuovo valore al puntatore

alla posizione corrente del file. Quest’operazione non richiede alcuna operazione di I/O.

Cancellazione di un file: si cerca il file nella directory associata al file designato. Quindi si rilascia tutto

lo spazio associato al file (in modo che possa essere adoperato da altri file) e si elimina l’elemento della

directory.

Troncamento di un file: consente di mantenere immutati gli attributi ( ad esclusione della lunghezza

del file) pur cancellando il contenuto del file.

Esistono comunque altre operazioni comuni che comprendono l’accodamento di nuove informazioni

alla fine di un file e la rinomina di un file. Queste operazioni primitive possono essere combinate per

effettuare altre operazioni.

Quelli di informatica www.quellidiinformatica.tk - Scaricato da www.cplusplus.it

Una directory può esser vista come un contenitore di file. In realtà può essere considerata come una

tabella di simboli che traduce i nomi dei file negli elementi in essa contenuti. Può essere organizzata in

diversi modi e l’insieme delle operazioni che possono essere eseguite su una directory sono le seguenti:

Ricerca di un file: consiste nello scandire una directory per trovare l’elemento associato ad un dato

file. Poiché i file possono avere nomi simbolici e poiché nomi simili possono indicare relazioni tra file,

deve esistere la possibilità di trovare tutti i file il cui nome soddisfi una particolare espressione.

Creazione di un file: consiste nell’aggiungere un nuovo file nella directory.

Cancellazione di file: consiste nella rimozione di un file dalla directory.

Listare una directory: consiste nella possibilità di elencare tutti i file di una directory, oltre al

contenuto dell’elemento della directory associato a ciascun file dell’elenco;

Rinomina di un file: consiste nella possibilità di rinominare un file, questa rinomina potrebbe

comportare anche la variazione della posizione del file nella directory.

Attraversamento del file system: consiste nella possibilità di accedere ad ogni directory e a ciascun

file contenuto in una directory. Per motivi di affidabilità è buona idea salvare il contenuto e la struttura

dell’intero file system a intervalli regolari (copia di backup).

43. Illustrare le operazioni di apertura e chiusura di un file, la loro utilità e le modalità di

realizzazione in Unix.

L’apertura di un file è un’operazione in cui si trasferisce un descrittore di file associato ad un file dalla

sua directory alla tabella dei file aperti. Quindi aprire un file significa assegnare una nuova entry nella

tabella dei file aperti; da questo momento in poi non bisogna più inizializzare il file con il suo nome

simbolico ma fare riferimento semplicemente alla sua entry nella tabella dei file aperti. Tipicamente, la

tabella dei file aperti ha anche un contatore delle open associato a ciascun file, che indica il numero di

processi che hanno aperto quel file. La chiusura di un file comporta la memorizzazione nella directory

del contenuto dell’entry presente in memori RAM e l’annullamento dell’entry stessa dalla memoria.

Ogni volta che si effettua una close si decrementa il contatore delle open, e quando esso raggiunge il

valore zero il file non è più in uso e viene eliminato l’elemento corrispondente (entry) dalla tabella dei

file aperti. In Unix le operazioni di apertura e chiusura di un file si realizzano attraverso le system call

open e close: fd = open (pathname, mode). La open tramite il pathname del file cerca il suo inode, se il

file non esiste o non è accessibile nella modalità richiesta restituisce al chiamante un codice di errore. Se

il file non è stato aperto in precedenza, la open copia l’inode nella in-core inode table, crea l’entry nella

tabella dei file aperti, inizializza la modalità di apertura, il reference count, crea una entry nella tabella

dei descrittori di file del processo e restituisce al chiamante l’indice di tale entry (fd). S = close (fd): la

close dealloca la entry associata al file dalla sua tabella dei descrittori di file. Se il reference count è

maggiore di 1 viene decrementato e la close termina. Se è uguale a 1 dealloca la in-core inode table ed

elimina l’entry associata al file dalla tabella dei file aperti.

44. Descrivere il concetto di condivisione di un file tra directory distinte e le modalità

realizzative della condivisione.

La condivisione di un file nasce dall’esigenza di voler accedere da diversi punti del disco ad uno stesso

file, senza generare incoerenza tra le varie copie del file come potrebbe succedere effettuando copie

fisiche dello stesso. La tecnica più naturale di implementazione della condivisione di file è quella

duplicare il descrittore di file. L’inconveniente principale di questa tecnica è che avendo più descrittori

che si riferiscono allo stesso file, si deve prevedere una accorta gestione dell’allineamento per evitare

incoerenze. Un altro problema riguarda la cancellazione dei files; infatti volendo cancellare un file

condiviso da una determinata directory si deve stare attenti a cancellare il descrittore ma non il file

altrimenti rimarrebbero appesi gli altri descrittori che facevano riferimento a quel file e che quindi

punterebbero a strutture non di loro appartenenza. Un terzo problema è che, avendo più descrittori di

uno stesso file magari anche con nomi differenti, non si ha conoscenza del numero di riferenze attive al

file per cui non può essere effettuata alcuna operazione sul file stesso. I due metodi utilizzati sono: 1)

soft link, 2) hard link. Un link è un elemento di una directory di tipo speciale che punta ad un elemento

Quelli di informatica www.quellidiinformatica.tk - Scaricato da www.cplusplus.it

di un’altra directory e quindi ad un file o a una sottodirectory condivisa. Il soft link funziona in questo

modo: quando viene effettuato un riferimento ad un file viene effettuata una ricerca nella directory che

contiene il link; parte una routine il cui compito è quello di risolvere un pathname fino ad accedere al

file reale. Il vantaggio enorme che si ha con il soft link è che non c’è possibilità che il sistema fallisca a

seguito di un accesso errato, cioè la routine che risolve il pathname prevede un’uscita di errore qualora

non sia stato possibile risolvere il percorso, invece di arrestare il sistema. Per questo cancellare un link

non comporta problemi. Con questa tecnica anche il problema del disallineamento dei descrittori non

esiste, perché il descrittore è unico e i link sono acceduti tramite puntatori. L’hard link, invece, prevede

un contatore di condivisione, che numera i link che insistono su di un certo file in modo da impedire la

cancellazione dello stesso fin quando questo contatore non ha valore zero. Per implementare questa

soluzione basta inserire nel descrittore reale del file un contatore, appunto. Ciò significa dire che

soltanto quando questo contatore vale 1, alla cancellazione del file vengono rimossi sia descrittore del

file, sia file stesso. Precisamente la cancellazione da una directory che contiene un link (la modalità di

collegamento del link in questione è simile a quello del soft link) si traduce nella eliminazione del link in

questione e nel decremento del contatore di condivisione. Se quest’ultimo è maggiore di 1 il descrittore

del file viene cancellato solo logicamente, cioè reso inaccessibile dalla directory proprietaria ma

accessibile da altri link.

45. Descrivere la differenza tra HW e SW link nella implementazione della condivisione dei

file.

La condivisione di un file nasce dall’esigenza di voler accedere da diversi punti del disco ad uno stesso

file, senza generare incoerenza tra le varie copie del file come potrebbe succedere effettuando copie

fisiche dello stesso. I due metodi utilizzati sono: 1) soft link, 2) hard link. Un link è un elemento di una

directory di tipo speciale che punta ad un elemento di un’altra directory e quindi ad un file o a una

sottodirectory condivisa. Il soft link funziona in questo modo: quando viene effettuato un riferimento

ad un file viene effettuata una ricerca nella directory che contiene il link; parte una routine il cui

compito è quello di risolvere un pathname fino ad accedere al file reale. Il vantaggio enorme che si ha

con il soft link è che non c’è possibilità che il sistema fallisca a seguito di un accesso errato, cioè la

routine che risolve il pathname prevede un’uscita di errore qualora non sia stato possibile risolvere il

percorso, invece di arrestare il sistema. Per questo cancellare un link non comporta problemi. Con

questa tecnica anche il problema del disallineamento dei descrittori non esiste, perché il descrittore è

unico e i link sono acceduti tramite puntatori. L’hard link, invece, prevede un contatore di condivisione,

che numera i link che insistono su di un certo file in modo da impedire la cancellazione dello stesso fin

quando questo contatore non ha valore zero. Per implementare questa soluzione basta inserire nel

descrittore reale del file un contatore, appunto. Ciò significa dire che soltanto quando questo contatore

vale 1, alla cancellazione del file vengono rimossi sia descrittore del file, sia file stesso. Precisamente la

cancellazione da una directory che contiene un link (la modalità di collegamento del link in questione è

simile a quello del soft link) si traduce nella eliminazione del link in questione e nel decremento del

contatore di condivisione. Se quest’ultimo è maggiore di 1 il descrittore del file viene cancellato solo

logicamente, cioè reso inaccessibile dalla directory proprietaria ma accessibile da altri link.

La differenza sostanziale tra i due metodi sta nel fatto che, nella tecnica del soft link, se si cancella un

file fisicamente e dopo si tenta di accedere ad esso tramite uno dei suoi link si avrà un messaggio di

errore da parte del sistema, senza comportare, però, arresti critici dello stesso. Con questa tecnica, se si

vogliono far rimanere attivi n link c’è bisogno di creare n copie fisiche di quell’unico file, creando così

possibili problemi di allineamento. Con al tecnica dell’hard link questo problema non sussiste, in quanto

non è possibile cancellare fisicamente un file fin quando esiste almeno un link attivo, cioè fin quando il

contatore di condivisione è maggiore di 1.

Quelli di informatica www.quellidiinformatica.tk - Scaricato da www.cplusplus.it

46. illustrare la tecnica di allocazione dei file di tipo linkato e la sua evoluzione con l’uso di

una FAT.

La tecnica di allocazione linkata prevede che i vari blocchi di un file siano linkati mediante puntatori

contenuti nei blocchi stessi; in pratica si tratta di una lista a puntatori i cui elementi sono blocchi del

disco. Per percorrere il file basta quindi entrare nel primo blocco e poi utilizzare i puntatori per

accedere agli altri. Con questo metodo non vi è più frammentazione esterna, non c’è bisogno di

compattare i blocchi dato che vengono scritti di seguito; inoltre si può risalire facilmente allo spazio

occupato dal file. Si hanno d’altro canto problemi per quanto riguarda l’accesso diretto ai file,perché

questo è implementato tramite un accesso sequenziale (per accedere al blocco N, si deve accedere agli

altri N-1 blocchi). L’evoluzione con l’uso della FAT di questa tecnica permette l’utilizzo dell’accesso

diretto. Per fare ciò si spostano i puntatori dei vari blocchi sul disco, in particolare si memorizzano le

catene di tutti i puntatori che stanno su di esso. Tutto questo migliora facendo il caching in RAM della

lista dei puntatori ai blocchi del disco; questa modifica pur sprecando della Ram per memorizzare la

FAT permette di supportare l’accesso diretto con buone prestazioni. Sintetizzando, sul disco esistono

uno o più blocchi che contengono un vettore di puntatori di lunghezza pari al numero di blocchi sul

disco. Con questa tecnica esiste una particolare lista che è il file dei blocchi liberi, questa permette di

gestire in modo semplice lo spazio libero su disco perché quando servono dei blocchi per un nuovo file

basta staccarli da essa e quando si elimina un file basta collegare ad essa i blocchi liberati.

47. Illustrare il metodo di allocazione dei file mediante FAT.

Mediante FAT, in pratica, l’allocazione dei file avviene sulla base di un blocco singolo. Ogni blocco

contiene un puntatore a quello successivo nella catena. La FAT serve solo per fornire l’ingresso iniziale

a ciascun file, e conterrà il blocco iniziale e la lunghezza di un file. Anche se è possibile effettuare la

preallocazione, si allocheranno i blocchi man mano che questi diventano necessari. Scegliere un blocco

è a questo punto molto semplice: ogni blocco libero può venire aggiunto alla catena; non ci si deve più

preoccupare della frammentazione esterna, in quanto si ha bisogno di un solo blocco alla volta. Questo

tipo di organizzazione fisica si adatta meglio a file sequenziali che vengono elaborati sequenzialmente;

quindi selezionare un singolo blocco significa scandire tutta la catena fino al blocco desiderato. Uno

svantaggio della tecnica di allocazione mediante FAT è che se è necessario portare in memoria diversi

blocchi di un file allo stesso tempo, come avviene nell’elaborazione sequenziale, si dovranno effettuare

una serie di accessi a differenti parti del disco. Per risolvere questo problema, si ricorre periodicamente

ad una compattazione dei file (e.g. DEFRAG per i sistemi Windows).

48. Illustrare il metodo di allocazione mediante blocco indice.

La tecnica di allocazione mediante blocco indice è quella usata anche da Unix; all’atto di allocare un file

si crea un blocco che contiene tutti i puntatori di quel file. Quando si vuole risalire all’allocazione del

file su disco bisogna sapere dove è memorizzato questo blocco di puntatori (blocco indice). La

directory contiene l’indirizzo del blocco indice. Per leggere l’i-esimo blocco occorre utilizzare il

puntatore che si trova nell’i-esimo elemento del blocco indice, per poi localizzare e leggere il blocco

desiderato. Una volta creato un file, tutti i puntatori del blocco indice sono impostati a NULL. Quando

l’i-esimo blocco viene scritto per la prima volta, viene fornito un blocco dalla lista dei blocchi liberi;

l’indirizzo di questo blocco viene inserito nell’i-esimo elemento del blocco indice. Con l’allocazione

indicizzata si ha uno spreco di spazio, perché se si vuole memorizzare un file con uno o due blocchi

occorre allocare un intero blocco indice utilizzandone solo due puntatori. Però se si riducesse di molto

la dimensione del blocco indice, questo non potrebbe contenere tutti i puntatori sufficienti ad allocare

file di grandi dimensioni. A tal proposito si usa la tecnica dell’indice Multilivello che consiste

nell’impiego di un blocco indice di primo livello che punta a un insieme di blocchi indice di secondo

livello che, a loro volta, puntano ai blocchi dei file stessi. Per accedere a un blocco il S.O. utilizza

l’indice di primo livello, con il quale individua il blocco indice di secondo livello, e con esso trova il

blocco di dati richiesto. Nell’I-Node di Unix ci sono 15 puntatori di 4 byte ciascuno di cui 12 puntano

Quelli di informatica www.quellidiinformatica.tk - Scaricato da www.cplusplus.it


ACQUISTATO

5 volte

PAGINE

40

PESO

349.29 KB

AUTORE

Menzo

PUBBLICATO

+1 anno fa


DETTAGLI
Corso di laurea: Corso di laurea in ingegneria informatica
SSD:
A.A.: 2013-2014

I contenuti di questa pagina costituiscono rielaborazioni personali del Publisher Menzo di informazioni apprese con la frequenza delle lezioni di Sistemi operativi e studio autonomo di eventuali libri di riferimento in preparazione dell'esame finale o della tesi. Non devono intendersi come materiale ufficiale dell'università Napoli Federico II - Unina o del prof Cotroneo Domenico.

Acquista con carta o conto PayPal

Scarica il file tutte le volte che vuoi

Paga con un conto PayPal per usufruire della garanzia Soddisfatto o rimborsato

Recensioni
Ti è piaciuto questo appunto? Valutalo!

Altri appunti di Sistemi operativi

Sistemi operativi - Appunti
Appunto
Sistemi operativi - Problema dei produttori e dei consumatori
Appunto
Sistemi operativi - Esercitazioni varie Linux
Esercitazione
Sistemi operativi - Implementazione programma in C
Appunto