Subsections

4 Biblioteka ZCCL

1 Wstęp

W różnych zastosowaniach coraz częściej potrzebne są wysoko wydajne mechanizmy komunikacji. Biblioteka gniazd umożliwia uniwersalną komunikację w sieciach rozległych, ale nie nadaje się do wydajnej komunikacji w szybkich sieciach. Do takich zastosowań zaprojektowano architekturę VIA i bibliotekę VIPL. Jednak jest to biblioteka niskopoziomowa i używanie jej bezpośrednio w aplikacjach jest uciążliwe. Dlatego powstała biblioteka ACL, która na bazie VIPL udostępnia wygodny interfejs komunikacyjny.

Biblioteka ACL jest częścią komercyjnego produktu i jako taka nie jest publicznie dostępna. Ponadto ACL jest ściśle dostosowana do wymagań projektu ISS i użycie jej w innej aplikacji jest trudne.

Celem mojej pracy magisterskiej jest stworzenie biblioteki opartej na idei ACL, wykorzystującej niektórej jej koncepcje, ale w pełni dostępnej, włącznie z kodem źródłowym, i dostosowanej do użytku ogólnego. Bibliotekę tę nazwałem ZCCL (od ang. Zero-Copy Communication Library).

2 Cele i założenia ZCCL

Podstawowe cele ZCCL są podobne do celów wyznaczonych dla biblioteki ACL.

  1. Stworzenie biblioteki umożliwiającej wydajną komunikację z możliwością uniknięcia pośredniego kopiowania.
  2. Ułatwienie użytkownikowi korzystania z mechanizmów komunikacyjnych poprzez implementację kontroli przepływu danych i innych udogodnień niekoniecznie zapewnianych przez protokół niższego poziomu.
Dodatkowe cele realizacji ZCCL są następujące.

  1. Stworzenie w pełni asynchronicznego modelu pracy. Odpowiada to modelowi pracy w trybie jądra i umożliwia przedstawienie działania aplikacji jako automatu skończonego o jasno określonych przejściach między stanami.
  2. Stworzenie interfejsu pozwalającego użyć jako wartwy transportowej różnych bibliotek komunikacyjnych. Szczególny nacisk jest położony na dostosowanie do bibliotek zapewniających brak pośredniego kopiowania, a zwłaszcza VIPL jako obecnego standardu.
  3. Udostępnienie jednolitego interfejsu do pracy z poziomu użytkownika i w trybie jądra.
Zakładanym zastosowaniem ZCCL są systemy zdalnego dostępu do danych, w których komunikacja oparta jest na modelu żądanie-odpowiedź (ang. request-reply), na przykład systemy zdalnego dostępu do plików lub pamięci masowej. Dostosowanie do innych sposobów komunikacji może nastąpić poprzez rozszerzenie interfejsu o kolejne typy połączeń komunikacyjnych.

Biblioteka ZCCL w założeniu nie ma być zgodna z innymi bibliotekami komunikacyjnymi i wymaga stworzenia aplikacji pod kątem ZCCL. ZCCL pozwala osiągnąć wysoką wydajność poprzez narzucenie odpowiedniej semantyki pracy, w szczególności w pełni asynchronicznego działania, co wymaga dostosowania ze strony aplikacji. Przenoszenie aplikacji korzystających ze standardowych bibliotek komunikacyjnych, wykorzystujących synchroniczny model działania, mija się z celem, ponieważ będzie to tworzenie synchronicznej otoczki wokół z założenia asynchronicznych operacji ZCCL. Aplikacja, żeby w pełni wykorzystać zalety ZCCL, musi zostać od początku zaprojektowana asynchronicznie. Dotyczy to nie tylko komunikacji sieciowej, lecz także całości działania, i ma w zamierzeniu sprzyjać tworzeniu wysoko wydajnych, skalowalnych aplikacji.

3 Model wysokiego poziomu

ZCCL jest biblioteką wysokiego poziomu, która ma udostępniać wspólny interfejs pozwalający korzystać z różnych mechanizmów fizycznej komunikacji. Obsługą poszczególnych mechanizmów komunikacji zajmują się podsystemy komunikacyjne, które korzystając z odpowiedniego interfejsu dostarczają funkcjonalność wymaganą przez ZCCL (por. rys. 4.1).

