nastepny
  1. Zarządzanie pamięcią
    1. Wsparcie sprzętowe na procesorach Intel 386
      1. Mechanizm tłumaczenia adresów
      2. Pamięci podręczne
    2. Struktura pamięci fizycznej
      1. Ramki i tablice ramek
      2. Wolne ramki
      3. Zwalnianie i zajmowanie ramek
    3. Pliki wykonywalne
      1. Obsługa różnych formatów plików wykonywalnych
      2. Format ELF
      3. Fork i Exec
    4. Segmenty pamięci dzielonej
    5. Pliki odwzorowane do pamięci
    6. Wirtualna przestrzeń adresowa procesu
    7. Przestrzeń adresowa procesu
    8. Funkcja brk()
    9. Katalogi i tablice stron
    10. Stronicowanie na żądanie
      1. Odwołanie do strony w pamięci
      2. Odwołanie do strony poza pamięcią
      3. Wymiana stron
        1. Mechanizm postarzania stron
        2. Demon kswapd
      4. Podręczna pamięć buforowa stron
    11. Zarządzanie plikami wymiany i urządzeniami wymiany
    12. Pamięć dla jądra

poprzedni nastepny

1. Mechanizmy tłumaczenia adresów.

segmentacja:
adres logiczny (wirtualny) -> adres liniowy

stronicowanie:
adres liniowy -> adres fizyczny


poprzedni nastepny

Segmentacja.

Adresy segmentowe są wykorzystywane do otrzymywania adresu liniowego z adresu logicznego (wirtualnego). Adres liniowy jest później przekształcany przez stronicowanie do adresu fizycznego. Każdy segment w systemie opisany jest przez ośmiobajtowy deskryptor segmentu, który zawiera niezbędne informacje (adres liniowy, wielkość, typ, prawa).


Segmenty dzielimy na dwa rodzaje:

  • Regularne (regular segments): 

  • - segmenty kodu/danych
  • Systemowe, w których wyróżniamy:

  • - Task State Segments (TSS)
    ...czyli segmenty stanu procesu
    - Local/Global Descriptor Tables (LDT/GDT)
    ...czyli lokalne tablice deskryptorów

poprzedni nastepny
Rejestry segmentowe (cs, ds, etc.) zawierają selektory właściwych segmentów.

Format selektora jest następujący:

bity: 15..3 2 1..0
znaczenie: indeks TI RPL

TI - Table Indicator (odnośnik do tabeli)
  0 - indeks jest do globalnej tablicy desk.
  1 - indeks jest do lokalnej tablicy desk.

RPL - poziom praw; Linux wykorzystuje tylko dwa poziomy:
  0 - poziom jądra
  1 - poziom użytkownika

Linux wykozystuje teraz tylko globalną tablicę deskryptorów, gdyż lokalne tablice zawierały zawsze te same wartości 1-segment kodu 2-segment danych.


poprzedni nastepny
Deskryptory segmentów opisują każdy segment obecny w pamięci.

Format deskryptora:

bity: 63-54 55 54 53 52 51-48 47 46 45 44-40 39-16 15-0
znacze-
nie:
adr.lin
b.31-24
G D R U wielkość
b.19-16
P DPL S Typ adr.lin
b.23-0
wielkość
b.15-0

