Potoki

Teraz, kiedy umiesz forkować, przydałoby się umieć komunikować z potworkami. Do tego użyteczne są potoki. Takie cuś funkcjonuje jak (oficjalnie) jednokierunkowy kanał komunikacyjny. Pisanie do i czytanie z potoku opiera się na standardowych funkcjach write() i read(), do potoku odwołujesz się przez normalne deskryptory plików. Wiesz już, jak tego używać, to przydałoby się umieć toto utworzyć. Do tego służy funkcja int pipe(int desk[2]). Funkcja przyjmuje tablicę dwuelementową, zwraca 0 dla poprawnej operacji, a w przypadku błędu -1 i odpowiednio ustawia zmienną errno.
Notka: W zasadzie jedynym przypadkiem, kiedy pipe() może zwrócić błąd, jest przekroczenie ograniczenia ilości otwartych deskryptorów.
Aż dwa inty są potrzebne po to, żeby jeden deskryptor mógł być otwarty do odczytu, a drugi do zapisu, a dokładnie desk[0] służy do odczytu, a desk[1] do zapisu (porównaj z deskryptorami 0 i 1 odpowiadającymi za stdin i stdout). To może przykładowy kod:

int main(void)
{
int desk[2];
char bufor[20];

pipe(desk);
if (fork()) /* rodzic czyta */
  {
  close(desk[1]);  /* nie bedziemy pisac */
  read(desk[0], bufor, sizeof(bufor));
  puts(bufor);
  }
else        /* potworek pisze */
  {
  close(desk[0]);  /* bedziemy tylko pisac */
  write(desk[1], bufor, sprintf(bufor, "Hell oWorld %d!", getpid()) + 1);
  puts("Potworek zapisal");
  }

return 0;
}

Zwróć uwagę na role rodzica i potworka. Gdybym pisał w rodzicu, musiałbym później zaczekać na potworka, inaczej rodzic mógłby zakończyć działanie zanim potworek coś zapisze na ekran. W przykładzie takiej synchronizacji dostarcza sam fakt odczytania czegokolwiek: póki w buforze potoku nic nie będzie, póty rodzic nie przejdzie dalej. A po co w ogóle synchronizacja? Rodzic zwalnia terminal powłoce, więc to, co wypisze potworek, może się nałożyć na prompt. Przy bardziej skomplikowanych programach możesz za wcześnie zwolnić jakiś zasób, który w tym momencie straci spójność i użyteczność.
Drugą ważną rzeczą w kodzie jest zamykanie niepotrzebnych deskryptorów. Jest to wbrew pozorom bardzo ważna czynność, co pokaże się później.

Już wiesz, jak się forkować, jak execować procesy i jak podmieniać standardowe strumienie. Pora na coś ciekawszego: odczyt standardowego wyjścia zewnętrznego programu. Jak się domyślasz, przebiega to w czterech etapach: tworzenie potoku, forkowanie, podmienianie deskryptorów i wreszcie execowanie odpowiedniego procesu.

int main(void)
{
int potok[2];
char bufor[81];
int wczytane;

/* etap 1: tworzenie potoku */
pipe(potok);

/* etap 2: forkowanie */
if (fork()) /* rodzic czyta */
  {
  close(potok[1]);

  while (( wczytane = read(potok[0], bufor, sizeof(bufor) - 1) ))
    {
    bufor[wczytane] = 0; /* warto zakonczyc ten string zerem */
    printf("Wczytane: '%s'\n", bufor);
    }
  }
else        /* potworek execuje "ps -Af" */
  {
  close(potok[0]);

  /* etap 3: podmiana deskryptora */
  dup2(potok[1], 1);
  close(potok[1]);

  /* etap 4: execowanie procesu */
  execlp("ps", "ps", "-Af", NULL);
  perror("Blad uruchamiania ps");
  }

return 0;
}

Zróbmy coś ciekawszego: wyexecujmy ps -Af przekierowane na grep root. Domyślasz się, że będzie trzeba podmieniać stdin dla grepa.

int main(void)
{
int potok[2];
char bufor[81];
int wczytane;

pipe(potok);

if (fork()) /* rodzic execuje grep root */
  {
  close(potok[1]);
  dup2(potok[0], 0);
  close(potok[0]);

  execlp("grep", "grep", "root", NULL);
  perror("Blad uruchamiania grep");
  }
else        /* potworek execuje ps -Af */
  {
  close(potok[0]);
  dup2(potok[1], 1);
  close(potok[1]);

  execlp("ps", "ps", "-Af", NULL);
  perror("Blad uruchamiania ps");
  }

/* to sie nie wykona, chyba ze byl gdzies blad */
return 0;
}