Rysunek 4.1: Schemat ogólny ZCCL
\resizebox*{0.6\textwidth}{!}{\includegraphics{rysunki//zccl-diagram-ogolny.eps}}



Najważniejszą rzeczą w ZCCL jest specyfikacja, określająca semantykę operacji. Sama biblioteka ZCCL stanowi tylko warstwę przekazującą żądania użytkowników do odpowiedniego podsystemu komunikacyjnego i wykonującą pewne wspólne operacje związane z zarządzaniem połączeniami, pamięcią itp. Cały ciężar komunikacji sieciowej spoczywa na oprogramowaniu podsystemu komunikacyjnego.

Aplikacje używające ZCCL widzą interfejs ZCCL i mają zapewnioną semantykę określoną przez ZCCL. Aplikacja musi jednak wybrać podsystem komunikacyjny, którego chce używać i musi być świadoma sposobu użycia tego podsystemu. Wynika to z tego, że część struktur jest specyficzna dla konkretnego podsystemu komunikacyjnego. Na przykład format adresu sieciowego dla podsystemu komunikacyjnego używającego gniazd i protokołu TCP/IP jest inny niż dla podsystemu komunikacyjnego używającego VIA. Parametry transmisji danych również mają przeważnie sens tylko w kontekście określonego podsystemu komunikacyjnego.

Podsystem komunikacyjny może określać dodatkowe wymagania względem użytkownika, dotyczące, na przykład, zachowania funkcji użytkownika wywoływanych przez ZCCL.

Z drugiej strony podsystem komunikacyjny może udostępniać użytkownikowi dodatkową funkcjonalność, wykraczającą poza minimum zapewniane przez ZCCL. Na przykład może być możliwa zmiana sposobu zarządzania pamięcią czy modyfikacja parametrów transmisji danych.

W ramach mojej pracy magisterskiej zaimplementowałem podsystem komunikacyjny używający jako warstwy transportowej biblioteki gniazd. Opis tego podsystemu znajduje się w rozdziale 5.

4 Sygnalizacja zdarzeń

Wszystkie zdarzenia związane z komunikacją są w ZCCL sygnalizowane asynchronicznie. W tym celu używa się mechanizmu wywołań funkcji obsługi zdarzeń (ang. callback).

Każdemu zdarzeniu odpowiada jedna funkcja obsługi zdarzenia. Jest ona wywoływana wówczas, gdy zdarzenie zajdzie, zaś w parametrach tej funkcji przekazywane są dodatkowe informacje o zdarzeniu.

Funkcje obsługi zdarzeń są zgrupowane w tablicach funkcji obsługi zdarzeń. Każda taka tablica zawiera funkcje obsługi zdarzeń związane z określoną czynnością.

Na przykład żądanie wysłania danych może być związane z tablicą funkcji określającą funkcje dla następujących zdarzeń:

Tablica funkcji obsługi zdarzeń jest podawana przy rozpoczęciu czynności i związana jest z jedną instancją danej czynności, na przykład każde żądanie wysłania danych może mieć inną tablicę funkcji obsługi zdarzeń. Umożliwia to różne traktowanie różnych rodzajów czynności, na przykład odróżnienie wysyłania żądania od wysyłania odpowiedzi na żądanie.

Dodatkowo, w celu odróżnienia poszczególnych instancji czynności, z każdą tablicą funkcji obsługi zdarzeń jest związany parametr funkcji obsługi zdarzeń (ang. callback argument), który zostanie przekazany przy każdym wywołaniu funkcji obsługi zdarzenia z tej tablicy. Parametr ten jest podawany przez użytkownika łącznie z tablicą i może być na przykład wskaźnikiem do struktury użytkownika opisującej daną transmisję, wskazywać dane do wysłania itp.

W ZCCL wywoływanie funkcji obsługi zdarzeń jest oparte na zaufaniu (por. p. 2.2.4), że użytkownik będzie się zachowywał poprawnie i na przykład nie spowoduje w funkcji zakleszczenia. Brak zabezpieczeń przed nieprawidłowym zachowaniem użytkownika umożliwia osiągnięcie maksymalnej wydajności. W przypadku pracy w trybie jądra, aplikacje używające ZCCL i tak muszą być obdarzone pełnym zaufaniem, ponieważ mają swobodny dostęp do całej pamięci i ich nieprawidłowe zachowanie może spowodować awarię całego systemu. Przy pracy w trybie użytkownika zalecane jest, by podsystem komunikacyjny izolował wywołania funkcji obsługi zdarzeń dla różnych procesów, tak by awaria w jednym z nich nie powodowała wstrzymania komunikacji w innych procesach.

Podsystem komunikacyjny może narzucać dodatkowe ograniczenia na sposób zachowania w funkcji obsługi zdarzenia, poza tymi określanymi przez ZCCL. Może to być na przykład powodowane wywoływaniem funkcji obsługi zdarzenia z procedury obsługi przerwania. Zmiany te mogą jednak wymagać zmiany semantyki innych operacji w podsystemie komunikacyjnym tak, by była możliwa zakładana semantyka działania całej aplikacji. Na przykład, zakaz zasypiania w funkcji obsługi zdarzenia wymaga od podsystemu komunikacyjnego takiej implementacji funkcji żądania wysłania danych, by była nieblokująca i mogła zostać wywołana z funkcji obsługi zdarzenia.

5 Zarządzanie pamięcią

Realizacja transmisji bez pośredniego kopiowania wymaga rejestrowania pamięci przeznaczonej do transmisji (por. p. 2.3.1 i 3.2.3). Ponieważ ZCCL ma za zadanie ukrywać szczegóły implementacyjne różnych podsystemów komunikacyjnych, zarządzanie rejestracją pamięci jest ukryte przed użytkownikiem. Zajmuje się tym odpowiedni podsystem komunikacyjny, a ZCCL dostarcza tylko ogólnych mechanizmów umożliwiających mu wykonywanie tych czynności. Z drugiej strony ZCCL wymaga od użytkownika używania pamięci przeznaczonej do komunikacji w odpowiedni sposób.

System zarządzania pamięcią w ZCCL został zaprojektowany pod kątem użycia schowka zarejestrowanej pamięci (por. p. 3.2.3). Prerejestracja (opisana w p. 3.2.3) ogranicza swobodę użytkownika w określaniu, gdzie chce umieścić dane, jest specyficzna dla użytej warstwy transportowej, a ponadto może ograniczyć ilość pamięci używanej do transmisji danych. Użycie schowka zarejestrowanej pamięci pozwala użytkownikowi w sposób swobodny operować pamięcią, przy jednoczesnym znaczącym zysku wydajności.

W celu umożliwienia rejestracji pamięci i implementacji schowka zarejestrowanej pamięci potrzebne są odpowiednie struktury opisujące pamięć i bufor do transmisji danych.

1 Obszary zarejestrowanej pamięci

W celu umożliwienia implementacji szybkiego schowka zarejestrowanej pamięci biblioteka ZCCL posługuje się obszarami zarejestrowanej pamięci. Aplikacja musi za pomocą funkcji zccl_register_mem_region() zarejestrować w ZCCL obszar pamięci, który ma być używany do transmisji danych. Rejestracja w ZCCL nie oznacza zarejestrowania tej pamięci w karcie sieciowej -- jest to tylko deklaracja ze strony aplikacji, że będzie używała tej pamięci do transmisji danych i, co ważniejsze, że jej samodzielnie nie zwolni.

Przy rejestracji aplikacja podaje adres i długość obszaru pamięci, a otrzymuje uchwyt obszaru zarejestrowanej pamięci, który jest potem używany przy deklarowaniu buforów do transmisji danych.

Przy odrejestrowywaniu obszaru pamięci aplikacja podaje adres funkcji obsługi, która będzie wywołana, kiedy pamięć bedzie mogła zostać zwolniona. Nie wolno jej zwolnić pamięci zanim ta funkcja obsługi nie zostanie wykonana.

ZCCL trzyma licznik odwołań do obszaru zarejestrowanej pamięci. Kiedy aplikacja zgłosi chęć odrejestrowania pamięci i ten licznik spadnie do zera, podsystem komunikacyjny zostaje o tym powiadomiony i może wywołać funkcję obsługi zwolnienia pamięci podaną przez aplikację. Zapewnia to, że nie ma żadnych odwołań do odrejestrowywanego obszaru pamięci wewnątrz ZCCL, czyli że obszar nie jest zarejestrowany w karcie sieciowej, używany jako fragment bufora do transmisji, przetwarzany w innym wątku itp.

Strukturą opisującą obszar zarejestrowanej pamięci oraz samą rejestracją zarządza podsystem komunikacyjny. To on decyduje kiedy i jaki fragment obszaru zarejestrować w karcie sieciowej. Może rejestrować pamięć w karcie sieciowej przed każdą transmisją, prerejestrować cały obszar lub zaimplementować schowek zarejestrowanej pamięci.

Implementacja schowka zarejestrowanej pamięci jest ułatwiona, ponieważ podystem komunikacyjny może w strukturze opisującej obszar trzymać tablicę zarejestrowanych stron pamięci w tym obszarze. Jako że rozmiar obszaru jest znany w momencie jego utworzenia, może to być tablica o rozmiarze dokładnie odpowiadającym liczbie stron wchodzących w skład obszaru. Dostęp do informacji czy strona jest zarejestrowana jest szybki, bo wymaga tylko jednego odwołania do pamięci w tej tablicy. Podsystem komunikacyjny może uprościć sobie zarządzanie pamięcią i sterować rejestracją na poziomie całego obszaru, trzymając tylko licznik wskazujący ile jest odwołań wymagających zarejestrowania pamięci w tym obszarze.

Narzucenie aplikacji zakazu samowolnego zwalniania pamięci zarejestrowanej w ZCCL zapewnia podystemowi komunikacyjnemu swobodę w zarządzaniu schowkiem zarejestrowanej pamięci bez obawy, że pamięć wirtualna zostanie usunięta bez zapewnienia spójności schowka i odrejestrowania pamięci w karcie sieciowej (problem ten jest opisany w p. 3.2.3). Aplikacja zostaje powiadomiona, kiedy te czynności zostaną wykonane i może wtedy zwolnić pamięć.

Podsystem komunikacyjny może udostępnić aplikacji mechanizm zmiany parametrów schowka lub określenia parametrów rejestracji na poziomie obszaru w celu uzyskania maksymalnej wydajności.


2 Deskryptory pamięci

Funkcje transmisji danych nie operują bezpośrednio na wskaźnikach do pamięci, lecz na deskryptorach pamięci. Deskryptor pamięci jest wektorem wskaźników do poszczególnych fragmentów bufora (por. rys. 4.2).

Rysunek 4.2: Deskryptor pamięci
\resizebox*{0.7\textwidth}{!}{\includegraphics{rysunki//deskryptor-pamieci.eps}}

Za jego przydział, zwalnianie i obsługę odpowiada podsystem komunikacyjny. Ma to na celu umożliwienie podsystemowi komunikacyjnemu trzymania danych w potrzebnym mu formacie, w miarę możliwości od razu odpowiednim do użycia przy transmisji danych. Podsystem komunikacyjny może również wymagać dodatkowych parametrów związanych z opisem bufora czy poszczególnych fragmentów bufora, na przykład w VIA z każdym zarejestrowanym obszarem pamięci jest związany tzw. uchwyt pamięci (ang. memory handle). Ponadto podsystem komunikacyjny może stworzyć schowek deskryptorów pamięci i w ten sposób przyspieszyć ich przydzielanie i zwalnianie.

Deskryptory składają się z pól, każde pole określa jeden ciągły fragment pamięci. Deskryptory mają ustaloną liczbę pól, deklarowaną przy przydzielaniu deskryptora.

Pamięć, w której znajduje się fragment bufora, musi należeć do zarejestrowanego obszaru pamięci. Uchwyt tego obszaru jest podawany przy inicjowaniu pola deskryptora. Skojarzenie pola deskryptora z obszarem zarejestrowanej pamięci umożliwia śledzenie odwołań do obszaru zarejestrowanej pamięci tak, by nie został zwolniony, gdy jakiś deskryptor z niego korzysta.

Aplikacja nie zna formatu deskryptora, wszystkie operacje na deskryptorze wykonuje za pomocą uchwytu deskryptora pamięci, który otrzymuje przy przydzieleniu deskryptora pamięci. Ustawianie pól deskryptora odbywa się za pomocą funkcji zdefiniowanej przez podsystem komunikacyjny.

Do zarządzania przydziałem deskryptorów służą funkcje:

Do dostępu do pól deskryptora służą funkcje:

6 Zarządzanie połączeniem

Do nawiązywania połączenia w ZCCL stosowany jest model klient-serwer.

Powiadamianie o zdarzeniach związanych z połączeniem jest w pełni asynchroniczne, oparte na wywoływaniu funkcji obsługi zdarzeń. Dotyczy to zarówno zainicjowania połączenia przez klienta, jak i oczekiwania na połączenia na serwerze.

Obsługa przyjmowania i nawiązywania połączeń jest analogiczna jak w przypadku gniazd, czy biblioteki VIPL.

Na rysunku 4.3 znajduje się diagram stanów połączenia na serwerze, a na rysunku 4.4 diagram stanów połączenia na kliencie.

Na rysunkach czarne prostokąty oznaczają stan kanału, zaś szarymi prostokątami oznaczone są wywołania funkcji obsługi zdarzeń. Przejścia między stanami i wywołaniami funkcji obsługi zdarzeń są wynikiem wywołań funkcji ZCCL, wystąpienia określonych zdarzeń (np. zerwania połączenia) lub wynikają z sekwencji wykonywania określonych czynności (np. po utworzeniu kanału jest wywoływana funkcja obsługi zdarzenia połączenia kanału).

Rysunek 4.3: Diagram stanów połączenia na serwerze
\resizebox*{15cm}{15cm}{\includegraphics{rysunki//diagram-oczekiwania-na-polaczenia.eps}}

Rysunek 4.4: Diagram stanów połączenia na kliencie
\resizebox*{12cm}{!}{\includegraphics{rysunki//diagram-utworzenia-polaczenia-na-kliencie.eps}}

1 Funkcje

Funkcje służące do przyjmowania połączeń klientów to:

Funkcje służące do nawiązania połączenia to:

Funkcja służąca do zerwania połączenia to:

2 Zdarzenia

Funkcje obsługi zdarzeń związanych z nawiązywaniem połączenia to:

Funkcje obsługi zdarzeń związanych z oczekiwaniem na połączenia na serwerze to:

A oto funkcja obsługi zdarzenia zerwania połączenia:


7 Kanały

Połączenia komunikacyjne w ZCCL noszą nazwę kanałów (ang. channel). Mogą być różne typy kanałów, każdy typ ma określoną semantykę i może mieć specyficzne dla siebie operacje.

W obecnej chwili określone są dwa typy kanałów: Message, służący do łatwego przesyłania komunikatów, oraz Post, do przesyłania dużej ilości danych. Szczegółowy opis tych kanałów znajduje się w punktach 4.9 i 4.10.

8 Żądania transmisji

Transmisja danych w ZCCL jest oparta na systemie żądań transmisji. Każda operacja żądania wysłania lub odebrania danych powoduje utworzenie struktury żądania transmisji, która zawiera między innymi takie dane jak deskryptor bufora z danymi, tablica funkcji obsługi zdarzeń i parametr funkcji obsługi zdarzeń. Struktura żądania transmisji nie jest widoczna dla użytkownika, jej zarządzaniem zajmuje się podsystem komunikacyjny. Dla użytkownika żądanie transmisji jest identyfikowane przez parametr funkcji obsługi zdarzeń, który jest przekazywany do każdej funkcji obsługi zdarzenia związanej z tym żądaniem transmisji.

Przetwarzanie żądań transmisji jest całkowicie asynchroniczne. Użytkownik wywołuje funkcję tworzącą żądanie transmisji, a reszta obsługi żądania odbywa się asynchronicznie za pomocą funkcji obsługi zdarzeń.

Kolejność obsługi żądań transmisji jest określona przez semantykę kanału, dla którego to żądanie transmisji zostało utworzone.

Żądanie transmisji może zostać utworzone nawet wówczas, gdy w obecnej chwili nie ma możliwości fizycznego wysłania danych. Zostanie w takim wypadku wstawione do kolejki żądań oczekujących na transmisję. O rozpoczęciu i zakończeniu fizycznej transmisji użytkownik może zostać powiadomiony za pomocą odpowiednich funkcji obsługi zdarzeń.


9 Kanał Message

Kanał Message został zaprojektowany z myślą o wymianie krótkich komunikatów o stałej maksymalnej długości, służących do przesyłania informacji kontrolnych, żądań transmisji większych ilości danych itp. Jego idea jest wzorowana na podsystemie przesyłania komunikatów kanału Exchange biblioteki ACL ([Calkowski], p. 4.8), chociaż semantyka odbiega znacząco od pierwowzoru.

Kanał Message jest dwukierunkowy, z kontrolą przepływu danych (ang. flow control) i zapewnia niezawodne połączenie. Porządek przesyłanych komunikatów nie jest zachowany.

1 Brak porządku danych

Brak zapewnienia porządku wynika z tego, że jest to kosztowne. W przypadku komunikacji typu żądanie-odpowiedź zachowanie porządku nie jest niezbędne, ponieważ każde żądanie jest niezależne i niesie informację pozwalającą je zidentyfikować niezależnie od kolejności.

Usunięcie założenia o porządku przesyłanych danych umożliwia zastosowanie różnych sposobów zwiększenia wydajności transmisji komunikatów:

  1. Użycie wielu połączeń. Możliwe staje się użycie kilku fizycznych połączeń dla jednego kanału. Zapewnienie porządku na kilku niezależnych połączeniach wymaga dodatkowej synchronizacji przy nadawaniu i odbiorze komunikatów, numerowania komunikatów itp. Użycie wielu połączeń może zwiększyć przepustowość i umożliwić tworzenie połączeń redundantnych w celu zwiększenia niezawodności[*].
  2. Użycie szybszych mechanizmów komunikacji. Protokoły komunikacyjne nie zapewniające porządku są wydajniejsze od tych zapewniających porządek pod względem przepustowości i opóźnienia transmisji (ang. latency). Użycie takich protokołów do transmisji komunikatów może zwiększyć wydajność komunikacji. W przypadku niektórych zastosowań opłacalne może stać się nawet użycie protokołu zawodnego. Oczywiście wymaga to zapewnienia niezawodności na poziomie ZCCL, ale w przypadku sieci o niskich stratach pakietów może się opłacić. Za przykład może posłużyć użycie protokołu UDP w sieci lokalnej LAN, w której straty pakietów są rzadkie.
  3. Zrównoleglenie wysyłania komunikatów. Przy wysyłaniu komunikatów nie ma potrzeby zapewnienia synchronizacji wysyłania danych na poziomie ZCCL. Zwiększa to możliwość wykorzystania wszystkich procesorów w systemie.
  4. Zrównoleglenie odbierania komunikatów. Komunikaty mogą być odbierane i dostarczane do aplikacji równolegle. W przypadku użycia kilku procesorów umożliwia to zwiększenie przepustowości połączenia.
Dodatkową zaletą jest ujednoznacznienie semantyki równoległego wysyłania. W przypadku równoległego inicjowania transmisji trudno w ogóle mówić o porządku wysyłania danych. W przypadku połączeń zachowujących porządek aplikacja przeważnie na własną rękę musi zapewnić synchronizację dostępu do kanału, żeby mieć określony porządek wysyłania.

Brak zapewniania porządku wysyłanych danych ma również wady:

  1. Większe wymagania względem aplikacji. Pisanie aplikacji jest trudniejsze i bardziej czasochłonne.
  2. Utrudniona synchronizacja. Synchronizacja pomiędzy nadawcą i odbiorcą za pomocą komunikatów jest utrudniona, ponieważ komunikat synchronizacyjny może dotrzeć przed komunikatami wysłanymi wcześniej. Na przykład komunikat informujący o zakończeniu komunikacji może dotrzeć zanim wszystkie wcześniej wysłane komunikaty zostaną obsłużone. To wymaga mechanizmów kontroli liczby otrzymanych komunikatów i inicjowania działania synchronizacyjnego dopiero wtedy, gdy wszystkie zostaną odebrane. Jednak w przypadku używania kilku kanałów w ramach jednego połączenia klient-serwer trzeba i tak przeprowadzać zliczanie otrzymanych danych na wszystkich kanałach, żeby komunikat zakończenia działania, który przyjdzie na kanale Message, nie spowodował przerwania transmisji danych na innym kanale, na którym dane są wciąż przesyłane. W większości aplikacji byłoby to więc i tak wykonywane.
  3. Komplikacja automatu skończonego kanału. Przejścia pomiędzy stanami automatu skończonego opisującego stan kanału nie są inicjowane przez odebranie komunikatu kontrolnego, lecz przez odebranie określonej liczby komunikatów i odebranie komunikatu kontrolnego.
Rozwiązaniem tych problemów mogłoby być wprowadzenie kanału typu OrderedMessage podobnego do Message, ale z zapewnianiem porządku. Służyłby on tylko do wysyłania komunikatów synchronizacyjnych.

Alternatywnym rozwiązaniem mogłoby być wprowadzenie operacji synchronizacyjnej dla kanału, która powodowałaby powiadomienie nadawcy o dostarczeniu do odbiorcy określonej liczby komunikatów.

W obecnej chwili operacje synchronizacji i zliczania odebranych transmisji muszą być zaimplementowane przez użytkownika. Pozostawia mu to swobodę zarządzania synchronizacją i umożliwia wprowadzenie optymalizacji. Na przykład użytkownik może zliczać odbiór transmisji na kilku kanałach i dopiero po odebraniu wszystkich transmisji na wszystkich kanałach może wysłać jedno wspólne potwierdzenie.

2 Wysyłanie komunikatów

Proces wysyłania komunikatu zaczyna się od tego, że użytkownik zgłasza chęć wysłania komunikatu, jednocześnie podając parametr funkcji obsługi żądań, który pozwoli mu później odróżnić żądania wysłania komunikatu. W chwili, kiedy wysłanie komunikatu jest możliwe, wywoływana jest funkcja obsługi zdarzenia przygotowania komunikatu, której przekazuje się adres bufora, do którego użytkownik ma wpisać komunikat. Użytkownik na podstawie wcześniej podanego parametru obsługi funkcji obsługi zdarzeń rozpoznaje co chciał zrobić i wypełnia odpowiednio bufor. Po powrocie z funkcji obsługi zdarzenia następuje wysłanie komunikatu. Użytkownik może zostać powiadomiony, kiedy transmisja komunikatu została zakończona. Wystąpienie błędów na którymkolwiek z etapów przetwarzania żądania jest sygnalizowane wywołaniem odpowiedniej funkcji obsługi błędu, więc stan żądania można określić za pomocą automatu skończonego (por. rys. 4.5).

Rysunek 4.5: Diagram stanów wysyłania komunikatu
\resizebox*{1\textwidth}{!}{\includegraphics{rysunki//diagram-stan-wysylania-komunikatu.eps}}

Przedstawiony sposób wysyłania komunikatów pozwala użytkownikowi uniknąć wszelkich problemów z zarządzaniem buforami odbiorczymi i nadawczymi, rejestrowaniem pamięci oraz zapewnieniem kontroli przepływu danych.

Bufory na komunikaty są przydzielane przez podsystem komunikacyjny, może je statycznie zaalokować i zarejestrować na początku działania kanału, a później ograniczać liczbę wysyłanych komunikatów (sterując wywołaniem funkcji obsługi przygotowania komunikatu) zgodnie z tym ograniczeniem. Alternatywnie, może przydzielać komunikaty dynamicznie, zwiększając lub zmniejszając liczbę buforów w miarę zapotrzebowania. Ograniczenie maksymalnej liczby jednocześnie wysyłanych komunikatów może wynikać z możliwości sprzętowych karty sieciowej i podsystem komunikacyjny, jako znający własności sprzętu, na którym operuje, jest w stanie najlepiej się do tego dostosować. Podsystem komunikacyjny może udostępnić użytkownikowi mechanizm zmiany parametrów pracy kanału za pomocą odpowiednich wywołań funkcji zccl_channel_special().

Sterowanie liczbą wywołań funkcji obsługi przygotowania komunikatu pozwala również w sposób niewidoczny dla użytkownika zrealizować kontrolę przepływu danych. Funkcja obsługi zostaje wywołana tylko wtedy, gdy jest wolny bufor nadawczy i odbiorca ma przygotowany bufor odbiorczy na komunikat. Sposób realizacji kontroli przepływu danych leży w gestii podsystemu komunikacyjnego. Może w tym celu albo wykorzystać kontrolę przepływu danych dostarczaną przez protokół niższego poziomu lub sprzęt, albo zaimplementować własny mechanizm kontroli.

3 Odbieranie komunikatów

W czasie tworzenia kanału użytkownik podaje adres swojej funkcji obsługi zdarzenia odebrania komunikatu. Przy wywołaniu funkcja ta otrzymuje adres bufora, do którego zapisano komunikat. Może w ten sposób przetworzyć komunikat w miejscu, bez potrzeby kopiowania. Komunikat istnieje tylko podczas wywołania funkcji obsługi zdarzenia odebrania komunikatu, więc jeśli w komunikacie są informacje, które mają zostać wykorzystane później, to trzeba je skopiować. Dlatego właśnie kanał Message nie służy do transportu dużej ilości danych -- trzeba by je skopiować w inne miejsce, co przeczy idei braku pośredniego kopiowania. Małe komunikaty kontrolne niosą tylko informację o tym, co należy zrobić, więc przeważnie nie wymagają kopiowania danych z bufora, a jeśli już jest taka potrzeba, to są to niewielkie ilości danych.

Podobnie jak przy wysyłaniu komunikatów, użytkownik nie musi troszczyć się o zarządzanie buforami odbiorczymi ani o zapewnienie kontroli przepływu danych. Zostaje poinformowany tylko o tym, co go najbardziej interesuje -- otrzymaniu danych.

4 Funkcje

 zccl_msg_request_send() -- żądanie wysłania komunikatu.

5 Zdarzenia

Funkcje obsługi zdarzeń związanych z wysyłaniem komunikatu to:

Funkcje obsługi zdarzeń związanych z odbieraniem komunikatu to:

6 Ograniczenia

Jeśli funkcje obsługi zdarzeń nie mogą zasnąć, to podsystem komunikacyjny musi zapewnić, że funkcja żądania wysłania komunikatu nie zaśnie.

Podsystem komunikacyjny może narzucić dodatkowe ograniczenia na funkcje obsługi zdarzeń, jeśli mogą być wywołane z procedury obsługi przerwania.

Maksymalny rozmiar komunikatu zależy od użytego podsystemu komunikacyjnego, jednak nie powinien być mniejszy niż 1024 bajty. Podsystem komunikacyjny może udostępnić mechanizm zmiany maksymalnego rozmiaru komunikatu przy tworzeniu kanału, bądź w czasie jego działania.


10 Kanał Post

Kanał typu Post służy do trasmisji dużych ilości danych bezpośrednio do przygotowanego przez użytkownika bufora. Jest podobny do kanału typu Post biblioteki ACL ([Calkowski], p. 4.9). Jego semantyka przekłada się w prosty sposób na operacje wystawiania buforów i wysyłania danych w VIA.

Kanał jest dwukierunkowy, zapewnia niezawodne połączenie i uporządkowanie przesyłanych danych oraz posiada szczególny mechanizm kontroli przepływu danych, opierający się na transporcie informacji kontrolnej przez użytkownika.

1 Wystawianie buforów

Dane odbierane są bezpośrednio do buforów użytkownika. W tym celu użytkownik musi taki bufor wystawić (ang. post buffer). Bufor może się składać z wielu fragmentów i opisuje się go za pomocą deskryptora pamięci (por. p. 4.5). Trzeba wcześniej stworzyć deskryptor i ustawić jego pola na odpowiednie fragmenty pamięci. Po odebraniu danych deskryptor może być użyty ponownie.

Wystawienie bufora przebiega dwuetapowo: najpierw użytkownik żąda wystawienia bufora, podając jego deskryptor i tablicę funkcji obsługi zdarzeń związanych z buforem. Kiedy bufor zostaje wystawiony, jest wywoływana funkcja obsługi zdarzenia wystawienia bufora. Następnie po odebraniu danych wywołuje się funkcję obsługi zdarzenia odebrania danych. Wystąpienie błędów na którymkolwiek z etapów przetwarzania żądania jest sygnalizowane wywołaniem odpowiedniej funkcji obsługi błędu, więc stan żądania można określić za pomocą automatu skończonego (por. rys. 4.6).

Rysunek 4.6: Diagram stanów wystawiania bufora
\resizebox*{1\textwidth}{!}{\includegraphics{rysunki//diagram-stan-wystawiania-bufora.eps}}

Podsystem komunikacyjny sterując wywoływaniem funkcji obsługi zdarzenia wystawienia bufora ogranicza liczbę jednocześnie wystawionych buforów zgodnie z ograniczeniami sprzętowymi, ograniczeniami podsystemu komunikacyjnego i ewentualnie zgodnie z podanymi przez użytkownika zaleceniami. Podsystem komunikacyjny może udostępnić użytkownikowi mechanizm ustalania ograniczenia liczby jednocześnie wystawionych buforów przy tworzeniu kanału lub w trakcie pracy kanału.

2 Wysyłanie danych

Użytkownik inicjuje wysłanie danych wywołując funkcję wysyłania danych. Podaje jej deskryptor pamięci, opisujący bufor z danymi do wysłania, oraz tablicę funkcji obsługi zdarzeń. Kiedy dane zostaną wysłane, zostanie wywołana funkcja obsługi zdarzenia wysłania danych. Jeśli wystąpi błąd, to jest wywoływana funkcja obsługi zdarzenia błędu wysyłania danych. Wystąpienie błędów na którymkolwiek z etapów przetwarzania żądania jest sygnalizowane wywołaniem odpowiedniej funkcji obsługi błędu, więc stan żądania można określić za pomocą automatu skończonego (por. rys. 4.7).

Rysunek 4.7: Diagram stanów wysyłania danych na kanale Post
\resizebox*{1\textwidth}{!}{\includegraphics{rysunki//diagram-stan-wysylania-post.eps}}

3 Zapewnienie porządku

Zapewnienie porządku odbywa się za pomocą systemu nazwanego w ZCCL mechanizmem numerów buforów (ang. buffer sequence number). Każdy bufor wystawiony przez użytkownika do odbioru danych otrzymuje kolejny numer i ten numer przekazuje się użytkownikowi w funkcji obsługi zdarzenia wystawienia bufora. Określa on w jakiej kolejności bufor zostanie wystawiony do odbioru. Trzeba następnie wykorzystać ten numer przy wysyłaniu danych. Oprogramowanie kanału zapewnia, że dane zostaną wysłane do bufora o podanym numerze.

Wymaganie zapewnienia porządku wynika z semantyki operacji przesyłania danych w VIA. Odebrane dane muszą trafić do bufora, który musi je pomieścić. Jeśli użytkownik wystawi bufory o różnej wielkości, to zmiana kolejności spowoduje, że jedna z transmisji będzie miała zbyt mały bufor, co w przypadku VIA jest traktowane jako nienaprawialny błąd i powoduje zerwanie połączenia.

Zalety użycia numerów buforów są następujące:

  1. Zrównoleglenie operacji wystawiania buforów. Użycie numerów buforów zamiast określania kolejności na podstawie kolejności wywoływania funkcji wystawiającej bufor sprawia, że użytkownik nie musi zapewniać sekcji krytycznej przy wywoływaniu funkcji wystawiającej bufor.
  2. Jednoznaczność porządku. Porządek jest jasno określony poprzez numer bufora.

4 Kontrola przepływu danych

Kontrolę przepływu danych zapewnia się w ten sposób, że użytkownik musi przy wysyłaniu podać numer bufora, który został wystawiony przez odbiorcę. Użytkownik po wystawieniu bufora musi sam dostarczyć numer bufora do nadawcy, na przykład przesyłając go w komunikacie poprzez kanał typu Message. Jeśli użytkownik korzysta z kanału zgodnie z tym schematem, to ma pewność, że bufor będzie gotowy na odebranie danych. Biblioteka nie kontroluje jednak w żaden sposób, czy naprawdę po drugiej stronie jest przygotowany bufor.

Użytkownik może, i w miarę możliwości powinien, za jednym razem wystawiać po kilka buforów i przesyłać kilka numerów buforów w jednym komunikacie.

5 Funkcje

Funkcje służące do transmisji danych poprzez kanał Post to:

6 Zdarzenia

Funkcje obsługi zdarzeń związanych z wysłaniem danych to:

Funkcje obsługi zdarzeń związanych z odbieraniem danych to:

11 Podobieństwa i różnice między ZCCL a ACL

Chociaż ZCCL jest oparte na ideach ACL, to jednak jest to zupełnie samodzielna biblioteka, zaprojektowana i zaimplementowana od zera. Jest więc między nimi wiele różnic, od drobnych, jak użycie innego języka programowania, do znaczących, jak zmiany w semantyce operacji transmisji.

1 Ogólne cechy

Najważniejszą cechą odróżniającą ZCCL od ACL jest asynchroniczne podejście do komunikacji. Większość pozostałych różnic jest skutkiem takiej zmiany podejścia.

Kolejną ważną różnicą jest możliwość korzystania z wielu podsystemów komunikacyjnych jednocześnie.

Do szczegółów implementacyjnych można zaliczyć język programowania. ZCCL jest napisany w C, ACL zaś w C++.

2 RDMA

W ZCCL nie ma w ogóle interfejsu umożliwiającego zdalny zapis za pomocą RDMA. Uznałem, że kanał Post wystarcza w znaczącej większości zastosowań, a wprowadzenie zdalnych zapisów znacznie komplikuje interfejs i implementację.

Brak operacji zdalnego zapisu w interfejsie nie wyklucza jednakże wykorzystania operacji zdalnego zapisu przez oprogramowanie kanału do transmisji danych bądź informacji kontrolnych.

W przyszłości, o ile będzie to potrzebne, możliwe jest stworzenie oddzielnego typu kanału przeznaczonego do transmisji z użyciem zdalnego zapisu.

3 Sygnalizacja zdarzeń

Sygnalizacja niektórych zdarzeń jest w ACL realizowana za pomocą generowania zdarzeń, podobnie jak w ZCCL. Jednak założenie o sekwencyjności wywoływania funkcji obsługi zdarzeń w ACL wymusza w praktyce użycie jednego wątku do generowania tych zdarzeń. ZCCL nie daje takiej gwarancji i umożliwia wywoływanie funkcji obsługi z wielu wątków lub nawet z procedur obsługi przerwań.

4 Zarządzanie pamięcią

Deskryptory pamięci używane w ZCCL są odpowiednikiem wektorów pamięci z ACL. Jednak dodanie dodatkowej rejestracji obszarów pamięci daje większe możliwości dynamicznego zarządzania rejestracją, w szczególności ułatwia implementację schowka zarejestrowanej pamięci.

5 Zarządzanie połączeniami

Podobnie jak w ACL, w ZCCL jednostkami połączenia są kanały o określonym typie. Kanały są jednak zupełnie od siebie niezależne -- w ACL były zgrupowane w połączenia o składzie określonym przy nawiązywaniu połączenia, co uniemożliwiało zmianę liczby kanałów w trakcie pracy. W ZCCL można w dynamiczny sposób zmieniać liczbę kanałów używanych do komunikacji.

Jedną z ważnych cech ACL jest wsparcie podłączenia do klastra. Jest to jednak przydatne tylko w bardzo specyficznych zastosowaniach, więc ZCCL używa standardowego modelu tworzenia połączenia punkt-do-punktu.

6 Nieblokująca transmisja danych

ZCCL pozwala zainicjować więcej transmisji niż wynosi fizyczne ograniczenie na liczbę jednoczesnych transmisji. ACL w takim wypadku powodował zablokowanie wątku. ZCCL, ze względu na swoją asynchroniczną architekturę, nie mógłby tego oczywiście zrobić.

7 Kanał Message

Kanał Message jest wzorowany na części kanału Exchange służącej do wymiany komunikatów. Wysyłanie komunikatów i danych poprzez jeden kanał komplikuje semantykę i utrudnia implementację kanału. Kanał Exchange posiada wspólne bufory służące do przesyłania danych i komunikatów. Jest to powodem na przykład takich wad jak zużycie bufora na komunikat, gdy nadejdą dane, czy tym, że liczba jednocześnie wysyłanych komunikatów i transmisji danych ma wspólne ograniczenie. O tym, że transmisja danych powinna być oddzielona od transmisji komunikatów świadczy chociażby zastrzeżenie poczynione w ACL o braku zachowania porządku pomiędzy wysyłaniem komunikatów a wysyłaniem danych. Dlatego kanał Message służy tylko i wyłącznie do transmisji komunikatów, a transmisja danych odbywa się za pomocą kanału Post.

Najpoważniejszą różnicą semantyczną między kanałem Message a kanałem Exchange jest brak zachowania porządku przesyłanych komunikatów. Powody takiej zmiany są podane w p. 4.9.

Użycie w ZCCL funkcji obsługi zdarzenia do wypełnienia treści komunikatu, zamiast używanej w ACL pary funkcji allocMsg() i sendMsg(), pozwoliło w sposób nieblokujący inicjować wysyłanie większej liczby komunikatów niż wynosi ograniczenie na liczbę jednocześnie przesyłanych komunikatów.

8 Kanał Post

Kanał Post z ZCCL różni się od kanału Post z ACL zastosowaniem numerów buforów do zapewnienia porządku. W ACL użytkownik musiał na własną rękę zapewniać porządek wywołań funkcji wysyłającej. W ZCCL nie ma takiej potrzeby, ponieważ porządek jest określony przez numer bufora. Powinno to w znaczący sposób uprościć korzystanie z kanału Post.

Krzysztof Lichota 2002-06-24