Zarządzanie pamięcią w Linuksie 2.4

Michał Politowski mp169814@students.mimuw.edu.pl

Wirtualna przestrzeń adresowa procesu

Przestrzeń adresowa procesu
Zbiór wszystkich adresów liniowych, z których może korzystać proces. Każdy proces ma swoją własną wirtualną przestrzeń adresową, niezależną od pozostałych procesów.
Przestrzeń adresowa procesu w Linuksie tworzona jest przez zbiór niezachodzących na siebie obszarów adresów liniowych. Na przestrzeń adresową procesu mają wpływ takie wywołania systemowe jak:
fork
tworzy przestrzeń adresową nowego procesu;
exit
niszczy przestrzeń adresową procesu;
brk
zmienia rozmiar sterty procesu (wykorzystywane przez malloc);
execve
ładuje plik wykonywalny, zmienia przestrzeń adresową;
mmap
odwzorowyje plik w przestrzeń adresową procesu;
munmap
niszczy odwzorowanie pliku.
Część adresów liniowych jest zarezerwowana dla celów jądra i nie może należeć do przestrzeni adresowej procesu. Początek tego obszaru określa stała PAGE_OFFSET zdefiniowana w include/asm-i386/page.h na 0xC0000000 = 3*230, co oznacza maksymalny rozmiar przestrzeni adresowej procesu równy 3GB.

Struktury danych

Deskryptor procesu: struktura task_struct (include/linux/sched.h)

struct task_struct {
	/* ... */
	struct mm_struct *mm;
	/* ... */
	struct mm_struct *active_mm;
	/* ... */
};
mm
Wskazuje na strukturę opisującą przestrzeń adresową danego procesu.
active_mm
wskazuje na strukturę opisującą aktualnie aktywną przestrzeń adresową. Dla procesów użytkownika jest to ta sama struktura na którą wskazuje mm, dla wątków jądra jest to przestrzeń adresowa ostatnio wykonywanego procesu użytkownika.

Deskryptor pamięci: struktura mm_struct (include/linux/sched.h)

struct mm_struct {
        struct vm_area_struct * mmap;           /* list of VMAs */
        struct vm_area_struct * mmap_avl;       /* tree of VMAs */
        struct vm_area_struct * mmap_cache;     /* last find_vma result */
        pgd_t * pgd;
        atomic_t mm_users;                      /* How many users with user space? */
        atomic_t mm_count;                      /* How many references to "struct mm_struct" (users count as 1) */
        int map_count;                          /* number of VMAs */
        struct rw_semaphore mmap_sem;
	/* ... */
        struct list_head mmlist;                /* List of all active mm's.  These are globally strung
                                                 * together off init_mm.mmlist, and are protected
                                                 * by mmlist_lock
                                                 */

	/* ... */
        unsigned long rss, total_vm, locked_vm;
        unsigned long def_flags;
	/* ... */
};
mmap
wskazuje listę struktur VMA opisujących obszary przestrzeni adresowej procesu, posortowaną według adresów tych obszarów;
mmap_avl
wskazuje drzewo AVL struktur VMA posortowane według adresów opisywanych przez nie obszarów;
mmap_cache
wskazuje strukturę VMA ostatnio znalezioną przez funkcję find_vma;
pgd
adres katalogu stron procesu;
mm_users
liczba wątków używających tego deskryptora pamięci;
mm_count
liczba odwołań do tego deskryptora;
map_count
liczba struktur VMA procesu;
mmap_sem
semafor zabezpieczjący dostęp do deskryptora;
mmlist
pole łączące wszystkie struktury mm_struct w listę cykliczną;
rss
liczba ramek przypisanych przestrzeni adresowej;
total_vm
liczba stron przestrzeni adresowej;
locked_vm
liczba stron przestrzeni adresowej, których nie można usunąć z pamięci fizycznej;
def_flags
domyślne flagi dla obszarów przestrzeni adresowej procesu.
Drzewo mmap_avl jest wykorzystywane tylko dla dużej liczby struktur VMA. Gdy map_count < AVL_MIN_MAP_COUNT (obecnie 32) to mmap_avl == NULL. Ponieważ obszary przestrzeni adresowej opisywane przez struktury VMA nie zachodzą na siebie nie ma znaczenia czy mmap i mmap_avl są sortowane według ich adresów początkowych, czy końcowych.

VMA: struktura vm_area_struct (include/linux/mm.h)

struct vm_area_struct {
        struct mm_struct * vm_mm;       /* The address space we belong to. */
        unsigned long vm_start;         /* Our start address within vm_mm. */
        unsigned long vm_end;           /* Our end address within vm_mm. */

        /* linked list of VM areas per task, sorted by address */
        struct vm_area_struct *vm_next;

