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’