Do spisu treści tematu 4

Wymiana stron z pamięci na dysk. Urządzenia wymiany

Spis treści


Wprowadzenie

W sytuacji, gdy w systemie zaczyna brakować pamięci głównej, np. dla utworzenia nowego lub dla powiększenia rozmiarów już istniejącego procesu, zachodzi potrzeba zwolnienia części pamięci. W takim wypadku jądro próbuje znaleźć w pamięci stronę zawierającą chwilowo niepotrzebne dane, po czym usuwa ją z pamięci, w razie potrzeby tworząc jej kopię w pamięci pomocniczej dla przyszłego wykorzystania (jest to nazywane wymianą, wymiataniem, lub swappingiem). Jądro zawsze próbuje zwalniać pojedyncze ramki. Jeżeli istnieje potrzeba zwolnienia większej porcji pamięci, odpowiedni algorytm jest wykonywany wielokrotnie.

W tym dokumencie są opisane metody, jakie jądro Linuxa wykorzystuje do zwalniania pamięci, tj. algorytm wyboru ramki do zwolnienia i metody zwalniania pojedynczych ramek, w tym obsługa logicznych urządzeń wymiany. 


Mechanizm postarzania stron

Przy zwalnianiu pamięci jest wykorzystywana strategia "najrzadziej używane najpierw" (Least Frequently Used), która przyjmuje założenie, że rzadko używane strony pamięci nie będą używane w najbliższej przyszłości. W związku z tym, każdej ramce pamięci głównej jest przypisana liczba zwana jej wiekiem, (a w zasadzie wiekiem strony w niej zawartej) pamiętana w tablicy ramek (Słowo wiek jest tu trochę mylące, gdyż im większa liczba, tym strona młodsza). Ogólnie rzecz biorąc, każde odwołanie do strony przez proces powoduje jej "odmłodzenie", zaś "postarzanie" stron jest wykonywane okresowo przez demona kswapd. Usuwane z pamięci głównej są tylko odpowiednio stare strony.

Domyślnie, kiedy do ramki jest zapisywana nowa strona, jej początkowy wiek jest ustalany na 3. Przy każdym odwołaniu do strony, jej wiek zwiększa się o 3, zaś demon kswapd okresowo (przy każdej nieudanej próbie usunięcia z pamięci) zmniejsza wiek nieużywanych stron o 1. Strona może być usunięta z pamięci, gdy jej wiek spada do 0.

Ta metoda, zwana liniowym postarzaniem, jest nietypowa, gdyż w większości systemów stosuje się postarzanie wykładnicze. 


Struktura pamięci wirtualnej procesu

Aby zrozumieć algorytm wybierania strony do usunięcia z pamięci, należy poznać sposób przechowywania informacji o (wirtualnej) pamięci przydzielonej pojedynczemu procesowi.

Każda pozycja tablicy procesów posiada pole mm typu mm_struct, zawierające wskaźniki do struktury danych opisującej przestrzeń adresową procesu. Ta struktura to zbiór struktur typu vm_area_struct, z których każda opisuje spójny obszar pamięci wirtualnej przydzielonej procesowi. Dla każdego obszaru pamiętany jest m. in. jego (wirtualny) adres początkowy i końcowy, rodzaj (np. dzielony) i prawa dostępu.

Na zbiór tych obszarów nałożone są 3 struktury:

  1. lista jednokierunkowa, posortowana po adresach początkowych,
  2. drzewo AVL, również utworzone ze względu na adresy początkowe,
  3. dwukierunkowa lista cykliczna, nieposortowana, mogąca łączyć obszary przynależne różnym procesom.
Przykładową postać takiej struktury dla jednego procesu przedstawia rysunek.
 

Pierwsze dwie nałożone struktury służą do przeszukiwania pamięci wirtualnej procesu. Lista jednokierunkowa służy do liniowego przeszukiwania pamięci w celu przejrzenia kolejnych stron przydzielonych procesowi. Drzewo AVL jest przeznaczone do szybkiego wyszukiwania obszaru pamięci zawierającego podany adres. Ta operacja jest wykonywana na tyle często (np. przy każdym błędzie braku strony), że trzeba było tu zastosować tak skomplikowaną strukturę danych.

Trzecia nałożona struktura, dwukierunkowa lista cykliczna, jest wykorzystywana tylko przy obszarach pamięci dzielonej i łączy obszary należące do przestrzeni adresowych różnych procesów. Służy ona do uzyskiwania dostępu do stron pamięci dzielonej.