        pgprot_t vm_page_prot;          /* Access permissions of this VMA. */
        unsigned long vm_flags;         /* Flags, listed below. */

        /* AVL tree of VM areas per task, sorted by address */
        short vm_avl_height;
        struct vm_area_struct * vm_avl_left;
        struct vm_area_struct * vm_avl_right;

	/* ... */

        /* Function pointers to deal with this struct. */
        struct vm_operations_struct * vm_ops;

        /* Information about our backing store: */
        unsigned long vm_pgoff;         /* Offset (within vm_file) in PAGE_SIZE
                                           units, *not* PAGE_CACHE_SIZE */
        struct file * vm_file;          /* File we map to (can be NULL). */
	/* ... */
};
vm_mm
wskazuje deskryptor pamięci procesu;
vm_start
adres liniowy początku obszaru opisywanego przez strukturę;
vm_end
pierwszy adres liniowy za końcem obszaru;
vm_next
wskazuje następną strukturę na liście vm_mm->mmap;
vm_page_prot
określa rzeczywiste prawa dostępu do pamięci danego obszaru;
vm_flags
flagi danego obszaru;
vm_avl_height, vm_avl_left, vm_avl_right
struktura poddrzewa drzewa vm_mm->mmap_avl o korzeniu w danej strukturze,
vm_ops
wskazuje na zestaw operacji na pamięci, zależny od rodzaju pamięci (prywatna czy dzielona) i od systemu plików dla obszarów z odwzorowanymi plikami;
vm_pgoff
pozycja w pliku odwzorowanym na ten obszar (jeśli jest odwzorowanie) odpowiadająca początkowi obszaru, w wielokrotnościach rozmiaru strony;
vm_file
wskazuje strukturę opisującą plik odwzorowany na ten obszar, wpp. NULL;
Każdy obszar przestrzeni adresowej procesu opisywany przez pojedynczą strukturę vm_area_struct obejmuje pewną całkowitą liczbę stron, a vm_start i vm_end są zawsze wielokrotnościami rozmiaru strony. Obszary przestrzeni adresowej mogą być rozdzielane na mniejsze przy usuwaniu fragmentu przestrzeni adresowej lub zmianie jego praw dostępu. Sąsiednie obszary mogą też być łączone jeśli spełnione są odpowiednie kryteria zgodności: mają one takie same prawa dostępu i nie są odwzorowaniem plików.

Niektóre flagi dla obszarów pamięci (vm_flags):

VM_READ, VM_WRITE, VM_EXEC
prawa do odczytu, zapisu i wykonywania;
VM_MAYREAD, VM_MAYWRITE, VM_MAYEXEC
ograniczenia na dopuszczalne prawa;
VM_SHARED
obszar dzielony;
VM_GROWSDOWN
obszar ma strukturę stosu, powinien być powiększany w kierunku mniejszych adresów;
VM_GROWSUP
obszar powinien być powiększany w kierunku większych adresów;
VM_DENYWRITE
odwzorowany plik nie może być otwarty do zapisu;
VM_LOCKED
strony tego obszaru będą na stałe umieszczone w pamięci fizycznej;
VM_IO
obszar odwzorowuje urządzenie wejścia/wyjścia, a nie plik dyskowy;
Ze względu na architekturę procesorów Intela rzeczywiste prawa dostępu określone przez vm_page_prot, zapisane w tablicach stron i kontrolowane przez jednostkę zarządzania pamięcią (MMU) nie mogą odpowiadać wszystkim możliwym kombinacjom flag VM_READ, VM_WRITE i VM_EXEC. W związku z tym w praktyce posiadanie prawa do zapisu oznacza także posiadanie prawa do odczytu, zaś posiadanie prawa do odczytu - posiadanie prawa do wykonywania.

Struktura vm_operations_struct (include/linux/mm.h)

struct vm_operations_struct {
    void (*open)(struct vm_area_struct * area);
    void (*close)(struct vm_area_struct * area);
    struct page * (*nopage)(struct vm_area_struct * area, unsigned long address, int write_access);
};
open
wołana przy otwarciu obszaru VMA, czyli przy tworzeniu obszaru, lub gdy fork kopiuje obszary istniejącego procesu do nowego;
close
wołana przy usuwaniu obszaru;
nopage
wołana przy błędzie braku strony lub próbie niedozwolonego zapisu;

Operacje na przestrzeniach adresowych

Tworzenie nowej przestrzeni adresowej: copy_mm (kernel/fork.c)

Funkcja copy_mm(unsigned long clone_flags, struct task_struct * tsk) jest wołana przez do_fork w celu utworzenia struktur opisujących przestrzeń adresową procesu potomnego na podstawie przestrzeni adresowej rodzica. Linux wykorzystuje mechanizm kopiowania przy zapisie (COW - Copy On Write), co oznacza, że same dane ze stron przestrzeni adresowej kopiowane są dopiero przy próbie ich modyfikacji przez jeden z procesów.

