Komunikacja międzyprocesowa

Transkrypt

Komunikacja międzyprocesowa
Projektowanie
oprogramowania
systemów
KOMUNIKACJA MIĘDZYPROCESOWA
plan

Informacje ogólne

Mechanizmy IPC

Pliki i blokady

Sygnały

Gniazda i potoki

Nazwane potoki

Pamięć współdzielona i pliki mapowane w pamięci

Semafory

Kolejki komunikatów

Message-passing interface

Komunikaty okien (Windows)
IPC – informacje ogólne

Komunikacja międzyprocesowa (interprocess
communications, IPC) to wymiana danych
pomiędzy procesami

Typowo wykorzystywana w modeli klient-serwer:
klient żąda określonych danych z serwera, serwer
wysyła dane do klienta

Procesy mają izolowane przestrzenie adresowe,
więc nie ma prostego sposobu aby
komunikować procesy bezpośrednio – muszą
być używane specjalne obiekty na poziomie
systemu operacyjnego

IPC jest najczęściej używana w złożonych
systemach – serwerach baz danych, www,
aplikacji (ale nie tylko: np. schowek)

IPC może być używana także w odniesieniu do
systemów sieciowych/rozproszonych, ale w tym
wykładzie ograniczamy się do procesów na
jednej maszynie
IPC – jak działa?

Nie ma sztywnych specyfikacji i reguł…

OS dostarcza niezbędnych konstrukcji (semafory, kolejki,
potoki…), które są wykorzystywane do przekazywania
komunikatów i dostarczania powiadomień do
skomunikowanych stron

Interpretacja komunikatów jest zależna od programu, każda
aplikacja ma swoje własne kody, nazwy itd. (w
przeciwieństwie do np. nagłówków ustandaryzowanych
protokołów sieciowych)

Komunikujące się aplikacje muszą stworzyć własny protokół
komunikacyjny, który będzie obsługiwany przez obie strony

Przekazywanie danych pomiędzy procesami zwykle wiąże się
z przesyłaniem ich przez jądro OS-u – czynnik ograniczający
wydajność
Mechanizmy IPC

Pliki i blokady

Sygnały

Gniazda i potoki

Nazwane potoki

Pamięć współdzielona i pliki mapowane w pamięci

Semafory

Kolejki komunikatów

Message-passing interface

Komunikaty okien (Windows)
Pliki

Procesy komunikują się poprzez zapis i odczyt tego samego pliku o określonej
ścieżce w systemie plików

Zwykle jeden proces pisze do pliku, a inne odczytują (np. pliki PID na Uniksie, które
służą do określenia, że dany demon działa i informują o jego identyfikatorze)

Generalnie, nie istnieją sposoby zapewnienia, że dane są odczytywane atomowo i
że nie są one zniekształcone/niekompletne: nie ma standardowych sposobów
zapobiegnięcia wyścigom!

Typowo, zakłada się, że odczyt/zapis porcji danych o rozmiarze nieprzekraczającym
bloku bufora dysku odbywa się atomowo, ale nie jest to w ogólności standardem!

Niektóre systemy operacyjne posiadają funkcje do blokowania plików,
zapobiegając współbieżnemu dostępowi do momentu aż dane będą w dobrzezdefiniowanym stanie

Używanie plików do realizacji IPC jest najprostszym, z reguły najwolniejszym i
najmniej bezpiecznym mechanizmem – warto go unikać w prawdziwych
aplikacjach ;)
Blokowanie plików

Najprostszy sposób zapewnienia synchronizacji pomiędzy kooperującymi
procesami

Jak to działa:

Nakładamy blokadę na plik

Odczyt/zapis chronionych danych

Zdejmujemy blokadę

Tylko jeden proces naraz może zablokować plik – w efekcie operacja
odczytu/zapisu staje się sekcją krytyczną

Dwa modele:


Blokada całego pliku (funkcja systemowa flock() na POSIX-ach)