Można się zastanawiać, czy rzeczywiście użycie takiej algorytmicznej "armaty" jaką są drzewa AVL bylo tu niezbędne. Nowy obszar pamięci wirtualnej procesu jest tworzony przy każdym powiększeniu się procesu (np. w wyniku wywołania funkcji systemowej brk), a także przy mapowaniu pliku dyskowego do pamięci wirtualnej. Autorzy tego fragmentu jądra piszą, że zazwyczaj liczba takich obszarów dla jednego procesu oscyluje w granicach 6, choć w pewnych sytuacjach może osiagnąć 3000.

Każdy obszar pamięci wirtualnej może mieć przypisany zbiór operacji (pamiętany w polu vm_ops) właściwych dla stron tego obszaru przy zapisywaniu ich na dysk, odczytywaniu, obsłudze błędów strony itp. Przy wykonywaniu takich operacji najpierw sprawdzane jest, czy odpowiednia funkcja jest zdefiniowana, a jeżeli tak, to jest ona wykonywana. Jeżeli nie, wykonywana jest operacja domyślna. W praktyce, specjalne operacje są zdefiniowane dla stron pamięci dzielonej i dla obrazów plikow dyskowych. 


Demon kswapd

Liczba wolnych ramek w systemie musi być stale utrzymywana powyżej pewnego poziomu. Inaczej mogłoby dojść do sytuacji, w której wszystkie ramki pamięci głównej i cała pamięć pomocnicza są zajęte, a wszystkie gotowe do wykonania procesy znajdują się całkowicie w pamięci pomocniczej (blokada). Ten poziom jest określany przy pomocy zmiennej min_free_pages (ustalanej standardowo na 48).

W pierwszych wersjach Linuxa, zwalnianie pamięci głównej następowało dopiero wtedy, gdy liczba wolnych ramek spadała poniżej tego poziomu i było wykonywane bezpośrednio przez jądro, przy czym jądro było zajęte wyłącznie tą operacją aż do chwili, gdy się ona powiodła. To powodowało przestoje w pracy systemu. Z tego powodu w nowszych wersjach jądra wprowadzono demona kswapd, który jest wątkiem jądra i pracuje przez cały czas działania systemu. Jest on odpowiedzialny za to, aby procedura zwalniania ramki "za wszelką cenę" była wykonywana jak najrzadziej.

Kswapd ma za zadanie utrzymywać liczbę wolnych ramek w pamięci powyżej pewnego poziomu, opisanego zmiennymi free_pages_low (standardowo 72) i free_pages_high (96). Jeżeli liczba wolnych ramek nie spada poniżej free_pages_high, demon pozostaje uśpiony. Jeśli jednak jest inaczej, demon budzi się i co pewien czas (standardowo 4 razy na sekundę) próbuje zwolnić pewną liczbę ramek pamięci głównej (standardowo 4). Jeśli liczba wolnych ramek spada poniżej free_pages_low, demon zaczyna budzić się 2 razy częściej niż zwykle.

Zazwyczaj kswapd dopuszcza niepowodzenie operacji zwolnienia ramki. Jeśli po przejrzeniu części zasobów stwierdzi, że nie udało się nic zwolnić, przerywa poszukiwania i czeka na następne przebudzenie. Jednak wraz ze zmniejszaniem się ilości wolnej pamięci głównej przeszukiwanie obejmuje coraz większą część zasobów i może trwać coraz dłużej. Jeżeli liczba wolnych ramek spadnie poniżej min_free_pages, demon zwalnia ramki aż do chwili, gdy wzrośnie ona z powrotem do tego poziomu.

Za każda próbą zwolnienia ramki pamięci demon wykonuje następujący algorytm. 


Algorytm wyboru strony do usunięcia

Ogólnie rzecz biorąc, w algorytmie wyboru strony do usunięcia z pamięci wykorzystuje się nastepującą ideę przeszukiwania: jeżeli chcemy przeszukać pewien zbiór obiektów (np. procesów czy obszarów pamięci), to przeszukujemy go po kolei cyklicznie, ale rozpoczynając od tego miejsca, w którym ostatnio zakończylismy przeszukiwanie. To zapewnia sprawiedliwe traktowanie obiektów, w odróżnieniu np. od metody, w której za każdym razem próbuje się usuwać z pamięci procesy poczynając od początku tablicy procesów.

