cerca
Sistemi Operativi - Lezione del 17 marzo 2008
modifica cronologia stampa login logout

Wiki

UniCrema


Materie per semestre

Materie per anno

Materie per laurea


Help

Sistemi Operativi - Lezione del 17 marzo 2008

 :: Sistemi Operativi - Lezione del 17 marzo 2008 ::

Torna alla pagina di Sistemi Operativi

Lezione 5 - Schedulazione dei thread

Quello che abbiamo detti per i processi vale anche per i thread, ma in più i thread hanno altre problematiche.

Una di queste nasce dal fatto che ci sono thread mappati nel SO, e altri invece gestiti autonomamente dal processo. Occorre quindi che il processo scheduli da solo i suoi thread, ed apparire come un flusso unico di istruzioni presso il SO. In genere si usa un algoritmo FCFS o Round Robin. A questo proposito si parla di process-contention scope, cioè di un processo che include lo scheduling dei suoi thread.

A livello di sistema, sia con LWP che con altre mappature, si parla invece di system-contention scope.

Modulo 3 - Lezione 1 - Processi cooperanti

I processi non sono sempre in lotta tra di loro, a volte un programma è stato scritto in modo tale da prevedere che processi diversi collaborino per lo stesso scopo. Nasce quindi l'esigenza di coordinare adeguatamente i vari processi (o i vari thread, al solito).

Se i processi hanno accesso a risorse ben separate, non c'è nessun problema perché non ci sono conflitti (tranne ovviamente quelli per l'accesso al processore). In caso contrario, occorre sincronizzarli.

Ad esempio, se ho processi che devono solo leggere un file, non c'è problema, possono leggere anche a pezzi. Ma se uno legge e l'altro scrive, allora nasce il problema della consistenza dei dati: può accadere infatti che il processo stia leggendo qualcosa, venga interrotto, il processo che scrive modifica, e poi il processo che legge torna a leggere i dati modificati interamente o parzialmente dal processo scrivente. Alla fine si troverà in mano dei dati inconsistenti e spesso inutilizzabili. Deve essere il SO a garantire che queste anomalie non si verifichino, deve prevenire il loro accadere in tutti i casi, per quanto rari possano essere.

Se i processi accedono a risorse condivise, vanno coordinati implicitamente. Altrimenti devo pensare ad un sistema esplicito per coordinarli, nel caso cooperino. I processi indipendenti non hanno niente in comune, quindi la loro coordinazione riguarda solamente le risorse comuni a tutti, eg il disco fisso, la tastiera, lo schermo. È quando i processi sono cooperanti che nascono i problemi, perché in qualche modo devono condividere informazioni, e influenzarsi l'un l'altro.

Ci sono dei vantaggi nello scrivere applicazioni fatte da processi cooperanti.

  • modularità: mi occupo di un aspetto alla volta, e poi metto insieme i moduli che riguardano i diversi aspetti. Certo, il "mettere insieme" va studiato, però in questo modo posso dividermi il lavoro, e non pensare a cose complicate come lo scheduling, perché ci pensa il SO o la libreria di thread che uso.
  • parallelizzazione: più processi possono esser fatti girare su più processori, nel caso ci siano, con vantaggi per tutti. Se ho un singolo processo monolitico ciò non è possibile.
  • scalabilità: se aggiungo dei processi al mio programma, ne amplio le capacità in modo semplice.
  • specializzazione: un processo fa una cosa, e posso chiamare uno specialista che mi scriva quel processo: non devo fare tutto io.
  • qualità del progetto e della realizzazione: sviluppando ogni aspetto in modo separato posso raggiungere qualità elevata.

Lezione 2 - Comunicazione dei processi

Ci sono, come al solito, meccanismi e politiche per far comunicare i processi. La necessità della comunicazione è evidente a tutti. Le caratteristiche della comunicazione stessa sono poi varie. Posso infatti privilegiarne una veloce, una sicura, affidabile, integrata nel linguaggio di programmazione che sto usando, e così via.

Importanti sono la qualità, la velocità e la scalabilità, ovvero la possibilità di aggiungere processi in modo semplice senza andare nei casini.

Implementazione

Ci sono diversi modi per implementare la comunicazione. Li dividiamo in modi diretti e modi indiretti. I modi diretti si hanno quando i processi che comunicano sono a conoscenza in modo esplicito l'uno dell'altro. Nei modi indiretti invece i processi conoscono solo il canale di comunicazione, ma non sanno chi sta ascoltando.

Il vantaggio dei modi indiretti è che posso cambiare interlocutori senza che nessuno ne soffra. In una modalità diretta, se uno dei processi in gioco se ne va, gli altri non saprebbero più che cosa fare, proprio perché avevano accesso alle informazioni specifiche di quel processo. Se invece i processi condividono un canale di comunicazione, allora può esserci chiunque in ascolto e non ci saranno mai problemi di questo genere. Il canale di comunicazione è una struttura passiva, messa a disposizione dal SO, con i soliti requisiti di sicurezza, affidabilità etc.

