Valid HTML 4.01!

Zarządzanie pamięcią

Spis treści


Wydajność stronicowania na żądanie

Przerwanie braku strony

Rysunek: Przerwanie braku strony (źródło: Silberschatz, Galvin, Podstawy systemów operacyjnych)

p = współczynnik błędów braku strony, 0 ≤ p ≤ 1.0

p = 0 => nie ma błędów braku strony
p = 1 => każde odwołanie powoduje błąd braku strony
efektywny czas dostępu = (1-p) * czas dostępu do pamięci + p * czas obsługi błędu braku strony
czas obsługi błędu braku strony = czas obsługi przerwania
+ [czas wysłania strony na dysk]
+ czas sprowadzenia strony z dysku
+ czas wznowienia obliczeń

Wymiana stron

Algorytmy wymiany stron

Migotanie (ang. thrashing)

Proces jest zajęty głównie przesyłaniem stron z dysku do pamięci i z pamięci na dysk.

Brak wystarczającej liczby stron jest powodem wysokiego współczynnika błędów braku strony:

  1. niskie wykorzystanie CPU,
  2. system operacyjny myśli, że trzeba zwiększyć poziom wieloprogramowości,
  3. do systemu dodaje się nowy proces.

Wykres zależności wykorzystania procesora od poziomu wieloprogramowości.

Migotanie

Rysunek: Migotanie (źródło: Silberschatz, Galvin, Podstawy systemów operacyjnych)

Model pola roboczego (ang. working-set)

Zasada lokalności odwołań: w każdej fazie wykonania program korzysta jedynie z drobnej części całego zbioru stron (lokalność czasowa, przestrzenna).

T = parametr => ustalona liczba odwołań do stron.
Jeśli T = nieskończoność, to zbiór roboczy obejmuje wszystkie strony.

Zasada pola roboczego a równoważenie obciążenia

Jeśli są jeszcze wolne ramki => można zainicjować nowy proces.

Jeśli Suma WSi > liczba dostępnych ramek => migotanie => wstrzymanie jednego z procesów.

Jak aproksymować zawartość pola roboczego:

     zegar + bit odwołania

Przykład: T = 10 000

Inne zagadnienia


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


Alokator płytowy (ang. slab allocator)

Wprowadzenie

Zrealizowany po raz pierwszy w systemie Solaris 2.4. Jest dostępna bogata literatura na jego temat:

  1. Wersję alokatora dla SunOS 5.4 opisuje ciekawie i przystępnie Jeff Bonwick z Sun Microsystems w artykule The Slab Allocator: An Object-Caching Kernel Memory Allocator).
  2. Wersja dla Linuksa 2.4.19 jest opisana bardzo szczegółowo przez A. Naynai'ego w Memory Management in Linux w rozdz. 3.
  3. Wersja dla Linuksa 2.4.22 (z informacją co się pojawi w 2.6) jest opisane bardzo szczegółowo przez Mela Gormana w Understanding the Linux Virtual Memory Manager w rozdz. 8.
  4. Wersja dla Linuksa 2.4 jest opisana szczegółowo w książce Boveta i Cesatiego, Linux Kernel w rozdz. 6.
  5. W zarysie opisuje go Vahalia w UNIX. Nowe horyzonty, w rozdz. 12.10.
  6. Anatomy of the Linux slab allocator, M. Tim Jones

W Linuksie kod alokatora płytowego znajduje się w plikach źródłowych mm/slab.c i include/linux/slab.h.

Alokator płytowy obsługuje przydział pamięci na potrzeby jądra. Jądro potrzebuje wielu różnych tymczasowych obiektów, takich jak np. struktury dentry, mm_struct, inode, files_struct. Obiekty tymczasowe jądra mogą mieć rozmiary zarówno bardzo małe, jak i bardzo duże, ponadto są często alokowane i często zwalniane, a więc trzeba wykonywać te operacje efektywnie.