R - zarezerwowane
DPL - poziomy: 0-jądra, 1-użytkownika
G - 1 oznacza granulację 4K (w Linux'ie zawsze 1)
D - dla segmentu danych rozmiar 0 - do 64K, 1 - do 4G. dla segmentu kodu typ instrukcji i adresowania 16/32 bitowy 
U - definiowalne przez programistę
P - 1-istniejący w pamięci fizycznej
S - 1-systemowy segment, 0-normalny segment (danych/kodu)
TYP - zależne od bitu S. Dla S=0 określa typ segmentu TSS/LDT/GDT/inne. Dla S=1 określa np. czy segment jest segmentem kodu/danych umożliwia odczyt/zapis, uruchomienie z dowolnego poziomu uprzywilejowania.
 


poprzedni nastepny

Stronicowanie

Pamięć wirtualna podzielona jest na kawałki nazywane stronami. Pamięć fizyczna podzielona na kawałki (tej samej wielkości co strony) nazywane ramkami.
Tłumaczeniem adresów zajmuje się Memory Management Unit (MMU) czyli sprzętowa jednostka zarządzania pamięcią.
Na procesorach Intel 386 dostępne są katalogi stron oraz tablice stron. Każdy proces ma własny katalog tablic stron. Kiedy proces jest wznawiany, jądro ładuje do rejestru CR3 adres fizyczny (!) katalogu tablic stron tego procesu. 
 

poprzedni nastepny

Format adresu liniowego uzyskiwanego z przekształcenia adresu logicznego (wirtualnego):
 
 
bity: 31..22 21..12 11..0
znaczenie: katalog tablica offset

Adres fizyczny oblicza się następująco:
adres katalogu tablic stron [rejestr CR3] + KATALOG
...daje adres_tablicy_stron

adres_tablicy_stron + TABLICA
...daje adres_ramki w pamięci fizycznej

adres_ramki + OFFSET
...adres właściwy (fizyczny)


poprzedni nastepny
Adresy katalogów stron (tablic stron) są wyrównane do wielkości strony a 12 najmłodszych bajtów jest wykorzystywane do przechowywania informacji o tablicy stron (stronie) wskazywanej przez element tablicy.

Format elementu katalogu/tablicy stron:

bity: 31..12 11..9 8 7 6 5 4 3 2 1 0
znaczenie: ADRES OS 0 0 D A 0 0 U/S R/W P

D - dirty page
R/W - 0 tylko do czytania przez użytkownika
U/S - 1 oznacza stronę użytkownika (nie systemowa)
P - czy jest w pamięci (1-tak, 0-nie i reszta bitów to położenie w swap'ie)
A - 1 oznacza, że strona była czytana/zapisana (accessed)
OS - do wykorzystania przez system (LRU, etc.)


poprzedni nastepny
Stronicowanie jest włączane przez ustawienie najwyższego bitu rejestru CR0. Przy każdym dostępie do strony sprawdzane są prawa dostępu i przy naruszeniu praw dostępu albo nieobecności strony w pamięci wystepują błędy strony (page faults). Sterowanie jest wtedy przekazywane do jądra, które wczytuje stronę albo robi coś innego, co trzeba.

Obsługiwanie błędów stron. W momencie zdarzenia, rejestr CR2 zawiera liniowy adres, który spowodował ostatni błąd strony.

Znaczenie bitów w kodzie błędu strony:

bit wartość 0 wartość 1
0 strona nie dostępna błąd poziomu ochrony
1 błąd przy odczycie błąd przy zapisie
2 tryb super-użytkownika tryb użytkownika

poprzedni nastepny

Pamięci podręczne

W procesorach Intel 386 istnieje Translation Lookaside Buffer (TLB), czyli bufor translacji bliskiego otoczenia. Zawiera on adresy fizyczne ramek ostatnio używanych adresów logicznych (liniowych) stron. Kiedy trzeba uzyskać adres fizyczny jakiejś strony, 386 pierw patrzy do TLB czy nie ma tam tej informacji. Jeśli nie ma, to wykonywanych jest kilka odwołań do pamięci tzn. do katalogu oraz tablicy stron i dopiero odczytywany jest adres fizyczny strony. Bez TLB pojedyncze odwołanie do pamięci wymagałoby trzech wcześniejszych odwołań co byłoby bardzo czasochłonne. 

TLB jest "czyszczone" przy zmianie wartości adresu CR3 oraz przy przełączaniu zadań (co powoduje zmianę rejestru CR0). W Linuxie jest także czyszczone bezpośrednio przez wywołanie procedury invalidate (), która ładuje ponownie wartość rejestru CR3.

 


 
 
 
poprzedni nastepny
Ramki i tablice ramek 
 

Pamięć w systemie linux podzielona jest na ramki rozmiaru   okreslonego poprzez zmiannę PAGE_SIZE w pliku include/asm-<platforma>/page.h. Informacja o wszystkich ramkach w systemie przechowywana jest w tablicy mem_map, której elemantami są strukty mem_map_t  zdefinowana w include/linux/mm.h. 

  Definicja mem_map_t 

  typedef struct page { 
          struct page *next; 
          struct page *prev; 
          struct inode *inode; 
          unsigned long offset; 
          struct page *next_hash; 
          atomic_t count; 
          unsigned long flags; 
          struct wait_queue *wait; 
          struct page **pprev_hash; 
          struct buffer_head * buffers; 
  } mem_map_t; 
 


poprzedni nastepny
Omówienie poszczegulnych pól: 
  • inode i offset - inode oraz przesuniecie zamapowanego pliku 
  • next i prev - dla wolnej ramki lista cykliczna wolnych obszarów o danym rozmiarze, lub lista ramek należących do i-węzła. 
  • next_hash i pprev_hash - kolejka haszująca dla szybszego wyszukiwania ramek należących do i-wezłów 
  • wait - kolejka procesów czekajacych na zakonczenie operacji I/O na tej ramce. 
  • count - Pole to mowi do ilu procesów, buforów itd. jest przydzielona dana ramka. 
  • buffers - cykliczna lista buforów zaalokowanych do danej ramki. 
  • flags - flagi opisujące stan ramki 
 

poprzedni nastepny
   Znaczenia istotniejszych flag 
  • PG_locked - bit 0 - ramka jest zablokowana np. w czasie operacji I/O 
  • PG_error - bit 1 - wystąpił błąd I/O na ramce 
  • PG_dirty - bit 3 - ramka jest brudna 
  • PG_uptodate - bit 4 - zawartość ramki jest aktualna np. od razu po wczytaniu z dysku 
  • PG_free_after - bit 5 - ramka jest zwalniana po zakończeniu operacji I/O 
  • PG_decr_after - bit 6 - asynchroniczna operacja I/O jest  wykonywana na ramce 
  • PG_swap_unlock_after - bit 7 - bo zakonczeniu operacji wymiany ramka jset zwalniana 
  • PG_DMA - bit 8 - ramka jset dostępna dla DMA
  • PG_reserved - bit 31 - do ramki nie można się odwoływać

poprzedni nastepny
Wolne ramki w systemie

Informacja o wolnych ramkach w systemie przechowywana jest w tablicy zdefiniowanej w pliku mm/page_alloc.c 

  struct free_area_struct { 
        struct page *next; 
        struct page *prev; 
        unsigned int * map; 
 }; 

static struct free_area_struct free_area[NR_MEM_LISTS];
 


poprzedni nastepny
Stała NR_MEM_LISTS wynosi 10 dla wszytkich typów komputerów oprócz AP1000, dla którego wynosi 12. 

W elemencie free_area[i] przechowywana jest informacja o wolnych blokach ramek wielkości 2^i. Pola next i prev to wskaźniki do kolejki cyklicznej, tworzą ją struktury page oraz ich pola next i prev

Wskaźnik map  jest wskaźnikiem na bitmapę. Każdy jej bit jest przeznaczony dla dwóch bloków tej samej wielkości (bloków bliźniaczych). Bit jest ustawiany gdy jeden z bloków jest wolny natomiast drugi częściowo lub całkowicie zajęty.


poprzedni nastepny
Algorytm buddy

 zajmowanie ramek:

  • żądany blok ma wielkość 2^order
  • znajdź najmniejszy blok niemniejszy od poszukiwanego (gdy nie ma to wywołaj try_to free_pages)
  • gdy znalazłeś blok wielkosci 2^size to usuń go z listy i zmień bit w free_area[size].map[nr_bl div 2]
  • dopóki size>order:

  • -podziel obszar na połowy; size:=size-1
    -wstaw pierwsza połówkę na odpowiednią listę (druga do do dalszej "obróbki")-uaktualnij odpowiedni bit w free_area[size].map

poprzedni nastepny
zwalnianie ramek:
  • zwalniany blok ma wielkość order
  • sprawdź czy z bloku nie korzysta inny proces
  • zmień bit w free_area[order].map[nr_bl div 2], jeśli było tam 1 (teraz jest 0) to usuń bliźniaka bloku z listy free_area[order] i połącz go z bratem
  • sprawdź w mapie bitowej free_area[size].map czy na liście free_area[order+1] nie ma bliźniaka połączonego bloku, powtarzaj tak łączenie dopóki jest to możliwe, uaktualniając pola map
  • dołącz połączony blok do listy

poprzedni nastepny

Algorytm buddy - tablica free_area


poprzedni nastepny

Algorytm buddy - zajmowanie bloków


poprzedni nastepny

Algorytm buddy - zajmowanie i dzielenie bloków


poprzedni nastepny

Algorytm buddy - zajmowanie i dzielenie bloków cz.2


poprzedni nastepny

Algorytm buddy - zwalnianie i łączenie bloków


poprzedni nastepny

Algorytm buddy - zwalnianie i łączenie bloków cz.2


 
 
 
poprzedni nastepny
Pliki wykonywalne

Typy plików (formaty binarne) - standardowe

  •  a.out,
  • ELF
  • java (klasy i aplety)
  • script (skrypty)


Opis formatu binarnego

- struktura linux_binfmt (include/binfmts.h)

int (*load_binary)(...) - funkcja ładująca program,

int (*load_shlib)(int fd) - funkcja ładująca bibliotekę,

int (*core_dump)(...) - funkcja dokonująca zrzutu obrazu 'core'
 


poprzedni nastepny
Formaty standardowe

- pliki

  • binfmt_aout.c
  • binfmt_elf.c
  • binfmt_java.c
  • binfmt_script.c
- definiują odpowiednie struktury linux_binfmt
  • aout_format
  • elf_format
  • java_format
  • script_format
Formaty niestandardowe

- można rejestrować:
int register_binmft( struct linux_binmft*)
int unregister_binmft( struct linux_binmft*)
 


poprzedni nastepny
Format a.out

- prosty ("klasyczny") format,

- zawartość pliku binarnego (w kolejności):

  • nagłówek
  • kod wykonywalny (text)
  • dane (data)
  • tablica relokacji dla kodu
  • tablica relokacji dla danych
  • tablica symboli

  • poprzedni nastepny
    nagłówek  
    (include/asm/a.out.h - struct exec)
     
    a_info flagi
    a_text długość kodu (text)
    a_data długość danych (data)
    a_bss wielkość obszaru danych niezainicjowanych
    a_syms wielkość tablicy relokacji
    a_entry punkt wejścia do programu
    a_trsize wielkość tablicy relokacji dla kodu
    a_drsize wiekość tablicy relokacji dla danych
    flagi:
    MACHTYPE (8 bitów) - docelowa platforma sprzętowa (procesor),
    MAGIC (16 bitów) - typ pliku:
    OMAGIC plik obiektowy
    NMAGIC czysty plik wykonywalny
    ZMAGIC plik wykonywalny ze stronicowaniem na żądanie
    QMAGIC plik wykonywalny ze stronicowaniem na żądanie, nagłówek w sekcji 'text', pierwsza strona nie mapowana, w celu wykrywania odwołań do wskaźników NULL

    poprzedni nastepny
    tablice relokacji  

    - tablice relokacji dla text i data mają ten sam format,
    - tablica struktur relocation_info (include/linux/a.out.h)
     
    r_address adres relokowanego pola
    r_length logarytm długości pola
    r_symbolnum (patrz r_extern)
    r_extern typ relokacji:
    0: relokacja o adres początku segmentu podanego w r_extern (N_TEXT, N_DATA, itd.),
    1: relokacja o wartość symbolu o indeksie r_symbolnum

    tablica symboli

    - tablica struktur nlist (include/linux/a.out.h)
     
    n_type typ symbolu
    n_value wartość symbolu (unsigned long)
    ...  


    poprzedni nastepny
    Format ELF
    (Executable and linking Format)
    Standardem dla plików wykonywalnych jest w Linuksie format ELF. Wprowadzony został aby rozwiazać min problem dostepu do bibliotek dzielonych (wczesniej byly one linkowane statycznie, każda pod innym adresem, pomiedzy sterta i stosem). Format elf jest formatem nowym, o dużej elastycznosci - biblioteki są linkowane do niego w czasie uruchamiania. Plik spelniający ten standard musi mieć zdefiniowaną architekturę docelową (platformę sprzetową); wystepuję min w jednym z typów:
     
  •        wykonywalny (ET_EXEC)
  •        definiowany dynamicznie
  •        biblioteka systemowa (interpreter)
  •        biblioteka dzielona
  •        zrzut pamieci (core file)

  • poprzedni nastepny
    Niezależnie istnieja dwa rodzaje klas plików (zależne od architektury) :
  • dlugość slowa maszynowego: 32 lub 64 bity,
  • kolejność bajtow w slowie: little endian (1234) i big endian (4321), gdzie 1 to LSB (bajt namniej znaczacy).
  • Wewnetrzna struktura pliku jest modularna - stałe jest polożenie nagłowka (na poczatku pliku), w którym są dowiązania do podbloków definiujących położenie segmentów logicznych w pliku i przestrzeni adresowej procesu. Jakkolwiek dla wykonania kodu zawartego w pliku informacje symboliczne nie są nieodzownie konieczne, to dla programow sledzących (debuggerow) tablica symboli stanowi źródlo, które ułatwia zrozumienie deasemblowanego kodu.

    poprzedni nastepny
    Tak wyglada obraz pamieci w pliku ELF.
     
    poprzedni nastepny
         Nagłówek pliku elf**_hdr :
    struct elf32_hdr{
        unsigned char e_ident[];    naglowek do rozpoznania
        Elf32_Half    e_type,e_machine;
        Elf32_Addr    e_entry;      adres startu
        Elf32_off     e_phoff       adres w pliku naglowka programu
                      e_shoff;      offset sekcji (np.: z nazwami)
        Elf32_Word    e_flags;      flagi pliku
        ...
    }
          Program header Elf**_Phdr - nagłówek segmentu, zawiera informacje o położeniu w pliku, pamieci, rozmiarach (też i  w pliku i pamięci), prawach dostepu do segmentu i wyrownaniu (wspólnym dla pamieci i pliku). Suma dlugośći deklaracji segmentów nie może być wieksza niz 64kB.
          Jednostki relokacji elf**_rel i elf**_rela służa do relokacji adresów podczas ładowania programu.
          Tablica symboli, złożona z jednostek elf**_sym pozwala definiować symbole w programie (adres, wskaźnik do tablicy nazw, typ  i wielkość).

    poprzedni nastepny
        Jest (w bloku kontrolnym pliku) informacja o interpreterze dla kodu (bibliotece systemowej, do której beda sie odnosily odwolania). Co do samych bibliotek, to jest wymaganie (z uwagi na dynamiczne ich linkowanie z programem), by były one relokowalne (ang. relocatable), co sprowadza sie do braku odwolan absolutnych (PIC - position independet code).
    Źrodla:
        "Linux Kernel - jądro systemu"
        ~/include/linux/elf.h
        ~/fs/binfmt_elf.c

    Dodatkowe informacje:
        "System V Release 4 Programmers Guide: ANSI C and Programming Support Tools"

     
     
     
     
     
    poprzedni nastepny

    fork

    Najważniejsze czynności wykonywane przez funkcję fork():
     
    • utworzenie nowej struktury task_struct dla procesu,
    • utworzenie nowego stanu kontekstu jadra,
    • przydzielenie nowego identyfikatora procesu,
    • kopiowanie zasobow procesu macierzystego,
    • utworzenie nowej struktury mm_struct,
    • utworzenie nowego katalogu stron, lub podłączenie do katalogu procesu macierzystego,
    • "kopiowanie" pamięci wirtualnej.
    Cztery ostatnie pozycje dotyczą pamięci.
     
     

    poprzedni nastepny

    Kopiowanie zasobów

    Kopiowanie zasóbow następuje w funkcji do_fork( unsigned long clone_flags, ... ) w zależnosci od ustawienia flag CLONE_* w parametrze clone_flags. Kazdy rodzaj zasóbow ma swoją flage CLONE_* oraz (zwykle) funkcję copy_*(), wywoływaną przez do_fork() w celu skopiowania danego zasobu. Jesli dana flaga jest ustawiona, to odpowiedni zasób będzie skopiowany, w przeciwnym razie - współdzielony.
     
    flaga funkcja opis
    CLONE_FILES copy_files() kopiowanie otwartych plików
    CLONE_FS copy_fs() kopiowanie systemów plików
    CLONE_SIGHAND copy_sighand() kopiowanie procedur obsługi sygnałów
    CLONE_PID potomek otrzymuje PID rodzica
    CLONE_VM copy_mm() kopiowanie pamięci wirtualnej

    Przy tworzeniu zwykłych procesów flaga CLONE_VM jest ustawiona - proces otrzymuje własną kopię pamięci wirtualnej (strukturę mm_struct, tablicę stron itd.).
    Przy tworzeniu wątkow flaga CLONE_VM jest wyzerowana - nowy proces (wątek) dzieli pamięc wirtualną z procesem macierzystym.
     


    poprzedni nastepny

    Struktura mm_struct

    • dla wątkow (flaga CLONE_VM = 0) jest dzielona z rodzicem (zwiększa się tylko licznik odwołań),
    • dla procesow (CLONE_VM = 1) tworzy się nową kopię struktury.


    Ta i następne czynności wykonywane są przez funkcję copy_mm() wywołaną w do_fork()
     

    Katalog stron

    • wątki (CLONE_VM = 0) dzielą katalog stron z procesem macierzystym, zatem wystarczy zapisać dla procesu potomnego wskaźnik na tablicę stron procesu macierzystego,
    • procesy (CLONE_VM = 1) otrzymują własną kopię katalogu stron. Pola katalogu są poczatkowo wypełnione zerami, ich alokacja nastąpi przy pierwszym odwołaniu. Obszar 3-4GB jest odwzorowywany na pamięć jądra.

    poprzedni nastepny

    "Kopiowanie" pamięci wirtualnej

    Nie jest wykonywane dla wątków (CLONE_VM = 0).
    Dla procesów (CLONE_VM = 1) jest wykonywane przez funkcję dup_mmap(). Nie powoduje ona rzeczywistego kopiowania danych, ewentualne kopiowanie, jesli zajdzie potrzeba, nastąpi pozniej. Tutaj następuje tylko tworzenie odpowiednich struktur vm_area_struct (drzewa AVL opisujące spójne obszary pamięci wirtualnej) oraz "logiczne kopiowanie", zależne od typów obszarów:
     
    • obszar pochodzący z i-wezła => jest po prostu dołączany do kolejki pliku,
    • zwykły obszar
      • obszar dzielony lub bez prawa zapisu => następuje tylko zwiększenie licznika odwołań do obszaru,
      • obszar zwykły (nie dzielony) z prawem zapisu => powinno się go skopiować, aby jeden proces nie zamazywał danych drugiego; stosuje się technikę Copy On Write.

     

    poprzedni nastepny

    Technika "Copy On Write"

    Jest to technika stosowana zamiast natychmiastowego kopiowania obszarów pamięci. Jej główne zalety to:
    • opóźnienie kopiowania danych do czasu, kiedy będzie to konieczne (do czasu modyfikacji danych),
    • kopiowanie tylko tych stron, które trzeba skopiowac (modyfikowane),
    • nie wymaga dodatkowego wsparcia sprzętowego (korzysta z bitu RW i wyjątków page fault)


    A oto jak przebiega "kopiowanie" obszaru z ustawionym prawem zapisu, który nie ma byc dzielony pomiędzy procesami, ale do którego na razie nie następuje próba zapisu:
     

    • dla każdej strony obszaru zeruje się bit RW, ustawia znacznik COW (Copy On Write) i zwiększa licznik odwołań do strony.

    poprzedni nastepny
    Co się stanie, jeżeli teraz nastąpi próba zapisu do tego obszaru przez jeden z procesów?
    • powstanie wyjątek page fault, gdyż nie ma prawa do zapisu (wyzerowano bit RW)
    • procedura obsługi wyjątku rozpozna flagę COW, i potraktuje tę sytuacje nie jak błąd ochrony, ale właśnie jako przypadek opóźnionego kopiowania w technice COW
    • teraz procedura obsługi sprawdza licznik odwołan do strony; zachodzą dwie możliwości:
      • licznik odwołan > 1 => wówczas następuje zmniejszenie licznika odwołań, skopiowanie strony (z ustawionym prawem zapisu RW) i udostępnienie kopii procesowi,
      • licznik odwołań = 1 => zatem jest to ostatni proces, który może zażądac zapisu do tej strony; można więc udostępnić mu tę stronę, zerując znacznik COW, ustawiając prawo zapisu RW, oraz bit DIRTY.

     

    poprzedni nastepny
       Exec
         Po wykonaniu funkcji fork() jądro tworzy - w przypadku sukcesu - nowy proces - bliźniaka. Aby nowopowstały proces wykonał kod leżacy w zewnetrznym pliku wykonywalnym należy wywołaż funkcje systemowa z rodziny exec().
        W Linuxie przyjeto rozwiązanie elastyczne - aby wykonac program musi on być rozpoznany przez właściwa dla niego (zależna od formatu) usługe rejestrowaną przez jądro (wbudowana lub w module). Standardowo sa to: binaria elf a.out java oraz skrypty i binaria em86. Do operacji na menedźerach formatów służa funkcje: register_bin_fmt() i unregister_bin_fmt(). Format wykonywalny powinien zapewniać funkcje uruchamiania plików wykonywalnych i bibliotek dzielonych.

    poprzedni nastepny
    Oto uproszczony schemat zarzadzania pamięcia podzczas uruchamiania pliku wykonywalnego:
  • lock_kernel()
  • do_execve()
  • ustaw strony dla env i argv
  • read_exec() - pierwsze 512 bajtow pliku
  • search_binary_handler()
  • flush_old_exec()
    • exec_mmap()
    • tworzy nowe struktury dla obsługi sygnalów
    • usuwa informacje o wątkach
    • usuwa stare struktury obsługi sygnalów
    • zamyka pliki z flagą close_on_exec

    poprzedni nastepny
    • ustawia segmenty:
      • systemowych bibliotek C
      • kodu (TXT),
      • danych inicjowanych (DATA)
      • danych nieinicjowanych (BSS)
      • sterty (BRK)

      • (mapowania segmentów do pamięci liniowej procesu dokonuje do_mmap(), stertę i brk ustawia  sys_brk())
    • wypełnia strony argumentów i środowiska (ustawione uprzednio)
    • dokonuje ustawienia nagłówka zadania i dołączenia procesu do kolejki czekajacych.
    • unlock_kernel()

     
        Zauważyc należy, że segment stosu został ustawiony podczas wywołania funkcji fork(), wiec nie ma potrzeby go ustawiac ponownie.


     


    poprzedni nastepny
        Dodatkowo dla plików ładowanych dynamicznie (wiekszość w Linuksie), fizycznie sprowadzany do pamięci będzie jedynie naglówek pliku, tworzony katalog stron i tworzony naglówek procesu. Właściwe ładowanie zostanie wykonane wskutek obsługi błedów braku strony.
        Zaznaczyć także należy, że uruchamianie non-binary executables (skrypty, applety javy) wymaga zaadowania rzeczywistego pliku wykonywalnego (interpretera) i to on po uruchomieniu będzie wykonywał polecenia odnalezione w skrypcie(aplecie).

    poprzedni nastepny
        Oto uproszczony schemat pamieci wirtualnej procesu:


    poprzedni nastepny
    Uwaga: taki wygląd mają pliki linkowane dynamicznie (uruchamiane z formatu ELF), dla plikąw łaczonych statycznie (jak a.out), segmenty bibliotek znajdują sie między sterta i stosem. Ponieważ dla róznych bibliotek właściwe są różne (odpowiednie dla każdej) adresy, prowadzi to do potencjalnego marnowania przestrzeni adresowej.


    Źródla:
        "Linux Kernel - jadro systemu"
        ~/fs/exec.c
        ~/mm/mmap.c
        ~/mm/memory.c

    Autor: Tomasz Fujak


     
     
     
    poprzedni nastepny
    Przestrzeń adresowa procesu

    Część pierwsza

    Całą pamięć procesu można umownie podzielić na trzy części.
      Pierwsza z nich zawiera między innymi argumenty z którymi wywołany był program, oraz środowisko i stos.
     
    Nazwa pliku procesu
    Środowisko
    Argumenty
    Informacje do ładowania dynamicznego
    Wskaźniki do argumentów i zmiennych środowiskowych
    Stos


    poprzedni nastepny
    Część druga





    Zawierająca między innymi kod programu i zmienne
       inicjalizowane w programie.
     
     
     
     
    BSS
    Dane - zmienne inicjowane
    Kod programu
    Nagłówek, segmenty pomocnicze


    poprzedni nastepny
    Część trzecia

    Zawiera biblioteki w C, jest to część współdzielona przez różne procesy.
     
     
    BSS
    Dane 
    Kod 
    Nagłówek, segmenty pomocnicze


    poprzedni nastepny
    Wirtualna przestrzeń adresowa procesu.
    • struktura mm_struct 
    • struktura task_struct 
    • deficja mm_struct jest w pliku include/linux/sched.h 
    count licznik odwołań do struktury przez różne procesy
    pgd tablica katalogów stron procesu
    context aktualny kontekst procesu
    start_code początek segmentu kodu
    end_code koniec segmentu kodu
    start_data początek segmentu danych
    end_data koniec segmentu danych

    poprzedni nastepny
    start_brk początek segmentu mapowania węzłów
    brk aktualna pozycja w segmencie
    start_stack początek stosu
    start_mmap adres początku pamięci wirtualnej procesu
    arg_start początek segmentu zawierającego parametry wywołania
    arg_end koniec segmentu parametrów wywołania
    env_start początek zmiennych środowiskowych
    env_end koniec zmiennych środowiskowych
    rss liczba ramek zajmowanych przez proces

    poprzedni nastepny
    total_vm liczba stron zajmowanych przez proces
    locked_vm liczba zablokowanych stron procesu
    def_flags flagi - analogicznie jak przy vm_area_struct
    mmap wskaźnik do jednokierunkowej listy struktur vm_area_struct (opisujących spójne obszary pamięci) uporządkowanej w kolejności początkowych adresów obszaru
    mmap_avl wskaźnik do korzenia drzewa AVL struktur vm_area_struct
    mmap_sem semafor do ochrony dostępu do struktury (przy operacjach na mmap i mmap_avl)

    poprzedni nastepny
      Najważniejsze pola struktury vm_area_struct:
    • definicja struktury w pliku include/linux/mm.h
       
      vm_mm wskaźnik do struktury typu mm_struct. Struktura ta opisuje całą pamięć wirtualną pojedynczego procesu.
      vm_start, vm_end odpowiednio przesunięcie początku i końca danego obszaru w przestrzeni adresowej procesu
      vm_inode, vm_offset informacje dotyczące mapowanych plików
     

    poprzedni nastepny
    vm_page_prot pole określa atrybuty ochrony danego obszaru
    vm_pte jeżeli obszar jest segmentem pamięci dzielonej to zawiera identyfikator tego segmentu.
    vm_flags pole określające wszystkie własności danego obszaru
    vm_ops wskaźnik do struktury vm_operations_struct 

    poprzedni nastepny
    Mapowanie plików do pamięci.

    Mapowanie obszaru.

    #ifndef __USE_FILE_OFFSET64

    extern __ptr_t mmap __P ((__ptr_t __addr, size_t __len, int __prot,

    int __flags, int __fd, __off_t __offset));

    #else

    extern __ptr_t mmap __P ((__ptr_t __addr, size_t __len, int __prot,

    int __flags, int __fd, __off_t __offset))
    __asm__ ("mmap64")

    #endif

    #ifdef __USE_LARGEFILE64

    extern __ptr_t mmap64 __P ((__ptr_t __addr, size_t __len, int __prot,

    int __flags, int __fd, __off64_t __offset));

    #endif


    poprzedni nastepny
    Dealokacja mapowań.
    extern int munmap __P ((__ptr_t __addr, size_t __len));
     

    Zmiana zabezpieczenia.
    extern int mprotect __P ((__ptr_t __addr, size_t __len, int __prot));
     

    Synchronizacja obszaru.
    extern int msync __P ((__ptr_t __addr, size_t __len, int __flags));
     


    poprzedni nastepny

     
     
     
     
     

    Zablokowanie stron mapowanych.
    extern int mlock __P ((__const __ptr_t __addr, size_t __len));
     

    Odblokowanie stron mapowanych.
    extern int munlock __P ((__const __ptr_t __addr, size_t __len));
     
     
     

     


    poprzedni nastepny
    Spowodowanie, że wszystkie aktualnie mapowane strony procesu staną się rezydujące w pamięci aż do momentu gdy się je odblokuje przy użyciu funkcji 'munlockall', lub proces się zakończy, lub proces wywoła funkcję 'execve'.

    extern int mlockall __P ((int __flags));

    Wszystkie aktualne mapowania stron procesu w przestrzeni adresowej zostaną odblokowane. 

    extern int munlockall __P ((void));
     


    poprzedni nastepny
    Funkcja brk()








    Funkcja brk() jest wykorzystywana do zmiany rozmiaru segmentu użytkownika procesu.
     

    • przydzielanie segmentu (np. funkcja malloc() w C). 
    • zwalnianie pamięci. 
    • oprócz funkcji brk() w systemie istnieje funkcja sbrk() realizująca to samo zadanie. 


     


    poprzedni nastepny
    Postać funkcji:






    int brk(void *end_data_segment);

    void sbrk(ptrdiff_increment);

     


    poprzedni nastepny



    Wirtualna przestrzeń adresowa





    poprzedni nastepny





    Konwersja adresu liniowego





    poprzedni nastepny



    Konwersja adresu liniowego c.d.



    poprzedni nastepny





    Konwersja adresu liniowego - i386





    poprzedni nastepny

    Konwersja adresu liniowego - i386. Przykład
    Adress logiczny: więc:
    poprzedni nastepny



    Katalog stron



    poprzedni nastepny





    Pośredni katalog stron







    poprzedni nastepny





    Tablica stron







    poprzedni nastepny

    Atrybuty stron

    poprzedni nastepny



    Operacje na PTE i ich atrybutach



    poprzedni nastepny



    Operacje na PTE i ich atrybutach c.d.



    poprzedni nastepny




    STRONICOWANIE
    NA ŻĄDANIE.







    poprzedni nastepny












    poprzedni nastepny













    poprzedni nastepny













    poprzedni nastepny













    poprzedni nastepny



    Odwołania do strony poza pamięcią.





    poprzedni nastepny



    do_no_page()

    void do_no_page(struct task_struct *task,
                    struct vm_areastruct *vma,
                    unsigned long adres,
                    int write_acces)
       

    Argumenty to:
  • informacja o procesie.
  • struktura vma do której należy adres.
  • adres powodujacy błąd.
  • informacja czy błąd spowodował zapis czy odczyt.





  • poprzedni nastepny





    poprzedni nastepny







    poprzedni nastepny





    Mechanizm
    wymiany stron








    poprzedni nastepny



    Kto o to dba?





    poprzedni nastepny
    poprzedni nastepny










    poprzedni nastepny








    poprzedni nastepny



    Próba usunięcia strony zależy od następujących czynników:


    poprzedni nastepny






    poprzedni nastepny






    poprzedni nastepny



    Podręczna pamięć buforowa stron




    poprzedni nastepny



    Struktura pamięci buforowej stron




    poprzedni nastepny



    Działanie funkcji filemap_nopage()






    poprzedni nastepny


    Budowa jądra Linuksa 2.2.12 - pamięć dzielona, urządzenia wymiany, wymiana, pamięć dla jądra.


    Plan wystąpienia


    poprzedni nastepny

    Pamięć dzielona w Linuksie

    Wstęp czyli zastosowania pamięci dzielonej w systemie Linux :


    poprzedni nastepny

    Realizacja pamięci dzielonej w modelu pamięci wirtualnej :


    poprzedni nastepny

    Pliki i urządzenia wymiany


    Zarządzanie plikami i urządzeniami wymiany.

    System Linux obsługuje dwa rodzaje dyskowych urządzen wymiany:

    Informacja o podłączonych urządzeniach przechowywana w tablicy swap_info o rozmiarze określonym przez stałą MAX_SWAPFILES.
    Elementy tablicy są typu:

    struct swap_info_struct
    {
            unsigned int flags;
            kdev_t swap_device;
            struct dentry * swap_file;         /* i-węzeł urządzenia*/
            unsigned short * swap_map;         /* mapa bajtowa dowiązań*/
            unsigned char * swap_lockmap;      /* mapa bitowa */
            unsigned int lowest_bit;
            unsigned int highest_bit;
            unsigned int cluster_next;
            unsigned int cluster_nr;
            int prio;                          /* priorytet */
            int pages;                         /* ile wolnych stron */
            unsigned long max;
            int next;
    }
    
    Urządzenia powiazane w listę priorytetową: pole next.

    Informacja o liście priorytetowej:

    struct swap_list_t {
            int head;                          /* głowa listy */
            int next;                          /* następny do rozpatrzenia */
    }
    

    poprzedni nastepny

    Dołączenie urządzenia wymiany.

    int sys_swapon(const char * specialfile, int swap_flags)

    Jako parametr nazwa pliku specjalnego (urządzenia), oraz flagi, które mogą określać priorytet urządzenia, jeśli nie określają - priorytet zmniejszający się z kolejnymi przyłączonymi urządzeniami jest nadawany automatycznie.

    Funkcja znajduje miejsce w tablicy swap_info, sprawdza uprawnienia, rodzaj urządzenia, inicjalizuje mapy bitową i bajtową, zwiększa liczbę dostępnego miejsca na urządzeniach wymiany (nr_swap_pages) o liczbę stron dostępnych na tym urządzeniu, na koniec wstawia do listy priorytetowej.


    poprzedni nastepny

    Odłączenie urządzenia wymiany.

    int sys_swapoff(const char * specialfile)

    Funkcja usuwa urządzenie z listy, ustawia flagę, mowiącą, że nie można pisać. Stara się zwolnić strony zrzucone na to urządzenie, używając funkcji:
    int try_to_unuse(unsigned int type)

    Jeżeli się uda, zwalnia odpowiednie pole w tablicy swap_info i ustawia flagę, że urządzenie jest nieużywane. Jeżeli się nie uda, wstawia z powrotem do listy i wychodzi z błędem.


    poprzedni nastepny

    Wybór urządzenia do zrzucenia strony

    Algorytm jest realizowany przez funkcje:

    unsigned long get_swap_page(void)

    Funkcja po prostu wybiera zgodnie z priorytetem: najpierw szuka miejsca na urządzeniu wskazywanym przez swap_list.next, jeśli nie znajdzie, to przeszukuje kolejno urządzenia o tym samym priorytecie, następnie zaczyna od początku listy. Kiedy drugi raz zaczyna szukać od początku (swap_list.head ) oznacza to, że nie ma wolnego miejsca i wychodzi z błędem.
    Jeśli się powiedzie zwracana liczba ma w sobie zakodowany numer urządzenia i numer ramki na tym urządzeniu.
    Wskaźnik swap_list.next jest ustawiany zgodnie z priorytetem.


    poprzedni nastepny

    Zapis/odczyt z urządzenia


    rw
    określa czy ma być wykonany odczyt czy zapis
    entry
    zakodowany numer urządzenia i ramki
    page
    opis strony
    wait
    operacja synchroniczna/asynchroniczna

    Algorytm zapisu/odczytu



    poprzedni nastepny

    Przydział miejsca na stronę

    Przydział miejsca na stronę - przykład



    poprzedni nastepny

    Zwolnienie miejsca na stronę


    poprzedni nastepny

    Przydział pamięci dla jądra Linuksa w wersji 2.2.12.


    poprzedni nastepny

    Cache

    Cache - logiczna struktura zawierająca stan wolnej i przydzielonej pamięci. Operacje: Inicjacja obiektów - tylko raz, przy konstrukcji: operacje przydziału i zwalniania obiektów bardzo proste.
    poprzedni nastepny

    Tafle (ang. slab).

    Tafla - logiczna struktura zawierająca listę buforów tego samego rozmiaru Korzyści:
    poprzedni nastepny

    Bufory.


    poprzedni nastepny

    Struktura kmem_cache_s.


    poprzedni nastepny

    Struktura kmem_slab_s.


    poprzedni nastepny

    Struktura kmem_bufctl_s.


    poprzedni nastepny

    Przydział pamięci dla jądra - kmalloc().

    1. Wyszukaj cache z cache_sizes dla obiektów o rozmiarach minimalnych większych od żądanego rozmiaru.
    2. Zablokuj przerwania.
    3. Jeśli wszystkie tafle są zapełnione - idź do (i).
    4. Jeśli żądanie dotyczy pamięci z dostępem bezpośrednim - idź do (j).
    5. Jeśli w liście tafli nie ma wskaźnika do wolnego obiektu - idź do (h).
    6. Odblokuj przerwania.
    7. Zakończ się zwracając obliczony wskaźnik do wolnego obiektu.
    8. Zaktualizuj wskaźnik do pierwszej tafli z wolnymi obiektami. Idź do (f).
    9. Dodaj nową taflę z wolnymi obiektami i idź do (c) (jeśli się to nie powiedzie - wyjdź z błędem).
    10. Zwróć taflę z obiektami w pamięci z dostępem bezpośrednim. Idź do (e).

    poprzedni nastepny

    Zwalnianie pamięci - kfree().

    1. Jeśli adres zwalnianego wskaźnika jest błędny, to wyjdź z błędem.
    2. Znajdź strukturę opisującą bufor (kmem_bufctl_s) z uwzględnieniem miejsca położenia w pamięci (na liście s_index lub bezpośrednio za buforem - sprawdzenie flagi w c_flags).
    3. Sprawdź poprawność wywołania przez sprawdzenie flagi s_magic - jeśli jest niewłaściwa, to wyjdź z błędem.
    4. Zwolnij pamięć pod wskazanym adresem i zaktualizuj struktury zarządzające pamięcią. Zakończ się sukcesem.

    poprzedni

    Uwagi końcowe.