Valid HTML 4.01!

Zarządzanie pamięcią

Spis treści


Wprowadzenie

Wykonywać można jedynie program umieszczony w pamięci głównej (pamięci operacyjnej)

Wiązanie instrukcji i danych z adresami w pamięci może się odbywać w czasie:

Dynamiczne ładowanie - program ładuje się do pamięci dopiero po wywołaniu.

Dynamiczne łączenie - łączenie opóźnione do czasu wykonania; zazwyczaj dotyczy bibliotek systemowych.

Nakładki - w pamięci przechowuje się tylko te części przestrzeni adresowej, które są niezbędne w danej chwili; umożliwia wykonywanie programów większych od przydzielonego im obszaru pamięci; implementowane przez użytkownika.

Logiczna i fizyczna przestrzeń adresowa:

Jednostka zarządzająca pamięcią (ang. Memory Management Unit, MMU): urządzenie sprzętowe, które odwzorowuje adresy wirtualne na fizyczne.

Wymiana (ang. swapping):


Techniki (niewirtualne) zarządzania pamięcią

Alokacja ciągła

Przydział pojedynczego obszaru

Przydział wielu obszarów

W systemach wieloprogramowych w pamięci równocześnie przebywa wiele programów, każdy we własnym obszarze.

Metoda stref statycznych: pamięć jest na stałe podzielona na obszary o ustalonej wielkości (np. IBM OS/360 MFT).

Metoda stref dynamicznych: liczba i wielkość stref zmienia się dynamicznie (np. IBM OS/360 MVT).

Dziura: wolny obszar pamięci; dziury różnych rozmiarów są rozrzucone po pamięci.

Strategia wyboru wolnego obszaru:

Fragmentacja zewnętrzna: suma wolnych obszarów w pamięci wystarcza na spełnienie żądania, lecz nie stanowi spójnego obszaru.

Fragmentacja wewnętrzna: przydzielona pamięć może być nieco większa niż żądana. Różnica między tymi wielkościami znajduje się wewnątrz przydzielonego obszaru, lecz nie jest wykorzystywana.

Redukowanie fragmentacji zewnętrznej przez defragmentację: umieszczenie całej wolnej pamięci w jednym bloku; możliwe tylko w przypadku, gdy adresy są wiązane w czasie wykonania; różne strategie defragmentacji; defragmentacja a operacje wejścia-wyjścia.


Stronicowanie

Sprzęt stronicujący

Rysunek: Sprzęt stronicujący (źródło: Silberschatz, Galvin, Podstawy systemów operacyjnych)

Implementacja tablicy stron:

Odwrócona tablica stron

Rysunek:Odwrócona tablica stron (źródło: Silberschatz, Galvin, Podstawy systemów operacyjnych)

Stronicowanie umożliwia dzielenie wspólnego kodu (taki kod musi być wielowejściowy, ang. reentrant, czyli nie może modyfikować sam siebie); edytory, kompilatory itp.

Do ochrony pamięci służą bity ochrony przypisane każdej ramce i zwykle umieszczone w tablicy stron.


Segmentacja

Schemat zarządzania pamięcią zgodny ze sposobem widzenia pamięci przez użytkownika.


Segmentacja ze stronicowaniem

Multics: każdy segment ma swoją tablicę stron, znika problem fragmentacji zewnętrznej; deskryptor segmentu zawiera adres tablicy stron segmentu (w pamięci), długość segmentu i pomocnicze bity; tablica segmentów jest też stronicowana.


Intel 80386 - translacja adresu

adresowanie

segmentacja

stronicowanie

Intel 80386 - translacja adresu (na jednym rysunku)

Intel 80386

Rysunek: Tłumaczenie adresu w procesorze Intel 80386 (źródło: Silberschatz, Galvin, Podstawy systemów operacyjnych)


Pamięć wirtualna

Umożliwia wykonywanie procesów, pomimo że nie są one w całości przechowywane w pamięci operacyjnej ani nie zajmują spójnych obszarów pamięci.