Poniżej opisany proces przeszukiwania zasobów systemu jest wielostopniowy: najpierw wybieramy metodę zwalniania pamięci, później proces, którego stronę usuniemy, następnie obszar jego pamięci wirtualnej, później stronę. Jeżeli podczas przeszukiwania jakiegoś zasobu (np. procesu) algorytm stwierdzi, że nie udało się nic zwolnić, cofa się o krok i kontynuuje przeszukiwanie, próbując przejrzeć następny zasób (np., w tym wypadku, proces).

Są 3 metody zwalniania ramek pamięci głównej: przez usuwanie prywatnych stron procesów (funkcja swap_out), stron pamięci dzielonej (shm_swap) i stron będących obrazami plików dyskowych (shrink_mmap). Te metody są wykonywane cyklicznie, przy czym jeżeli użycie którejś z nich zaowocowało zwolnieniem ramki, za kolejną próbą będzie ona wykonana ponownie.

Usuwanie stron SHM jest opisane w rozdziale o pamięci dzielonej. Usuwanie obrazów plików dyskowych jest opisane w rozdziale o stronicowaniu plików. Teraz opiszę usuwanie prywatnych stron procesów.

W tej metodzie, najpierw przeszukiwana jest tablica procesów, w celu znalezienia procesu posiadającego ustawiony atrybut swappable i zajmującego jakieś ramki w pamięci głównej (pole mm->rss>0). Przeszukiwanie rozpoczyna się od miejsca, w którym skończyło się poprzednie. Przeszukiwana jest większa lub mniejsza część tablicy procesów, w zależności od priorytetu, jaki został przydzielony całej operacji. Ten priorytet wzrasta, gdy poprzednia próba zwolnienia ramki zakończyła się niepowodzeniem i zmniejsza się w przeciwnym wypadku. Jest on też zwiększany, gdy liczba wolnych ramek w systemie spada poniżej wartości min_free_pages.

Po znalezieniu odpowiedniego procesu, ustalana jest liczba ramek, jakie będą mu odbierane (standardowo jest to 1/32 zajmowanych ramek, ale nie mniej niż 4). Za jednym razem będzie usuwana tylko jedna strona, ale ustalona wartość będzie zapamiętana i wykorzystana przy przyszłych próbach zwalniania pamięci.

Następnie, przeszukiwana jest pamięć wirtualna wybranego procesu. I znowu, przeszukiwanie rozpoczyna się od adresu, na którym zakończylo się poprzednie przeszukiwanie dla tego procesu. Przeszukiwane są kolejne obszary przydzielonej procesowi pamięci (pierwszy obszar do przeszukania jest znajdowany przy użyciu drzewa AVL obszarów, kolejne - liniowo przy pomocy listy jednokierunkowej).

Dla każdego obszaru, o ile nie jest on zablokowany lub dzielony, przeszukujemy go "po stronie wirtualnej". Dla każdej strony wirtualnej odnajdujemy jej fizyczny adres przy pomocy trzypoziomowych (na i386 - dwupoziomowych) katalogów i tablic stron (p. stronicowanie).

Po odnalezieniu wybranej strony w tablicach stron procesu, próbujemy ją usunąć z pamięci (p. Usuwanie strony z pamięci).

Jeżeli usunięcie strony się nie powiodło (np. była ona zbyt młoda), cofamy się o krok i szukamy dalej. Podobnie na wszystkich szczeblach tego algorytmu: jeśli nie udało się np. zwolnić żadnej strony z obszaru pamięci wirtualnej, szukamy w następnym; podobnie z procesami itd. 


Usuwanie pojedynczej strony z pamięci

Po ustaleniu strony (pozycji w tablicy stron procesu), którą będziemy próbowali usunąć z pamięci, sprawdzamy, czy zajmuje ona ramkę w pamięci fizycznej. Jeśli tak, ustalamy numer ramki, ktorą ona zajmuje (jest on zapisany w odpowiednich bitach pozycji tablicy stron).

Następnie podejmujemy decyzję, co zrobić ze stroną, na podstawie nastepujących informacji:

Sprawdzane są kolejno następujące przypadki:
  1. Strona ma ustawiony bit ACCESSED, co oznacza, że była niedawno używana. Czyścimy ten bit, odmładzamy stronę i zgłaszamy niepowodzenie próby zwolnienia ramki.
  2. Strona ma ustawiony bit DIRTY oraz ma kopię na dysku (pozycję w tablicy swap_cache). Odmładzamy stronę i kasujemy kopię dyskową, bo jest już nieaktualna. Strona byla zmodyfikowana niedawno, skoro nieaktualna kopia nie była jeszcze skasowana. Nie kasujemy bitu DIRTY. Zwracamy niepowodzenie.
  3. Strona jest zbyt młoda, aby ją zwolnić. Postarzamy ją i zwracamy niepowodzenie.
  4. Strona ma ustawiony bit DIRTY, nie ma kopii dyskowej, jest dostatecznie stara. Pobieramy wolną ramkę pamięci pomocniczej z urządzenia wymiany, zapisujemy na niej kopię strony przy pomocy funkcji rw_swap_page, uaktualniamy tablicę stron procesu oraz sprzętowy bufor translacji (TLB, Translation Lookaside Buffer). W tablicy stron procesu wpisujemy dane o miejscu w pamięci pomocniczej, gdzie znajdzie się strona, oraz czyścimy bit PRESENT tej strony. Zwracamy sukces, tzn. zwalniamy ramkę.
  5. Strona jest odpowiednio stara i nie ma ustawionego bitu DIRTY oraz ma kopię dyskową. Zwalniamy stronę, a w pozycji tablicy stron procesu wpisujemy dane jak wyżej. Zwracamy sukces.
  6. Strona jest odpowiednio stara, nie ma ustawionego bitu DIRTY i nie ma kopii dyskowej. Oznacza to, że jest to świeżo przydzielona procesowi strona, której nie zdążył on jeszcze zmodyfikować, lub że jest to strona będąca obrazem pliku dyskowego. Ramkę zwalnia się przy użyciu funkcji page_unuse, obsługującej w zasadzie ten drugi przypadek, ale działającej poprawnie i w pierwszym (p. stronicowanie plików). Różnica między tym przypadkiem a poprzednim polega na tym, ze teraz nie przeprowadzamy żadnej aktualizacji tablicy stron procesu poza skasowaniem bitu PRESENT: strona jest po prostu "gubiona".

  7. Zwracamy sukces. Uwaga: do usuwania stron będących obrazami plików dyskowych służy w zasadzie inny algorytm (p. algorytm wyboru strony do usunięcia). Usuwanie takich stron w opisywanym algorytmie następuje niejako "przy okazji".

Urządzenia wymiany

Zapis i odczyt stron do/z pamięci pomocniczej odbywa się za pośrednictwem spójnych na dysku urządzeń blokowych. Informacje o stanie dostępnych urządzeń moduł zarządzający pamięcią przechowuje w tablicy tzw. logicznych urządzeń wymiany (jedno urządzenie to struktura swap_info_struct). Maksymalna liczba urządzeń wymiany w systemie to (domyślnie) 8.

Dla każdego urządzenia wymiany pamięta się m.in. i-węzeł odpowiadającego urządzenia blokowego oraz mapę bajtową tego urządzenia. W jednym bajcie mapy pamięta się licznik odwołań do odpowiedniej ramki urządzenia. Gdy ten licznik spada do zera, można zwolnić ramkę pamięci pomocniczej do dalszego wykorzystania.

Przy przydzielaniu ramek urządzenia, system stara się przydzielać je pakietami. Pakiety nie muszą być spójne na dysku, ale kolejne ramki w pakiecie powinny być na dysku uporządkowane rosnąco. Dopiero po przydzieleniu całego pakietu ramek poszukiwanie wolnych ramek urządzenia rozpoczyna się od początku urządzenia blokowego. To ma na celu zmniejszenie średniego czasu dostępu (dyskowego) do ramki pamięci pomocniczej. (Domyślna wielkość pakietu to 32).

Logiczne urządzenia wymiany są powiązane w listę priorytetową. Upraszczając, przy poszukiwaniu wolnej ramki pamięci pomocniczej najpierw są przeszukiwane urządzenia o najniższym priorytecie.

Przy kopiowaniu strony z pamięci głównej do pomocniczej, najpierw znajdowana jest wolna ramka na ktorymś urządzeniu wymiany, a nastepnie przy pomocy funkcji rw_swap_page strona jest kopiowana na tę ramkę. 


Bibliografia

  1. Dave Rusling: The Linux Kernel rozdział 3 - przystępny opis działania mechanizmów zarządzania pamięcią systemu Linux.
  2. L. Banachowski, K. Diks, W. Rytter: Algorytmy i struktury danych - opis działania drzew AVL
  3. Pliki źródłowe jądra Linuxa (wersja 2.0.32):

Autor: Bartosz Klin