Blokada regionu w pliku (blokowanie rekordów)
Kluczowy mechanizm dla tworzenia opartych na plikach systemów
bazodanowych (SQLite, BTrieve…)
Blokowanie plików
Sygnały

AKA “przerwania programowe” (realizowane przez OS, w przeciwieństwie do
przerwań sprzętowych, które realizowane są przez specjalny układ PIC w procesorze)

Komunikat systemowy wysyłany z jednego procesu do drugiego, zwykle nie używany
do transmisji danych ale raczej do przesyłania poleceń (brak mechanizmu do
transmisji innych danych poza kodem sygnału)

Szeroko używane w Uniksie, właściwie nieużywane na Windows

Sygnał jest uruchamiany asynchronicznie w stosunku do kodu programu i przerywa
jego regularne działanie

Sygnały wysyłamy za pomocą funkcji systemowej kill() (patrz man 2 kill) i zwykle są
używane przez OS aby powiadomić proces o sytuacji błędu (np. dzielenie przez zero
albo naruszenie ochrony pamięci)

Typowo, nieobsłużony sygnał spowoduje awaryjne zakończenie działania programu
i crash ze zrzutem pamięci

Program może zainstalować własne procedury obsługi sygnałów za pomocą funkcji
sytemowej signal()
Użycie sygnałów dla IPC

Istnieje szereg predefiniowanych identyfikatorów sygnałów w nagłówku <signal.h>,
które są używane przez OS w określonych okolicznościach i nie powinny być
wykorzystywane przez aplikacje w innych celach

Poza powyższymi, systemy Unix posiadają 2 sygnały definiowane przez użytkownika:
SIGUSR1 & SIGUSR2, których można używać w celu dostarczania komend do
działającego procesu

Użycie sygnałów dla IPC generalnie jest trudne, ponieważ obsługa sygnałów
odbywa się asynchronicznie i nie ma prostego sposobu aby zakomunikować sygnał
do „zwykłego” biegu programu

Zakres operacji dozwolonych do wykonania w procedurze obsługi sygnału jest
bardzo ograniczony: nie powinien on blokować działania na dłuższy czas, kod
powinien być reentrant

Nie ma sposobu na przekazanie dodatkowych danych: tylko (ograniczona liczba)
kod sygnału jest istotny

Na Windows sygnały są emulowane poprzez uruchomienie przez OS dodatkowego
wątku, więc obsługa sygnału odbywa się współbieżnie do kodu programu (nie
przerywa jego działania!)
Przykład obsługi sygnału

Uruchamiamy program powyżej i sprawdzamy co się stanie, gdy
wyślemy do niego sygnał SIGINT (naciskając Ctrl+C w konsoli)
Gniazda i potoki

Gniazdo sieciowe jest punktem końcowym (endpoint) komunikacji
międzyprocesowej poprzez sieć komputerową

Istnieją również gniazda lokalne, które mogą być używane
identycznie jak gniazda sieciowe, ale jedynie do zapewnienia
komunikacji pomiędzy procesami na tej samej maszynie

Para połączonych gniazd lokalnych (tzw. gniazd domeny Uniksa –
Unix-domain sockets) jest nazywana potokiem (pipe)

Nie ma różnic pomiędzy używaniem gniazd sieciowych a
lokalnych dla zapewnienia IPC (poza tym, że gniazda Uniksowe są
jednokierunkowe, a sieciowe – zwykle dwukierunkowe)

Każdy program domyślnie startuje z minimum 3 potokami, które są
używane do IPC z kontrolującą go powłoką (shell): stdin, stdout &
stderr – zwykle nazywamy je „standardowymi plikami”, ponieważ
na Uniksie gniazdo lub endpoint potoku zachowuje się identycznie
jak plik
Użycie gniazd do IPC

Gniazda (i potoki) tworzą abstrakcję stream, który umożliwia przesłanie dużych
porcji danych w sposób uporządkowany (w określonej kolejności)

