Mgr inż. Marcin Borkowski UNIX Pipe i FIFO. mgr inż. Marcin Borkowski Oczekiwanie na I/O ● Pipe'y, FIFO, socket'y oraz stdin nie zawsze mogą udostępnić.

1 mgr inż. Marcin Borkowski UNIX Pipe i FIFO ...
Author: Michał Domański
0 downloads 0 Views

1 mgr inż. Marcin Borkowski UNIX Pipe i FIFO

2 mgr inż. Marcin Borkowski Oczekiwanie na I/O ● Pipe'y, FIFO, socket'y oraz stdin nie zawsze mogą udostępnić danych na żądanie tak jak zwykłe pliki (zapis i odczyt) ● Mówimy o dostępności danych do odczytu lub o dostępności bufora do zapisu, jeśli dane są niedostępne proces musi czekać na I/O

3 mgr inż. Marcin Borkowski Oczekiwanie na I/O ● Oczekiwanie na I/O można zorganizować na wiele sposobów: – funkcje select, pselect – funkcja poll (ppoll,epoll) – sygnał SIGIO, ustalany poprzez fcntl (nie POSIX) – aio (patrz następny wykład)

4 mgr inż. Marcin Borkowski Oczekiwanie na I/O ● Funkcja (p)select – fd_set – tablica bitów, zbiór deskryptorów ● FD_SETSIZE – maksymalna liczba deskryptorów, którą można zmieścić w tablicy (liczone od 0 do FD_SETSIZE-1) ● dla systemów gdzie liczba deskryptorów jest limitowana - stała ta powinna być nie mniejsza od limitu ● dla systemów bez limitów (np. Linux/GNU) - stała wprowadza ograniczenie na możliwość użycia funkcji select ● FD_ZERO – czyści zbiór ● FD_SET oraz FD_CLR – ustaw/wyczyść numer deskryptora ● FD_ISSET – test obecności deskryptora w zbiorze

5 mgr inż. Marcin Borkowski Oczekiwanie na I/O – Argumenty funkcji (p)select : ● nfds – maksymalna wartość deskryptora (+1) użytego we wszystkich 3 zbiorach testowanych (patrz nast. argumenty) – ponieważ test wartości jest w postaci fd

6 mgr inż. Marcin Borkowski Oczekiwanie na I/O – Argumenty funkcji (p)select cd: ● readfds – zbiór deskryptorów testowanych pod kątem możliwości odczytu – koniec pliku (EOF – odczyt 0 bajtów)także daje możliwość odczytu (zatrzymuje select) – deskryptory wstanie EOF powinny być usuwane ze zbioru wszędzie tam gdzie w cyklicznie testowana jest możliwość odczytu, typowym błędem jest tzw. busy waiting na select – parametr ten jest modyfikowany przez funkcję select, dlatego należy go rekonstruować w wywołaniach cyklicznych – wolno podać NULL zamiast zbioru

7 mgr inż. Marcin Borkowski Oczekiwanie na I/O – Argumenty funkcji (p)select cd : ● writefds – zbiór deskryptorów testowanych pod kątem możliwości zapisu – nie wiadomo jak dużo danych można zapisać bez blokowania, na pewno 1 bajt, ale w praktyce zazwyczaj dostępny jest więcej (strona pamięci) – parametr ten jest modyfikowany przez funkcję select, dlatego należy go rekonstruować w wywołaniach cyklicznych – wolno podać NULL zamiast zbioru

8 mgr inż. Marcin Borkowski Oczekiwanie na I/O – Argumenty funkcji (p)select cd : ● exceptfds – zbiór deskryptorów testowanych pod kątem nietypowych zdarzeń – nie chodzi tu o błędy I/O ! – np. dane „Out of Band” dla socket'ów – parametr ten jest modyfikowany przez funkcję select, dlatego należy go rekonstruować w wywołaniach cyklicznych – wolno podać NULL zamiast zbioru W trzech omawianych zborach można podać te same deskryptory.

