Dynamiczna alokacja pamięci dla systemów czasu rzeczywistego. Kamil Deryński <kamil@derynski.net> 1. WSTĘP Krytycznym elementem podczas projektowania systemów czasu rzeczywistego jest oczywiście czas, w którym wykonywane są poszczególne zadania. Mając na uwadze to kryterium twórcy systemów operacyjnych czasu rzeczywistego zaimplementowali specjalne nie trywialne mechanizmy, dzięki, którym podstawowe założenia deterministycznego czasu wykonywania zadań są spełnione. Mowa tu oczywiście o specjalnym przeprojektowaniu głównych funkcji umożliwiających sprawne działanie każdego systemu operacyjnego, czyli szeregowanie zadań i podział czasu wykonywania. Jak widać wiele istniejących już mechanizmów trzeba było implementować na nowo tak, aby spełniały one specjalne wymogi systemów RT. Jednym z takich mechanizmów, które są powszechnie stosowane w wielu programach jest dynamiczna alokacja pamięci. Znana każdemu funkcja malloc, w wielu popularnych implementacjach nie spełnia wymogów czasowych systemów RT, a więc nie można zastosować takich implementacji w oprogramowaniu czasu rzeczywistego. Istnieją jednak algorytmy i ich implementacje, które umożliwiają dynamiczną alokację pamięci w systemach czasu rzeczywistego. 2. CZYM JZST DYNAMICZNA ALOKACJA PAMIĘCI? Dynamiczna alokacja pamięci to rezerwacja pamięci dla programu podczas jego działania. Pamięć ta pozostaje zarezerwowana do czasu uwolnienia jej przez programistę, a więc użycie odpowiedniej funkcji zwalniającej pamięć wywoływanej podczas działania programu po dynamicznej alokacji. Wszystkie zarezerwowane obszary pamięci są również zwalniane w chwili zakończenia działania procesu. Do pamięci alokowanej dynamicznie programista może dostać się jedynie przez adres pamięci (wskaźniki). Zmienne przechowywane w pamięci tego typu znajdują się na stercie (ang. heap) w przeciwieństwie np. do zmiennych lokalnych znajdujących się na stosie (ang. stack). 1
3. DLACZZGO "ZWYKŁZ" IMPLZMZNTACJZ DYNAMICZNZJ ALOKACJI NIZ NADAJĄ SIĘ DO ZASTOSOWAŃ RT? Chcąc zrozumieć powód, dla którego stosuje się inną implementacje dynamicznej alokacji pamięci dla zastosowań czasu rzeczywistego trzeba wiedzieć, co to jest złożoność obliczeniowa algorytmu. Jest to niejako ilość zasobów potrzebna do działania algorytmu. Na potrzeby systemów RT rozpatrujemy algorytmy pod względem czasu i dlatego będziemy mówić o złożoności czasowej. Złożoność czasowa jest liczbą operacji podstawowych w zależności od rozmiaru wejścia. Miara rzeczywistego czasu zegarowego jest nie praktyczna przy porównaniach algorytmów (np. różnice w taktowaniu procesora), dlatego "zliczamy" operacje podstawowe, a nie np. sekundy. Opisem zachowania złożoności obliczeniowej jest funkcja, której argumentem jest ilość danych wejściowych. Miarę tę często określa się jako asymptotyczne tempo wzrostu, które zapisuje się korzystając z notacjo O-duże w postaci f(x)=o(g(n)), gdzie g(n) - funkcja ograniczająca funkcję, f(x). Mówimy, więc, że złożoność danego algorytmu jest w najgorszym przypadku funkcją g(n), gdzie n to ilość danych. Mając wiedzę o podstawach teorii złożoności oraz znając podstawowe założenia systemów czasu rzeczywistego możemy dojść do wniosku, że algorytmy wykorzystywane w celach RT muszą mieć złożoność stałą - O(1), a więc czas ich działania nie zależy od ilości danych wejściowych. Oczywiście jest to często nie możliwe z prostej przyczyny niewykonalności niektórych zadań przy takich założeniach. Jednak w przypadku dynamicznej alokacji pamięci istnieją algorytmy o stałej złożoności, O(1), co gwarantuje nam wykonanie zadania (alokacji) w stałym deterministycznym czasie. 4. KRÓTKI PRZZGLĄD ALGORYTMÓW DYNAMICZNZJ ALOKACJI. Istnieje wiele algorytmów służących do dynamicznej alokacji pamięci. Wszystkie rozwiązania poddają się klasyfikacji, która opiera się na podstawowych zasadach ich działania. Następujący 2
podział po raz pierwszy został zaprezentowany przez Wilson a w 1995 roku. Jest to zbiór podstawowych strategii dynamicznej alokacji pamięci. Klasyfikacja strategii alokacji: - dopasowanie sekwencyjne (ang. Sequential Fits) - segregacja list wolnych obszarów (ang. Segregated Free Lists) - Buddy Systems odmiana strategii segregacji list - dopasowanie indeksowane (ang. Indexed Fits) - dopasowanie mapami bitowymi (ang. Bitmap Fits) - alokacja hybrydowa Nie będę opisywał tu zasady działania wszystkich wymienionych strategii. Przy okazji opisu w punkcie 6-stym alokator TLSF zostaną opisane dwie strategie segregacji list i dopasowania mapami bitowymi. Już teraz widać, że alokator ten wykorzystuje alokację hybrydową (łączenie kilku koncepcji) podobnie jak najpopularniejszy alokator w systemach ogólnego zastosowania DLmalloc, który wykorzystuje dopasowanie sekwencyjne i elementy heurystyczne. 5. TLSF ALOKATOR PAMIĘCI DLA SYSTZMÓW RT. Alokator pamięci TLSF (Two-Level Segregate Fit) został pierwotnie stworzony przez grupę badaczy z Hiszpani (M. Masmano, I. Ripoll, A. Crespo) głównie do zastosowań w systemach czasu rzeczywistego. TLSF posiada wiele zalet, które czynią tę metodę dynamicznej alokacji pamięci szczególnie opłacalną. Główne zalety to: - czas alokacji/dealokacji jest znany i nie zależy od danych programu (stały koszt O(1)) - mimo spełnionego założenia stałego kosztu, alokacja jest nadzwyczaj szybka - efektywne korzystanie z pamięci (niski procent fragmentacji) - dualna licencja (GPL/LGPL) TLSF z racji swoich licznych zalet znajduje zastosowania nie tylko w systemach czasu rzeczywistego. Wszędzie gdzie ilość danych przetwarzanych przez program nie jest przewidywalna alokatory o stałej złożoności i małej fragmentacji są pożądane. Niektóre z zastosowań TLSF to: - aplikacje multimedialne (np. przetwarzanie wideo) - oprogramowanie urządzeń sieciowych (np. routery i switche) - zaawansowane gry komputerowe 3
TLSF używany jest w wielu kernelach czasu rzeczywistego: - RTLinux-GPL 1 - Xenomai - RTAI Znalazł też zastosowanie w niektórych systemach ogólnego przeznaczenia m.in.: - CRUX 2 (dystrybucja Linuksa) - Amiga OS Warto powiedzieć kilka słów o przeznaczeniu TLSF w systemie RTLinux-GPL. Dynamiczny alokator jest niezbędny np. do działania implementacji lekkiego stosu TCP/IP IwIP dla RTLinuksa. Można znaleźć źródła TLSFa (1.3.1) w repozytorium cvs projektu 3. Alokator kompiluje się do modułu rt_malloc. Do kompilacji i działania modułu wymagany jest jednak patch na jądro RTLinuksa w postaci łaty BIGPHYSARZA 4. 6. JAK DZIAŁA TLSF? TLSF opiera swój mechanizm działania na dwóch strategiach: segregacji list wolnego obszaru oraz na mapach bitowych. Segregacja list wolnego obszaru opiera się na zbiorze list, które przechowują bloki wolnej pamięci o z góry ustalonym rozmiarze lub o ustalonym zakresie rozmiarów. Gdy blok pamięci jest zwalniany dołączany jest do listy, która reprezentuje dany rozmiar. Dopasowanie mapami bitowymi znajduje blok wolnej pamięci natychmiast nie angażując w to czasochłonnych metod wyszukiwania. Dzięki temu złożoność może być stała O(1). W praktyce mapa bitowa realizowana jest jako szereg list, w których węzły są wskaźnikami do list wolnego obszaru pamięci. Jeśli wskaźnik nie wskazuje na nic (NULL) oznacza to, że nie ma wolnych obszarów pamięci odpowiednich zakresowi reprezentowanemu przez ten wskaźnik. Był to ogólny opis struktur danych przeznaczonych dla poszczególnych strategii alokacji. TLSF jak już wiadomo jest hybrydową metodą alokacji, czas, więc przedstawić strukturę, na jakiej opiera się działanie tego dynamicznego alokatora. Najlepiej strukturę tą opisują sami twórcy w artykule Implementation of a constant-time dynamic storage allocator 5 z 2007 roku. Korzystając z zawartego tam schematu struktury dowiemy się, jaka jest podstawa TLSFa. 1 http://www.rtlinux-gpl.org 2 http://crux.nu/ 3 http://www.rtlinux-gpl.org/cgi-bin/viewcvs.cgi/rtlinux-3.2-rc1/ 4 http://wwwcs.uni-paderborn.de/cs/heiss/linux/bigphysarea.html 5 http://rtportal.upv.es/rtmalloc/files/tlsf_paper_spe_2007.pdf 4
Ten bardzo obrazowy schemat odkrywa podstawę, na jakiej opiera się TLSF. Mowa tu oczywiście o strukturze danych, która jak widać podzielona jest na trzy części, a w praktyce na dwie części wykorzystywane bezpośrednio przez algorytm (stąd nazwa TLSF - Two-Level Segregate Fit). Pierwsza część odpowiada strategii segregacji list wolnego obszaru, a druga część odpowiada koncepcji map bitowych. Pierwsza część dokonuje wstępnej decyzji o wielkościach wolnych obszarów dostępnych do alokacji (szare pola sygnalizują dostępny obszar w tym zakresie zapalony bit). Dzieje się to za pomocą listy reprezentującej kolejne potęgi dwójki. Przykładowo szare pole (nie NULL) z wartością 64 mówi o dostępności jakiegoś obszaru z zakresu od 64 do 127. Pole to jest wskaźnikiem do listy zawierającego wskaźniki do list obszarów wolnej pamięci. Tym razem pola w liście drugiego poziomu reprezentują bardziej precyzyjnie dostępne zakresy. Oczywiście sama struktura niczego nie gwarantuje. Potrzebny jest jeszcze algorytm postępowania przy danej operacji (alokacja/dealokacja), aby struktura ta spełniała swoje zadanie. Szczegółowo algorytmy te wraz z opisem implementacji dostępne są w bogatej dokumentacji TLSFa 6. 6. PRZYKŁADOWZ CZASY ALOKACJI PAMIĘCI Wykorzystując przykładowe programy testujące udostępnione wraz z implementacją TLSFa można stworzyć różnego rodzaju porównania zwykłej implementacji alokacji pamięci oraz rozwiązania dostarczanego przez TLSF. 6 http://rtportal.upv.es/rtmalloc/node/7 5
Wykresy poniżej dotyczą zwykłej alokacji pamięci, udostępnianej przez funkcję tlsf_malloc. void *tlsf_malloc(size_t size); Punkty na wykresach reprezentują maksymalny czas alokacji występujący podczas alokacji NUM_MALLOC razy losowego obszaru pamięci definiowanego w następujący sposób: t = (size_t) (1 + drand48 () * SIZE_MALLOC); NUM_MALLOC i SIZZ_MALLOC podczas testów zostały zdefiniowane na 10 000. Test był przeprowadzony na maszynie wirtualnej z zainstalowanym systemem Debian GNU/Linux w wersji 4.0. Jako kompilator został użyty gcc w wersji 4.1. Testy są bardzo niedokładne, ponieważ na ich wyniki ma wpływ bardzo wiele czynników jak obsługa w czasie trwania alokacji przerwań maszyny wirtualnej jak i maszyny rzeczywistej, przełączanie kontekstów między procesami itd. Jednak w tak bardzo zajętym środowisku można dostrzec pewne cechy alokatorów. Na poniższym wykresie widać jak zachowują się czasy alokacji dla systemowego alokatora pamięci. W środkowej części widać jak relatywnie duże mogą być różnice w zależności od ilości alokowanej pamięci (jeden punkt odpowiada innej wielkości żądanej pamięci do alokacji.) 0,04 0,035 0,03 0,025 0,02 0,015 0,01 0,005 0 6
0,04 0,035 0,03 0,025 0,02 0,015 0,01 0,005 0 Na powyższym wykresie natomiast prezentowane są czasy alokacji przy użyciu TLSFa. Widać, że czasy są bardziej unormowane i bardziej przewidywalne niż w poprzednim przypadku. Chociaż oczywiście nie dało się uniknąć czasów znacznie odbiegających od normy, które za pewne są wynikiem środowiska, w jakim alokator musi wykonywać swoje zadanie. 6. PODSUMOWANIZ Alokator TLSF jest interesującym mechanizmem szczególnie dla zastosowań w systemach RT z racji na swoją stałą złożoność (jest przewidywalny), wysoką szybkość działania (porównywalną do popularnych alokatorów) oraz bardzo mały procent fragmentacji pamięci (systemy RT z reguły muszą działać przez długi czas bez spadku wydajności). Do wszystkich zalet TLSFa należy dodać także fakt, że projekt jest prężnie rozwijany od kilku lat. Czego dowodem są aktualne informacje na stronie projektu, bardzo aktualna dokumentacja i publikacje naukowe. Bibliografia: 1. A constant-time dynamic storage allocator for real-time systems. Miguel Masmano, Ismael Ripoll, et al. Real-Time Systems. [2008] 2. Implementation of a constant-time dynamic storage allocator. Miguel Masmano, Ismael Ripoll, et al. Software: Practice and Experience. [2007] 3. http://wikipedia.org 4. http://rtportal.upv.es/rtmalloc/ 7