Użycie potoków do odbierania poleceń programu jest równie naturalne, co
czytanie ze standardowego wejścia (stdin)

W istocie jest możliwe w sposób programowy zastąpić standardowe wejście/wyjście
jakimkolwiek innym wejściowym/wyjściowym potokiem lub otwartym gniazdem
sieciowym – tworzymy aplikację sieciową „z niczego”

Obie komunikujące się strony muszą zgodzić się co do protokołu używanego
do transmisji poleceń i danych

Dla bezpieczeństwa, wszystkie dane pochodzące z potoków wejściowych
muszą być walidowane i traktowane jako potencjalnie uszkodzone
Użycie gniazd do IPC


Ilość danych możliwych do przesłania potokiem naraz jest
ograniczona

Jeśli proces piszący zapisuje dane szybciej niż proces czytający je
konsumuje, zaś potok nie może buforować więcej danych, proces piszący
jest blokowany tak długo, aż kolejny zapis stanie się możliwy

Jeżeli proces czytający próbuje odczytać dane, a są one niedostępne, to
blokuje on, aż dane zostaną zapisane do potoku

Potok automatycznie synchronizuje obydwa procesy
Potoki utworzone za pomocą funkcji systemowej pipe() są
ograniczone do komunikacji pomiędzy procesem rodzicem i
potomnym, ponieważ nie ma prostego sposobu na przekazanie
dostępu do potoku z jednego procesu do drugiego (proces
potomny dziedziczy deskryptor potoku z rodzica)
Potok
Łączenie niepowiązanych programów
potokami za pomocą powłoki

Zarówno na Uniksie jak i na Windows, powłoka systemowa (shell) umożliwia
tworzenie potoków łączących niepowiązane programy za pomocą znaku „|”
(nazywanym z tego powodu pipe):

Przykład:
$ ps -a | sort | uniq | grep -v sh

Wyjście polecenia “ps –a” jest przekierowywane jako standardowe wejście dla
“sort”, którego wyjście staje się wejściem dla “uniq”, którego wyjście jest wejściem
dla “grep –v sh” (w skrócie: wylistuj działające procesy, posortuj je alfabetycznie,
usuń powtórzenia i wystąpienia procesu „sh”)

Czas życia (anonimowego) potoku utworzonego w ten sposób jest zarządzany przez
powłokę, która jest procesem-rodzicem dla wszystkich pozostałych i umożliwia im
odziedziczenie otwartego potoku jako stdin/stdout

Można również przekierować stderr – używając “|&” zamiast “|”
Nazwane potoki

“Zwykłe” potoki są anonimowe

Istnieje sposób, aby powiązać potok z systemem plików, nadając mu nazwę jak
typowemu plikowi

Taki plik nazywamy zwykle FIFO (kolejka first-in, first-out), może on zostać „otwarty”
przez każdy proces jak każdy inny plik

Otwarcie FIFO do odczytu daje dostęp do endpointa „tylko do odczytu” potoku,
otwarcie do zapisu – endpoint „tylko do zapisu”

Umożliwia to nawiązanie komunikacji pomiędzy procesami bez relacji
rodzic/potomek

FIFO mogą mieć wiele procesów piszących i wiele odczytujących – może to zostać
wykorzystane do dystrybucji zadań do wielu procesów „roboczych” – system typu
„producent-konsument”

Poza faktem, że otwieramy FIFO jak plik, użycie nazywanych potoków na Uniksie nie
różni się w żaden sposób od użycia gniazd sieciowych lub plików (z wykorzystaniem I/O
blokującego lub nieblokującego)
Nazwane potoki na Windows

Na Windows nazwane potoki stanowią inny mechanizm niż „zwykłe” potoki
stworzone jako para gniazd (nie ma również gniazd lokalnych ani funkcji pipe())

