Subsections


5 Przykładowy podsystem komunikacyjny

1 Wstęp

W celu przetestowania koncepcji ZCCL powstał podsystem komunikacyjny SockZCCL. W podsystemie tym do transportu danych użyto biblioteki gniazd.

Użycie gniazd wyklucza oczywiście eliminację pośredniego kopiowania, ponieważ gniazda nie zapewniają takiej semantyki. W celu pełnego wykorzystania możliwości ZCCL należałoby zaimplementować podsystem komunikacyjny oparty na VIPL lub innej bibliotece zapewniającej brak pośredniego kopiowania. Jednak użycie VIPL wymaga posiadania odpowiednich kart sieciowych, które są drogie i w związku z tym nie są powszechnie dostępne.

Dlatego zdecydowałem się użyć powszechnie dostępnych gniazd. W oparciu o działający na każdym komputerze podsystem SockZCCL można tworzyć aplikacje korzystające z ZCCL, które powinny potem wymagać tylko kosmetycznych poprawek przy przeniesieniu do innego podsystemu komunikacyjnego, na przykład właśnie opartego na VIPL.

Podsystem SockZCCL jest pomyślany głównie jako narzędzie do testowania ZCCL, dlatego przy jego projektowaniu nie brano pod uwagę wydajności użytych konstrukcji, lecz łatwość implementacji i możliwość modyfikacji, niezbędnych w procesie precyzowania specyfikacji ZCCL.

2 Sposób implementacji

W trybie użytkownika podystem SockZCCL jest zwykłą biblioteką i używa standardowych uniksowych wywołań funkcji z biblioteki gniazd, więc powinien bez modyfikacji działać w każdym systemie uniksopodobnym. Podsystem był testowany w systemie Linux (dystrybucja RedHat 7.1).

W trybie jądra SockZCCL działa jako moduł jądra systemu Linux. Do jego działania niezbędne jest nałożenie łaty i przekompilowanie jądra, mające na celu wyeksportowanie z jądra niezbędnych symboli. Moduł SockZCCL był testowany z jądrem 2.4.2, ale powinien bez większych przeróbek działać ze wszystkimi jądrami z serii 2.4.x.

3 Zarządzanie pamięcią

Ponieważ gniazda nie wymagają rejestracji pamięci używanej do transmisji danych, zarządzanie pamięcią w SockZCCL jest ograniczone do implementacji podstawowych funkcji.

SockZCCL przechowuje informacje o zarejestrowanych obszarach pamięci, ale używa ich tylko do sprawdzenia, czy pola deskryptora pamięci są wypełniane prawidłowo. Z każdym obszarem związany jest licznik odwołań do tego obszaru, który umożliwia stwierdzenie, kiedy można go zwolnić.

Deskryptory pamięci są zaimplementowane jako prosta struktura zawierająca adresy i długości fragmentów bufora oraz uchwyty zarejestrowanych obszarów pamięci. Przydzielanie i zwalnianie deskryptorów odbywa się w sposób dynamiczny.

4 Transmisja danych

1 Wybór rodzaju gniazd

Przyjąłem założenie, że do komunikacji używana będzie sieć oparta na protokole TCP/IP. Wybór sieci opartych na TCP/IP jako bazy dla implementacji, podobnie jak wybór gniazd jako warstwy transportowej, podyktowany był powszechną dostępnością tego typu sieci.

Ze względu na charakter ZCCL najbardziej odpowiednie do przesyłania danych byłyby gniazda typu SOCK_SEQPACKET, ale w przypadku sieci opartych na TCP/IP nie są one dostępne.

W SockZCCL używane są gniazda typu SOCK_STREAM, oferujące niezawodne połączenia strumieniowe. Gniazda typu SOCK_STREAM zapewniają niezawodność transmisji, zachowanie porządku i kontrolę przepływu danych, co uprościło implementację. Ponieważ w gniazdach tego typu dane są przesyłane strumieniowo, więc dodaje się do nich informacje kontrolne pozwalające zidentyfikować granice pakietów.

2 Wysyłanie i odbieranie danych

Do obsługi jednego kanału używa się jednego gniazda i dwóch wątków -- wysyłającego i odbierającego.

1 Wątek wysyłający

Wątek wysyłający zajmuje się wysyłaniem danych. Żądania wysłania danych są kolejkowane i przetwarzane asynchronicznie bez potrzeby blokowania wątku zlecającego transmisję.

Wątek ten zajmuje się również obsługą zdarzeń związanych z wysyłaniem danych. W przypadku kanału Message tuż przed wywołaniem funkcji send() wywołuje funkcję obsługi zdarzenia przygotowania komunikatu, zaś po powrocie z tej funkcji -- funkcję obsługi zdarzenia zakończenia transmisji. Dla kanału Post nie ma potrzeby generowania zdarzenia przed wysłaniem danych, w związku z czym wywoływana jest tylko funkcja obsługi zdarzenia zakończenia transmisji danych po powrocie z funkcji send().

2 Wątek odbierający

Wątek odbierający zajmuje się odbieraniem danych z sieci i obsługą zdarzeń związanych z odbieraniem danych. W przypadku kanału Post dodatkowo zajmuje się wystawianiem buforów odbiorczych i generowaniem zdarzeń związanych z tą czynnością.

3 Budzenie wątków oczekujących na transmisję

Do obsługi gniazd w SockZCCL stworzono specjalną bibliotekę sockzccl_socket. Głównym powodem powstania tej biblioteki były problemy z zaimplementowaniem skutecznego i poprawnego sposobu budzenia wielu wątków oczekujących na transmisję na gnieździe. Po wielu próbach przyjęto rozwiązanie polegające na stworzeniu dodatkowego lokalnego gniazda, które służy tylko i wyłącznie do przerwania spania wątków czekających na dane. Każda operacja odbierania i wysyłania danych jest tak naprawdę realizowana przez wywołanie funkcji poll() na gnieździe dodatkowym i głównym. Obudzenie procesu następuje przez zapisanie danych do gniazda dodatkowego. Takie rozwiązanie wiąże się z dodatkowym kosztem spowodowanym użyciem funkcji poll(), ale jest jednakowo skuteczne zarówno w trybie jądra, jak i w trybie użytkownika.

5 Praca w trybie jądra

Przystosowanie SockZCCL do pracy w trybie jądra było zadaniem dość łatwym dzięki stworzeniu pośredniej warstwy bibliotek, udostępniających taki sam interfejs w trybie jądra, jak i w trybie użytkownika.

Jedną z tych bibliotek jest biblioteka sockwrap, udostępniająca operacje na gniazdach, zarówno podstawowe, jak tworzenie, wysyłanie czy odbieranie, jak również bardziej skomplikowane, jak odpowiednik funkcji poll(). W trybie użytkownika wykorzystuje się zwykłe funkcje interfejsu gniazd, natomiast w trybie jądra wywołuje się bezpośrednio odpowiednie funkcje jądra.

Inną biblioteką, będącą częścią ZCCL i również używaną przez ZCCL, jest zccl_lib. Biblioteka ta udostępnia interfejs do różnych podstawowych funkcji, takich jak alokacja pamięci, tworzenie wątków czy operacje na semaforach. W przestrzeni użytkownika do synchronizacji używana jest biblioteka pthreads, a w jądrze dostępne tam standardowe mechanizmy synchronizacji.

Stworzenie takiej warstwy pośredniej ułatwia przeniesienie SockZCCL i innych części ZCCL do innego systemu operacyjnego, ponieważ w nowym systemie wystarczy zaimplementować te właśnie biblioteki.

Krzysztof Lichota 2002-06-24