Radomir Małaczek
Algorytm tłumaczenia adresu odbywa się w dwóch fazach:
Zarówno segmentacja i stronicowanie realizowane są przez sprzętowe jednostki. Dotychczas w jądrach serii 2.2 mechanizm stronicowania dla procesorów serii i386 był redukowany od dwupoziomowego - dla procesorów 32 bitowych, zdolnych adresować maksymalnie 4GB pamięci mechanizm dwupoziomowy był całkiem wystarczający. Trójpoziomowy mechanizm używany jest tylko dla procesorów 64 bitowych. Jednak w najnowszych procesorach Intela pojawiło się rozszerzenie zwane PAE (Physical Address Extension) umożliwiające zaadresowanie max. 64 GB pamięci. Przy takiej adresacji pamięci procesor korzysta z trójpoziomowego mechanizmu stronicowania. Jądra serii 2.4 wykorzystują już to rozszerzenie procesora.
Mechanizmy segmentacji i stronicowania są trochę nadmiarowe, twórcy Linuxa postanowili położyć nacisk na mechanizmy stronicowania, używając segmentacji w bardzo ograniczonym zakresie -- głównie do ochrony pamięci.
W Linuxie zdefiniowane jest tylko kilka segmentów. Wszystkie one
przechowywane są w Globalnej Tablicy Deksryptorów (Global Description Table).
Każdy proces może posiadać własną tablicę deskryptorów (jeśli zechce).
Istotną zmianą w stosunku do jąder serii 2.2 jest to, że w GDT nie są
przechowywane ani TSS (Task Segment Struct), ani LDT (Local Description
Table) procesów. Nakładało to istotne ograniczenie na liczbę jednoczesnych
procesów. W obecnych jądrach w GDT przechowywane są TSS aktualnie wykonywanego
procesu na każdym z procesorów (to oczywiście przekłada się na ograniczenie
na liczbę obsługiwanych procesorów, ale jeszcze długo nie będzie ono tak
istotne jak poprzednie ograniczenie). Ciekawym rozwiązaniem jest zostawianie
dwóch wolnych pozycji między poszczególnymi wpisami TSS i LDT każdego z
procesorów. Dzięki takiemu podejściu informacja dla każdego procesora
wyrównana jest do 32 bajtów, więc tylko ile ma linia pamięci podręcznej
procesora. Lista deskryptorów przechowywanych w GDT znajduje się w tabeli
.
Adres logiczny składa się z dwóch części: segmentu i offsetu (przesunięcia).
segment | : | offset | ||
selektor segmentu | przesunięcie względem początku segmentu | |||
16 bitów | 32 bity |
Możliwymi selektorami segmentu mogą być rejestry: cs, ss,
ds, es, fs, gs. Rejestr cs przechowuje
segment kodu, ss - segment stosu, ds
- segment danych statycznych i zewnętrznych. Pozostałe segmenty mogą być
dowolnie wykorzystywane przez programistę. W tabeli opisane
jest znaczenie poszczególnych bitów selektora.
Każdy segment jest reprezentowany przez deskryptor segmentu. Adres
tablicy tych deskryptorów (GDT) trzymana jest w 32bitowym rejestrze
procesora gdtr. Tabeli GDT zajmuje w pamięci jedną stronę (4KB), a
rozmiar jednego deskryptora to 8 bajtów (64 bity). Tabele LDT działają
podobnie. Deskryptory przechowywane przez nie mają identyczny format, a
tabela zajmuje 4KB pamięci. Opis pojedynczego wpisu w tych tabelach
(deskryptor segmentu) opisany jest w tabeli .
|
|
Aby przyspieszyć mechanizm segmentacji, za każdym razem gdy zmieniamy zawartość selektora segmentu deskryptor segmentu jest automatycznie ładowany do odpowiedniego nieprogramowalnego rejestru procesora.
Mechanizm segmentacji nie jest mocno wykorzystywany przez Linuksa. Mało tego, wspominałem o rozszerzeniu PAE, które pozwala adresować 64GB pamięci na nowych procesorach Intela. Przy wyłączonym mechanizmie stronicowania, nie uda nam się zaadresować pamięci powyżej 4GB. Już ten fakt pokazuje, że segmentacja nie jest wystarczającym mechanizmem przy projektowaniu nowoczesnego systemu operacyjnego.
Mechanizm segmentacji może różnić się na różnych architekturach, przez co nie jest zbyt przenośnym mechanizmem. Na niektórych procesorach segmentacja sprzętowa właściwie nie istnieje. Praktycznie każdy procesor wspierający mechanizm segmentacji inaczej ją implementuje. Stronicowanie dzięki swej prostocie jest zrealizowane praktycznie tak samo na różnych architekturach.
Linux ma zaimplementowany niezależny od platformy trójpoziomowy mechanizm stronicowania. Jednak architektura procesorów rodziny i386 do niedawna pozwalała zaadresować tylko 4GB pamięci. Mechanizm trójpoziomowy jest w takim przypadku zbyteczny -- sprzętowa jednostka adresowania w takich procesorach realizuje mechanizm dwupoziomowy. Implementacja stronicowania w Linuksie dla tych procesorów ignoruje po prostu środkowy poziom.
Niedawno, w procesorach Pentium Pro i lepszych, wprowadzono rozszerzenie zwane PAE (Phisical Address Extension), które pozwala na zaadresowanie na tych procesorach 64GB pamięci RAM. Rozszerzenie to dotyczy tylko adresów fizycznych, adres liniowy wciąż jest 32 bitowy, przez co tylko 4GB pamięci mogą być ,,stale odwzorowane'' przez jądro. Dodatkowo -- możliwe jest uzywanie bloków stronicowych w wielkości 2MB.
Dla takich procesorów zmienił się również mechanizm stronicowania. Dla tych procesorów używa się trójpoziomowego stronicowania. Linux wspiera to rozszerzenie dopiero od wersji jądra 2.4.
Warto odnotować, że ani AMD K5, ani AMD K6 nie obsługuje tego rozszerzenia. Dopiero procesor AMD Athlon posiada PAE.
W procesorach Pentium (i AMD K5) wprowadzono inne ciekawe rozszerzenie -- PSE (Page Size Extension). Rozszerzenie to (przy nie używanym PAE) pozwala na stosowanie bloków stronicowych o wielkości nie tylko 4KB, ale też 4MB.
W Linuksie zdefiniowano trzy rodzaje tablic stronicowania:
Adres tablicy PGD przechowywany jest w rejestrze cr3, który zachowywany jest w TSS procesu1. Dzięki temu każdy proces posiada własny globalny katalog stron.
W stronicowaniu dwupoziomowym zrezygnowano z Pośredniego Katalogu Stron (PMD).
Oto wycinek pliku pgtable-2level.h:
/* Od którego bitu zaczyna się indeks z Page Global Directory */ #define PGDIR_SHIFT 22 /* Liczba pozycji w Page Global Directory */ #define PTRS_PER_PGD 1024 /* Od którego bitu zaczyna się indeks PMD (jak widać równy PGDIR_SHIFT) */ #define PMD_SHIFT 22 /* Rozmiar Page Middle Directory */ #define PTRS_PER_PMD 1 /* Liczba pozycji w Tablicy Stron */ #define PTRS_PER_PTE 1024Adres liniowy interpretowany jest w następujący sposób:
Dla stron 4KB:
bity | |
31-22 | Pozycja w PGD (Globalnym Katalogu Stron), 10 bitów |
21-12 | Pozycja w PTE (Tablicy Stron), 10 bitów |
11-0 | Przesunięcie w stronie |
Dla stron 4MB:
bity | |
31-22 | Pozycja w PGD (Globalnym Katalogu Stron), 10 bitów |
21-0 | Przesunięcie w stronie |
Opis wpisów w tabelach stron i zawartość rejestru cr3 w tableli
.
|
Oto wycinek pliku pgtable-3level.h:
/* PGDIR_SHIFT determines what a top-level page table entry can map */ #define PGDIR_SHIFT 30 #define PTRS_PER_PGD 4 /* PMD_SHIFT determines the size of the area a middle-level page table can map */ #define PMD_SHIFT 21 #define PTRS_PER_PMD 512 /* entries per page directory level */ #define PTRS_PER_PTE 512
Tu interpretacja adresu liniowego jest następująca:
Strony 4KB:
bity | |
30-31 | Pozycja w PGD (Globalnym Katalogu Stron), 2 bity |
21-29 | Pozycja w PMD (Pośrednim Katalogu Stron), 9 bitów |
12-20 | Pozycja w PTE (Tablicy Stron), 9 bitów |
0-11 | Przesunięcie w stronie |
Strony 2MB:
bity | |
30-31 | Pozycja w PGD (Globalnym Katalogu Stron), 2 bity |
21-29 | Pozycja w PMD (Pośrednim Katalogu Stron), 9 bitów |
0-20 | Przesunięcie w stronie |
Deskryptory tablicśtron przechowywane w PGD, PMD i PTE różnią się od
przypadku stronicowania dwupoziomowego. Różnica polega na tym, że adres
bazowy kolejnej tablicy czy strony podawany jest jako adres 36-bitowy
(konkretnie tylko 24 strasze bity, ponieważ adresy tablic i same strony
są wyrównane do 4 KB). Format takiego deskryptora opisany jest w tabeli
.
|
Oczywistą zauważalną różnicą jest zmiana rozmiaru deskryptora. Dla architektur wspierających PAE (i oczywiście ustawionym bicie używania PAE) wszystkie katalogi i tablice stron są teraz 64 bitowe.
Szybki rzut oka na plik linux/include/page.h tylko potwierdza ten fakt:
... #if CONFIG_X86_PAE typedef struct { unsigned long pte_low, pte_high; } pte_t; typedef struct { unsigned long long pmd; } pmd_t; typedef struct { unsigned long long pgd; } pgd_t; #define pte_val(x) ((x).pte_low | ((unsigned long long)(x).pte_high << 32)) #else typedef struct { unsigned long pte_low; } pte_t; typedef struct { unsigned long pmd; } pmd_t; typedef struct { unsigned long pgd; } pgd_t; #define pte_val(x) ((x).pte_low) #endif ...
Adres Globalnego Katalogu Stron, dotąd przechowywany w cr3, również uległ nieznacznej zmianie.