Parametry:

clone_flags
flagi przekazane funkcji do_vfork; dla copy_mm istotna jest tylko flaga CLONE_VM oznaczająca tworzenie nowego wątku - procesu współdzielącego przestrzeń adresową z rodzicem.
tsk
wskazuje deskryptor procesu potomnego (rodzic jest bieżącym procesem, wskazywanym przez zmienną globalną current).
  1. Jeśli ustawiona jest flaga CLONE_VM zwiększa current->mm->mm_users, i przypisuje na tsk->mm oraz tsk->active_mm bieżącą przestrzeń adresową (current->mm).
  2. W przeciwnym przypadku:
    1. przydziela pamięć na nowy deskryptor pamięci, w przypadku niepowodzenia zwracając -ENOMEM;
    2. kopiuje deskryptor pamięci bieżącego procesu do nowo przydzielonego obszaru;
    3. (wykorzystując funkcję mm_init) ustawia w nowym deskryptorze niektóre pola (w szczególności mm_users i mm_count na 1); przydziela pamięć na nowy katalog stron, w razie niepowodzenia zwracając -ENOMEM;
    4. przy zamkniętym semaforze current->mm->mmap_sem woła funkcję dup_mmap kopiującą struktury VMA oraz (przy wykorzystaniu copy_page_range) tablice stron (i oznaczającą strony jako przeznaczone do kopiowania przy zapisie);
    5. dodaje nowy deskryptor do listy deskryptorów (current->mm->mmlist).

Usuwanie przestrzeni adresowej: __exit_mm (kernel/exit.c)

Funkcja __exit_mm(struct task_struct * tsk) jest wołana przez do_exit w celu zwolnienia struktur opisujących przestrzeń adresową procesu. Istnieje też funkcja exit_mm, której jedynym działaniem jest wywołanie __exit_mm.

Parametr:

tsk
deskryptor procesu, którego dotyczy operacja
W przypadku systemów jednoprocesorowych najważniejszą czynnością wykonywaną przez __exit_mm jest wywołanie mmput (z kernel/fork.c) dla tsk->mm.

mmput:

  1. zmniejsza licznik mm->mm_users
  2. usuwa deskryptor z listy deskryptorów mm->mmlist
  3. woła funkcję exit_mmap (z mm/mmap.c) zwalniającą struktury VMA i tablice stron;
  4. woła funkcję mmdrop zwalniającą pamięć zajętą przez katalog stron i deskryptor pamięci

Powiększanie przestrzeni adresowej i odwzorowanie plików do pamięci: do_mmap_pgoff (mm/mmap.c)

Wywołanie systemowe sys_mmap2 lub dla zachowania zgodności old_mmap wywołuje funkcję
static inline long do_mmap2( unsigned long addr, unsigned long len, unsigned long prot, unsigned long flags, unsigned long fd, unsigned long pgoff)
(z arch/i386/kernel/sys_i386.c)
która z zamkniętym semaforem current->mm->mmap_sem woła
unsigned long do_mmap_pgoff(struct file * file, unsigned long addr, unsigned long len, unsigned long prot, unsigned long flags, unsigned long pgoff).

Parametry:

