Comunicazione tra processi - Tubi

Pipe è un mezzo di comunicazione tra due o più processi correlati o interrelati. Può essere all'interno di un processo o una comunicazione tra il bambino e i processi genitore. La comunicazione può anche essere multilivello, come la comunicazione tra il genitore, il bambino e il nipote, ecc. La comunicazione è ottenuta tramite un processo che scrive nel tubo e l'altro che legge dal tubo. Per ottenere la chiamata di sistema pipe, creare due file, uno da scrivere nel file e un altro da leggere dal file.

Il meccanismo del tubo può essere visualizzato con uno scenario in tempo reale come il riempimento dell'acqua con il tubo in un contenitore, ad esempio un secchio, e qualcuno che lo recupera, ad esempio con una tazza. Il processo di riempimento non è altro che scrivere nel tubo e il processo di lettura non è altro che il recupero dal tubo. Ciò implica che un'uscita (acqua) è l'ingresso per l'altra (secchio).

#include<unistd.h>

int pipe(int pipedes[2]);

Questa chiamata di sistema creerebbe una pipe per la comunicazione unidirezionale, cioè crea due descrittori, il primo è connesso per leggere dalla pipe e l'altro è connesso per scrivere nella pipe.

Il descrittore pipedes [0] è per la lettura e pipedes [1] per la scrittura. Qualunque cosa sia scritta in pipedes [1] può essere letta da pipedes [0].

Questa chiamata restituirà zero in caso di successo e -1 in caso di fallimento. Per conoscere la causa del fallimento, controlla con la variabile errno o la funzione perror ().

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

Anche se le operazioni di base per il file sono di lettura e scrittura, è essenziale aprire il file prima di eseguire le operazioni e chiudere il file dopo il completamento delle operazioni richieste. Di solito, per impostazione predefinita, 3 descrittori aperti per ogni processo, che vengono utilizzati per input (standard input - stdin), output (standard output - stdout) ed errore (standard error - stderr) con descrittori di file 0, 1 e 2 rispettivamente.

Questa chiamata di sistema restituirebbe un descrittore di file utilizzato per ulteriori operazioni sui file di lettura / scrittura / ricerca (lseek). Di solito i descrittori di file iniziano da 3 e aumentano di un numero all'aumentare del numero di file aperti.

Gli argomenti passati alla chiamata di sistema aperta sono pathname (percorso relativo o assoluto), flag che menzionano lo scopo dell'apertura del file (diciamo, apertura per lettura, O_RDONLY, per scrivere, O_WRONLY, per leggere e scrivere, O_RDWR, per aggiungere al file esistente O_APPEND, per creare file, se non esiste con O_CREAT e così via) e la modalità richiesta che fornisce i permessi di lettura / scrittura / esecuzione per utente o proprietario / gruppo / altri. La modalità può essere menzionata con simboli.

Leggi - 4, Scrivi - 2 ed Esegui - 1.

Ad esempio: valore ottale (inizia con 0), 0764 implica che il proprietario ha i permessi di lettura, scrittura ed esecuzione, il gruppo ha i permessi di lettura e scrittura, l'altro ha i permessi di lettura. Questo può anche essere rappresentato come S_IRWXU | S_IRGRP | S_IWGRP | S_IROTH, che implica o il funzionamento di 0700 | 0040 | 0020 | 0004 → 0764.

Questa chiamata di sistema, in caso di successo, restituisce il nuovo id descrittore di file e -1 in caso di errore. La causa dell'errore può essere identificata con la variabile errno o la funzione perror ().

#include<unistd.h>

int close(int fd)

La chiamata di sistema precedente chiude il descrittore di file già aperto. Ciò implica che il file non è più in uso e le risorse associate possono essere riutilizzate da qualsiasi altro processo. Questa chiamata di sistema restituisce zero in caso di successo e -1 in caso di errore. La causa dell'errore può essere identificata con la variabile errno o la funzione perror ().

#include<unistd.h>

ssize_t read(int fd, void *buf, size_t count)

La chiamata di sistema sopra è quella di leggere dal file specificato con gli argomenti del descrittore di file fd, un buffer appropriato con memoria allocata (statica o dinamica) e la dimensione del buffer.

L'id descrittore di file serve per identificare il rispettivo file, che viene restituito dopo aver chiamato la chiamata di sistema open () o pipe (). Il file deve essere aperto prima di leggere dal file. Si apre automaticamente in caso di chiamata alla chiamata di sistema pipe ().

