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

Wprowadzenie

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.

Informacje ogolne

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).
\includegraphics[scale=0.5]{adres.eps}
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!

Obsługa tablic stron

Wprowadzenie

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:
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(pmdj);+
pmd_free(pmd);
}


Szczegóły dotyczące wykorzystania znajdą się w częściach poświęconych konkretnym algorytmom.

Algorytm tłumaczenia adresu - Intel

W czym mamy problem?

Tłumaczenie przebiega w dwóch etapach: Obie te części są ściśle związane ze sprzętem, system operacyjny w zasadzie wspomaga te operacje.

Jak przetłumaczyć adres logiczny na liniowy?

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.

Jak przetłumaczyć adres liniowy na fizyczny?

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.

Pożegnanie

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