Projekt: "Ćwiczenia z budowy systemu operacyjnego Linux 2.4.7"
Obsługa tablicy stron
Algorytm tłumaczenia adresu (Intel).
Janusz Kuligowski, nr albumu 171815
Grudzień 2001
W poniższym tekście przedstawiam podstawowe informacje dotyczące tłumaczenia
adresu na procesorach serii i386 oraz obsługi
tablicy stron. Tekst ten zawiera zatem informacje wstępne,
których znajomość okaże się przydatna przy zgłębianiu tematów zawartych w
następnych rozdziałach.
W systemie Linux stosowana jest segmentacja z trójpoziomowym stronicowaniem: na
pierwszym poziomie znajduje się główny katalog stron - PGD
(ang. page global directory). Zawiera on wskaźniki do podkatalogów
stron PMD (ang. page middle directory). Podkatalogi te z kolei
przechowują wskaźniki do właściwych tablic stron - PTE (ang. page
table entry).
W architekturze i386 zastosowano jednak mechanizm dwupoziomowego stronicowania
(główny katalog stron zawiera wskaźniki do tablic stron). Środkowy poziom
jest zatem emulowany w jądrze systemu. (Problem ten
nie ma miejsca w procesorach Pentium Pro, który obsługuje stronicowanie
trójpoziomowe). Najnowsze modele procesorów Intel posiadają
rozszerzenie PAE - Physical Address Extension, które zwiększa przestrzeń
adresową do 64 GB. Jądra w wersjach 2.4.x umożliwiają wykorzystanie
tego rozszerzenia (realizując w ten sposób pełną, trójpoziomową segmentację).
W dalszej części zajmę się jednak wersją dla architektury i386
Jak wiadomo, rozmiar strony (i zarazem ramki) w pamięci wynosi 4096 bajtów.
Procesory architektury i386 korzystają z adresów 32 bitowych.
Należy zatem tak dobrac wielkości katalogów stron, aby jak najdokładniej
wykorzystać pamięć. Tak też się dzieje. Na jednej stronie o rozmiarze 4096 bajtów
można przechować 1024*4B, a ponieważ adres składa się z 32 bitów -
daje nam to możliwość przechowania na jednej stronie 1024 adresów. Wybór
jednego z 1024 adresów wymaga 10 bitów. Mamy dwa poziomy stronicowania. Łącznie
daje nam to zatem:
2 * 10 + 12 = 32 bity do identyfikacji komórki w pamięci. I proszę - zgadza
się to całkowicie z wielkością adresu na Intelu!
Głównym zastosowaniem struktury katalogów stron jest obsługa dostępu do tych
stron, czyli
umożliwienie przetłumaczenia adresu liniowego na adres fizyczny.
Jak wiemy, rozmiar strony wynosi 4096 bajtów.
W pliku /include/asm-i386/pgtable.h znajduje się stałe i makra potrzebne przy
obsłudze tablic stron. Dowiadujemy się z niego między innymi, że wirtualna przestrzen adresowa jądra
zaczyna się 8MB za końcem pamięci fizycznej - zabezpiecza to przed skutkami "mazania po pamięci".
W celu przyspieszenia odwołań do pamięci stosuje się tzw. bufory TLB (ang. Translation Look-aside
Buffers). Jest to rodzaj pamięci podręcznej procesora (o bardzo szybkim dostępie), w którj
przechowuje się najczęściej używane pozycje tablic stron. Jeśli w TLB znajduje się adres
strony, której szukamy, wówczas oszczędzamy dużo czasu, który potrzebowalibyśmy na odnalezienie szukanej
strony poprzez drzewo katalogów stron.
Możemy tam też znaleźć definicję bitów ochrony stron - zdefiniowane jak następuje:
#define _PAGE_BIT_PRESENT 0
#define _PAGE_BIT_RW 1
#define _PAGE_BIT_USER 2
#define _PAGE_BIT_PWT 3
#define _PAGE_BIT_PCD 4
#define _PAGE_BIT_ACCESSED 5
#define _PAGE_BIT_DIRTY 6
#define _PAGE_BIT_PSE 7
#define _PAGE_BIT_GLOBAL 8
/* Global TLB entry PPro+ */
#define _PAGE_PRESENT 0x001
#define _PAGE_RW 0x002
#define _PAGE_USER 0x004
#define _PAGE_PWT 0x008
#define _PAGE_PCD 0x010
#define _PAGE_ACCESSED 0x020
#define _PAGE_DIRTY 0x040
#define _PAGE_PSE 0x080
/* 4 MB (or 2MB) page, Pentium+, if present.. */
#define _PAGE_GLOBAL 0x100
/* Global TLB entry PPro+ */
#define _PAGE_PROTNONE 0x080
/* If not present */
#define _PAGE_TABLE (_PAGE_PRESENT | _PAGE_RW | _PAGE_USER |
_PAGE_ACCESSED | _PAGE_DIRTY)
#define _KERNPG_TABLE (_PAGE_PRESENT | _PAGE_RW | _PAGE_ACCESSED |
_PAGE_DIRTY)
#define _PAGE_CHG_MASK (PTE_MASK | _PAGE_ACCESSED | _PAGE_DIRTY)
#define PAGE_NONE __pgprot(_PAGE_PROTNONE | _PAGE_ACCESSED)
#define PAGE_SHARED __pgprot(_PAGE_PRESENT | _PAGE_RW | _PAGE_USER |
_PAGE_ACCESSED)
#define PAGE_COPY __pgprot(_PAGE_PRESENT | _PAGE_USER |
_PAGE_ACCESSED)
#define PAGE_READONLY __pgprot(_PAGE_PRESENT | _PAGE_USER |
_PAGE_ACCESSED)
#define __PAGE_KERNEL \
(_PAGE_PRESENT | _PAGE_RW | _PAGE_DIRTY | _PAGE_ACCESSED)
#define __PAGE_KERNEL_NOCACHE \
(_PAGE_PRESENT | _PAGE_RW | _PAGE_DIRTY | _PAGE_PCD | _PAGE_ACCESSED)
#define __PAGE_KERNEL_RO \
(_PAGE_PRESENT | _PAGE_DIRTY | _PAGE_ACCESSED)
Stąd widać, że najważniejsze bity (których powstają inne poprzez złożenie) to:
- PRESENT - informuje, czy strona jest obecna w pamięci głównej,
- RW - 0 oznacza stronę tylko do odczytu, 1 do odczytu i zapisu,
- USER - 0 oznacza dostęp tylko w trybie jądra, 1 także w trybie użytkownika,
- ACCESSED - ustawiany na 1 przy każdym odwołaniu do strony (służy do oceny jej wieku),
- DIRTY - 1 oznacza, że strona musi zostać zapisana na dysk przed usunięciem z pamięci.
Znajdują się tam też funkcje służące do testowania i
ustawiania bitów ochrony - oto kilka przykładowych:
static inline int pte_read(pte_t pte) return (pte).pte_low & _PAGE_USER;
static inline int pte_exec(pte_t pte) return (pte).pte_low & _PAGE_USER;
static inline int pte_dirty(pte_t pte) return (pte).pte_low & _PAGE_DIRTY;
static inline int pte_young(pte_t pte) return (pte).pte_low &
_PAGE_ACCESSED;
static inline int pte_write(pte_t pte) return (pte).pte_low & _PAGE_RW;
static inline pte_t pte_rdprotect(pte_t pte) {
(pte).pte_low &= ~_PAGE_USER; return pte; }
static inline pte_t pte_exprotect(pte_t pte) {
(pte).pte_low &= ~_PAGE_USER; return pte; }
static inline pte_t pte_mkclean(pte_t pte) {
(pte).pte_low &= ~_PAGE_DIRTY; return pte; }
static inline pte_t pte_mkold(pte_t pte) {
(pte).pte_low &= ~_PAGE_ACCESSED; return pte; }
static inline pte_t pte_wrprotect(pte_t pte) {
(pte).pte_low &= ~_PAGE_RW; return pte; }
Jak widać - sa to proste operacje na bitach.
Funkcje czyszczące tablice stron, zwalniające te tablice itp. można znaleźć w pliku
/mm/memory.c - są to proste funkcje, jako przykład mogą posłużyć następujące:
static inline void free_one_pmd(pmd_t * dir)
{
pte_t * pte;
if (pmd_none(*dir))
return;
if (pmd_bad(*dir)) {
pmd_ERROR(*dir);
pmd_clear(dir);
return;
}
pte = pte_offset(dir, 0);
pmd_clear(dir);
pte_free(pte);
}
static inline void free_one_pgd(pgd_t * dir)
{
int j;
pmd_t * pmd;
if (pgd_none(*dir))
return;
if (pgd_bad(*dir)) {
pgd_ERROR(*dir);
pgd_clear(dir);
return;
}
pmd = pmd_offset(dir, 0);
pgd_clear(dir);
for (j = 0; j < PTRS_PER_PMD ; j
+)+
free_one_pmd(pmd
j);+
pmd_free(pmd);
}
Szczegóły dotyczące wykorzystania znajdą się w częściach poświęconych konkretnym algorytmom.
Tłumaczenie przebiega w dwóch etapach:
- segmentacji - tłumaczenia adresu logicznego na liniowy
- stronicowania - tłumaczenia adresu liniowego na fizyczny.
Obie te części są ściśle związane ze sprzętem, system operacyjny w zasadzie
wspomaga te operacje.
Adres logiczny składa się z 48 bitów: 16 bitów oznacza identyfikator
selektora segmentu, 32 bity - przesunięcie względem początku segmentu (tzw.
offset).
Aby otrzymać adres liniowy należy zatem odczytać adres początku segmentu,
wykorzystując selektor segmentu oraz tablice deskryptorów - Global
Description Table albo Local Description Table (w zależności
od informacji zawartej w selektorze) a następnie dodać do otrzymanej wartości
przesunięcie (offset) - i już mamy adres liniowy.
Adres liniowy składa się z 32 bitow. Pierwsze 10 bitów oznacza pozycję w
głównym katalogu stron. Następne 10 bitów to informacja o pozycji w tablicy
stron. Najmłodsze 12 bitow jest już adresem wewnątrz strony.
Co to oznacza? Otóż najpierw odczytujemy pierwsze dziesięć bitów. Znajdujemy
opisaną przez nie pozycję w głównym katalogu stron. Jest to wskaźnik do
tablicy stron. W tej tablicy odnajdujemy pozycję o numerze opisanym
przez następne 10 bitów naszego adresu. Otrzymujemy w ten sposób wskaźnik
do strony w pamięci, w ktorej znajduje się komórka pamięci, którą chcemy
odczytać. Ostatnie 12 bitów oznacza pozycję na stronie - gdyż strona ma wielkość
4096 B, czyli adresy wewnątrz niej są opisywanę za pomocą 12 bitów.
Na tym kończę podstawowe informacje o obsłudze tablic stron i tłumaczeniu
adresu. Szczegóły wykorzystania wspomnianych przeze mnie struktur i funkcji zostaną omówione w dalszych rozdziałach
prezentacji.
Dziękuję za lekturę!
Janusz Kuligowski
Janusz Kuligowski, nr albumu 171815