Questa chiamata restituirà il numero di byte letti (o zero in caso di incontro alla fine del file) in caso di successo e -1 in caso di errore. I byte di ritorno possono essere inferiori al numero di byte richiesti, solo nel caso in cui non siano disponibili dati o il file sia chiuso. Il numero di errore corretto viene impostato in caso di guasto.

Per conoscere la causa del fallimento, controlla con la variabile errno o la funzione perror ().

#include<unistd.h>

ssize_t write(int fd, void *buf, size_t count)

La chiamata di sistema di cui sopra consiste nello scrivere nel file specificato con gli argomenti del descrittore di file fd, un buffer appropriato con memoria allocata (statica o dinamica) e la dimensione del buffer.

L'id descrittore di file serve per identificare il rispettivo file, che viene restituito dopo aver chiamato la chiamata di sistema open () o pipe ().

Il file deve essere aperto prima di scrivere sul file. Si apre automaticamente in caso di chiamata alla chiamata di sistema pipe ().

Questa chiamata restituirà il numero di byte scritti (o zero nel caso in cui non venga scritto nulla) in caso di successo e -1 in caso di errore. Il numero di errore corretto viene impostato in caso di guasto.

Per conoscere la causa del fallimento, controlla con la variabile errno o la funzione perror ().

Programmi di esempio

Di seguito sono riportati alcuni programmi di esempio.

Example program 1 - Programma per scrivere e leggere due messaggi utilizzando pipe.

Algoritmo

Step 1 - Crea una pipa.

Step 2 - Invia un messaggio al tubo.

Step 3 - Recupera il messaggio dalla pipe e scrivilo sullo standard output.

Step 4 - Invia un altro messaggio al tubo.

Step 5 - Recupera il messaggio dalla pipe e scrivilo sullo standard output.

Note - Il recupero dei messaggi può essere eseguito anche dopo aver inviato tutti i messaggi.

Source Code: simplepipe.c

#include<stdio.h>
#include<unistd.h>

int main() {
   int pipefds[2];
   int returnstatus;
   char writemessages[2][20]={"Hi", "Hello"};
   char readmessage[20];
   returnstatus = pipe(pipefds);
   
   if (returnstatus == -1) {
      printf("Unable to create pipe\n");
      return 1;
   }
   
   printf("Writing to pipe - Message 1 is %s\n", writemessages[0]);
   write(pipefds[1], writemessages[0], sizeof(writemessages[0]));
   read(pipefds[0], readmessage, sizeof(readmessage));
   printf("Reading from pipe – Message 1 is %s\n", readmessage);
   printf("Writing to pipe - Message 2 is %s\n", writemessages[0]);
   write(pipefds[1], writemessages[1], sizeof(writemessages[0]));
   read(pipefds[0], readmessage, sizeof(readmessage));
   printf("Reading from pipe – Message 2 is %s\n", readmessage);
   return 0;
}

Note- Idealmente, lo stato di ritorno deve essere controllato per ogni chiamata di sistema. Per semplificare il processo, non vengono effettuati controlli per tutte le chiamate.

Fasi di esecuzione

Compilazione

gcc -o simplepipe simplepipe.c

Esecuzione / output

Writing to pipe - Message 1 is Hi
Reading from pipe – Message 1 is Hi
Writing to pipe - Message 2 is Hi
Reading from pipe – Message 2 is Hell

Example program 2 - Programma per scrivere e leggere due messaggi attraverso la pipe utilizzando i processi genitore e figlio.

Algoritmo

Step 1 - Crea una pipa.

Step 2 - Crea un processo figlio.

Step 3 - Il processo padre scrive sulla pipe.

Step 4 - Il processo figlio recupera il messaggio dalla pipe e lo scrive nell'output standard.

Step 5 - Ripetere i passaggi 3 e 4 ancora una volta.

Source Code: pipewithprocesses.c

#include<stdio.h>
#include<unistd.h>

int main() {
   int pipefds[2];
   int returnstatus;
   int pid;
   char writemessages[2][20]={"Hi", "Hello"};
   char readmessage[20];
   returnstatus = pipe(pipefds);
   if (returnstatus == -1) {
      printf("Unable to create pipe\n");
      return 1;
   }
   pid = fork();
   
   // Child process
   if (pid == 0) {
      read(pipefds[0], readmessage, sizeof(readmessage));
      printf("Child Process - Reading from pipe – Message 1 is %s\n", readmessage);
      read(pipefds[0], readmessage, sizeof(readmessage));
      printf("Child Process - Reading from pipe – Message 2 is %s\n", readmessage);
   } else { //Parent process
      printf("Parent Process - Writing to pipe - Message 1 is %s\n", writemessages[0]);
      write(pipefds[1], writemessages[0], sizeof(writemessages[0]));
      printf("Parent Process - Writing to pipe - Message 2 is %s\n", writemessages[1]);
      write(pipefds[1], writemessages[1], sizeof(writemessages[1]));
   }
   return 0;
}