Jakie korzyści daje alokator płytowy w porównaniu z tradycyjnymi metodami alokacji pamięci?

Tak wygląda struktura pamięci podręcznej alokatora płytowego:

Slab - na plycie


Alokator płytowy w Linuksie

Głównym zadaniem alokatora płytowego w Linuksie jest zmniejszenie liczby odwołań do systemu bliźniaków. Alokator płytowy utrzymuje wiele różnych pamięci podręcznych (ang. cache). Dla często używanych obiektów (takich jak np. files_struct) utrzymuje dedykowaną pamięć podręczną, a dla pozostałych obiektów pewną liczbę ogólnych pamięci podręcznych, po jednej dla obszarów o rozmiarze będącym kolejną potęgą dwójki. W pliku /proc/slabinfo można obejrzeć listę dostępnych pamięci podręcznych alokatora płytowego, liczbę wolnych i liczbę zaalokowanych obiektów w każdej pamięci podręcznej.

Oto dedykowane pamięci podręczne:


/* System wide caches */
extern kmem_cache_t     *vm_area_cachep;
extern kmem_cache_t     *names_cachep;
extern kmem_cache_t     *files_cachep;
extern kmem_cache_t     *filp_cachep;
extern kmem_cache_t     *fs_cachep;
extern kmem_cache_t     *sighand_cachep;
extern kmem_cache_t     *bio_cachep;

A to ogólne pamięci podręczne:


/* Size description struct for general caches. */
struct cache_sizes {
        size_t           cs_size;
        kmem_cache_t    *cs_cachep;
        kmem_cache_t    *cs_dmacachep;
};

extern struct cache_sizes malloc_sizes[];

Możliwe rozmiary to 32, 64, 96, 128, ..., 65 536, ..., 33 554 432.

A to przykładowa zawartość /proc/slabinfo. W kolejnych kolumnach znajdują się:

cache-name nazwa pamięci podręcznej
num-active-obj liczba obiektów w użyciu
total-objs łączna liczba dostępnych obiektów (także nieużywanych)
obj-size rozmiar obiektu
num-active-slabs liczba płyt zawierających aktywne obiekty
total-slabs łączna liczba płyt
num-pages-per-slabs liczba stron na jedną płytę


kmem_cache            61     68    112    2    2    1
tcp_open_request       0     40     96    0    1    1
blkdev_requests      128    160     96    4    4    1
dnotify cache          0      0     20    0    0    1
sigqueue               0     29    132    0    1    1
cdev_cache          1881   1888     64   32   32    1
bdev_cache             3     59     64    1    1    1
mnt_cache             11     59     64    1    1    1
inode_cache        83974  83979    512 11997 11997  1
dentry_cache       87337  87360    128 2912 2912    1
dquot                  0      0    128    0    0    1
filp                 683    690    128   23   23    1
names_cache            0     10   4096    0   10    1
buffer_head        24662  24680     96  617  617    1
mm_struct             38     48    160    2    2    1
vm_area_struct      1899   1920     96   48   48    1
fs_cache              37     59     64    1    1    1
files_cache           37     45    416    5    5    1
signal_act            39     45   1312   13   15    1
size-131072(DMA)       0      0 131072    0    0   32
size-131072            0      0 131072    0    0   32
size-4096(DMA)         0      0   4096    0    0    1
size-4096             24     48   4096   24   48    1
size-128(DMA)          0      0    128    0    0    1
size-128             967   1020    128   34   34    1
size-32(DMA)           0      0     32    0    0    1
size-32             9504   9605     32   85   85    1

Pamięć jest fizycznie alokowana i inicjowana całymi płytami (ang. slab). Każda płyta składa się z jednej lub większej liczby ramek, zawierających zarówno zaalokowane, jak i wolne obiekty.


Struktury danych alokatora płytowego

Alokator płytowy używa trzech struktur danych:

Układ tych struktur w pamięci zależy od rozmiaru obiektów przechowywanych w pamięci podręcznej. W przypadku małych obiektów deskryptory płyty i obiektu są umieszczone NA płycie:

Slab - na plycie

W przypadku dużych obiektów deskryptory płyty i obiektu są umieszczone POZA płytą:

Slab - poza plyta

Deskryptor pamięci podręcznej

W obrębie jednej pamięci podręcznej trzymane są dowiązania do płyt z obiektami tego samego rozmiaru. Płyty są umieszczone na trzech listach:

Alokator w pierwszej kolejności przydziela obiekty z płyt częściowo zapełnionych, następnie z płyt pustych. Na potrzeby kolejnych żądań alokuje nowe płyty (odwołując się do systemu bliźniaków).

Deskryptor płyty

Płyta to ciągły obszar pamięci, do którego można wrzucić pewną liczbę obiektów o konkretnym rozmiarze. Deskryptory płyty mogą być przechowywane wewnątrz płyty (tak dzieje się w przypadku małych obiektów, mniejszych niż 1/8 strony) bądź na zewnątrz (w przypadku dużych obiektów).


struct slab {
        struct list_head    list;
        unsigned long       colouroff;
        void                *s_mem; 
        unsigned int        inuse;  
        kmem_bufctl_t       free;
};

list: lista, do której należy ta płyta
colouroff: kolor tej płyty
s_mem: adres pierwszego obiektu na płycie
inuse: liczba aktywnych obiektów na płycie
free: pole używane do łączenia wolnych obiektów w listę

Deskryptor obiektu


typedef unsigned int kmem_bufctl_t;

Deskryptor obiektu służy do łączenia obiektów wolnych w listę. Jest on potrzebny tylko dla wolnego obiektu, musi jednak być rozłączny z samym obiektem, gdyż nie chcemy niszczyć poprawnie skonstruowanego obiektu.

Deskryptory obiektów, tak jak deskryptory płyt mogą być przechowywane na dwa sposoby: wewnątrz płyty (dla małych obiektów) bądź na zewnątrz (dla dużych obiektów).

Deskryptor obiektu może być prostą liczbą całkowitą, gdyż do ustalenia pamięci podręcznej i płyty, do której należy obiekt wystarczy dowiązanie do deskryptora ramki zawierającej obiekt.

Wszystkie dowiązania do wolnych obiektów są trzymane w tablicy elementów typu kmem_bufctl_t, która ma tyle pozycji, ile jest obiektów na płycie. Tablica jest trzymana tuż ZA deskryptorem płyty i nie ma do niej bezpośredniego wskaźnika. Jest natomiast dostępna pomocnicza funkcja:


static inline kmem_bufctl_t *slab_bufctl(struct slab *slabp)
{
        return (kmem_bufctl_t *) (slabp + 1);
}

Dostęp do i-tego elementu tablicy odbywa się więc następująco:


    slab_bufctl(slabp)[i]

Indeks następnego wolnego obiektu na płycie jest przechowywany w slabp->free.

Inicjowanie płyty polega na wypełnieniu tablicy kmem_bufctl_t kolejnymi indeksami obiektów, zaczynając od 1 i kończąc na znaczniku BUFCTL_END. Do slab_t->free wstawia się 0. Ogólnie, dla wolnego obiektu o indeksie n indeksem następnego wolnego obiektu jest kmem_bufctl_t[n].

Adres pierwszego obiektu jest pamiętany w slabp->s_mem. Jeśli do tego adresu dodamy slabp->free (indeks pierwszego wolnego obiektu) pomnożone przez cachep->objsize (rozmiar obiektu), to otrzymamy adres pierwszego wolnego obiektu na płycie.


Funkcje alokatora płytowego

Do inicjowania ogólnych pamięci podręcznych służy funkcja:


void __init kmem_cache_init(void)