Zwróć uwagę na role potworka i rodzica. Jest to analogiczne jak w przypadku pierwszego przykładu. Chociaż tutaj raczej nie zauważysz różnicy w kolejności, bo grep kończy się tak szybko, jak zamyka się mu wejście, a to z kolei zamyka się natychmiast po zakończeniu ps. Ale już chyba widzisz, kiedy różnica będzie widoczna?

int main(void)
{
int potok[2];
char bufor[81];
int wczytane;

pipe(potok);

if (fork()) /* rodzic execuje less */
  {
  close(potok[1]);
  dup2(potok[0], 0);
  close(potok[0]);

  execlp("less", "less", NULL);
  /* fallback do more, ktory jest prawie wszedzie */
  execlp("more", "more", NULL);
  perror("Blad uruchamiania less i more");
  }
else        /* potworek execuje ps -Af */
  {
  close(potok[0]);
  dup2(potok[1], 1);
  close(potok[1]);

  execlp("ps", "ps", "-Af", NULL);
  perror("Blad uruchamiania ps");
  }

/* to sie nie wykona, chyba ze byl gdzies blad */
return 0;
}

To się wykona ładnie, ale jak zamienisz miejscami kody rodzica i potworka (najprościej zamienić linijkę if (fork()) na if (!fork())), to się rozsypie. ps jest uruchomiony jako rodzic, zatem przy jego zakończeniu, które nastąpi wcześniej od less, terminal zostanie zwrócony powłoce, a less zostanie bez wejścia z klawiatury. Na to się jeszcze nakłada fakt, że less zmienia właściwości terminala, które po jego zabiciu nie są przywracane. Dlatego trzeba dobrze przemyśleć, w którym procesie ma zostać uruchomiony który program.
Alternatywnie możesz execować się dwa razy, po czym zaczekać na oba procesy:

int main(void)
{
int potok[2];
char bufor[81];
int wczytane;

pipe(potok);

if (!fork()) /* jeden potworek uruchamia ps -Af */
  {
  close(potok[0]);
  dup2(potok[1], 1);
  close(potok[1]);

  execlp("ps", "ps", "-Af", NULL);
  perror("Blad uruchamiania ps");
  return 1;
  }

if (!fork()) /* drugi potworek uruchamia less albo more */
  {
  close(potok[1]);
  dup2(potok[0], 0);
  close(potok[0]);

  execlp("less", "less", NULL);
  /* fallback */
  execlp("more", "more", NULL);
  perror("Blad uruchamiania less i more");
  return 1;
  }

/* bardzo wazne! */
close(potok[0]);
close(potok[1]);

wait(NULL);
wait(NULL);

puts("\nKoniec obu potworkow");

return 0;
}

Gdyby nie ostatnie dwa close'y, mogłoby być źle. less czeka, aż nie zostanie zamknięte jego standardowe wejście, a to nie jest zamknięte bez close() w rodzicu. Bardziej widoczne to jest w przypadku grepa:

int main(void)
{
int potok[2];
char bufor[81];
int wczytane;

pipe(potok);

if (!fork()) /* jeden potworek uruchamia ps -Af */
  {
  close(potok[0]);
  dup2(potok[1], 1);
  close(potok[1]);

  execlp("ps", "ps", "-Af", NULL);
  perror("Blad uruchamiania ps");
  return 1;
  }

if (!fork()) /* drugi potworek uruchamia grep root */
  {
  close(potok[1]);
  dup2(potok[0], 0);
  close(potok[0]);

  execlp("grep", "grep", "root", NULL);
  perror("Blad uruchamiania grep");
  return 1;
  }

/* bardzo wazne! */
close(potok[0]);
close(potok[1]);

wait(NULL);
wait(NULL);

puts("\nKoniec obu potworkow");

return 0;
}

Bez oznaczonych close'ów (zwłaszcza close(potok[1])) program się nie zakończy. Sam się parę razy na tym naciąłem :) Oczywiście ten sam komentarz tyczy się closeowania niepotrzebnych deskryptorów w obu potworkach.

A jeśli będziesz chciał się komunikować między dwoma programami bez wspólnego rodzica? Tu już nie daje się zastosować pipe(). Sorry, ale to jest troszkę niewykonalne. Do tego będziesz już potrzebował nazwanych potoków.