9 mgr inż. Marcin Borkowski Oczekiwanie na I/O ● timeout – limit czasowy operacji – bez limitu jeśli podano NULL – każdy limit może być skrócony przez obsługę sygnału (EINTR) – inne typ danych dla select i inny dla pselect ! – wartość parametru może być modyfikowana przez funkcję, ale standard POSIX nie narzuca standardowego zachowania ! – implementacja timeout'u nie może używać SIGALARM zatem można bezpiecznie łączyć tą cechę z funkcjami alarm lub setitimer – podanie wartości zero służy natychmiastowemu sprawdzeniu dostępności deskryptorów ● sigmask – maska sygnałów obowiązująca jedynie podczas wywołania pselect – połączenie w jednej funkcji możliwości czekania na I/O i sygnał jednocześnie

10 mgr inż. Marcin Borkowski Oczekiwanie na I/O – Wartość zwracana z funkcji (p)select : ● w razie powodzenia operacji: – sumaryczna ilość deskryptorów spełniających zadane warunki w 3 zbiorach – zero jeśli nastąpił timeout – zbiory (parametry) wejściowe są modyfikowane tak aby zawierały tylko dostępne deskryptory, makro FD_ISSET pozwala sprawdzić, które deskryptory są w zbiorze ● -1 w razie błędu lub przerwania (EINTR), w takim przypadku zbiory deskryptorów pozostają niezmienione – Przykład użycia – na końcu tego wykładu

11 mgr inż. Marcin Borkowski Oczekiwanie na I/O ● Funkcja poll – struktura pollfd zawiera: ● fd – otwarty deskryptor ● events– pożądane zdarzenia, wejście (POSIX definiuje więcej stałych) – POLLIN – odczyt (także EOF) – POLLOUTI - zapis – POLLPRI – dane priorytetowe (socket) ● revents – odnotowane zdarzenia, wyjście – POLLIN, POLLOUT,POLLPRI – POLLERR,POLLHUP – POLLNVAL - nieotwarty deskryptor

12 mgr inż. Marcin Borkowski Oczekiwanie na I/O ● Funkcja poll – każdy deskryptor opisywany jest przez swoją strukturę, oczekiwane zdarzenia są bitowo zapisane w events – po wywołaniu funkcji revents danej struktury bitowo opisuje dostępne zdarzenia swojego deskryptora – parametry poll : ● *fds – wskazanie na tablicę struktur pollfd, bez limitu rozmiaru !!! ● nfds – rozmiar tablicy pollfd

13 mgr inż. Marcin Borkowski Oczekiwanie na I/O – timeout ● 0 limit czasu w milisekundach ● czas może zostać skrócony przez obsługę sygnału (EINTR), w tym przypadku funkcja nie raportuje pozostałego czasu – zwykłe pliki zawsze są dostępne do odczytu, czytanie z soket'u, FIFO lub pipe zamkniętego z drugiej strony zawsze prowadzi do EOF. Deskryptory tych typów powodują natychmiastowe zakończenie poll i w przypadku sprawdzania cyklicznego mogą prowadzić do busy waiting

14 mgr inż. Marcin Borkowski Oczekiwanie na I/O – Wartość zwracana: ● -1 błąd lub EINTR ● 0 time-out, żaden z deskryptorów nie zgłasza żądanego zdarzenia ● >0 liczba niezerowych revents (liczy także błędy) ● Funkcja ppoll nie jest zgodna z POSIX ● Funkcja epoll jest specyficzna dla Linux'a, wydajniejsza dla dużych zbiorów deskryptorów dla których jednocześnie zachodzi niewiele zdarzeń

15 mgr inż. Marcin Borkowski Pipe ● Jednokierunkowy kanał komunikacji pomiędzy spokrewnionymi procesami : – rodzic do dziecka (lub odwrotnie) – rodzeństwo ● Dla komunikacji dwukierunkowej należy utworzyć parę pipe ● Połączenie pipe musi zostać utworzone (f. pipe ) przed kreacją procesów ● Możliwe jest pisanie i czytanie tego samego pipe przez wiele różnych procesów w tym samym czasie

16 mgr inż. Marcin Borkowski Pipe ● Zazwyczaj proces czytający pipe nie pisze do niego i odwrotnie. Należy zatem zamknąć nieużywane deskryptory – technicznie możliwe jest czytanie i pisanie do tego samego pipe w obrębie tego samego procesu i wątku. Należy jednak pamiętać o rozmiarze wewnętrznego bufora pipe aby uniknąć dead lock'a ● filedes[0] odczyt z pipe ● filedes[1] zapis do pipe

