Potok (ang. pipe) – jeden z mechanizmów komunikacji międzyprocesowej umożliwiający wymianę danych pomiędzy dwoma procesami. Odbywa się to najczęściej poprzez połączenie standardowego wyjścia jednego procesu ze standardowym wejściem drugiego.
Historia
Mechanizm potoku i jego notacja (pionowa linia) zostały wymyślone przez Douglasa McIlroya, jednego z autorów wczesnych powłok systemowych po tym, jak zaobserwował, że w wielu przypadkach wyjście jednego programu stanowiło wejście dla drugiego. Jego pomysł został zaimplementowany w 1973 roku, przez Kena Thompsona w systemie operacyjnym UNIX[1]. Mechanizm potoku został następnie dodany w innych systemach operacyjnych, takich jak DOS, OS/2, Microsoft Windows i BeOS, często z tą samą notacją.
Potoki procesowe
W uniksowych powłokach systemowych używa się symbolu „|” (pionowej linii), aby połączyć dwa lub więcej procesów w potok.
Przykład wykorzystania potoków w systemie UNIX:
$ ps -a | sort | uniq | grep -v sh
Powyższa konstrukcja zwróci listę uruchomionych procesów (ps -a
), posortowaną alfabetycznie (sort
), niezawierającą powtórzeń (uniq
), oraz bez linii zawierających wzorzec sh (grep -v sh
).
Do stworzenia nienazwanego potoku służy wywołanie systemowe pipe()
. Prototyp funkcji bibliotecznej znajduje się w pliku nagłówkowym unistd.h i ma następującą postać:
Funkcja pipe()
umieszcza dwa nowe deskryptory plików w tablicy fields[]
(fields[0]
– deskryptor pliku tylko do odczytu, fields[1]
– deskryptor pliku tylko do zapisu) i zwraca 0 w przypadku powodzenia lub -1 w przypadku błędu.
Funkcja pipe()
często używana jest w połączeniu z funkcją fork()
w celu zapewnienia komunikacji między procesem macierzystym oraz jego procesami potomnymi.
Złożony przykład
Przykład poniżej implementuje korektor pisowni dla zasobów sieciowych wskazywanych przez URL. Znak „\” służy umieszczeniu wszystkich sześciu linii w jednej linii wiersza poleceń.
curl "https://en.wikipedia.org/wiki/Pipeline_(Unix)" | \
sed 's/[^a-zA-Z ]/ /g' | \
tr 'A-Z ' 'a-z\n' | \
grep '[a-z]' | \
sort -u | \
comm -23 - /usr/share/dict/words
- curl odczytuje kod HTML ze strony internetowej (można użyć wget w niektórych systemach)
- sed usuwa wszystkie znaki, które nie są spacjami ani literami zawartymi w treści strony, zastępując je spacjami
- tr zamienia wszystkie wielkie litery małymi i zamienia wszystkie spacje w liniach tekstu na nowe linie
- grep uwzględnia tylko linie, które zawierają przynajmniej jedną małą literę (usuwając wszystkie puste linijki)
- sort sortuje listę „wyrazów” w porządku alfabetycznym, a przełącznik -u usuwa duplikaty
- comm(inne języki) znajduje wspólne linie w dwóch plikach, -23 likwiduje linie występujące tylko w drugim pliku i te, które są wspólne dla obu, pozostawiając tylko te, które występują jedynie w pierwszym z nich. Znak „-” wstawiony w miejscu nazwy pliku powoduje, że comm używa swojego standardowego wejścia (w tym przypadku z potoku). W efekcie powstaje lista „wyrazów” (linii), które nie występują w /usr/share/dict/words.
Specjalny znak „|” mówi interpreterowi powłoki, żeby to, co pojawia się na wyjściu jednego procesu, przekazał na wejście następnego. Tak więc wyjście komendy curl jest przekazywane na wejście komendy sed i tak dalej.
Wszystkie powszechnie używane powłoki Unix i Windows posiadają specjalną strukturę składniową dla tworzenia potoków. W standardowym użyciu polecenia filtra pisane są w sekwencji, oddzielone znakiem „|” (który, z tego powodu jest często nazywane „znakiem pipe”). Powłoka rozpoczyna proces i tworzy niezbędne połączenia pomiędzy standardowymi strumieniami (uwzględniając pewien bufor).
Strumień błędu
Z założenia standardowe strumienie błędów procesów w potoku nie są przez niego przekazywane, lecz łączone i kierowane na konsolę. Jednakże wiele powłok posiada dodatkową składnię zmieniającą to zachowanie. W powłoce csh, na przykład, używanie „|&” zamiast „|” oznacza, że standardowy strumień błędu powinien także zostać połączony ze standardowym wyjściem i przekazany do następnego procesu. Powłoka Bourne’a także potrafi połączyć standardowy strumień błędów ze standardowym wyjściem (za pomocą 2>&1), jak również przekierować go do innego pliku.
Pipemill (walcownia)
W najpowszechniej używanych prostych potokach powłoka łączy serię podprocesów poprzez potok i wykonuje zewnętrzne komendy w obrębie każdego podprocesu. W efekcie powłoka sama w sobie nie wykonuje żadnego bezpośredniego przetwarzania danych wpływających poprzez potok. Jednakże powłoka może wykonać przetwarzanie bezpośrednio. Konstrukt ten, ogólnie rzecz biorąc, wygląda mniej więcej następująco:
command | while read var1 var2 ...; do
# przetwórz każdą linię, używając zmiennych parsowanych do $var1, $var2, etc
# (zauważ że to jest podpowłoka: var1, var2 etc nie będą dostępne
# po zakończeniu pętli)
done
...co jest nazywane z angielskiego „pipemill” (walcownia).
Implementacja
W większości systemów uniksowych, wszystkie procesy potoku są rozpoczynane jednocześnie, z ich strumieniami odpowiednio połączonymi i zarządzanymi przez algorytm szeregowania wraz z innymi procesami uruchomionymi na maszynie. Istotnym aspektem, odróżniającym potoki uniksowe od innych, jest koncept buforowania: program wysyłający może wyprodukować do 5000 bajtów na sekundę, a program odbierający może ich przyjąć jedynie 100 na sekundę, jednak żadne dane nie giną. Zamiast tego, strumień wyjściowy programu wysyłającego jest trzymany w kolejce. Kiedy program odbierający jest gotów odczytać dane, system operacyjny przesyła mu dane z kolejki, następnie usuwa te dane z kolejki. Jeżeli bufor kolejki się zapełni, program wysyłający zostaje zawieszony (zablokowany) aż do momentu kiedy program odbierający ma możliwość odczytać jakąś część danych i zrobić miejsce w buforze. W systemie GNU/Linux rozmiar buforu to 65536 bajtów.
Potoki sieciowe
Narzędzia takie jak netcat i socat mogą połączyć potoki z gniazdami TCP/IP, zgodnie z jedną z zasad filozofii UNIX głoszącą, że „wszystko jest plikiem”.
Potok nazwany
Potok nazwany (lub łącze nazwane) jest to rozwinięcie idei potoku, polegające na stworzeniu specjalnego pliku, który ma służyć jako łącznik między procesami. Zapewnia to wygodny mechanizm przekazywania danych pomiędzy niespokrewnionymi ze sobą procesami. Wtedy ten specjalny plik potoku jest „nazwą” potoku w tym sensie, że operacje pisania i czytania wykonane na pliku są w istocie czytaniem z i pisaniem do potoku, a plik potoku służy jako swoisty punkt dostępowy.
W Uniksie do tworzenia potoków nazwanych stosuje się program mkfifo lub mknod.
$ mkfifo plik_fifo
$ mknod plik_fifo p
Z poziomu programu można skorzystać z następujących dwóch standardowych funkcji:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *path, mode_t mode);
int mknod(const char *path, mode_t mode, dev_t dev);
Przypisy
Linki zewnętrzne