Metody komunikacji międzyprocesowej w systemach lokalnych i rozproszonych 1. Co to jest komunikacja międzyprocesowa? Komunikacja międzyprocesowa (ang. Inter-Process Communication IPC) umowna nazwa zbioru sposobów komunikacji pomiędzy procesami systemu operacyjnego, uruchomionymi na jednym komputerze lub kilku połączonych w sieci. Metody IPC dzielą się na: przekazywanie komunikatów synchronizację współdzielenie pamięci zdalne wywoływanie procedur (RPC) Procesy mogą używać różnych sposobów komunikacji, a najpowszechniejsze z nich to: pliki i blokady najprostsza i najstarsza forma IPC sygnały (ang. signals) w niektórych systemach (jak np. DOS) znane jako przerwania programowe łącza nienazwane (ang. pipes) znane też jako łącza komunikacyjne łącza nazwane (ang. named pipes) znane też jako nazwane łącza komunikacyjne kolejki komunikatów (ang. message queues) pamięć dzieloną (ang. shared memory) znane też jako segmenty pamięci dzielonej (ang. shared memory segments) semafory (ang. semaphores) gniazda dziedziny Uniksa (ang. Unix domain sockets) Ponieważ procesy mogą komunikować się nie tylko w obrębie jednego systemu, możemy wyróżnić też: gniazda (ang. sockets) RPC 2. Systemy lokalne i rozproszone System lokalny - jest to system składający się z jednego komputera oraz środowiska na nim uruchomionego. Systemu rozproszony - Wiele autonomicznych komputerów (lub systemów automatyki) komunikujących się poprzez sieć komputerową (lub magistralę komunikacyjną), oddziałujacych ze sobą w celu osiągnięcia wspólnego celu. Programam roproszony, rozproszone programowanie. Oprogramowanie pośredniczące: gniazda, RPC, DCE, CORBA, RMI, DCOM. Cechy systemów rozproszonych: Dzielenie zasobów (ang. resource sharing) (np. drukarek, plików, usług, itp.).
Otwartość (ang.openness) podatność na rozszerzenia, możliwość rozbudowy systemu sprzętowo, jak i pod względem oprogramowania. Współbieżność (ang.concurrency) zdolność do przetwarzania wielu zadań jednocześnie Skalowalność (ang. scalability) zachowanie podobnej wydajności systemu przy zwiększaniu skali systemu (np. liczby procesów, komputerów, itp.). Tolerowanie awarii (ang. fault tolerance) działania systemu mimo pojawiania się błędów i (lub) uszkodzeń (np. przez utrzymywanie nadmiarowego sprzętu). Przezroczystość (ang. transparency) postrzeganie systemu przez użytkownika jako całości, a nie poszczególnych składowych. 3. Łącza nienazwane Najprostsza metoda komunikacji. Możliwa jest ona dla procesów pozostających w relacji macierzysty-potomny. Łącze tworzy jednokierunkowy kanał komunikacyjny pomiędzy dwoma procesami, zwykle standardowe wyjście jednego procesu łączone jest ze standardowym wejściem drugiego. W uniksowych powłokach systemowych można użyć pionowej linii aby połączyć dwa lub więcej procesów. Przykład: $ 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). Tworzenie łącza za pomocą funkcji pipe(): #include <unistd.h> int pipe(int fildes[2]) Po wywolaniu funkcji pipe() otrzymujemy dwa deskryptory plikow: fildes[0] - deskryptor strumienia do czytania fildes[1] - deskryptor strumienia do pisania Nieużywany w procesie deskryptor musi być zamknięty (funkcją close). Deskryptory łącz utworzonych przy pomocy funkcji pipe są dziedziczone przez proces potomny utworzony przez funkcje fork(). Jeden z procesów może zapisywać bajty do łącza za pomocą funkcji write, podczas gdy drugi z procesów może je odczytywać korzystając z funkcji read. 4. Łącza nazwane Gdy procesy nie pozostają w relacji macierzysty - potomny komunikacja przy pomocy łącz nienazwanych nie może być zastosowana. Należy zastosować wtedy łącza nazwane zwane inaczej plikami FIFO. Pliki FIFO są plikami specjalnymi. Posiadają takie atrybuty zwykłych plików jak nazwa, właściciel, grupa i prawa dostępu. Pliki FIFO różnią się tym od zwykłych plików tym, że element odczytany jest z pliku usuwany. Pliki FIFO tworzy się przy pomocy funkcji mkfifo lub mknod. int mkfifo(char *name, mode_t mode, int flags) int mknod(const char *path, mode_t mode, dev_t dev);
W Uniksie do tworzenia potoków nazwanych stosuje się program mkfifo lub mknod. 5. Funkcja select Funkcja select powoduje zablokowanie procesu bieżącego do czasu wystąpienia gotowości lub błędu na którymś z deskryptorów. Odblokowuje się również wtedy, gdy upłynie zadany okres oczekiwania (timeout). int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval * timeout) Gdzie: nfds - liczba deskryptorów plików (maksymalne FD_SETSIZE) readfds - maska deskryptorów plików do odczytu (gotowość odczytu) writefds - maska deskryptorów plików do zapisu (gotowość zapisu) errorfds - maska deskryptorów plików dotyczących błędów timeout - maksymalny okres zablokowania Deskryptor jest uważany za dostępny, jeżeli możliwe jest wykonanie na nim określonej operacji, np. wykonanie odczytu bez blokowania. 6. Kolejki komunikatów Kolejki komunikatów POSIX są wygodnym mechanizmem komunikacji międzyprocesowej działającym w obrębie jednego węzła. Są jest asynchronicznym protokołem komunikacyjnym, co oznacza, że odbiorca i nadawca wiadomości nie muszą łączyć się z kolejką w tym samym czasie. Komunikaty przesłane kolejce są przechowywane aż do czasu odebrania przez inny proces (jeżeli proces się kończy, to wszystkie otwarte przez niego kolejki są zamykane, nie zostają one jednak usunięte). Kolejka komunikatów posiada następujące własności: Posiada nazwę, którą procesy mogą zidentyfikować. Więcej niż jeden proces może czytać lub pisać z/do kolejki. Komunikaty odczytywane z kolejki zachowują strukturę są separowane. W kolejce mogą znajdować się komunikaty różnej długości. Własności tej nie mają kolejki FIFO. Można zadać maksymalną długość kolejki komunikatów. Gdy zostanie ona przekroczona, proces piszący do kolejki komunikatów będzie zablokowany. Można testować status kolejki (np. liczbę komunikatów w kolejce). Nie jest to możliwe w przypadku kolejek FIFO. Komunikatom można nadać priorytet. Komunikaty o wyższym priorytecie będą umieszczane na początku kolejki. Kolejkę komunikatów tworzy się za pomocą funkcji mq_open(char *name, int oflag, int mode, mq_attr *attr). Pozostałe funkcje obsługi kolejki: mq_send() - Zapis do kolejki komunikatów mq_receive() - Odczyt z kolejki komunikatów
mq_close() - Zamykanie kolejki komunikatów. mq_unlink() - Kasowanie kolejki komunikatów 7. Pamięć dzielona i semafory Pamięć dzielona - Sposób wymiany danych pomiędzy procesami uruchomionymi jednocześnie. Jeden proces tworzy obszar w pamięci RAM, do którego pozostałe procesy mają dostęp. Procesy muszą wykonywac się na maszynie jednoprocesorowej lub wieloprocesorowej ze wspólną pamięcią, należy zadbać o zachowanie spójności danych zawartych w dzielonym obszarze pamięci, synchronizację dostępu. Funkcje wykorzystywane do obsługi pamięci współdzielonej: shm_open() - Utworzenie wspólnego segmentu pamięci ftruncate() - Ustalenie rozmiaru segmentu map() - Ustalenie odwzorowanie segmentu munmap() - Cofnięcie odwzorowanie segmentu mprotect() - Zmiana trybu dostępu shm_unlink() - Skasowanie segmentu pamięci Semafory - nazwane/nienazwane(odwolanie przez adres, szybsze), abstrakcyjne obiekty służące do kontrolowania dostępu do ograniczonego zasobu. Semafory są szczególnie przydatne w środowisku gdzie wiele procesów lub wątków komunikuje się przez wspólną pamięć. Rodzaje semaforów: Semafor binarny (wartości 0 i 1) Semafor ze zbiorem procesów oczekujących (nie zapewnia spełnienia warunku zagłodzenia) Semafor z kolejką procesów oczekujących FIFO Licznik zasobu L oraz operacje atomowe: sem_open(), Sem_close(), Sem_unlink sem_init(s,n) - inicjalizacja semafora S na wartośc N sem_wait(s) - zajmowanie semafora L=L-1 lub zablokowanie (sem_trywait() nieblokujące) sem_post(s) - sygnalizajca L=L+1 8. Sygnały Sygnały są reprezentacja asynchronicznych i zwykle awaryjnych zdarzeń zachodzących w systemie w systemie. Sygnały mogą być generowane: przez system operacyjny, gdy wystąpi zdarzenie awaryjne, w programie za pomocą funkcji kill(), alarm() i raise(), z konsoli za pomocą polecenia kill.
Sygnał może być obsłużony przez program aplikacyjny. Funkcja systemowa signal() pozwala na zainstalowanie procedury obsługi sygnału: signal(int sig, void(*funct) (int)) Gdzie: sig - Numer sygnału funct - Nazwa funkcji obsługującej sygnał lub akcja zdefiniowana w systemie (SIG_DFL lub SIG_IGN) Procedura void funct(int) wykonana będzie gdy pojawi się sygnał sig. W systemie pierwotnie zdefiniowane są dwie funkcje obsługi sygnałów: SIG_DFL - akcja domyślna, powoduje zwykle zakończenie procesu, SIG_IGN - zignorowanie sygnału (nie zawsze jest to możliwe). 9. RPC, RMI, CORBA RPC - remote procedure call (zdalne wywoływanie procedur) jest to protokół komunikacji pomiędzy procesami w systemach rozproszonych. Z założenia polega to na komunikacji klient - serwer, w przypadku której program klienta wywołuję jakąś procedurę znajdującą się na serwerze. Problemem mogą być różnice w reprezentacji danych przekazywanych parametrów lub lokalizacja procedur serwera. Ich rozwiązaniem jest kodowanie danych oraz korzystanie z łącznika (usługa RPC tłumacząca nazwy na porty procedur). Korzystanie z RPC jest dużym ułatwieniem dla programistów ponieważ wywołanie procedury przebiega tak jakby znajdowała się ona w systemie lokalnym. CORBA Standard opracowany przez Object Management Group mający na celu umożliwienie współpracy procedur napisanych w różnych językach programowania oraz znajdujących się na różnych komputerach. Jedną z wad architektury CORBA jest brak standardowego i szeroko zaimplementowanego mechanizmu bezpieczeństwa. Popularne implementacje standardu CORBA to np. ORBit, OMNIOrb. Z technologii CORBA korzysta m.in. GNOME. Obiekty mają swoje adresy IOR (ang. Interoperable Object Reference). Są to kilkusetznakowe adresy kodujące wiele informacji o obiekcie, m.in. adres komputera, adres programu na komputerze, informacje o kolejności zapisu bajtów (czy jest to big endian, czy little endian), numer obiektu, typ obiektu, itd. RMI - remote metod invocation. Standard opracowany przez firmę Sun. Wykorzystywany w języku java. Obiekty zdalne rejestrowane są pod wybranymi nazwami w serwisie RMI Registry. Aplikacja kliencka ściąga z RMI Registry tzw. stub tego obiektu, który umożliwia komunikację z obiektem zdalnym przy użyciu wyeksportowanych metod w ten sam sposób, jakby chodziło o obiekt lokalny. Rola RMI Registry w tym miejscu kończy się - nie pośredniczy on w komunikacji pomiędzy aplikacją kliencką a obiektem zdalnym. Parametry metod będące obiektami przy wywołaniu zdalnym są serializowane. (Zasada działania bardzo podobna do RPC) Gniazdo w telekomunikacji (ang. socket) pojęcie abstrakcyjne reprezentujące dwukierunkowy punkt końcowy połączenia. Dwukierunkowość oznacza możliwość wysyłania i przyjmowania danych. Wykorzystywane jest przez aplikacje do
komunikowania się przez sieć w ramach komunikacji międzyprocesowej. W systemach typu Unix obsługa gniazd jest implementowana w jądrze, a wykonywanie na nich operacji umożliwiają funkcje systemowe podobne do tych, jakich używa się w stosunku do plików. Asocjacja następuje jeżeli znamy oba adresy IP i porty oraz typ gniazda. Gniazdo posiada trzy główne właściwości: 1. typ gniazda identyfikujący protokół wymiany danych 2. lokalny adres (np. adres IP, IPX, czy Ethernet) 3. opcjonalny lokalny numer portu identyfikujący proces, który wymienia dane przez gniazdo (jeśli typ gniazda pozwala używać portów) Gniazdo może posiadać (na czas trwania komunikacji) dwa dodatkowe atrybuty: 1. adres zdalny (np. adres IP, IPX, czy Ethernet) 2. opcjonalny numer portu identyfikujący zdalny proces (jeśli typ gniazda pozwala używać portów) 10. Komunikacja połączeniowa i bezpołączeniowa Komunikacja bezpołączeniowa (przykład - protokół UDP) polega na przesyłaniu ciągu oddzielnych porcji informacji. Każda z nich jest zaopatrzona w konkretny, unikalny adres odbiorcy i może wędrować do celu niezależnie od pozostałych, brak potwierdzenia odbioru. Drogi przebywane przez poszczególne przesyłki mogą być różne, czasy osiągania celu też mogą być różne (przesyłki mogą docierać do odbiorcy w innej kolejności, niż zostały wysłane). Zawodna (przesyłki mogą być gubione na trasie, duplikowane lub mogą przychodzić w zmienionej kolejności). Aby była niezawodna należy: numerowac przesyłki, wysyłać potwierdzenia odbioru (positive/negative acknowledgement), odbiorca powinien układać przesyłki według numeracji. Należy podkreślić, że wiadomość zostanie odebrana tylko wtedy, gdy adresat oczekuje na odbiór datagramu. Datagram max 500bajtów. Komunikacja połączeniowa (przykład - protokół TCP) polega na przesyłaniu strumienia informacji o (teoretycznie) nieograniczonej długości przez otwarty przedtem kanał komunikacyjny. W przypadku takiej komunikacji elementy informacji docierają do odbiorcy w niezmienionej kolejności. Trzy fazy: nawiązania połączenia (nadanie uniklnego numeru sesji), przesyłania informacji, rozwiązania połączenia. Niezawodna (dopóki trwa, zapewnia przekazywanie informacji bez zniekształceń). Potwierdzenia odbioru. Komunikacja: full-duplex, half-duplex, simplex, unicast, multicast, broadcast, klient-serwer. Protokól komunikacyjny - zbiór reguł określających ciąg czynności, jakie trzeba wykonać, aby przekazać porcję informacji. Narzut - suma czynności nie będących samym przekazywaniem informacji.
11. Źródła http://achilles.tu.kielce.pl/members/ppaduch/wyk142ady/programowaniewspo142biezne/10-ipc-posix-kolejki-komunikatow/wyk_posix_ipc_kolejki.pdf/view http://jedrzej.ulasiewicz.staff.iiar.pwr.wroc.pl/programowaniewspolbiezne/wyklad /SSOKolejKomPOSIX-10.pdf http://pl.wikipedia.org/wiki/komunikacja_mi%c4%99dzyprocesowa https://en.wikipedia.org/wiki/inter-process_communication Programowanie aplikacji współbieżnych i rozproszonych w systemie Linux - materiały do ćwiczeń laboratoryjnych, Jędrzej UŁASIEWICZ http://www.wstkt.pl/materialy/dr_leszczuk_08.pdf