Logiczna przestrzeń adresowa może być dużo większa od fizycznej przestrzeni adresowej.

Tworzy iluzję dużej, jednorodnej i szybkiej pamięci.

Procesy mogą współdzielić fragmenty swojej przestrzeni adresowej.

Części przestrzeni adresowej procesów są relokowalne, czyli mogą być umieszczone w dowolnym miejscu w pamięci fizycznej i mogą być przemieszczane w trakcie wykonania procesu.

Techniki realizacji pamięci wirtualnej: stronicowanie na żądanie, segmentacja na żądanie, segmentacja ze stronicowaniem.

Stronicowanie na żądanie

O różnych algorytmach wymiany, efektywności pamięci wirtualnej, zjawisku migotania i innych zagadnieniach związanych z zarządzaniem pamięcią wirtualną będzie na następnym wykładzie.


A jak to wszystko wygląda w Linuksie ...

Zarządzanie pamięcią w Linuksie 2.4.19 jest opisane bardzo szczegółowo przez A. Naynai'ego w Memory Management in Linux (ma 393 strony!).

Zarządzanie pamięcią w Linuksie 2.4.20 (z informacją co się pojawi w 2.6) jest opisane bardzo szczegółowo przez Mela Gormana w Understanding the Linux Virtual Memory Manager (zdalnie) i (lokalnie, PDF).

O systemie bliźniaków można także przeczytać w krótszym opracowaniu Mela Gormana: Buddy Algorithm (zdezauktualizowała się jedynie informacja o mapach bitowych).

O stronach, ramkach, przerwaniach i innych podstawach traktuje krótkie opracowanie Mela Gormana The Basics.


Pamięć fizyczna

Ramki

W Linuksie część pamięci fizycznej RAM (ang. Random Access Memory) jest alokowana na stałe na potrzeby kodu i statycznych struktur danych jądra. Cała pozostała pamięć podlega stronicowaniu i jest wykorzystywana w celu:

Pamięć fizyczna RAM podlegająca stronicowaniu jest podzielona na ramki, rozmiar ramki definiuje stała PAGE_SIZE (plik include/asm-i386/page.h):

Wszystkie ramki w systemie są opisane w tablicy mem_map (tzw. tablicy ramek):


      extern struct page *mem_map;

która zawiera elementy typu struct page. Podczas inicjowania systemu jądro zaczyna zajmować dla siebie miejsce od dolnych adresów pamięci. Po zakończeniu ładowania systemowych danych i kodu cała pozostała pamięć jest dzielona na ramki. W tym momencie jest inicjowana tablica mem_map.

Definicja struktury page:


typedef struct page {
        unsigned long flags;
        atomic_t _count; 
        atomic_t _mapcount;
        struct {
           unsigned long private;
           struct address_space *mapping;
        }
        pgoff_t index;
        struct list_head lru;
        ...
};

struct page - pole count

Wartością pola jest licznik odwołań do strony. Jeśli ma wartość -1, to ramka jest wolna. Jeśli ma wartość większą lub równą 0, to ramka jest przydzielona jednemu lub większej liczbie procesów lub przechowuje struktury danych jądra.

struct page - pole private

Pole używane przez kilka modułów jądra, z różnym przeznaczeniem. Jest wskaźnikiem do listy buforów, w przypadku gdy strona zawiera bufory. Jeśli ramka jest wolna, to jest wykorzystywana w systemie bliźniaków.

struct page - pole mapping

Używana gdy strona trafia do pamięci podręcznej stron lub gdy należy do anonimowego obszaru.

struct page - pole index

Używane przez kilka modułów jadra, z różnym przeznaczeniem. Na przykład identyfikuje pozycję przechowywanych w ramce danych w ich obrazie na dysku lub w ramach anonimowego obszaru. Może też przechowywać identyfikator strony wymiecionej na dysk.

struct page - pole lru

Zawiera wskaźniki do podwójnie wiązanej listy stron, uporządkowanej w kolejności LRU. Gdy ramka jest wolna, to jest wykorzystywane w systemie bliźniaków do łączenia wolnych obszarów w listę.

