Model pamięci w systemie Linux Patryk Konopka Paweł Piecyk Wydział Fizyki i Informatyki Stosowanej 2013.05.08
Plan prezentacji 1. Tryb rzeczywisty i segmentowy model pamięci 2. Tryb chroniony i płaski model pamięci 3. Pamięć wirtualna Stronicowanie Physical Address Extension Swapowanie Mapowanie pamięci logicznej na fizyczną Problemy dynamicznej alokacji pamięci Pamięć współdzielona 4. Przydatne narzędzia
Trochę historii tryb rzeczywisty (real mode) Tryb, który nie zapewnia ochrony pamięci ani wielozadaniowości Procesor pracuje tak jak Intel 8086 20-bitowa szyna danych można zaadresować max. 1MiB pamięci Wszystkie procesory od 286 do obecnie stosowanych konstrukcji startują w real mode w celu zapewnienia kompatybilności 640K ought to be enough for anybody Charakterystyczny dla MS-DOS
Trochę historii segmentowy model pamięci Program ma wydzielone w pamięci 3 segmenty: kodu, danych i stos (+ opcjonalne dodatkowe segmenty danych) Adres dwie 16-bitowe liczby: numer segmentu (segment) + przemieszczenie względem początku segmentu (offset) PhysicalAddress = Segment * 16 + Offset Format adresu segment:offset Segmenty mogą na siebie nachodzić przez to wiele adresów logicznych może wskazywać na jeden adres fizyczny (dokładnie 4096) Wszystkie procesory od 286 do obecnie stosowanych konstrukcji startują w real mode w celu zapewnienia kompatybilności
Protected mode Tryb pracy mikroprocesorów x86 wprowadzony w mikroprocesorze Intel 80286. Umożliwienie adresowania pamięci przekraczającej 1MB. Wprowadzenie wielozadaniowości Podstawowy tryb pracy dla systemów Linux, Windows i BSD
Protected mode W rejestrze segmentowym przechowujemy selektor segmentu : Do dyspozycji mamy dwie tablice: -GDT Global Descriptor Table Jedna w systemie zawiera informacje o kodzie OS i jego danych, dostępna dla wszystkich procesów -LDT Local Descriptor Table Osobna dla każdego zadania - 8192 deskryptorów globalnych - 8192 deskryptorów lokalnych Physical or
Protected mode deskryptor segmentu Limit segmentu wielkość segmentu, połączenie obu pól (20 bitowa liczba) Adres bazowy oraz Baza offset segmentu, połączenie trzech pól (32 bitowa liczba) Typ prawa dostępu do segmentu (Write / Read / Execute) DPL poziom uprzywilejowania segmentu S typ deskryptora, jeżeli 0 to opisuje segment systemowy, w przeciwnym wypadku segment danych lub kodu G ziarnistość (0 ziarnistość 1B, 1 ziarnistość 4kB) P informuje czy segment jest załadowany do pamięci
Protected mode
Protected mode To system musi zarządzać trybem chronionym. Programy nie mają dostępu do danych lub kodu z wyższym priorytetem. Stosowane w obecnych systemach operacyjnych.
Pamięć wirtualna Pamięć wirtualna mechanizm zarządzania pamięcią komputera zapewniający procesowi wrażenie pracy w jednym dużym, ciągłym obszarze pamięci operacyjnej podczas gdy fizycznie może być ona pofragmentowana, nieciągła i częściowo przechowywana na urządzeniach pamięci masowej. Implementacja w systemie Linux: segmentacja + stronicowanie
Stronicowanie Przestrzeń adresowa segmentu jest podzielona na równe części strony Rozmiar strony 4kB dla procesora Intel Pamięć fizyczna podzielona jest na części o tej samej wielkości co strony nazywane ramkami Adres logiczny jest tłumaczony na adres fizyczny z wykorzystaniem tablic stron
Stronicowanie wielopoziomowe tablice stron Jeśli używalibyśmy prostych jednopoziomowych tablic stron to musiałyby one zawierać 2^20 wpisów (4 bajty na wpis = 4 MB pamięci RAM dla każdego procesu) Dwupoziomowe tablice stron redukują tą ilość przez tworzenie tablic stron tylko dla tych obszarów pamięci wirtualnej, które są aktualnie używane Każdy aktywny proces ma przypisany Page Directory Adres fizyczny PD aktualnie używanego procesu jest przechowywany w rejestrze cr3
Stronicowanie wielopoziomowe tablice stron
PAE Disabled PAE Enabled PAE Physical Address Extension Rozszerzenie umożliwiające procesorom x86 (32-bit) dostęp do fizycznej przestrzeni adresowej (RAM) o rozmiarze większym niż 4 GB. Rejestr cr3 wskazuje na PDPT, który zawiera wskaźniki do 4 PD Rozszerzenie to nie powiększa liniowej przestrzeni adresowej procesu, więc operowanie na pamięci > 4GB jest możliwe tylko z poziomu kernela
Linux paging model 32bit używane 2 poziomy. Nie używamy PUD i PMD 32bit z PAE używane są 3 poziomy. Nie używamy PUD 64bit używane 3 lub 4 poziomy w zależności od architektury procesora
Swapping Realizowane przez kswapd kernel swap daemon (kernel thread) kswapd jest inicjalizowany przy uruchamianiu systemu i jest uruchamiany okresowo zgodnie z licznikiem zwanym kernel swap timer Przy każdym uruchomieniu kswapd sprawdza czy ilość wolnej pamięci nie jest zbyt niska, jeśli tak to stara się ją zwolnić Do wyznaczenia stron, które mogą być zapisane na dysk używany jest algorytm postarzania Każda strona ma licznik określający jej wiek Licznik przy alokacji ustawiany jest domyślnie na 3 Przy każdym użyciu strony licznik+=3 (maksymalnie 20) Co pewien czas licznik-- dla każdej ze stron Gdy licznik = 0 dana strona jest dobrym kandydatem do zapisania na dysk
Swapping Podczas projektowania aplikacji działającej w obrębie systemu czasu rzeczywistego chcemy zminimalizować czas dostępu do pamięci. Nie chcemy, aby nasza pamięć była swap owana, swap = długi czas odczytu pamięci. Jak się uchronić przed swap owaniem? mlock() lub mlockall() gwarancja na to, że nasza pamięć pozostanie w pamięci fizycznej (nie będzie swap owana) #include <sys/mman.h> int mlock(const void *addr, size_t len); int munlock(const void *addr, size_t len); int mlockall(int flags); int munlockall(void);
MMU Memory Management Unit User space Kernel space
MMU Memory Management Unit Układ realizujący dostęp do pamięci fizycznej żądanej przez CPU Realizacja translacji pamięci wirtualnej do pamięci fizycznej (hardware owo) Ochrona pamięci Pamięć podręczna Dzielenie pamięci wirtualnej na strony o Motorola 68451 MMU dla procesora Motorola 68010 w postaci osobnego układu rozmiarze 2 N
MMU Memory Management Unit TLB Translation Lookaside Buffer Tablica buforująca strony Szybki dostęp do stron MMU: Odpowiedzialny za Segmentation fault! Sygnał SIGSEGV
Segmentation fault - Dereferencja wskaźników wskazujących na NULL - Próba dostępu do pamięci do której nie mamy uprawnień - Próba dostępu do nieistniejącego adresu w pamięci - Próba zapisu do pamięci typu read-only - Przepełnienie bufora - Użycie niezainicjalizowanego wskaźnika - Próba odpalenia źle skompilowanego programu
Text segment
Text segment Zawiera kod maszynowy przeznaczony do wykonania przez procesor komputera. Segment kodu jest wskazywany przez rejestr segmentowy CS. Pamięć oznaczona przez system jako read-only dane nie mogą być modyfikowane przez proces. Procesy mogą współdzielić kod, w momencie gdy uruchomiona jest kopia programu. Można wyłączyć współdzielenie kodu w momencie kompilacji za pomocą flagi N.
Data segment
Data segment Miejsce, w którym przechowywane są : -zmienne globalne z przypisaną wartością int i = 1; int main() {} - zmienne statyczne z przypisaną wartością int main() { static char *text = Hello ; }
BSS segment
BSS segment Wykorzystywane przez kompilatory i linkery w celu przechowywania zaalokowanych, ale niezainicjowanych zmiennych statycznych. void dummy() { static int i; } Przechowywanie niezainicjowanych zmiennych globalnych. int i; int main() {} Język C: int i; == int i = 0; Rozmiar początkowy BSS segment 4 lub 8 bajtów.
Funkcje sbrk() oraz brk()
Funkcje sbrk() oraz brk() Podstawowe funkcje do zarządzania pamięcią w systemach Unix Kontrolowanie rozmiaru dostępnej pamięci konkretnego procesu w sekcji Heap Funkcje wysokopoziomowe Funkcje wykorzystywane przez malloc() Kiedyś w systemach Unix owych: jedyny sposób na zażądanie dostępu do dodatkowej pamięci.
Funkcje sbrk() oraz brk() Program brake wskazuje na pierwszy adres, który jest niedostępny dla procesu (w kontekście sterty). #include <unistd.h> int brk(void *end_data_segment) - ustawia Program brake na adres, który wskazuje end_data_segment i zmienia rozmiar dostępnej pamięci. void *sbrk(intptr_t increment) przesuwa Program brake o ilość bajtów przekazaną w parametrze increment. void * ptr = sbrk(0); zwróci bieżący adres, na który wskazuje Program break.
Wywołanie systemowe mmap()
Wywołanie systemowe mmap() mmap() wywołanie systemowe, które nakazuje systemowi operacyjnemu odwzorowanie danej części wybranego pliku w przestrzeni adresowej procesu. Do obszaru pliku odnosimy się jak do zwykłej tablicy bajtów w pamięci. Eliminacja wywołań systemowych typu read/write. Przyspieszenie działania na dużych plikach.
Wywołanie systemowe mmap() #include <sys/mman.h> void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset); start - określa adres, w którym chcemy widzieć odwzorowanie pliku. Nie jest wymagane zazwyczaj 0. length - ilość bajtów jaką chcemy odwzorować w pamięci. prot - flagi określające uprawnienia jakie chcemy nadać obszarowi pamięci. PROT_READ PROT_WRITE PROT_EXEC PROT_NONE flags - dodatkowe flagi określające sposób działania wywołania mmap. MAP_SHARED MAP_PRIVATE MAP_ANONYMOUS fd - deskryptor pliku, który chcemy odwzorować w pamięci. offset - liczba określająca od którego miejsca w pliku chcemy rozpocząć
Wywołanie systemowe mmap() void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset); Wartość zwracana: Sukces zwrócenie wskaźnika na odwzorowaną pamięć. Porażka zwrócenie MAP_FAILED == (void*)-1.
Wywołanie systemowe munmap() munmap() - Wywołanie systemowe nakazujące zlikwidowanie odwzorowania pliku w pamięci. int munmap(void *start, size_t length); start - określa adres odwzorowania do skasowania. length - liczba bajtów zajmowana przez odwzorowanie. Wartość zwracana: Sukces zwrócenie 0. Porażka zwrócenie -1.
Problem dynamicznej alokacji pamięci Funkcja malloc() alokuje pamięć z wykorzystaniem dwóch funkcji: - sbrk() - mmap() Alokacja pamięci za pomocą sbrk(), gdy n < MMAP_THRESHOLD Alokacja pamięci za pomocą mmap(), gdy n >= MMAP_THRESHOLD, gdzie n rozmiar w bajtach Możliwość zmiany MMAP_THRESHOLD za pomocą funkcji mallopt(). int result = mallopt(m_mmap_threshold, 128*1024); - wartość domyślna
Problem dynamicznej alokacji pamięci Pojedynczy blok pamięci to tzw. chunk Puste (zwolnione) bloki pamięci tworzą listę Ważne: wywołanie malloc(96) wcale nie zaalokuje bloku pamięci o rozmiarze 96 bajtów!!!
Wywołanie malloc() Chunk size Pointer to next free chunk Data Rys 1. Pojedynczy chunk Rys 2. Przykład wywołania malloc(96);
Wywołanie free() Rys 3. Przykład wywołania free(ptr);
Negatywne skutki dynamicznej alokacji - fragmentacja Problem pośredni: długi czas przejścia listy w celu znalezienia odpowiedniego chunk a
Fragmentacja rozwiązanie problemu Zastosowanie innego algorytmu alokacji np. Best-Fit Allocation Przed: Po: Czas działania algorytmu O(n), gdzie n ilość wolnych bloków
Stos
Stos Liniowa struktura danych, w której dane są dokładane na wierzch stosu. Dwie operacje: - push umieszczenie wartości na szczycie stosu, czyli przesunięcie rejestru ESP o odpowiednią ilość bajtów do tyłu i wpisanie tam wartości - pop zdjęcie wartości ze stosu, czyli przesunięcie ESP do przodu Odpowiedzialne rejestry: SS rejestr segmentowy, wskazujący na początek stosu ESP rejestr wskazujący na element znajdujący się na szczycie stosu
Stos Jak sprawdzić maksymalny rozmiar stosu? Wywołanie: ulimit -s
Kernel space
Kernel space Przechowuje: Kod kernela Dane wykorzystywane przez kernel Page tables Kernel też potrzebuje swojej przestrzeni adresowej! Cała pamięć (1GB) jest od razu mapowana na pamięć fizyczną. Komunikacja tylko za pomocą wywołań systemowych.
Pamięć współdzielona Pamięć współdzielona pamięć do której może mieć jednoczesny dostęp kilka procesów Gdzie może się przydać: - komunikacja między procesami (inter-process communication) - współdzielenie bibliotek
Pamięć współdzielona komunikacja między procesami int segment_id = shmget(segment_id, size, flags) - alokuje pamięć współdzieloną void* shmat(segment_id, NULL, flags) - mapuje pamięć współdzieloną do przestrzeni pamięci procesu void shmdt(adress) odłącza pamięć współdzieloną shmctl() - służy m.in. do zwalniania pamięci współdzielonej Przydatne narzędzia: ipcs pozwala uzyskać informacje dotyczące pamięci współdzielonej ipcrm pozwala usuwać obiekty związane z IPC
Przydatne narzędzia - top top narzędzie do monitorowania systemu W kontekście pamięci najbardziej powinny interesować nas kolumny: VIRT ilość pamięci do której proces ma dostęp w aktualnym momencie, czyli suma pamięci aktualnie używanej, plików z HDD, która są do niej zmapowane oraz pamięci współdzielonej. RES określa ile pamięci fizycznej jest używane przez proces. SHR określa jaka część pamięci z kolumny VIRT jest współdzielona.
Przydatne narzędzia - /proc/pid/status free proste narzędzie do statystyki, wyświetla ilość dostępnej/zajętej pamięci w systemie. /proc/pid/status interesujące informacje z przedrostkiem Vm VmPeak maksymalny rozmiar pamięci użyty przez proces w ciągu jego życia, VmSize rozmiar pamięci wirtualnej, VmLck ilość zablokowanej pamięci, VmHWM High water mark, maksymalny rozmiar pamięci fizycznej użyty przez proces, VmRSS aktualny rozmiar wykorzystywanej pamięci fizycznej, VmData, VmStk, VmExe poszczególne segmenty, VmPTE rozmiar tablic stron VmLib rozmiar kodu bibliotek współdzielonych
Przydatne narzędzia - pmap pmap -x pid wyświetla mapę pamięci procesu Kolumny: Address adres początku mapy Kbytes rozmiar mapy RSS rozmiar zajmowanej pamięci fizycznej Dirty - dirty pages (both shared and private) Mode - uprawnienia: read, write, execute, shared, private (kopia przy zapisie) Mapping plik z którego pochodzi mapa, lub '[ anon ]' dla zaalokowanej pamięci, lub '[ stack ]' dla stosu procesu
Przydatne narzędzia pmap przykładowy wynik $ pmap -x 4195 4195:./process1 Address Kbytes RSS Dirty Mode Mapping 0000000000400000 4 4 0 r-x-- process1 0000000000600000 4 4 4 r---- process1 0000000000601000 4 4 4 rw--- process1 00007f770aae7000 1784 276 0 r-x-- libc-2.17.so 00007f770aca5000 2044 0 0 ----- libc-2.17.so 00007f770aea4000 16 16 16 r---- libc-2.17.so 00007f770aea8000 8 8 8 rw--- libc-2.17.so 00007f770aeaa000 20 12 12 rw--- [ anon ] 00007f770aeaf000 140 116 0 r-x-- ld-2.17.so 00007f770b0a9000 12 12 12 rw--- [ anon ] 00007f770b0c7000 28 4 4 rw-s- [ shmid=0x2e0001 ] 00007f770b0ce000 12 12 12 rw--- [ anon ] 00007f770b0d1000 4 4 4 r---- ld-2.17.so 00007f770b0d2000 8 8 8 rw--- ld-2.17.so 00007fff62113000 132 12 12 rw--- [ stack ] 00007fff621fe000 8 4 0 r-x-- [ anon ] ffffffffff600000 4 0 0 r-x-- [ anon ] ---------------- ------ ------ ------ total kb 4260 496 96
Analiza pamięci - narzędzia Valgrind narzędzie do debugowania pamięci, wykrywania wycieków pamięci oraz profilowania aplikacji. Geneza nazwy: nazwa głównego wejścia do Valhali w nordyckiej mitologii. Współpraca z gdb: 1. valgrind --vgdb=yes --vgdb-error=0./small_app 2. gdb./small_app 3. (gdb) target remote vgdb --pid=xxxx 4. (gdb) monitor leak_check full reachable any
Bibliografia 1. http://duartes.org/gustavo/blog/post/anatomy-of-a-program-in-memory 2. http://www.advancedlinuxprogramming.com 3. http://www.cs.rit.edu/~ark/lectures/gc/03_03_03.html Best-Fit Algorithm 4. http://www.ualberta.ca/cns/research/linuxclusters/mem.html Understanding Memory 5. http://technology-shettyprasad.blogspot.com/2010/08/dynamic-memory-management.html 6. http://en.wikipedia.org 7. Linux man pages