Comunicazione tra processi (IPC)
Comunicazione tra processi (IPC)
Processi Cooperanti
Cooperazione: lavoro congiunto di processi/thread per:
Condividere informazioni (es. file o directory condivisa).
Raggiungere scopi applicativi comuni (modularizzando e velocizzando la computazione).
Influenzare o essere influenzati da altri processi/thread.
Forme di cooperazione:
Coordinamento della computazione: Sincronizzazione.
Scambio di informazioni: Comunicazione.
Esempi di Processi Cooperanti
Produttore - Consumatore
Client - Server
Compilatore - Assemblatore - Loader
Comunicazione Inter-Process Communication (IPC)
Due principali modelli:
Memoria condivisa.
Scambio di messaggi.
Vari meccanismi e politiche.
Caratteristiche:
Quantità di informazioni da trasmettere.
Velocità di esecuzione.
Scalabilità.
Affidabilità.
Sicurezza.
Protezione.
Integrazione nel linguaggio di programmazione.
Semplicità d'uso nelle applicazioni.
IPC: Modelli Implementativi
Memoria condivisa: Processo A e Processo B accedono direttamente a una zona di memoria condivisa.
Scambio di messaggi: Processo A e Processo B comunicano tramite il kernel del sistema operativo, che gestisce lo scambio dei messaggi.
Memoria Condivisa
Caratteristiche:
Condivisione di un segmento di memoria tra due o più processi.
Esempio: il buffer nel producer-consumer.
Comunicazione locale, veloce e bidirezionale.
Più veloce su architetture mono-CPU; su architetture parallele, preferibile lo scambio di messaggi (problemi di allineamento delle cache di memoria).
Svantaggi:
Identificazione dei processi comunicanti.
Consistenza degli accessi in lettura/scrittura.
Il SO non controlla (no protezione del segmento di memoria).
Richiede sincronizzazione dei processi/thread per realizzare la mutua esclusione.
Memoria Condivisa: Passi da Eseguire
Creazione del segmento di memoria.
Unix:
shmget
Connessione al segmento (mediante un puntatore).
Unix:
shmat
Operazioni di lettura/scrittura.
Disconnessione dal segmento.
Unix:
shmdt
Cancellazione del segmento.
Unix:
shmctl
Memoria Condivisa in Unix (Esempio 1)
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>
int main ()
{
int segment_id;
char bogus;
char* shared_memory;
struct shmid_ds shmbuffer;
int segment_size;
const int shared_segment_size = 0x6400; // 25600
/* Allocate a shared memory segment. */
segment_id = shmget (IPC_PRIVATE, shared_segment_size, IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
/* Attach the shared memory segment. */
printf("Shared memory segment ID is %d\n", segment_id);
shared_memory = (char*) shmat (segment_id, 0, 0);
printf ("shared memory attached at address %p\n", shared_memory);
/* Write a string to the shared memory segment. */
sprintf (shared_memory, "Hello, world.");
/* Detach the shared memory segment. */
shmdt (shared_memory);
printf("Wrote Hello World to the segment\n");
/* Now remove the memory segment */
shmctl (segment_id, IPC_RMID, 0);
return 0;
}
IPC_PRIVATE: Indica l'identificatore del segmento di memoria; in questo caso, un segmento condiviso.S_IRUSR | S_IWUSR: Significa che il proprietario del segmento può leggere e scrivere.shmatrestituisce il puntatore alla prima cella della memoria.Parametro 2: il SO decide dove annettere la memoria.
Parametro 3:
0per sola lettura,>0per scrittura.
Comunicazione mediante Pipe
Meccanismo di comunicazione riconducibile al modello “memoria condivisa”.
Caratteristiche:
Comunicazione unidirezionale o bidirezionale?
Se bidirezionale, half-duplex (dati viaggiano in una sola direzione alla volta) o full-duplex (dati viaggiano in entrambe le direzioni)?
Deve esistere una relazione padre-figlio tra i processi comunicanti?
Usabili in locale o anche in rete?
Due tipi:
Convenzionali (o anonime).
Named pipe (note anche come FIFO).
Pipe Convenzionali
Canale di comunicazione tra due processi secondo la modalità del produttore e consumatore.
Il produttore scrive da un’estremità (write-end).
Il consumatore legge dall’altra estremità (read-end).
Fa fluire un flusso di dati da un processo ad un altro in modo unidirezionale.
P1 (Write-end) --pipe-- P2 (Read-end)
Implementazione: file in memoria con una coppia di descrittori di file, uno per scrivere solo in aggiunta e uno per leggere sequenzialmente.
Deve esistere una relazione padre-figlio tra i due processi per condividere i descrittori di file.
In Unix, può essere creato con la chiamata
pipe(int fd[]).fdè il descrittore del file:fd[0]scrittura,fd[1]lettura.
Lettura e scrittura tramite le chiamate
read()ewrite()su file.In Win32:
CreatePipe(),ReadFile()eWriteFile().
Pipe Convenzionali in Unix
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#define BUFFER_SIZE 25
#define READ_END 0
#define WRITE_END 1
int main(void)
{
char write_msg[BUFFER_SIZE] = "Greetings";
char read_msg[BUFFER_SIZE];
pid_t pid;
int fd[2];
/** create the pipe */
if (pipe(fd) == -1) {
fprintf(stderr, "Pipe failed");
return 1;
}
/** now fork a child process */
pid = fork();
if (pid < 0) {
fprintf(stderr, "Fork failed");
return 1;
}
if (pid > 0) { /* parent process */
/* close the unused end of the pipe */
close(fd[READ_END]);
/* write to the pipe */
write(fd[WRITE_END], write_msg, strlen(write_msg)+1);
/* close the write end of the pipe */
close(fd[WRITE_END]);
}
else { /* child process */
/* close the unused end of the pipe */
close(fd[WRITE_END]);
/* read from the pipe */
read(fd[READ_END], read_msg, BUFFER_SIZE);
printf("child read %s\n", read_msg);
/* close the write end of the pipe */
close(fd[READ_END]);
}
return 0;
}
In Unix, una pipe viene ereditata da un processo figlio (inclusi i descrittori!).
Named Pipe
Possono essere bidirezionali.
Relazione padre-figlio non necessaria.
Comunicazione fra più di due processi, anche in ambito distribuito.
Continuano ad esistere anche dopo che i processi comunicanti terminano, se non esplicitamente rimosse.
Supportati sia da Windows che da SO Unix-like (ma con differenze nell’implementazione).
Windows offre un meccanismo più ricco.
Named Pipe in Unix
Sono chiamate FIFO.
Create con
mkfifo().Sono normali file del file system gestiti dalle chiamate
open(),read(),write()eclose().Half-duplex.
Si usano due FIFO per avere una comunicazione full-duplex.
I programmi comunicanti devono risiedere nella stessa macchina (comunicazione solo locale!).
Dati solo byte-oriented.
Named Pipe in Windows
Funzioni:
createNamedPipe()per crearle,ConnectNamedPipe()per connettersi,readFile()ewriteFile()per comunicare.Full-duplex: i dati viaggiano contemporaneamente in entrambe le direzioni.
Dati byte-oriented e message-oriented.
I programmi comunicanti possono risiedere localmente alla stessa macchina o in macchine diverse.
Scambio dei Messaggi
Il modello a memoria condivisa richiede di scrivere molto codice per gestire la memoria condivisa.
Nella comunicazione basata su scambio di messaggi, queste problematiche sono gestite dal SO mediante operazioni del tipo:
send(messaggio)receive(messaggio)
Caratteristiche progettuali:
No memoria condivisa tra processi (utilizzato in contesti distribuiti).
Identificazione dei processi comunicanti e instaurazione di un canale di comunicazione su cui spedire i messaggi.
Scambio dei Messaggi: Messaggi
Contenuto:
Processo mittente.
Processo destinatario.
Informazioni da trasmettere.
Eventuali altre informazioni di gestione.
Dimensione:
Fissa.
Variabile.
Canale di comunicazione:
Fisico (come memoria condivisa, hardware bus).
Logico (come le proprietà logiche).
Scambio dei Messaggi: Implementazione
Domande:
Come si stabiliscono le connessioni (canali)?
Una connessione può essere associata a più di due processi?
Quante connessioni possono esserci fra ogni coppia di processi?
Cos’è la capacità di una connessione?
La dimensione di un messaggio che una connessione può ospitare è fissa o variabile?
Una connessione è unidirezionale o bidirezionale?
Aspetti chiave:
La denominazione dei processi
Comunicazione diretta
Comunicazione indiretta
La sincronizzazione tra mittente e ricevente
Lo scambio dei messaggi può essere bloccante (sincrono) oppure non bloccante (asincrono).
La bufferizzazione
I messaggi scambiati risiedono in una coda temporanea.
Comunicazione Diretta
I processi devono conoscere esplicitamente il nome del destinatario o del mittente:
send(P, msg)– manda un messaggio al processo Preceive(Q, msg)– riceve un messaggio dal processo Q
Proprietà di un canale di comunicazione:
Le connessioni sono stabilite automaticamente.
Una connessione è associata esattamente a due processi (connessione binaria).
Fra ogni coppia di processi esiste esattamente una connessione.
La connessione può essere unidirezionale, ma di norma è bidirezionale.
Comunicazione Indiretta
I messaggi sono mandati e ricevuti attraverso una mailbox o porte.
Ciascuna mailbox ha un identificatore univoco.
I processi possono comunicare solo se hanno una mailbox condivisa.
Le primitive sono definite come:
send(A, msg)– manda un messaggio alla mailbox Areceive(A, msg)– riceve un messaggio dalla mailbox A
Proprietà di un canale di comunicazione:
Viene stabilita una connessione fra due processi solo se entrambi hanno una mailbox condivisa.
Una connessione può essere associata a più di due processi (non binaria).
Fra ogni coppia di processi comunicanti possono esserci più connessioni.
La connessione può essere unidirezionale o bidirezionale.
Proprietario di una mailbox: un processo o il SO
Se il proprietario è un processo:
Risiede nel suo spazio di indirizzi.
Quando il processo proprietario termina, la mailbox viene deallocata.
Il proprietario può solo ricevere msg dalla mailbox.
Gli altri processi (utenti) possono solo inviare msg alla mailbox.
Se il proprietario è il SO, occorrono ulteriori primitive per:
creare una mailbox
inizialmente il processo che la crea è l’unico ricevente (proprietario di default)
cancellare una mailbox
trasferire il privilegio di ricevere msg dalla mailbox
per dar luogo a più processi riceventi per mailbox
Scenario di Condivisione di una Mailbox
P1, P2, e P3 condividono una mailbox A
P1 invia un messaggio ad A; P2 e P3 eseguono una receive da A
Quale processo riceverà il messaggio spedito da P1?
La risposta dipende dalla semantica della consegna adottata:
Permettere che una connessione sia associata con al più due processi
Permettere ad un solo processo alla volta di eseguire un’operazione di receive
Permettere al sistema di decidere arbitrariamente quale processo riceverà il messaggio
Il sistema può poi anche notificare il ricevente al mittente
Comunicazioni con Molti Mittenti o Riceventi
Comunicazioni da molti mittenti a un ricevente: * 1
Comunicazioni da un mittente a molti possibili riceventi: 1 *
Comunicazioni da molti mittenti a molti possibili riceventi: * *
Sincronizzazione
Scambio Bloccante (Sincrono)
Invio bloccante: il processo che invia viene bloccato finché il messaggio viene ricevuto dal processo che riceve o dalla mailbox.
Ricezione bloccante: il ricevente si blocca sin quando un messaggio non è disponibile.
Rendezvous.
Scambio Non Bloccante (Asincrono)
Invio non bloccante: il processo che invia manda il messaggio e riprende l’attività.
Ricezione non bloccante: il ricevente acquisisce un messaggio valido o nullo.
Bufferizzazione
Tre modi per implementare il buffer o coda:
Capacità zero – 0 messaggi
Richiede rendezvous
Capacità limitata – Il mittente deve bloccarsi se la coda è piena
Capacità illimitata – Il mittente non si blocca mai
Nel caso 1, si parla di bufferizzazione esplicita o assente; negli altri due casi, si dice che la bufferizzazione è automatica.
Politiche di Ordinamento delle Code dei Messaggi
First In, First Out.
Priorità.
Scadenza.
Scambio dei Messaggi in Windows XP
Local Procedure Call.
I programmi applicativi sono come client del server di subsystem di Windows XP.
Windows XP usa due tipi di porte:
Di connessione (chiamate object): visibili a tutti i processi per instaurare un canale di comunicazione.
Di comunicazione
Il client apre la porta di connessione del subsystem con il suo identificatore e manda una richiesta di connessione.
Il server crea due porte private di comunicazione e restituisce l’identificatore di una di queste al client.
Il client ed il server usano il corrispettivo identificatore di porta per mandare i msg (memorizzati temporaneamente nella coda dei msg della porta se di lunghezza inferiore a 256 bytes o altrimenti in un section object condiviso) o la segnalazione di richiamo (callback), e per ascoltare le risposte.
Comunicazione in Ambito Distribuito
Socket.
Remote Procedure Call (RPC).
Remote Method Invocation (RMI) -- in Java.
Socket
Un socket è definito come un estremo di un canale di comunicazione.
Concatenazione di numero di IP e numero di porta.
Il socket 161.25.19.8:1625 è assegnato alla porta 1625 sul server 161.25.19.8.
La connessione consisterà in una coppia di socket.
I server che implementano specifici servizi (ftp, http, ecc.) ascoltano porte note (ftp su 21, http su 80, ecc.).
Tutte le porte sotto la 1024 sono considerate note.
I socket possono essere orientati alla connessione (TCP) e senza connessione (UDP).
Servizi con Connessione
Il servizio è offerto attraverso tre fasi:
Apertura della connessione tra due punti della rete.
Utilizzo della connessione per inviare i dati.
Chiusura della connessione.
Caratteristica fondamentale: i dati sono ricevuti nello stesso ordine in cui vengono inviati.
Analogia: sistema telefonico.
Due varianti: stream di messaggi e stream di byte.
Servizi senza Connessione
Non c’è alcuna connessione.
I dati sono inviati impacchettati in messaggi, ognuno dei quali contiene l’indirizzo completo del destinatario.
I messaggi non arrivano necessariamente nell’ordine in cui sono inviati.
Analogia: sistema postale.
Protocolli
Applicazione | Protocollo strato applicazione | Protocollo strato trasporto | . |
|---|---|---|---|
Posta elettronica | SMTP | TCP | |
Accesso a terminale remoto | telnet | TCP | |
Trasferimento file | FTP | TCP | |
Web | HTTP | TCP | |
Streaming Audio/Video | RTSP/RTP | TCP (comandi) + UDP (flusso) | |
Server di file remoto | NFS | tipicamente UDP | |
Telefonia su internet (VoIP) | SIP, H.323, altri | tipicamente UDP | |
Gestione della rete | SNMP | tipicamente UDP | |
Protocollo di routing | RIP | tipicamente UDP | |
Risoluzione dei nomi | DNS | tipicamente UDP |
Comunicazione via Socket TCP in Java
Server
public class DateServer {
public static void main(String[] args) {
try {
ServerSocket sock = new ServerSocket(6013);
// now listen for connections
while (true) {
Socket client = sock.accept();
PrintWriter pout = new PrintWriter(client.getOutputStream(), true);
// write the Date to the socket
pout.println(new java.util.Date().toString());
// close the socket and resume
// listening for connections
client.close();
}
}
catch (IOException ioe) {
System.err.println(ioe);
}
}
}
La chiamata al metodo
accept()è bloccante.
Client
public class DateClient {
public static void main(String[] args) {
try {
//make connection to server socket
Socket sock = new Socket("127.0.0.1", 6013);
InputStream in = sock.getInputStream();
BufferedReader bin = new BufferedReader(new InputStreamReader(in));
// read the date from the socket
String line;
while ((line = bin.readLine()) != null)
System.out.println(line);
// close the socket connection
sock.close();
}
catch (IOException ioe) {
System.err.println(ioe);
}
}
}
Socket: Vantaggi e Svantaggi
Semplice.
Efficiente.
Di basso livello (livello di trasporto): permette la trasmissione di un flusso non strutturato di byte.
È responsabilità di Client e Server interpretare e organizzare i dati in forme complesse.
RPC e RMI risolvono questo problema.
Remote Procedure Call (RPC)
La chiamata di procedura remota (RPC) astrae il meccanismo di chiamata di procedura per usarlo fra sistemi con una connessione di rete.
Il client può invocare una procedura remota nello stesso modo in cui ne invocherebbe una locale.
Il server ha una porta per ogni RPC.
RPC nasconde i dettagli della comunicazione assegnando al client uno stub (uno per ogni procedura remota).
Stub: segmento di codice che permette di invocare la procedura remota.
Il client invoca una procedura remota passando i parametri allo stub.
Lo stub lato client esegue il marshalling dei parametri e li trasmette al server usando tecniche di scambio di messaggi.
Marshalling: strutturazione dei parametri in un formato che può essere trasmesso via rete.
Necessario per via dell’uso di strutture dati complesse e differente rappresentazione lato client/server dei dati, ad es. 32 vs 64 bit (little-endian vs big-endian).Nel server un analogo del client, lo skeleton, riceve la chiamata di procedura, spacchetta i parametri tradotti e invoca la procedura stessa e (se necessario) restituisce il risultato al client.
Eventuali valori di ritorno sono passati al client con la stessa tecnica.
Semantica della Chiamata
Un problema è la semantica della chiamata:Le chiamate RPC possono fallire o essere duplicate ed eseguite più volte, come risultato di problemi sulla rete
Due possibili interpretazioni:
Il SO può assicurare che la chiamata venga eseguita
1) al più una volta
2) esattamente una volta
Semantica:La semantica “al più una volta” è garantita associando a ciascun msg una marca di tempo; il server mantiene uno storico delle marche delle chiamate già eseguite. Se riceve una chiamata già eseguita la scarta.
La semantica "esattamente una volta" garantisce l’esecuzione della chiamata. Il server implementa il protocollo(a) e in più notifica al client che la chiamata RPC è stata ricevuta ed eseguita (msg ACK di acknowledgement). Il client deve rispedire ciascuna chiamata RPC periodicamente fino a quando non riceve un ACK per ogni chiamata
Come il client conosce il numero di porta del server?
Tramite indirizzo prefissato: associazione fissa e nota RPC- Porta.
Dinamicamente mediante un servizio del SO di rendezvous (o matchmaker -- accoppiatore) che riceve una richiesta dal client e restituisce il numero di porta prefissato del server.
Remote Method Invocation (RMI)
Meccanismo IPC di Java basato su RPC.
RMI permette ad un programma Java di invocare metodi su oggetti remoti.
I parametri in RPC sono strutture dati ordinarie, mentre in RMI sono oggetti!
Marshalling dei Parametri (RMI)
Se i parametri sono oggetti:
Se sono oggetti locali, essi vengono passati per copia tramite una tecnica nota come serializzazione.
lo stato di un oggetto è scritto in uno stream di byte tipi semplici sono mappati direttamente si serializzano ricorsivamente i membri dell’oggetto Molti oggetti delle API Java sono serializzabili (ovvero implementano l’interfaccia java.io.Serializable)Se sono oggetti remoti, vengono passati per riferimento. È un oggetto remoto un oggetto che estende l’interfaccia java.rmi.Remote
Esempio RMI
Interfaccia remota:
public interface RemoteDate extends Remote {
public abstract Date getDate() throws RemoteException;
}
Implementazione del server RMI:
public class RemoteDateImpl extends UnicastRemoteObject implements RemoteDate {
public RemoteDateImpl() throws RemoteException { }
public Date getDate() throws RemoteException {
return new Date();
}
public static void main(String[] args) {
try {
RemoteDate dateServer = new RemoteDateImpl();
// Bind this object instance to the name "DateServer"
Naming.rebind("DateServer", dateServer);
}
catch (Exception e) {
System.err.println(e);
}
}
}
Client RMI:
public class RMIClient {
public static void main(String args[]) {
try {
String host = "rmi://127.0.0.1/DateServer";
RemoteDate dateServer = (RemoteDate) Naming.lookup(host);
System.out.println(dateServer.getDate());
}
catch (Exception e) {
System.err.println(e);
}
}
}
La classe Naming fornisce metodi statici per memorizzare (metodo rebind slide precedente) e ottenere (metodo lookup) un riferimento ad un oggetto remoto in un registro remoto (nameserver)