5. Podsumowanie, uzupełnienia, przykłady i uwagi




Poznaliśmy algorytmy i struktury danych realizujące koncepcję autonomicznej i wyabstrachowanej przestrzeni adresowej procesu użytkownika. Pamięć wirtualna jest, obok wieloprocesowości, największym krokiem uczynionym na polu systemów operacyjnych od czasu epoki DOS.

Jak widzielismy, jeden Region Pamięci jest przydzielony dla jednego spójnego obszaru pamięci ze spójnymi prawami dostępu i tym samym 'backing store' - 'zapleczem', czyli np. urządzeniem lub plikiem. Stąd np. załadowanie biblioteki powoduje, w ogólnym przypadku, utworzenie co najmniej dwóch Regionow - jednego dla kodu wykonywalnego tej biblioteki, i drugiego dla jej danych.


Struktura Regionów Pamięci bieżącego procesu odwzorowana jest w filesystemie proc - pisząc np.

     cat /proc/self/maps

można zobaczyć mapę Regionów Pamięci dla procesu cat. Pary liczb po lewej stronie wyznaczają zakresy odwzorowywanych adresów liniowych:

08048000-0804c000 r-xp 00000000 03:03 223031     /bin/cat
0804c000-0804d000 rw-p 00003000 03:03 223031     /bin/cat
0804d000-08051000 rwxp 00000000 00:00 0
40000000-40015000 r-xp 00000000 03:03 206397     /lib/ld-2.2.2.so
40015000-40016000 rw-p 00014000 03:03 206397     /lib/ld-2.2.2.so
40016000-40017000 rw-p 00000000 00:00 0
40017000-40018000 r--p 00000000 03:03 47636      /usr/lib/locale/en_US/LC_IDENTIFICATION
40018000-40019000 r--p 00000000 03:03 47638      /usr/lib/locale/en_US/LC_MEASUREMENT
40019000-4001a000 r--p 00000000 03:03 47641      /usr/lib/locale/en_US/LC_TELEPHONE
4001a000-4001b000 r--p 00000000 03:03 47634      /usr/lib/locale/en_US/LC_ADDRESS
4001b000-4001c000 r--p 00000000 03:03 47639      /usr/lib/locale/en_US/LC_NAME
4001c000-4001d000 r--p 00000000 03:03 47640      /usr/lib/locale/en_US/LC_PAPER
4001d000-4001e000 r--p 00000000 03:03 285751     /usr/lib/locale/en_US/LC_MESSAGES/SYS_LC_MESSAGES
4001e000-4001f000 r--p 00000000 03:03 63519      /usr/lib/locale/en_US/LC_MONETARY
4001f000-40025000 r--p 00000000 03:03 111155     /usr/lib/locale/en_US/LC_COLLATE
40025000-40146000 r-xp 00000000 03:03 206439     /lib/libc-2.2.2.so
40146000-4014b000 rw-p 00120000 03:03 206439     /lib/libc-2.2.2.so
4014b000-4014f000 rw-p 00000000 00:00 0
4014f000-40150000 r--p 00000000 03:03 47642      /usr/lib/locale/en_US/LC_TIME
40150000-40151000 r--p 00000000 03:03 111149     /usr/lib/locale/en_US/LC_NUMERIC
40151000-4016c000 r--p 00000000 03:03 301623     /usr/lib/locale/en_US/LC_CTYPE
bfffb000-c0000000 rwxp ffffc000 00:00 0

Nawet bez wgłębiania się w specyfikację pliku /proc/self/maps widać, że większość z używanych przez proces cat Regionów jest READ ONLY, gdyż zajmowane są przez dzielone biblioteki. Zresztą te same, które można zobaczyć oglądając ten sam plik /proc/self/maps komendą "F3" w programie Midnight Commander.


Dlaczego właściwie utrzymywanie liczby Regionów Pamięci procesu na niskim poziomie jest takie ważne?

Po pierwsze, każdy Region Pamięci wymaga przechowywania pewnej dodatkowej ilości danych w strukturach jądra (vm_area_struct).
Po drugie i najważniejsze, jądro, jak łatwo się domyslić, operuje na listach/drzewach Regionów Pamięci co chwila, i to nie tylko przy alokacji czy zwalnianiu obszarow pamieci. Np. efektywność całej procedury obsługi wyjątku błędu strony opiera się w dużym stopniu na efektywności wyszukiwania Regionów zawierających (sąsiednich do) dany adres liniowy (find_vma()). To może się wydawać nieco przesadzone, gdy pomyśli się o logarytmicznych drzewach AVL, nie zapominajmy jednak że sprawa dotyczy pojedyńczego odwołania do pamięci, a więc już przy np. tysiącu Regionów na jedno odwołanie do pamięci powodujące błąd stronicowania (a więc np. przy próbie zmiany wartości zmiennej odziedziczonej po rodzicu, sięgnięcia do swojej własnej zaalokowanej pamięci lub kolejnym wywołaniu rekurencyjnym) przypada 10 odwołań spędzonych na samo wyszukiwanie Regionu. To sporo.


Właśnie poruszony przed chwilą problem łączenia Regionów Pamięci jest obecnie "na topie" jeśli chodzi o rozwój i patchowanie bieżącej wersji jądra. Mianowicie nie tak dawno zostało wykryte, że nie wszystko działa do końca tak jak powinno - mianowicie Chris Wedgewood, poirytowany szybkoscią działania swojej przeglądarki Mozilla (Netscape), zauważył, że zużywała ona ponad 5000 Regionów! Co więcej, inne aplikacje (zwłaszcza GNOME) zachowywały się podobnie. Nastąpiła spora konfuzja, gdy okazało się, że jest mnóstwo sąsiadujących ze sobą Regionów 'anonymous', które mogłyby być połączone w jeden. Na dzień dzisiejszy nie wiadomo dokładnie, jakie są przyczyny takiego stanu rzeczy. Najbardziej prawdopodobna hipoteza wskazuje na biblioteczną funkcję malloc(), której algorytm przydzielania pamięci jest dosyć skomplikowany, i przewiduje m.in. alokowanie pewnych adresów, następnie zwalnianie niektórych z nich, a na koniec dokonywanie pewnych zmian w prawach dostępu do pamięci. I z tego właśnie wynika problem - przy zmianie praw dostępu do fragmentu pamięci, stanowiącego tylko część danego Regionu Pamięci, jądro musi podzielić ten region na dwa. Następnie powinno spróbować dołączyć tę "nowszą" część do sąsiedniego Regionu, sprawdzając czy prawa dostępu na to pozwalaja. Niestety, w obecnej wersji jądra nie jest to wykonywane. W rezultacie pewne ciągi alokacji powodują tzw. "eksplozję VMA", czyli szybki wzrost liczby Regionów Pamieci procesu.

Nie wiadomo czy i jak ten problem zostanie rozwiązany, jako że wielu spośród programistów tworzących Linuxa wydaje się go bagatelizować, zrzucając całą winę na bibliotekę C. Nie jest to zresztą pierwszy przypadek nieporozumień na linii jądro - biblioteka C...