Nazwane potoki na Windows nie mogą być powiązane z systemem plików, istnieją
w odrębnej przestrzeni nazw jądra

Mogą być używane do łączenia komputerów w sieci

Są dwukierunkowe

Pod względem cech są bardzo podobne do zwykłych gniazd sieciowych, poza
tym, że wykorzystują inne API niż gniazda (co czyni je niekompatybilnymi)

Nie ma sposobu stworzenia nazwanego potoku z potoku anonimowego (to są inne
mechanizmy)

Nazwane potoki zasadniczo nie dają zysku w stosunku do użycia gniazd sieciowych

Są nieprzenośne, więc używamy ich tylko w „hardkorowych” aplikacjach dla
Windows
Pamięć współdzielona

Kilka procesów uzyskuje dostęp do tego samego bloku pamięci,
który stanowi współdzielony bufor wymiany danych dla
skomunikowanych procesów

Brak narzutów związanych z wywoływaniem funkcji systemowych –
zapis i odczyt równie szybkie jak w przypadku każdego innego
dostępu do pamięci

Nadal jednak potrzebujemy obiektów jądra zapewniających
wzajemną wyłączność aby uniknąć wyścigów
Pamięć współdzielona
Odwołania do pamięci
współdzielonej w wielu procesach
Utworzyliśmy blok pamięci współdzielonej w naszym kodzie, w jaki sposób teraz
przekazać innym procesom informacje jak się do tego bloku dostać?
 Pamięci współdzielonej zwykle używamy w połączeniu z nazwą pliku, która może
być wykorzystywana przez wszystkie procesy, zaś tworzenie pamięci współdzielonej
to w rzeczywistości mapowanie zawartości pliku do pamięci



Otwieramy plik o ustalonej nazwie

Mapujemy region pliku do pamięci we wszystkich skomunikowanych procesach

Wszystkie procesy „widzą” tą samą zawartość bloku pamięci – w istocie ten sam blok
pamięci, który jest używany również przez menadżera pamięci cache systemu plików

Uprawnienia pliku określają kto może mieć dostęp do bloku pamięci

Wszystkie zmiany w pamięci są synchronizowane do pliku

Ta technika nazywa się “pliki mapowane w pamięci” i istnieje na POSIXach, Windows i
wielu innych systemach
Istnieje również możliwość stworzenia anonimowego bloku pamięci poprzez
mapowanie zawartości pliku swap. Dostęp do takiego bloku może być
odziedziczony przez proces potomny, lub przekazany przez inne metody IPC (np.
przez potok)
Ograniczenia pamięci
współdzielonej

Ten sam blok pamięci w każdym procesie jest widoczny pod innym
adresem – możliwe jest tylko adresowanie względne w odniesieniu
do adresu bazowego bloku

Zazwyczaj nie jest możliwe konstruowanie wysokopoziomowych
obiektów w pamięci współdzielonej, ponieważ wskaźniki będą
prawidłowe tylko w jednym procesie

Istnieje biblioteka Boost.Interprocess, która dostarcza alokator pamięci oparty
na pamięci współdzielonej, umożliwiając w istocie współdzielenie obiektów
C++

Bez tego typu dodatków, bezpiecznie można przechowywać w pamięci
współdzielonej tylko typy POD (plain old data – struktury i tablice typów
podstawowych lub POD, bez żadnych wskaźników)
API pamięci współdzielonej

Windows: CreateFileMapping()/MapViewOfFile()

Unix/System V: shmat()/shmget()

POSIX
 mmap()
 shm_open()

Na samych systemach Unix-owych istnieją 3 różne API,
więc dla najlepszej przenośności należy używać bibliotek
tyakich jak Boost.Interprocess
Semafory

Obiekty systemu operacyjnego, które umożliwiają kontrolę
dostępu z poziomu wielu procesów do wspólnych zasobów
w środowisku przetwarzania równoległego