filestruktura określająca plik do odwzorowania (jeśli nowy obszar ma odwzorowywać plik)
addradres liniowy, od którego należy rozpocząć szukanie wolnego obszaru
len długość żądanego obszaru
protżądane prawa dostępu do obszaru: PROT_NONE dla obszaru niedostępnego lub bitowa suma dowolnych spośród PROT_READ, PROT_WRITE, PROT_EXEC
flagsflagi dla regionu
pgoffprzesunięcie wewnątrz pliku file obszaru do odwzorowania (w wielokrotnościach rozmiaru strony)
Najważniejsze flagi:
MAP_ANONYMOUS
żądanie przydzielenia obszaru niezwiązanego z plikem
MAP_SHARED
zmiany widoczne dla innych procesów odwzorujących ten plik
MAP_PRIVATE
kopiowanie przy zapisie, przeciwieństwo MAP_SHARED
MAP_FIXED
żądanie przydziału obszaru zaczynającego się dokładnie od addr
MAP_NORESERVE
żądanie niesprawdzania czy jest dostępne dość pamięci dla prywatnego mapowania
MAP_LOCKED
żądanie przydzielenia obszaru trzymanego stale w pamięci
MAP_GROWSDOWN
żądanie przydzielenia obszaru, który może się powiększać w kierunku mniejszych adresów
  1. jeśli odwzorowujemy plik (bez flagi MAP_ANONYMOUS) sprawdza istnienie operacji file->f_op->mmap
  2. dla odwzorowania długości 0 zwraca addr
  3. sprawdza czy koniec odwzorowania nie wypada za dopuszczalną wielkością przestrzeni adresowej
  4. sprawdza czy nie przekroczono maksymalnej liczby odwzorowań (MAX_MAP_COUNT == 65536 (include/linux/sched.h)
  5. woła funkcję get_unmapped_area, która:
    1. dla ustawionej flagi MAP_FIXED sprawdza czy addr jest wielokrotnością rozmiaru strony; jeśli tak to go zwraca, jeśli nie - zwraca -EIVAL
    2. jeśli file ma specjalną operację file->f_op->get_unmapped_area (system plików ext2 nie ma takiej operacji) zwraca zwrócony przez nią adres
    3. w przeciwnym przypadku zwraca adres zwrócony przez funkcję arch_get_unmapped_area, która przeglądając przy użyciu funkcji find_vma przestrzeń adresową procesu znajduje pierwszy wolny obszar addresów liniowych odpowiedniej długości za addr lub zwraca -ENOMEM
  6. dla ustawionej flagi MAP_LOCKED sprawdza czy nie przekroczono limitu niewymienialnych stron
  7. sprawdza czy podano typ odwzorowania (MAP_SHARED lub MAP_PRIVATE) i czy nie zachodzi jeden z następujących błędnych przypadków:
  8. woła do_munmap w celu usunięcia poprzedniego mapowania obejmującego ten sam obszar
  9. sprawdza czy nie przekroczono maksymalnego rozmiaru przestrzeni adresowej dla tego procesu
  10. jeśli obszar ma być zapisywalny i ustawiono MAP_PRIVATE (więc zmian nie można zapisać w pliku) sprawdza czy dostępne jest wystarczająco dużo pamięci (chyba że ustawiono MAP_NORESERVE)
  11. jeśli nie chodzi o odwzorowanie do pliku sprawdza czy istnieje już obszar nieodwzorowywanej przestrzeni adresowej kończący się tuż przed adresem nowego obszaru i mający takie same flagi; jeśli tak wydłuża go do końca nowego obszaru i kończy działanie uaktualniając dane w deskryptorze pamięci
  12. przydziela pamięć na nową strukturę VMA i ją inicjalizuje
  13. dla odwzorowania pliku woła operację file->f_op->mmap
  14. dodaje nową strukturę VMA do odpowiednich struktur deskryptora pamięci
  15. uaktualnia dane w deskryptorze pamięci

Zmniejszanie przestrzeni adresowej i usuwanie odwzorowania plików: do_munmap (mm/mmap.c)

Wywołanie systemowe sys_munmap wywołuje z zamkniętym semaforem current->mm->mmap_sem funkcję
int do_munmap(struct mm_struct *mm, unsigned long addr, size_t len)

Parametry:

mm
deskryptor pamięci procesu którego dotyczy operacja
addr
adres początku obszaru do usunięcia
len
długość obszaru
  1. sprawdza czy wyspecyfikowany obszar mieści się w dopuszczalnej przestrzeni adresowej procesu
  2. sprawdza czy długość obszaru nie jest równa 0
  3. znajduje pierwszy obszar przestrzeni adresowej kończący się za addr
  4. jeśli obszar ten także zaczyna się za addr zwraca 0
  5. jeśli obszar ten zaczyna się przed addr a kończy za addr + len i należy go podzielić na dwa, sprawdza czy nie spowoduje to przekroczenia maksymalnej liczby obszarów
  6. przydziela pamięć na dodatkową strukturę VMA
  7. znajduje wszystkie obszary przestrzeni adresowej mające część wspólną z usuwanym obszarem, usuwa ich struktury VMA ze struktur deskryptora pamięci tworząc z nich pomocniczą listę
  8. przechodzi listę pomocniczą uaktualniając tablice stron i unieważniając bufory tłumaczenia adresów (TLB); do modyfikacji struktur VMA wykorzystuje funkcję unmap_fixup, która:
    1. dla obszarów przestrzeni adresowej całkowicie zawartych w usuwanym obszarze, zwalnia strukturę VMA
    2. dla obszarów przestrzeni adresowej, których jeden koniec znajduje się w usuwanym obszarze, skraca je uaktualniając strukturę VMA i wstawiając ją ponownie do struktur deskryptora pamięci
    3. dla obszaru przestrzeni adresowej całkowicie zawierającego usuwany obszar, tworzy nową strukturę VMA dla adresów powyżej addr+len, skraca oryginalny obszar uaktualniając jego strukturę VMA i wstawia obie do struktur deskryptora pamięci
  9. zwalnia dodatkową strukturę VMA, jeśli nie została wykorzystana