Fasi di esecuzione

Compilation

gcc pipewithprocesses.c –o pipewithprocesses

Execution

Parent Process - Writing to pipe - Message 1 is Hi
Parent Process - Writing to pipe - Message 2 is Hello
Child Process - Reading from pipe – Message 1 is Hi
Child Process - Reading from pipe – Message 2 is Hello

Comunicazione bidirezionale tramite pipe

La comunicazione pipe è vista solo come una comunicazione unidirezionale, ovvero il processo genitore scrive e il processo figlio legge o viceversa ma non entrambi. Tuttavia, se sia il genitore che il bambino hanno bisogno di scrivere e leggere simultaneamente dalle pipe, la soluzione è una comunicazione bidirezionale che utilizza le pipe. Sono necessari due tubi per stabilire una comunicazione a due vie.

Di seguito sono riportati i passaggi per ottenere una comunicazione bidirezionale:

Step 1- Crea due tubi. Il primo è che il genitore scriva e il figlio legga, diciamo come pipe1. Il secondo è che il bambino scriva e il genitore legga, diciamo come pipe2.

Step 2 - Crea un processo figlio.

Step 3 - Chiudere le estremità indesiderate poiché è necessaria una sola estremità per ciascuna comunicazione.

Step 4 - Chiudi le estremità indesiderate nel processo genitore, leggi la fine di pipe1 e scrivi end of pipe2.

Step 5 - Chiudi le estremità indesiderate nel processo figlio, scrivi end of pipe1 e leggi end of pipe2.

Step 6 - Eseguire la comunicazione come richiesto.

Programmi di esempio

Sample program 1 - Realizzazione di una comunicazione bidirezionale tramite pipe.

Algoritmo

Step 1 - Crea pipe1 per il processo genitore da scrivere e il processo figlio da leggere.

Step 2 - Crea pipe2 per il processo figlio da scrivere e il processo genitore da leggere.

Step 3 - Chiudere le estremità indesiderate del tubo dal lato genitore e figlio.

Step 4 - Processo genitore per scrivere un messaggio e processo figlio da leggere e visualizzare sullo schermo.

Step 5 - Processo figlio per scrivere un messaggio e processo genitore da leggere e visualizzare sullo schermo.

Source Code: twowayspipe.c

#include<stdio.h>
#include<unistd.h>

int main() {
   int pipefds1[2], pipefds2[2];
   int returnstatus1, returnstatus2;
   int pid;
   char pipe1writemessage[20] = "Hi";
   char pipe2writemessage[20] = "Hello";
   char readmessage[20];
   returnstatus1 = pipe(pipefds1);
   
   if (returnstatus1 == -1) {
      printf("Unable to create pipe 1 \n");
      return 1;
   }
   returnstatus2 = pipe(pipefds2);
   
   if (returnstatus2 == -1) {
      printf("Unable to create pipe 2 \n");
      return 1;
   }
   pid = fork();
   
   if (pid != 0) // Parent process {
      close(pipefds1[0]); // Close the unwanted pipe1 read side
      close(pipefds2[1]); // Close the unwanted pipe2 write side
      printf("In Parent: Writing to pipe 1 – Message is %s\n", pipe1writemessage);
      write(pipefds1[1], pipe1writemessage, sizeof(pipe1writemessage));
      read(pipefds2[0], readmessage, sizeof(readmessage));
      printf("In Parent: Reading from pipe 2 – Message is %s\n", readmessage);
   } else { //child process
      close(pipefds1[1]); // Close the unwanted pipe1 write side
      close(pipefds2[0]); // Close the unwanted pipe2 read side
      read(pipefds1[0], readmessage, sizeof(readmessage));
      printf("In Child: Reading from pipe 1 – Message is %s\n", readmessage);
      printf("In Child: Writing to pipe 2 – Message is %s\n", pipe2writemessage);
      write(pipefds2[1], pipe2writemessage, sizeof(pipe2writemessage));
   }
   return 0;
}

Fasi di esecuzione

Compilazione

gcc twowayspipe.c –o twowayspipe

Esecuzione

In Parent: Writing to pipe 1 – Message is Hi
In Child: Reading from pipe 1 – Message is Hi
In Child: Writing to pipe 2 – Message is Hello
In Parent: Reading from pipe 2 – Message is Hello