Le pipe sono la forma più “antica” di comunicazione tra processi UNIX.
Si possono inviare dati da un lato della pipe e riceverli dal lato opposto.
Tecnicamente, la pipe è una porta di comunicazione con send asincrona e receive sincrona.
Esistono pipe senza nome e con nome:
Le pipe senza nome sono utilizzate per combinare comandi Unix direttamente dalla shell tramite il simbolo “|” (pipe).
Provate i semplici esempi illustrati in aula la lezione scorsa.
Per creare una pipe si utilizza la systemcall pipe(int filedes[2]) che restituisce in filedes due descrittori:
filedes[0] per la lettura; filedes[1] per la scrittura; Notiamo quindi che le pipe sono half-duplex (monodirezionali): esistono due distinti descrittori per leggere e scrivere.
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main() {
int fd[2];
pipe(fd); /* crea la pipe */
if (fork() == 0) {
char *phrase = "prova a inviare questo!";
close(fd[0]); /* chiude in lettura */
write(fd[1],phrase,strlen(phrase)+1); /* invia anche 0x00 */
close(fd[1]); /* chiude in scrittura */
} else {
char message[100];
memset(message,0,100);
int bytesread;
close(fd[1]); /* chiude in scrittura */
bytesread = read(fd[0],message,99);
printf("ho letto dalla pipe %d bytes: '%s' \n",bytesread,message);
close(fd[0]); /* chiude in lettura */
}
}
Le pipe si comportano “quasi” come dei normali file.
Ci sono casi particolari tipici delle pipe:
La read ritorna 0, corrispondente a un end-of-file.
Viene generato il segnale SIGPIPE che di default termina il processo. Se si ignora o si gestisce il segnale la write ritorna un errore e errno è settata a EPIPE
ESERCIZIO: modificare il programma precedente in modo da osservare i due eventi discussi qui sopra.
#include <stdio.h>
#include <unistd.h>
int main(int argc, char * argv[])
{
int fd[2], bytesread;
pipe(fd);
if (fork() == 0) {
close(fd[0]); /* chiude in lettura */
dup2(fd[1],1); /* fa si che 1 (stdout) sia una copia di fd[1] */
/* da qui in poi l'output va sulla pipe */
close(fd[1]); /* chiude il descrittore fd[1] */
execlp(argv[1],argv[1],NULL); /* esegue il comando */
perror("errore esecuzione primo comando");
} else {
close(fd[1]); /* chiude in scrittura */
dup2(fd[0],0); /* fa si che 0 (stdin) sia una copia di fd[0] */
/* da qui in poi l'input proviene dalla pipe */
close(fd[0]); /* chiude il descrittore fd[0] */
execlp(argv[2],argv[2],NULL); /* esegue il comando */
perror("errore esecuzione secondo comando");
}
}
Le pipe senza nome non possono essere utilizzate da processi che non hanno un antenato in comune.
Per ovviare a questa limitazione esistono le pipe con nome. Tali pipe possono essere create con il comando mkfifo.
$ mkfifo myPipe <==== (oppure mknod myPipe p)
$ ls -al
totale 36
...
prw-rw-r-- 1 focardi focardi 0 mag 23 00:57 myPipe
...
$
La pipe esiste nel filesystem e qualsiasi processo con i diritti di accesso al file può utilizzarla.
Consideriamo un processo lettore (destinatario) che accetta, su una pipe con nome, messaggi provenienti da più scrittori (mittenti).
Gli scrittori mandano 3 messaggi e poi terminano.
Quando tutti gli scrittori chiudono la pipe il lettore ottiene 0 come valore di ritorno dalla read ed esce.
Lettori e scrittori sono processi distinti lanciati indipendentemente (non necessariamente parenti).
#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <stdlib.h>
#define PNAME "/tmp/aPipe"
int main() {
int fd;
char leggi;
mkfifo(PNAME,0666); // crea la pipe con nome, se esiste gia' non fa nulla
fd = open(PNAME,O_RDONLY); // apre la pipe in lettura
if ( fd < 0 ) {
perror("errore apertura pipe");
exit(1);
}
while (read(fd,&leggi,1)) { // legge un carattere alla volta fino a EOF
if (leggi == '\0'){
printf("\n"); // a capo dopo ogni stringa
} else {
printf("%c",leggi);
}
}
close(fd); // chiude il descrittore
unlink(PNAME); // rimuove la pipe
return 0;
}
#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <string.h>
#include <stdlib.h>
#define PNAME "/tmp/aPipe"
int main() {
int fd, i, lunghezza;
char *message;
mkfifo(PNAME,0666); // crea la pipe con nome, se esiste gia' non fa nulla
// crea la stringa
lunghezza = snprintf(NULL,0,"Saluti dal processo %d",getpid());
message = malloc(lunghezza+1);
snprintf(message,lunghezza+1,"Saluti dal processo %d",getpid());
fd = open(PNAME,O_WRONLY); // apre la pipe in scrittura
if ( fd < 0 ) {
perror("errore apertura pipe"); exit(1);
}
for (i=1; i<=3; i++){ // scrive tre volte la stringa
write (fd,message,strlen(message)+1); // include terminatore
sleep(2);
}
close(fd); // chiude il descrittore
free(message);
return 0;
}
Le scritture sulle pipe sono atomiche se inferiori alla dimensione PIPE_BUF (limits.h), usualmente 4096 bytes.
L’opzione O_RDWR nella open permette di aprire una pipe con nome in lettura e scrittura.
Esercizio 1: Le pipe gestiscono stream di byte: non c’è nessuna nozione di messaggio. Non è detto, quindi, che due write vengano lette tramite due read. Sperimentare inviando due write distinte consecutive e osservando che vengono lette da un’unica read (fare attenzione al terminatore).
Esercizio 2: Anche se non è ovvio da visualizzare, si può provare a superare PIPE_BUF (4096) per vedere che le write interferiscono una con l’altra. Si suggerisce di ridirezionare l’output del lettore su file (tramite > nomefile) in modo da esaminare l’output con un editor alla ricerca di messaggi ‘mescolati’