Tworzenie potoków wraz z C może być troszeczkę trudniejsze niż to wynika z naszych doświadczeń z powłoki. Aby utworzyć prosty potok w C musimy posłużyć się wywołaniem systemowym pipe(). Podaje się jeden argument, który jest tablicą dwóch liczb całkowitych, jeżeli nasze wywołanie powiodło się macierz zawiera dwa deskryptory plików, które urzywamy do operacji na potoku. Najczęściej po takiej operacji proces tworzy swojego potomka, który dziedziczy wszystkie otwarte deskryptory plików.
WYWOŁANIE SYSTEMOWE: pipe();
PROTOTYP: int pipe( int fd[2] );
ZWRACA: 0 - sukces
-1 błąd: errno = EMFILE ( brak wolnych deskryptorów )
EMFILE ( tablica systemu plików jest pełna )
EFAULT ( tablica fd jest nieprawidłowa )
UWAGI: fd[0] służy do czytania, fd[1] służy do pisania
Pierwsza liczba całkowita w macierzy ( element 0 ) jest ustawiana i otwierana w celu odczytu, natomiast drugi element służy do pisania. Unaoczniając, wyjście fd1 staje się wejściem dla fd0. Oczywiście wszelkie dane przesyłane przez potok są przesyłane przez jądro.
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
main()
{
int fd[2];
pipe(fd);
.
.
}
Pamiętaj, że nazwa macierzy w C wskazuje pierwszy element. Czyli fd jest wskaźnikiem do &fd[0]. Po stworzeniu potoku forkujemy naszego potomka.
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
main()
{
int fd[2];
pid_t childpid;
pipe(fd);
if((childpid = fork()) == -1)
{
perror("fork");
exit(1);
}
.
.
}
Jeżeli rodzic chce otrzymywać dane od procesu potomnego powinien zamknąć fd1, potomek powinien to uczynić z fd0. Jeżeli ma być odwrotnie, rodzic zamyka fd0, a potomek zamyka fd1. Ponieważ deskryptory są wspólne dla potomka i rodzica musimy pamiętać aby zamknąć końcówkę, której nie będziemy używać, gdyż jeżeli tego nie zrobimy nigdy nie otrzymamy EOF.
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
main()
{
int fd[2];
pid_t childpid;
pipe(fd);
if((childpid = fork()) == -1)
{
perror("fork");
exit(1);
}
if(childpid == 0)
{
/* Potomek zamyka końcówkę wejściową potoku */
close(fd[0]);
}
else
{
/* Rodzic zamyka końcówkę wyjściową potoku */
close(fd[1]);
}
.
.
}
Po ustaleniu kierunku przepływu możemy używać deskryptorów jako zwykłe deskryptory plików.
/*****************************************************************************
Zaczerpnięte z "Linux Programmer's Guide - Rozdział 6"
(C)opyright 1994-1995, Scott Burkett
*****************************************************************************
MODUŁ: pipe.c
*****************************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(void)
{
int fd[2], nbytes;
pid_t childpid;
char string[] = "Pozdrowienia przesyła Zdzisiek z rodziną!\n";
char readbuffer[80];
pipe(fd);
if((childpid = fork()) == -1)
{
perror("fork");
exit(1);
}
if(childpid == 0)
{
/* Potomek zamyka końcówkę wejściową */
close(fd[0]);
/* Przesyłamy "string" poprzez końcówke wyjściową potoku */
write(fd[1], string, strlen(string));
exit(0);
}
else
{
/* Rodzic zamyka końcówkę wyjściową */
close(fd[1]);
/* oraz wczytuje dane z potoku */
nbytes = read(fd[0], readbuffer, sizeof(readbuffer));
printf("Otrzymany łańcuch: %s", readbuffer);
}
return(0);
}
Często deskryptory w procesie potomnym są duplikowane na standardowe wejście lub wyjście, następnie proces potomny może wykonać - exec() inny program, który dziedziczy standardowe strumienie. Przyjżyjmy się wywołaniu dup():
WYWOŁANIE SYSTEMOWE: dup();
PROTOTYP: int dup( int staryfd );
ZWRACA: jeżeli się powiodło otrzymujemy nowy deskryptor
-1 - błąd: errno = EBADF ( staryfd nie jest prawidłowym deskryptorem )
EBADF ( nowyfd jest poza zasięgiem )
EMFILE ( za dużo deskryptorów dla procesu )
UWAGI: stary deskryptor nie jest zamykany! Można ich używać zamiennie.
Pomimo tego, iż stary deskryptor może być używany zamiennie z nowowo utworzonym najczęściej najpierw zamyka się standardowe strumienie. Wywołanie systemowe dup() używa pierwszego wolnego deskryptora. new one.
Rozważ:
.
.
childpid = fork();
if(childpid == 0)
{
/* Zamykamy standardowe wejście potomka */
close(0);
/* Duplikujemy wejście potoku jako stdin */
dup(fd[0]);
execlp("sort", "sort", NULL);
.
}
Zamkneliśmy deskryptor pliku 0 ( stdin ), poprzez wywołanie dup() zduplikowaliśmy deskryptor potoku (fd0) na stardardowe wejście potomka. Po tym wywołaliśmy execlp() aby zamienić segment tekstowy potomka ( kod ) programem sort. Dzięki temu, że programy uruchomione poprzez exec dziedziczą standardowe strumienie programów wywołujących je, sort dziedziczy wejściową część potoku jako swoje standardowe wejście! Teraz wszystko co wysysła rodzic do potoku kierowane jest do programu sort.
Istnieje inne wywołanie systemowe - dup2(), którego można również używać. To wywołanie oryginalnie powstało w 7 Wersji Unixa i zostało przeniesione do BSD, obecnie wymagane jest przez standard POSIX.
WYWOŁANIE SYSTEMOWE: dup2();
PROTOTYP: int dup2( int staryfd, int nowyfd );
ZWRACA: nowy deskryptor jeżeli wszystko ok
-1 - błąd:errno = EBADF ( staryfd nie jest prawidłowym deskryptorem )
EBADF ( nowyfd poza zasięgiem )
EMFILE ( zbyt dużo deskryptorów dla procesu )
UWAGI: dup2() zamyka stary deskryptor!
Dzięki temu wywołaniu mamy duplikację deskryptora i zamknięcie poprzedniego w jednym. Dodatkowo, mamy zapewnione atomowe(atomic) działanie, które oznacza, że nie zostanie ono przerwane przez nadchodzący sygnał. Cała operacja zostanie wykonana z wyłączeniem przesyłania sygnałów przez jądro. Używając oryginalnego dup() programiści musieli zamknąć deskryptor przed użyciem wywołania. Przez co tworzył się pewien słaby punkt pomiędzy tymi wywołaniami - jeżeli sygnał dotarł pomiędzy wywołaniami close() i dup() duplikacja deskryptorów mogła zawieść. Dup2() rozwiązuje ten problem.
Rozważ:
.
.
childpid = fork();
if(childpid == 0)
{
/* Zamknij stdin, duplikuj wejściową stronę potoku na stdin */
dup2(0, fd[0]);
execlp("sort", "sort", NULL);
.
.
}