Lezione 3 - Comunicazione con memoria condivisa

Condividere dati in memoria centrale

I modi sono quelli di utilizzare le variabili globali, o dei buffer.

Variabili globali

2 processi hanno le variabili globali condivise, ovvero hanno lo spazio di indirizzamento che in certi punti si sovrappone. È un sistema molto rapido, e basta garantire la corretta alternanza delle op. di lettura e scrittura.

Inoltre, il fatto stesso che due processi siano stati scritti per collaborare, in un certo modo mi garantisce che faranno di tutto per non crearsi guai a vicenda, proprio perché è nel loro interesse.

Ovviamente, trattandosi di un metodo diretto, i processi devono conoscersi esplicitamente, con tutti gli svantaggi del caso.

Dal punto di vista dei meccanismi, ci sono diversi modi di realizzare una cosa del genere. Uno è quello di copiare l'area delle variabili globali dallo spazio di indirizzamento di un processo nello spazio di un altro. Quindi, deve essere il SO a dare l'illusione che l'area sia condivisa, e l'illusione la ottiene copiando di qua e di là in continuazione. Lo svantaggio di questo sistema è che è lento, nonostante il fatto che si operi in memoria centrale, perché va a finire che si fanno un sacco di operazioni di copia e incolla in tutti e due i sensi.

Un altro sistema è invece far sì che il SO dia uno spazio di memoria fisicamente condiviso, e che lo gestisca bene.

Condivisione di buffer

Il buffer deve contenere solo le informazioni che vogliamo trasferire. Uno lo riempie, e l'altro lo svuota. Rimaniamo quindi nello schema produttore - consumatore.

Occorre quindi che nella mia memoria condivisa esista una struttura dati, limitata o virtualmente illimitata, la quale andrà ben gestita dal punto di vista della sincronizzazione. La scrittura è possibile fino a che il buffer non è pieno. Allo stesso modo, è possibile leggere solo quando il buffer non è vuoto. In generale si usa un buffer circolare, in cui quando si arriva alla fine si ricomincia a scrivere dall'inizio.

La lunghezza del buffer dipende da quanti messaggi voglio che esso contenga. Se è limitata, vuol dire che si possono scrivere solo n messaggi. Posso per esempio stabilire per ogni coppia di processi comunicanti quanto deve essere grosso il loro buffer, a seconda delle loro esigenze.

Il problema dei buffer è che quelli che scrivono tanto si impongono sui processi che invece scrivono poco, monopolizzando la struttura dati.

In termini di funzioni primtive, abbiamo la send(processo, messaggio) che è bloccante: se il buffer è pieno, il processo scrivente viene bloccato finché il buffer non viene svuotato dal ricevente. Posso anche avere la cond_send, ovvero una scrittura condizionale: se il buffer è libero, bene, altrimenti ritorna un errore ma non si blocca. Dall'altro lato avremo la receive e la cond_receive. Da notare che il buffer viene identificato dal processo con cui l'abbiamo concordato.

La comunicazione tramite buffer in generale è asincrona: io scrivo, ma non so quando l'altro leggerà. Per avere una comunicazione sincrona, devo allora avere un buffer di dimensione nulla: in questo modo le scritture saranno tutte bloccanti, e saranno evase solo quando qualcuno in quello stesso istante leggerà dal mio buffer. In pratica sto obbligando i processi a scrivere e leggere nello stesso momento.

La comunicazione è simmetrica quando mittente e ricevente sono noti in modo esplicito. Come abbiamo visto, è uno svantaggio, e quindi è possibile permettere anche una comunicazione asimmetrica, in cui l'accesso in scrittura ad un buffer non è limitato ad un processo solo, ma ad un gruppo di processi, i quali possono decidere di far leggere il loro messaggio a tutti quelli che ascoltano, o solo ad uno del gruppo degli ascoltatori.

Siccome il buffer è una struttura messa a disposizione dal SO, tutte le funzioni per accedervi saranno chiamate di sistema. Ecco quindi che tutti i processi, volenti o nolenti, quando accederanno al buffer manderanno insieme anche il loro PID. Questo serve per identificare chi sta comunicando in quel momento. Le chiamate di sistema relative al buffer possono anche prevedere l'indicazione di una priorità del messaggio, oppure di una deadline, ovvero quanto tempo un messaggio è disponibile a passare in coda.

Lezione 5 - Comunicazione con Mailbox

È un sistema indiretto, in cui non c'è conoscenza esplicita tra i processi che comunicano. Già il sistema dei gruppi permette di rendere non necessaria la conoscenza esplicita dei processi, però rimane sempre la necessità di identificare l'id dei gruppi. Questo sistema invece è del tutto anonimo.

Il processo sa solo che deve scrivere i suoi messaggi in un certo posto, la mailbox, che viene richiesta al SO. Un sinonimo di mailbox è porta. Il ricevente pesca i messaggi dalla porta, e non sa chi è il mittente.