struct page - pole flags

Oto niektóre flagi używane w polu flags. Uwaga: zmiana oraz testowanie wartości tego pola zazwyczaj jest operacją niepodzielną.


#define PG_locked                0
#define PG_error                 1
#define PG_referenced            2
#define PG_uptodate              3
#define PG_dirty                 4
#define PG_lru                   6
#define PG_active                7
#define PG_slab                  8
#define PG_highmem              11
#define PG_reserved             14

Bit PG_locked

Wszystkie strony procesów mogą uczestniczyć w operacjach wejścia-wyjścia:

Bit ten służy do zakładania blokady na ramce na czas operacji wejścia-wyjścia. Ustawia się go przed zainicjowaniem operacji i czyści po zakończeniu. Pole page->wait służy jako kolejka procesów oczekujących na zakończenie wejścia-wyścia na tej stronie.

Bit PG_error

Bit ten informuje o tym, czy operacja wejścia-wyjścia w ramce zakończyła się sukcesem.

Bit PG_referenced

Bit ten jest wykorzystywany w procesie wyboru strony do odesłania na urządzenie wymiany. Bit ten jest zapalany za każdym razem kiedy system odwołuje sie do strony w tej ramce poprzez kolejkę mieszającą (mapping, index). Ten bit razem z bitem odwołania w tablicy stron jest używany do manipulowania wiekiem strony page->age i przesuwania strony na listach active, inactive_dirty i inactive_clean. Warto zauważyć, że do ochrony bitu PG_referenced, listy page_lru oraz list active, inactive_dirty i inactive_clean służy klucz pagemap_lru_lock, a nie bit PG_locked. Bit jest sprawdzany i modyfikowany w funkcji odpowiedzialnej za wybór ramki do zwolnienia. Jeśli ramka ma zapalony bit PG_referenced, to jest on zerowany, a strona nie jest odsyłana na urządzenie wymiany.

Bit PG_uptodate

Bit ten mówi, czy zawartość ramki jest aktualna. Ustawia się go po pomyślnym zakończeniu operacji czytania z dysku do ramki.

Bit PG_highmem

Strony z zapalonym bitem PG_highmem nie są na stałe odwzorowane do wirtualnej przestrzeni adresowej jądra, trzeba je odwzorować osobno, żeby móc wykonać na nich operację wejścia-wyjścia. Struktura struct page (bity z informacją) jest zawsze odwzorowana do przestrzeni adresowej jądra.

Bit PG_reserved

Ustawia się go w przypadku ramek zarezerowanych na potrzeby jądra lub nieużywalnych.


Strefy

Pamięć fizyczna RAM podlegająca stronicowaniu jest dzielona na strefy. Na komputerach klasy PC mamy trzy strefy. Oto fragment definicji strefy.


/*
 * ZONE_DMA       < 16 MB       ISA DMA capable memory
 * ZONE_NORMAL  16-896 MB       direct mapped by the kernel
 * ZONE_HIGHMEM  > 896 MB       only page cache and user processes
 */
struct zone {
        spinlock_t              lock;
        unsigned long           free_pages;
        unsigned long           pages_min, pages_low, pages_high;

        struct free_area        free_area[MAX_ORDER];

        /* Fields commonly accessed by the page reclaim scanner */
        spinlock_t              lru_lock;       
        struct list_head        active_list;
        struct list_head        inactive_list;
        ....
};

#define ZONE_DMA                0
#define ZONE_NORMAL             2
#define ZONE_HIGHMEM            3

Strefy są związane z różnym przeznaczeniem pamięci fizycznej. Obsługa wolnych ramek w każdej strefie przebiega analogicznie.


Zarządzanie wolnymi ramki czyli System bliźniaków

Struktury danych

Informacje o ramkach zawierających wolne obszary pamięci fizycznej są przechowywane w strukturze free_area, a informacje o ramkach zajętych w tablicach stron procesów i w tablicy stron jądra.


struct free_area  free_area[MAX_ORDER];

