Ritorna indietro

Lezione 6 - Pipe e FIFO

Pubblicato il 4/5/22 da SeekBytes – 1222 parole

Concetti fondamentali

Una PIPE è un flusso di dati in byte che permette a processi di scambiare byte. Tecnicalmente parlando è un buffer nella memoria del kernel. Una PIPE ha le seguenti proprietà:

Creazione e apertura pipe

La system call pipe crea una nuova PIPE.

#include <unistd.h>

// Returns 0 on success, or -1 on error
int pipe(int filedes[2]);

Una chiamata riuscita a pipe restituisce due descrittori di file aperti nell’array filedes.

Come con qualsiasi descrittore di file, possiamo usare le chiamate di sistema read e write per eseguire I/O sulla PIPE. Normalmente, usiamo una PIPE per permettere la comunicazione tra processi correlati (ovvero con una relazione di parentela). Per collegare due processi usando un PIPE, seguiamo la chiamata pipe con una chiamata a fork.

Creazione di una PIPE - esempio 1
int fd[2];
// checking if PIPE successed
if (pipe(fd) == -1)
	errExit("PIPE");
// Create a child process
switch(fork()) {
	case -1:
		errExit("fork");
	case 0: // Child
		//...child reads from PIPE
		break;
	default: // Parent
		//...parent writes to PIPE
		break;
}
  1. Pipe crea una nuova PIPE. fd[0] è l’estremità per la lettura, f[1] è l’estremità per la scrittura.

  2. fork() crea un processo figlio che eredita la file descriptor table dal processo genitore.

Processo figlio

char buf[SIZE];
ssize_t nBys;
// close unused write-end
if (close(fd[1]) == -1)
	errExit("close - child");
// reading from the PIPE
nBys = read(fd[0], buf, SIZE);
// 0: end-of-file, -1: failure
if (nBys > 0) {
	buf[nBys] = ’\0;
	printf("%s\n", buf);
}
// close read-end of PIPE
if (close(fd[0]) == -1)
	errExit("close - child");

Processo parent

char buf[] = "Ciao Mondo\n";
ssize_t nBys;
// close unused read-end
if (close(fd[0]) == -1)
	errExit("close - parent");
// write to the PIPE
nBys = write(fd[1], buf, strlen(buf));
// checkig if write successed
if (nBys != strlen(buf)) {
	errExit("write - parent");
}
// close write-end of PIPE
if (close(fd[1]) == -1)
	errExit("close - child");

Buone pratiche

Perché dovremo chiudere i file descriptor non utilizzzati? Che problema potremo avere?

Esempio su come utilizzare le PIPE IN MODO SBAGLIATO 1

Processo figlio:

// close unused write-end
//if (close(fd[1]) == -1)
// errExit("close - child");
char buf[SIZE];
ssize_t nBys;
// reading from the PIPE
nBys = read(fd[0], buf, SIZE);
// 0: end-of-file, -1: failure
if (nBys > 0)
printf("%s\n", buf);
// close read-end of PIPE
if (close(fd[0]) == -1)
errExit("close - child");

Processo padre:

// close unused read-end
if (close(fd[0]) == -1)
errExit("close - parent");
// ...nothing to send
// close write-end of PIPE
if (close(fd[1]) == -1)
errExit("close - child");

Perché questo programma è sbagliato? Il processo in lettura sta aspettando dati.

Esempio su come utilizzare le PIPE IN MODO SBAGLIATO 2
// close unused write-end
if (close(fd[1]) == -1)
	errExit("close - child");

// ...nothing to read

// close read-end of PIPE
if (close(fd[0]) == -1)
	errExit("close - child");
// close unused read-end
//if (close(fd[0]) == -1)
// errExit("close - parent");
char buf[] = "Ciao Mondo\n";
size_t len = strlen(buf);
// write to the PIPE
nBys = write(fd[1], buf, len);
// checkig if write successed
if (nBys != len)
	errExit("write - parent");
// close write-end of PIPE
if (close(fd[1]) == -1)
	errExit("close - child");

Perché questo programma è sbagliato? Il programma che scrive a chi sta inviando i dati?

FIFO

Una FIFO è un flusso di byte che permette ai processi di scambiare informazioni. Tecnicalmente parlando è un buffer nella memoria del kernel. Semanticamente, una FIFO è simile ad una PIPE.

La principale differenza tra una PIPE e una FIFO è che la FIFO ha un nome all’interno del file system, è aperta ed eliminata allo stesso modo di un file. Questo consente ad una FIFO di essere utilizzata per la comunicazione tra processi che non condividono alcuna relazione.

Come le PIPE, anche le FIFO hanno un’estremità per leggere e per scrivere, i dati sono letti dalle FIFO nello stesso ordine in cui sono stati scritti.

Creazione ed apertura di una FIFO

La system call mkfifo crea una nuova FIFO.

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

// Returns 0 on success, or -1 on error
int mkfifo(const char *pathname, mode_t mode);

Il parametro pathname specifica dove la FIFO è aperta. Come per un normale file, il parametro mode specifica i permessi della FIFO (vedi prima lezione). Una volta che la FIFO è stata creata, qualsiasi processo può aprirla.

Open per le FIFO

La system call open apre una FIFO.

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

// Returns file descriptor on success, or -1 on error.
int open(const char *pathname, int flags);

Il parametro pathname specifica la posizione del FIFO nel file system. L’argomento flags è una maschera di bit di una delle seguenti costanti che specificano la modalità di accesso per il FIFO.

FlagDescrizione
O_RDONLYApre in sola lettura
O_WRONLYApre in sola scrittura

L’unico uso sensato di un FIFO è quello di avere un processo di lettura e uno di scrittura su ogni estremità. Per impostazione predefinita, l’apertura di una FIFO per la lettura (flag O_RDONLY) è bloccante (quindi il processo si blocca finché un altro processo non apre la FIFO per la scrittura (flag O_WRONLY)). Al contrario, l’apertura del FIFO per la scrittura blocca finché un altro processo apre la FIFO per la lettura. In altre parole, l’apertura di una FIFO sincronizza i processi di lettura e scrittura. Se l’estremità opposta di una FIFO è già aperta (forse perché una coppia di processi ha già aperto ciascuna estremità della FIFO), allora l’apertura ha successo immediatamente.

Esempio FIFO e sincronizzazione

Receiver

char *fname = "/tmp/myfifo";
int res = mkfifo(fname, S_IRUSR|S_IWUSR);
// Opening for reading only
int fd = open(fname, O_RDONLY);
// reading bytes from fifo
char buffer[LEN];
read(fd, buffer, LEN);
// Printing buffer on stdout
printf("%s\n", buffer);
// closing the fifo
close(fd);
// Removing FIFO
unlink(fname);

Sender

char *fname = "/tmp/myfifo";
// Opening for wringing only
int fd = open(fname, O_WRONLY);
//reading a str. (no spaces)
char buffer[LEN];
printf("Give me a string: ");
scanf("%s", buffer);
// writing the string on fifo
write(fd, buffer, strlen(buffer));
// closing the fifo
close(fd);

Rimuovere una FIFO

Per rimuovere una FIFO si utilizza la funzione unlin che permette di rimuovere un collegamento e se il collegamento è direttamente il file, allora lo rimuove. Unlink non può rimuovere una cartella (per questo vedi rmdir).

#include <unistd.h>
// Returns 0 on success, or -1 on error
int unlink(const char *pathname);