La mailbox, essendo creata dal SO, è globale, e ha un nome per identificarla. I messaggi contengono il pid del processo che scrive, il nome della mailbox, le info costituenti il messaggio vero e proprio e altre info a supporto della gestione dei messaggi nella mailbox.

Ferma un attimo: abbiamo appena detto che la Mailbox garantisce "anonimità" tra processi, e poi qui invece si dice che i messaggi contengono il pid di chi li scrive? Com'è sta storia? La storia è che il pid viene innanzitutto scritto in automatico, visto che si tratta di una chiamata di sistema e il sistema sa esattamente quale processo sta scrivendo in un dato istante. Inoltre, se il processo A (scrivente) vuole una risposta, il processo B che controlla la mailbox non può usare la mailbox stessa per rispondere, in quanto essa è monodirezionale. Quindi, si creerà una mailbox che stavolta viene controllata da A. E B, in ascolto sulla prima mailbox saprà dire: "la domanda arriva da A che ha questo pid, io ho associato questo pid ad una certa mailbox, e quindi so dove andare a scrivergli le risposte".

In modo simile al buffer, la mailbox può avere una capienza limitata o illimitata, indipendentemente dal numero di processi che poi vi scriveranno dentro. Come prima per i buffer, le chiamate di sistema sono sintetizzabili in create(M), delete(M), send(M, messaggio), receive(M, messaggio), cond_send(M, messaggio). M è la mailbox. Per accedere ad una mailbox, un processo deve richiederne i diritti al SO. Dal punto di vista della sicurezza quindi siamo allo stesso livello dei buffer. Se le mailbox sono di proprietà invece del processo, quando il processo che detiene i diritti della mailbox muore, anche la mailbox muore con esso.

E sempre come per i buffer, le send e le receive sono bloccanti a seconda della dimensione della mailbox stessa (vedi sopra per il motivo), quindi per sincronizzare invio e ricezione userò una mailbox di dimensione nulla. Se la dimensione della mailbox è limitata, la mia comunicazione sarà bufferizzata, ovvero passerà un po' di tempo prima che il mio messaggio inviato venga ricevuto. Una mailbox sincrona è detta meccanismo di rendez-vous, perché sostanzialmene obbliga i due processi a inviare e ricevere nello stesso tempo.

All'interno della mailbox, l'ordinamento dei messaggi può essere di un tipo qualsiasi.

Comunicazione molti a uno, uno a molti, molti a molti

La mailbox si presta comodamente a questo tipo di comunicazioni.

Nello scenario molti a uno, il processo che legge dalla mailbox è il server, e chi scrive sono i client. Se il processo server muore, non c'è problema: la stessa mailbox può essere assegnata ad un altro processo.

Per la comunicazione uno a molti, ci sono più processi di servizio che soddisfano le richieste. Se uno è occupato, ce n'è magari un altro che può rispondere, con un vantaggio in termini di tempo di risposta percepito dal client.

Poi c'è la comunicazione molti a molti, in cui diversi processi di servizio comunicano con diversi processi client.

Lezione 6 - Comunicazione con i file

File condivisi

Un po' come per la memoria centrale condivisa, chiedo al SO dei permessi su di un certo file che il SO controlla ma a cui si può accedere. Come al solito, occorre che il SO controlli la sincronia di scritture e letture.

L'ordine dei messaggi non è prestabilito, ma dipende da come i processi si mettono d'accordo. L'ordine di accesso, trattandosi di processi che vogliono accedere alla risorsa disco, dipende invece dallo scheduler del SO.

Pipe

È come un file, ma realizzato in memoria centrale. Le funzioni per accedervi sono le stesse che si usano per accedere ai file, ma il SO non scrive niente sul disco. La sincronia viene attuata con le solite tecniche.

La pipe è una struttura dati FIFO, il che vuol dire che ciò che viene scritto per primo sarà letto per primo dal processo all'altro capo della pipe.

Lezione 7 - Comunicazione coi Socket

È una generalizzazione in rete delle pipe, dal punto di vista concettuale, anche se dal punto di vista pratico è molto complessa.

Si crea un'architettura client-server: invio richieste ad 1 porta, ed il server ivi in ascolto risponde. Tra una richiesta ed un'altra, il server attende.

L'identificazione del server avviene tramite l'accoppiata indirizzo:porta.

I messaggi hanno dimensione fissa o variabile, dipende dalle applicazioni, e sono ordinati FIFO. I socket si possono creare o distruggere, e si può leggerci o scriverci tramite due canali monodirezionali. Ciò vuol dire che nel momento in cui una connessione via socket viene accettata, è possibile sia per il server che per il client comunicare su due canali separati. Ma questo implica una cosa: se il server usa quella porta per comunicare con un client, tutti gli altri client troveranno la porta chiusa. Ecco quindi che è possibil fare in modo che la comunicazione di risposta tra server e client avvenga su di una porta che il client specifica nella richiesta.

Il tipo di connessione tra le due macchine può essere di diverso tipo, gestita oppure no, o ancora multicast, ma questa è roba da reti e non da SO.

Torna alla pagina di Sistemi Operativi