Do tworzenia dedykowanych pamięci podręcznych służy funkcja:


/**
 * kmem_cache_create - Create a cache.
 * @name: A string which is used in /proc/slabinfo to identify this cache.
 * @size: The size of objects to be created in this cache.
 * @align: The required alignment for the objects.
 * @flags: SLAB flags
 * @ctor: A constructor for the objects.
 * @dtor: A destructor for the objects.
 */

struct kmem_cache *
kmem_cache_create (const char *name, size_t size, 
       size_t align, unsigned long flags, 
       void (*ctor)(void*, struct kmem_cache *, unsigned long),
       void (*dtor)(void*, struct kmem_cache *, unsigned long))

Jeśli podczas tworzenia pamięci podręcznej jest ustawiona flaga SLAB_HWCACHE_ALIGN, to rozmiar obiektu jest wyrównywany do L1_CACHE_BYTES (dla większości architektur równe 32), dzięki czemu obiekty będą wyrównane w sprzętowej pamięci podręcznej. Powoduje to powstanie odstępów między obiektami na płycie.

Funkcja kmalloc służy do przydziału pamięci na potrzeby jądra.

Funkcja najpierw próbuje zaalokować obiekt w częściowo wypełnionej płycie, potem w wolnej płycie. Jeśli zakończy się to niepowodzeniem, to próbuje zaalokować nowe ramki z systemu bliźniaków.

Funkcja kfree służy do zwalniania pamięci przedzielonej na potrzeby jądra. Zwalnianie pustych płyt odbywa się podczas usuwania pamięci podręcznej. To z niej ostatecznie zostanie wywołana funkcja znana nam z systemu bliźniaków funkcja.


Kolorowanie płyt

Jedna linia sprzętowej pamięci podręcznej odwzorowuje wiele różnych bloków pamięci RAM. Obiekty o tym samym rozmiarze powinno się przechowywać z tym samym przesunięciem względem pamięci podręcznej alokatora. Obiekty mające to samo przesunięcie względem różnych płyt będą z dużym prawdopodobieństwem odwzorowane do tej samej linii sprzętowej pamięci podręcznej. Alokator płytowy stara się zredukować to niekorzystne zjawisko poprzez mechanizm kolorowania płyt: różnym płytom są przypisywane różne kolory, które określają przesunięcie obiektów w ramach płyty.

Obiekty na różnych płytach będą umieszczane z różnym przesunięciem, zależnie od ilości wolnej pamięci. Przesunięcie jest mierzone w jednostkach będących wielokrotnością BYTES_PER_WORD, chyba, że jest ustawiona flaga SLAB_HWCACHE_ALIGN, gdyż wówczas jest wielokrotnością L1_CACHE_BYTES.

Podczas tworzenia pamięci podręcznej liczy się liczbę obiektów, które można umieścić na płycie oraz liczbę niewykorzystanych bajtów. Na ich podstawie wylicza się:

colour: liczbę dostępnych kolorów (przesunięć na różnych płytach),
colour_off: kolejne przesunięcie

Alokator płytowy wykorzystuje do kolorowania płyty niewykorzystane bajty wewnątrz płyty (zmienna left_over w funkcji kmem_cache_create). Płyty o różnych kolorach przechowują pierwszy obiekt płyty na różnych miejscach w pamięci, uwzględniając wymagane wyrównanie obiektu w pamięci oraz kolor płyty. Pierwszy kolor jest oznaczany jako 0, a ostatni (którego wartość znajduje się w polu colour deskryptora pamięci podręcznej) jako left_over/offset. Kolorowanie powoduje przesunięcie części wolnego obszaru płyty z końca na początek.

Różne kolory są po równo rozdzielane pomiędzy płyty określonego typu obiektu. Każda nowo tworzona płyta ma kolor różny od poprzedniej, aż do osiągnięcia maksymalnej liczby dostępnych kolorów.


Janina Mincer-Daszkiewicz