17 mgr inż. Marcin Borkowski Pipe ● Deskryptor pipe posiada flagi, które podobnie jak w przypadku zwykłych plików można zmieniać za pomocą funkcji fcntl, np.: O_NONBLOCK ● Dane przesyłane do pipe są przechowywane w kolejce First In First Out ● Pipe nie ma nazwy w systemie plików ● Pipe jest tworzony a nie otwierany

18 mgr inż. Marcin Borkowski Pipe ● Pipe do pod-procesu (f. popen, pclose ) – proces może uruchomić komendę powłoki (tak jak f. s ystem ) i przechwycić jej wejście albo wyjście. Do komunikacji z komendą używa się pojedynczego pipe – Rozłączne tryby komunikacji: ● mode “w” - proces wysyła dane na stdin komendy ● mode “r” - proces czyta dane z stdout komendy – Zazwyczaj czytane są wszystkie dane z stdout komendy zanim pipe zostanie zamknięty – Zamknięcie pipe piszącego oznacza koniec danych

19 mgr inż. Marcin Borkowski Pipe – Ponieważ stdin procesu wywołującego i komendy dzielą pozycje karetki i oba strumienie są buforowane, może dojść do nieoczekiwanego przesunięcia. Aby tego uniknąć należy unikać jednoczesnego czytania z stdin i popen, oraz pamiętać o oczyszczeniu bufora stdin poprzez wywołanie fflush – popen może być zaimplementowany przez funkcje fork, dup2 oraz execv

20 mgr inż. Marcin Borkowski Pipe – Strumień plikowy do pod-procesu uruchomionego przez popen musi zostać zamknięty przez funkcję pclose która zamyka deskryptor i czeka na zakończenie procesu – Zgodnie z POSIX pclose nie może zostać przerwane przed końcem działania (nawet przez obsługę sygnału) – Główny proces nie powinien wywoływać wait (lub waitpid ) tak aby „łapać” zakończenie procesu komendu. Jeśli to nastąpi pclose zwróci błąd ECHLD – pclose zwraca status zakończenia komendy lub kod 127 jeśli nie ma dostępnego interpretera powłoki (/bin/sh)

21 mgr inż. Marcin Borkowski FIFO ● Kolejka „First In First Out” ● Działanie podobne do pipe plus: – jest obecne w systemie plików ● ścieżka i nazwa ● uprawnienia – może być otwarte przez dowolny proces pod warunkiem posiadania praw do pliku FIFO – może być utworzone przez proces (f. mkfifo ) lub w linii poleceń (komenda mkfifo) – jest niezależne od procesu tworzącego, pozostaje w systemie pomiędzy użyciami

22 mgr inż. Marcin Borkowski FIFO ● Proces może otworzyć FIFO (f. open ) do: – czytania, wywołanie open będzie czekać aż inny process otworzy to samo FIFO do zapisu – pisania, wywołanie open będzie czekać aż inny process otworzy to samo FIFO do odczytu – czytania i pisania, wywołanie open nie będzie czekać, rzadko spotykany przypadek

23 mgr inż. Marcin Borkowski FIFO ● Gdy proces otwiera FIFO z flagą O_NONBLOCK do: – odczytu, nie występuje blokowanie ale o ile nie pojawi się proces piszący do tego FIFO, próba czytania zwróci EOF – zapisu, o ile nie pojawi się proces czytający próba zapisu zwróci błąd ENXIO

24 mgr inż. Marcin Borkowski FIFO ● Usunięcie ( unlink ) kolejki FIFO nie wpływa na działanie już podłączonych procesów, kolejka nie będzie dostępna dla nowych połączeń. Fizyczne skasowanie nastąpi po odłączeniu ostatniego procesu ● Zmiana uprawnień pliku FIFO nie wpływa na wcześniej otwarte połączenia (strumienie, deskryptory), ta sama zasada obowiązuje także zwykłe pliki