Już omówione na poprzednim wykładzie 
Kolejki komunikatów

Strumienie danych podobne do gniazd, zwykle dostarczane przez
OS, umożliwiające wielu procesom odczyt i zapis komunikatów nie
będąc bezpośrednio ze sobą połączonymi

Kolejka komunikatów może być anonimowa (dziedziczona przez
potomków) lub identyfikowana przez nazwę – dostęp przez dowolne
niepowiązane procesy

Komunikaty umieszczone w kolejce są przechowywane dopóki
odbiorca ich nie pobierze (również po śmierci procesów)

Podobnie do gniazd datagramowych, komunikaty muszą być
odebrane w całości – podejście wszystko albo nic (komunikat jest
niepodzielny)

Podobnie do gniazd strumieniowych, transmisja komunikatów jest
niezawodna
Kolejki komunikatów
Kolejki komunikatów

Komunikat jest dowolnym, zdefiniowanym przez aplikację
blokiem danych identyfikowanym przez kod (int)

Rozmiar komunikatu jest ograniczony ustawieniami OS-a

Niektóre systemy dostarczają również możliwość
przechowywania komunikatów w bazie danych lub systemie
plików – są one w stanie wówczas przetrwać restart systemu

Aplikacje mogą zarejestrować procedurę obsługi
powiadomień o nowych komunikatach (UNIX: sygnały,
Windows: zdarzenia)
API kolejkowania komunikatów


Windows

Cały interfejs graficzny Windows oparty jest na kolejkach komunikatów, do
których wysyłane są powiadomienia o zdarzeniach takich jak akcje użytkownika
- SendMessage()/PostMessage()/GetMessage()…

Microsoft Message Queuing – podejście klient/serwer będące podstawą
systemu DCOM, umożliwia kolejkowanie komunikatów poprzez sieć
System V/Unix


POSIX


msgget()/msgsnd()/msgrcv() – proste API oferujące niewielką kontrolę
mq_open()/mq_send()/mq_receive() – bardziej zaawansowane
Przenośne: używaj Boost.Interprocess
Komunikaty Windows

Każda aplikacja GUI Windows posiada wbudowaną kolejkę
komunikatów, która stanowi kluczowy element konstrukcji pętli
przetwarzania komunikatów

Aplikacje otrzymują komunikaty w odpowiedzi na akcje użytkownika,
zdarzenia systemu operacyjnego, zakończenie I/O, zegary lub jawne
wywołanie funkcji PostMessage()/SendMessage()

Komunikaty są dostarczane do określonego okna, każde okno ma
procedurę obsługi komunikatów (procedurę okna), która podejmuje
pewne akcje w odpowiedzi na zawartość komunikatu


Każdy element UI (przyciski, pola edycji, etc) stanowi osobne okno i jest możliwym
odbiorcą komunikatów
Komunikaty Windows mogą być wysyłane również z innych procesów,
tworząc metodę IPC
Pompa komunikatów Windows

W zasadzie wygląda to podobnie w każdej aplikacji
Windows
Komunikaty Windows jako metoda
IPC

Używamy funkcji RegisterWindowMessage()aby otrzymać unikatowy
w skali systemu identyfikator komunikatu dla określonej nazwy, znanej
wszystkim komunikującym się aplikacjom

W aplikacji wsyłającej komunikat, używamy funkcji FindWindow(),
żeby odnaleźć w systemie okno o określonej nazwie klasy, które
będzie odbiorcą komunikatu

Nadawca wywołuje PostMessage() podając jako cel odnalezione
okno oraz zarejestrowane ID komunikatu

Odbiorca otrzymuje komunikat w swojej pętli obsługi komunikatów
Ograniczenia komunikatów
Windows

Rozmiar danych do przesłania jest niewielki: 1 ID + 2 liczby 32-bitowe (WPARAM,
LPARAM)

Mechanizm kompletnie nieprzenośny

Podobne dokumenty