Struktura free_area została zaprojektowana z myślą o algorytmie bliźniaków. Jest to tablica cyklicznych list wolnych obszarów pamięci fizycznej o różnych rozmiarach. Wielkość tablicy określa stała MAX_ORDER równa 11. Do i-tego pola tablicy jest dowiązana lista wolnych i spójnych kawałków pamięci o wielkości: (2^i) * PAGE_SIZE, gdzie i = 0..MAX_ORDER -1. Czyli pierwsza lista zawiera kawałki pamięci wielkości jednej strony, a ostatnia kawałki wielkości 1024 stron. Każdy taki kawałek pamięci, mimo że może składać się z wielu stron, jest spójnym blokiem pamięci fizycznej.

Pole tablicy free_area zawiera strukturę typu struct free_area (Uwaga: struktura w tej postaci pojawiła się po raz pierwszy w jądrze 2.6.11, poprzednio jednym z pól była mapa bitowa):


struct free_area {
        struct list_head        free_list;
        unsigned long           nr_free;
};

Jedenaście takich struktur jest głowami jedenastu list cyklicznych. Pole nr_free zawiera liczbę wolnych obszarów ustalonego rozmiaru. Informacja o byciu wolnym obszarem określonego rozmiaru jest zawarta w strukturze page (gdy ramka jest wolna): pole private pierwszej ramki w bloku 2^k wolnych ramek przechowuje liczbę k, a pole flags ma ustawioną flagę PG_BUDDY. Funkcja page_is_buddy() przekazuje TRUE, gdy strona jest początkiem obszaru wolnego o rozmiarze 'order' stron:


#define PageBuddy(page)         test_bit(PG_buddy, &(page)->flags)

static inline int page_is_buddy(struct page *page, int order)
{
        if (PageBuddy(page) && page_order(page) == order) {
                BUG_ON(page_count(page) != 0);
                return 1;
        }
        return 0;
}

Zajmowanie ramek


unsigned long __get_free_pages(unsigned int gfp_mask, unsigned int order)

Funkcja ta żąda przydzielenia wolnego obszaru pamięci i przekazuje adres liniowy przydzielonego obszaru (parametr order mówi, w której kolejce obszarów zacząć szukać, gdyż jest żądaniem bloku wielkości (2^order) * PAGE_SIZE). Szukanie obszaru do przydzielenia zaczyna się od kolejki o numerze order. Jeśli nie jest ona pusta, to wyjmuje się jeden element i przekazuje jego adres jako wynik funkcji. Jeśli kolejka jest pusta, to szuka się pierwszej niepustej kolejki na prawo (tzn. jednej z kolejek zawierających większe obszary pamięci). Gdy taka się znajdzie, wyjmuje się jeden jej element. Jest on oczywiście za duży więc trzeba go podzielić. Dotąd dzieli się go na połowy, aż otrzymany fragment jest żądanej wielkości. Pozostałe po dzieleniu połówki wstawia się do odpowiednich kolejek jako obszary gotowe do przydzielenia, uaktualniając równocześnie informacje o przynależności obszarów do systemu bliźniaków.


Zwalnianie ramek


static void __free_pages_ok (struct page *page, unsigned int order)

Funkcja ta zwalnia obszar wskazany (pośrednio) przez page o wielkości: (2^order) *PAGE_SIZE i wstawia go do jednej z kolejek w tablicy free_area.Obszar zwalniany musiał być kiedyś przydzielony. Skoro był przydzielony, to musiał być wydzielony z bloku dwa razy większego. Być może jego bliźniak (czyli druga połowa tego większego bloku) też jest wolny i można je będzie połączyć z powrotem w jeden większy spójny obszar. Jeśli to się uda, to można szukać bliźniaka dla tego większego kawałka itd. Wykonuje się to w pętli dotąd, aż szukany bliźniak okaże się być zajęty. Dodaje się wtedy blok do odpowiedniej kolejki, a także aktualizuje informacje o przynależności obszarów do systemu bliźniaków.


Uwagi


Janina Mincer-Daszkiewicz