25 mgr inż. Marcin Borkowski Pipe i FIFO – Zamykanie Połączenia – Gdy żaden z procesów połączonych z pipe lub FIFO nie otworzył go do zapisu, a wewnętrzny bufor jest pusty, próba odczytu zwróci EOF – poprawny odczyt 0 bajtów – Gdy żaden z procesów połączonych z pipe lub FIFO nie otworzył go do odczytu, próba zapisu skutkuje otrzymaniem sygnału SIGPIPE, jeśli ten sygnał jest blokowany, ignorowany lub obsługiwany, pisanie zwróci błąd EPIPE, tzw „broken pipe” – EPIPE nie jest błędem krytycznym i dość łatwo go obsłużyć w inny sposób niż zakończeniem całej aplikacji, jest to wymagane w pracach studentów

26 mgr inż. Marcin Borkowski Atomowy dostęp do pipe i FIFO ● Tam gdzie zaledwie jeden proces pisze i jeden czyta FIFO (pipe) rozmiar wysyłanych danych nie ma znaczenia ● Tam gdzie występuje wiele procesów piszących, rozmiar wysyłanych danych nie może przekraczać PIPE_BUF oraz najlepiej utrzymać ten sam rozmiar dla wszystkich rodzajów komunikatów ● Przesyłanie danych w porcjach nie większych od PIPE_BUF nie może być przerwane obsługą sygnału w trakcie transmisji

27 mgr inż. Marcin Borkowski Atomowy dostęp do pipe i FIFO ● Gdy wiele procesów w obrębie tego samego pipe/FIFO: – pisze – będzie działać poprawnie o ile jednorazowa transmisja nie przekracza rozmiaru PIPE_BUF – czyta – nie musi działać poprawnie, POSIX nie przewiduje atomowego odczytu

28 mgr inż. Marcin Borkowski Atomowy dostęp do pipe i FIFO ● Transfer atomowego bloku danych może zostać przerwany zanim jakiekolwiek dane zostaną wysłane (EINTR) ● Typowe błędy związane z atomowym dostępem do współdzielonego FIFO/pipe – arbitralnie dobrany rozmiar wiadomości bez sprawdzenia czy nie przekracza PIPE_BUF – różne długości wiadomości różnych typów – wysyłanie wiadomości w częściach (np.: oddzielnie typ) – przesłanie ostatniej części danych w krótszej wiadomości

29 mgr inż. Marcin Borkowski Pipe oraz FIFO w przykładach ● Oczekiwanie na I/O ( select ): fd_set in1,in2; FD_ZERO(&in1); FD_SET(STDIN_FILENO, &in1); FD_SET(fd, &in1); in2 = in1; if (TEMP_FAILURE_RETRY(select(fd+1, &in2, NULL, NULL, NULL)) < 0) ERR(); if (FD_ISSET(fd, &in2))... if (FD_ISSET(STDIN_FILENO, &in2))...

30 mgr inż. Marcin Borkowski Pipe oraz FIFO w przykładach ● Oczekiwanie na I/O ( poll ): struct pollfd fds[2]; fds[0].fd = fd1; fds[1].fd = fd2; fds[0].events = POLLOUT | POLLWRBAND; fds[1].events = POLLIN | POLLPRI; ret = TEMP_FAILURE_RETRY(poll(fds, 2, 300)); if (ret < 0) ERR(); else if (0 == ret){... } /*timeout*/ else { if (fds[0].revents & POLLERR) ERR(); else if (fds[0].revents & POLLOUT)...... }

31 mgr inż. Marcin Borkowski Pipe oraz FIFO w przykładach ● Jak czytać atomowe bloki danych (pipe,FIFO) ze sprawdzeniem błędu rozłączenia if (size > PIPE_BUF) ERR(); len = TEMP_FAILURE_RETRY(read(fd, buffer, size)) if (len < 0) ERR(); if (0 == len)... /*rozlaczenie*/

32 mgr inż. Marcin Borkowski Pipe oraz FIFO w przykładach ● Jak wysyłać atomowe bloki danych (pipe,FIFO) ze sprawdzeniem „broken pipe” if (sethandler(SIG_IGN,SIGPIPE)) ERR();... len2 = TEMP_FAILURE_RETRY(write(fds[current], (char*)(&len), sizeof(size_t))); if ((len2 < 0) && (EPIPE == errno)) { if (TEMP_FAILURE_RETRY(close(fds[current]))) ERR(); fds[current] =- 1; children--; } if (len2 < sizeof(size_t)) ERR();