Wstęp
Tryb wirtualny to specjalny tryb działania układu mikroprocesora, który stosowany był w procesorach rodziny Intel typu 80x86. 16-bitowy mikroprocesor 80286 był pierwszym mikroprocesorem przystosowanym do tzw. pracy wielozadaniowej i charakteryzującym się tym, iż miał możliwość pracy w dwóch trybach:
adresowania rzeczywistego (ang. Real Address Mode). Tryb ten polega na tym, iż mikroprocesor ten działa jak szybki procesor typu 8086.
adresowania wirtualnego z tzw. ochroną (ang. Protected Virtual Address Mode). Tryb ten udostępnia środowisku systemu operacyjnego trzy podstawowe mechanizmy:
wielozadaniowości (ang. multitasking)
pamięci wirtualnej (ang. virtual memory)
ochrony zadań (ang. task protection).
Zaczynając od mikroprocesora 80286, można powiedzieć, że sposób określania adresu fizycznego pamięci zależy od trybu pracy mikroprocesora.
W trybie rzeczywistym sposób określenia adresu fizycznego pamięci jest taki sam jak w mikroprocesorze 8086 lub 8088. Czyli 20-bitowy adres pamięci jest tworzony przez dodanie przesuniętej o cztery bity w lewo zawartości odpowiedniego rejestru segmentu do przemieszczenia w segmencie. W tym trybie mikroprocesor może zaadresować jedynie 1 MB pamięci fizycznej, tak samo jak starsze typy mikroprocesorów rodziny 80x86 firmy Intel.
Adresowanie w trybie wirtualnym różni się od adresowania w trybie rzeczywistym sposobem określenia adresu segmentu w fizycznej przestrzeni adresowej. W trybie tym przestrzeń adresowa widoczna dla programów wykonywanych przez mikroprocesor - tzw. przestrzeń adresów logicznych lub przestrzeń pamięci wirtualnej jest również podzielona na segmenty. W tym trybie każdy segment znajdujący się w pamięci fizycznej ma określony 24-bitowy adres bazowy i wielkość. W tym trybie mikroprocesor 80826 może zaadresować już 16 MB pamięci fizycznej. Położenie segmentu w przestrzeni pamięci, jego wielkość i atrybuty określa w trybie wirtualnym zawartość deskryptora segmentu. Mechanizmy segmentacji zapewniają tłumaczenie adresu wirtualnego (logicznego) na adres fizyczny pamięci, ochronę zasobów zadania przed innymi zadaniami oraz wspomagają realizację pamięci wirtualnej.
Następcą procesora 80286 jest pierwszy 32-bitowym procesor firmy Intel - 80386. Układ ten wykonywany jest w dwu wersjach różniących się szerokością magistrali danych: SX 16-bitowa, DX - 32 bitowa. Różnica ta ma głównie wpływ na szybkość procesora. Procesor posiada 32-bitową magistralę adresową, co umożliwia zaadresowanie aż 4 GB pamięci fizycznej. W mikroprocesorze tym każdy segment w trybie wirtualnym może być umieszczony pod dowolnym adresem w przestrzeni adresów liniowych i mieć wielkość do 4 GB. Znacznie powiększono także wielkość pamięci wirtualnej - aż do 64 TB. W procesor 80386 wbudowano jednostkę stronicowania umożliwiającą między innymi całkowite zerwanie zależności pomiędzy logicznym adresem pamięci widzianym przez program, a realizowanym układowo adresem fizycznym. Całą pamięć dzieli się na bloki, zazwyczaj po 4 KB, których dane (adres fizyczny i atrybuty) umieszczone są w specjalnej tablicy z indeksem, przekodowującej numer strony na jej adres fizyczny.
Sposób określania adresu fizycznego pamięci zależy od trybu pracy mikroprocesora. Procesor 80386 może pracować w dwu a nawet w trzech trybach. W trybie rzeczywistym jest on w pełni zgodny z procesorem 8086. W tym trybie adres fizyczny jest tworzony w sposób identyczny jak w 8086, bez stosowania stronicowania, tak, że obszar pamięci widziany w tym trybie zawsze pokrywa się z początkiem fizycznie występującej w systemie pamięci.
W trybie chronionym stosuje się podobny jak w procesorze 80286 podział na segmenty, z tym, że długość segmentu jest ograniczona do 4 GB - jeden segment może, więc w szczególności obejmować całą pamięć operacyjną. Mechanizm segmentacji wykonuje tłumaczenie adresu wirtualnego na adres liniowy. Dodatkowo na mechanizm ten nakłada się nowy mechanizm stronicowania pamięci tłumaczący adres liniowy na adres fizyczny pamięci, dzięki czemu tracą znacznie ewentualne nieciągłości adresowania.
Dodatkowym trybem jest tryb wirtualny 8086 (virtual 8086 mode) umożliwiający wykonywanie programów przeznaczonych dla mikroprocesora 8086 w środowisku wielozadaniowym oraz charakteryzujący się identycznym jak w trybie rzeczywistym sposobem pracy bloku segmentacji. W trybie rzeczywistym i trybie wirtualnym 8086 sposób określania adresu liniowego (w trybie rzeczywistym jest to jednocześnie adres fizyczny) pamięci jest taki sam jak w mikroprocesorze 8086. 20-bitowy adres pamięci jest tworzony przez dodanie przesuniętej o cztery bity w lewo zawartości odpowiedniego rejestru segmentu do przemieszczenia w segmencie. W trybie rzeczywistym mikroprocesor może zaadresować jedynie 1 MB pamięci fizycznej, tak samo jak starsze typy mikroprocesorów. W trybie wirtualnym 8086 jednomegabajtowy obszar pamięci widoczny dla zadania może być umieszczony, przez wykorzystanie mechanizmu stronicowania, w dowolnym (także nieciągłym trybie) obszarze pamięci fizycznej. Zatem tryb wirtualny 8086 jest pewnym specjalnym podtrybem trybu wirtualnego mikroprocesora 80386 i 486 (nie istnieje w mikroprocesorze 80286). W trybie wirtualnym 8086, adres fizyczny jest otrzymywany poprzez przekodowanie na adres fizyczny adresu widzianego przez wirtualny 8086 za pomocą układu stronicowania. Dzięki temu przy pracy wielozadaniowej każdemu procesowi można przyporządkować własny obszar pamięci, który jest przez program widziany jako ciągły obszar adresowy procesora 8086. Tryb wirtualny pozwala na wykonywanie programów napisanych na procesor 8086 bez konieczności ich modyfikacji.
Architektura procesora x86
Częścią mikroprocesora przeznaczoną do przechowywania adresów i danych oraz wykonywania operacji na nich jest rejestr. Dowolna operacja matematyczna czy logiczna jest wykonywana na danych umieszczonych w rejestrach. Każde odwołanie do komórki pamięci operacyjnej wymaga podania jej adresu (numeru), który także przechowywany jest w rejestrach. Wydajność procesora zależy między innymi właśnie od liczby i rozmiaru wbudowanych rejestrów.
Mikroprocesor 80386 posiada wszystkie rejestry mikroprocesora 8086 oraz kilka dodatkowych. Wszystkie rejestry z wyjątkiem segmentowych są 32-bitowe.
Wyróżniamy więc 8 rejestrów ogólnego przeznaczenia: EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP.
Do 32-bitowych rejestrów zaliczamy jeszcze licznik rozkazów EIP i rejest flagowy EFLAGS. Młodsze słowo (bity 15..0) każdego z powyższych rejestrów to znane z procesora 8086 rejestry 16-bitowe: AX, BX, CX, DX, SI, DI, BP, SP oraz IP i FLAGS.
Oprócz czterech rejestrów segmentowych znanych z 8086: CS, DS, ES i SS mikroprocesor 80386 ma dodatkowe dwa rejestry segmentowe: FS i GS. Rejestry segmentowe są nadal 16-bitowe.
Dodatkowe rejestry mikroprocesora 80386:
GDTR (Global Descriptor Table Register) - 48-bitowy rejestr składający się z dwóch części: 32-bitowy adres liniowy globalnej tablicy deskryptorów i 16-bitowa wielkość tej tablicy.
IDTR (Interrupt Descriptor Table Register) - również 48-bitowy rejestr zawierający: 32-bitowy adres liniowy tablicy deskryptorów przerwań i 16-bitową wielkość tej tablicy.
LDTR (Local Descriptor Table Register) - rejestr 64-bitowy złożony z 16-bitowego selektora lokalnej tablicy deskryptorów, 32-bitowego adresu tablicy LDT i 16-bitowej wielkości tej tablicy; każdy proces w systemie wielozadaniowym dysponuje własną, lokalną tablicą deskryptorów.
TR (Task Register) - 64-bitowy rejestr bieżącego procesu
CR0, CR1, CR2, CR3, (CR4 dla procesora Pentium) - 32-bitowe rejestry kontrolne (Control Registers); służą do serowania rybem pracy procesora i trybem pracy bloku stronicowania
rejestr CR0:
:: PG - bit stronicowania
:: ET - bit typu koprocesora
:: TS - bit przełączenia zadania
:: EM - bittrybu emulacji koprocesora
:: MP - bit obserwacji koprocesora
:: PE - bit trybu wirtualnego
budowa rejestru CR3 jest pokazana przy stronicowaniu
DR0, DR1, DR2, DR3, DR4 DR5, DR6, DR7 - 32-bitowe rejestry uruchomieniowe; służą do sterowania trybem pracy procesora i trybem pracy bloku stronicowania
:: LEN0, LEN1, LEN2, LEN3 - 2 bitowe pole określające długość danych dla adresów podanych w DR0...DR3 (00 - 1 bajt, 01 - 2 bajty, 10 - zarezerwowane, 11 - 4 bajty)
:: R/W0, R/W1, R/W2, R/W3 - 2 bitowe pole określające rodzaj dostępu do pamięci dla poszczególnych pułapek (00 - wykonanie instrukcji, 01 - tylko zapisanie danych, 10 - zarezerwowane, 11 - zapisanie lub odczytanie danych)
:: GE (Global Exact breakpoint) i LE (Local Exact breakpoint) - wymuszają dokładne wskazywanie w programie tej instrukcji, która odwoływała się do dancyh objętych pułapką
:: L0...L3, G0...G3 - pierwsza czwórka bitów odblokowuje pułapki traktowane jako pułapki lokalne, druga czwórka bitów odblokowuje pułapki globalne
:: GD (Debug register access detect) - możliwość ustawienia pułapki na odwołaniu do rejestrów uruchomieniowych
:: B0...B3 - pułapka na adresie określonym przez DR0, LEN0 i R/W0 ... pułapka na adresie określonym przez DR3, LEN3 i R/W3
:: BD - odwołanie do rejestrów uruchomieniowych
:: BS - praca krokowa
:: BT - pułapka przełączania zadania
TR6, TR7, (TR12 dla procesora Pentium)(Test Registers) - 32-bitowe rejestry testowe; służą do testowania poprawności działania pamięci asocjacyjnej elementów tablic stron (TLB). Rejestry te wykorzystywane są także podczas autotestowania procesora
Podstawowe zagadnienia dotyczące pamięci wirtualnej
Na początek co zapewnia nam pamięć wirtualna:
"process isolation" (rozłączność procesów) - dwa procesy posiadają rozłączne przestrzenie adresowe i nie mają (mają ograniczone) możliwości ingerencji w przestrzeń adresową innych procesów
ochrona pamięci - CPU może pracować w kilku trybach (przynajmniej dwóch), a adresy mogą być zaznaczane jako "valid" tylko w odpowiednim trybie
brak limitów pamięci - system może symulować istnienie większej ilości pamięci niż jest w rzeczywistości
wsparcie dla nieciągłej i nieużywanej pamięci - system nie musi alokować pamięci fizycznej na przestrzeń adresową która nie jest aktualnie w użyciu
wsparcie dla współdzielenia pamięci między dwoma przestrzeniami adresowymi - odkąd wirtualne adresowanie zostało zaimplementowane za pomocą nie-bezpośredniego adresowania tablicowego, dane mogą być współdzielone
wsparcie dla pamięci "copy-on-write" - system może pozwolić na współdzielenie pamięci między procesami do czasu gdy jeden z nich zaczyna do niej pisać - wtedy pamięć jest kopiowana (proces, który nie zapisywał ma starą kopię pamięci, a piszący wersję z własnymi zmianami) i kończy się współdzielenie
Realizacja pamięci wirtualnej (logicznej) o wielkości większej niż rozmiar faktycznie zainstalowanej pamięci operacyjnej polega na uzupełnieniu pamięci operacyjnej przez pomocniczą pamięć masową i na odpowiednim zarządzaniu przesyłaniem danych między tymi pamięciami. Pamięć dyskowa służy do przechowywania obrazów tych fragmentów pamięci (logicznej), które aktualnie nie mieszczą się w pamięci fizycznej. Niezbędne w trakcie wykonywania programu fragmenty (kod, dane) są odczytywane z dysku i umieszczane w pamięci operacyjnej (swap-in), z kolei niektóre fragmenty spośród obecnych w pamięci muszą być przenoszone na dysk w celu uzyskania wolnego miejsca (swap-out).
Zarządzaniem pamięcią wirtualną zajmuje się system operacyjny korzystając z mechanizmów wbudowanych w mikroprocesor - odbywa się to bez udziału programu. Realizacja pamięci wirtualnej w systemach z mikroprocesorami 80286, 80386 i i486 jest ściśle związana z segmentacją i stronicowaniem (tylko w przypadku procesora 80386 i i486) - fragmenty pamięci wymieniane między pamięcią operacyjną a pamięcią pomocniczą odpowiadają bowiem segmentom lub stronom.
Tu należy wspomnieć o bitach związanych z implementacją pamięci wirtualnej, które występują w deskryptorze segmentów oraz w elementach katalogu tablic i elementach tablic stron. W przypadku deskryptorów segmentów są to: bit obecności P (Present) i bit A (Accessed) oznaczający segment użyty, zaś w odniesieniu do atrybutów elementów tablic związanych ze stronicowaniem - bity P (Present), A (Accessed) i bit zmiany D (Dirty).
W mikroprocesorze 80286 pamięć wirtualna może być zrealizowana jedynie z zastosowaniem segmentacji. Podczas ładowania selektora do rejestru segmentu procesor automatycznie odwołuje się do odpowiedniego deskryptora i sprawdza wartość bitu obecności P. Jeżeli P=1, po sprawdzeniu praw dostępu deskryptor jest umieszczany w ukrytym rejestrze deskryptora stowarzyszonym z rejestrem segmentu. Wartość P=0 oznacza, że segment opisywany przez deskryptor nie jest dostępny w pamięci operacyjnej i w takiej sytuacji procesor generuje wyjątek (exception). Procedura obsługi wyjątku związanego z nieobecnością segmentu w pamięci stanowi fragment systemu operacyjnego i jej zadaniem jest znalezienie miejsca w pamięci na brakujący segment, odczytanie segmentu z dysku, uaktualnienie deskryptora - tzn. wpisanie właściwych wartości adresu bazowego i ewentualnie wielkości oraz ustawienie bitu P. Po zakończeniu obsługi wyjątku następuje ponowne wykonanie instrukcji, która wyjątek spowodowała. Podczas tego ponownego wykonania nie nastąpi już zgłoszenie wyjątku braku segmentu.
Istotnym elementem implementacji pamięci wirtualnej, wpływającym na efektywność działania systemu, jest strategia usuwania segmentów z pamięci operacyjnej w celu uzyskania miejsca do wprowadzenia brakujących segmentów. Często stosowana jest strategia usuwania tych części pamięci, które były najdawniej używane (LRU - Least Recently Used). Strategia taka jest uzasadniona, gdyż ze względu na lokalność odwołań do pamięci w programie zakłada się, że w najbliższej przyszłości nie będzie również do nich odwołań. Inna stosowana strategia polega na usuwaniu z pamięci segmentów najrzadziej używanych (LFU - Least Frequently Used), przy czym w pierwszym rzędzie usuwa się segmenty nie modyfikowane, gdyż nie jest wówczas konieczne zapisywanie obrazów pamięci na dysku. Najprostszą strategią jest usuwanie tych segmentów, które zostały do pamięci załadowane jako pierwsze (FIFO - First In First Out).
Realizacja pamięci wirtualnej w oparciu o mechanizm segmentacji może być stosowana w systemach z mikroprocesorem 80286 jak i 80386 (i486). W tym rozwiązaniu pojawiają się jednak problemy związane z faktem, że segmenty mają różną długość. Często wprowadzenie nowego segmentu do pamięci operacyjnej wymaga usunięcia kilku mniejszych segmentów. Wielokrotne wykonanie operacji wymiany obrazów pamięci z dyskiem prowadzi do znacznego stopnia fragmentacji pamięci tzn. pojawia się wiele małych obszarów pamięci nie używanej (czasem stosuje się przesuwanie segmentów w celu uzyskania dużego spójnego obszaru wolnego). Pewną zaletą, jednak nie rekompensującą wymienionych wad, jest silny związek podziału na segmenty z logiczną strukturą programu, co na ogół zmniejsza liczbę koniecznych przesłań w porównaniu z przedstawioną niżej metodą korzystającą ze stronicowania.
W mikroprocesorach 80386 i i486 wbudowano mechanizmy umożliwiające efektywną realizację pamięci wirtualnej w oparciu o stronicowanie. Podobnie jak w przypadku segmentów, również i tu najważniejszą rolę odgrywa bit obecności P w elementach katalogu tablic stron i samych tablic stron. W sytuacji, gdy następuje odwołanie do strony, dla której w odpowiednim elemencie tablicy stron bit P=0, mikroprocesor zgłasza wyjątek braku strony. Reakcja programu obsługi polega na przygotowaniu wolnego miejsca (strony) w pamięci operacyjnej, wprowadzeniu pożądanej strony i uaktualnieniu opisu strony w tablicy stron. Strategia wymiany stron może korzystać z bitu A, który jest ustawiany przez procesor w następujących okolicznościach: w elemencie tablicy stron - gdy następuje odwołanie do strony opisywanej przez ten element, w katalogu tablic stron - gdy następuje odwołanie do którejkolwiek ze stron opisanych w tablicy stron wskazywanej przez ten element katalogu. Bit ten nie jest nigdy samoczynnie zerowany przez procesor. Dodatkowe ułatwienie dla oprogramowania systemowego stanowi bit D. Jest on ustawiany przez procesor (jedynie w elementach tablicy stron), gdy odbywa się zapis do odwzorowywanej przez element strony. Na podstawie wartości tego bitu system operacyjny może określić, czy podczas usuwania z pamięci danej strony jest konieczne jej zapisanie na dysku, gdyż w trakcie jej obecności w pamięci nastąpiły zmiany, czy że obraz przechowywany na dysku jest właściwy, ponieważ nie było operacji zapisu na danej stronie. Przy realizacji pamięci wirtualnej w oparciu o stronicowanie unika się fragmentacji (wszystkie bloki mają jednakową długość). W systemach z mikroprocesorem 80386 i i486 wskazana jest implementacja pamięci wirtualnej w oparciu o stronicowanie, zaś innych mechanizmów systemowych - z wykorzystaniem segmentacji lub łącznie: segmentacji i stronicowania.
Adres logiczny (wirtualny) jest adresem, do którego odwołuje się proces. Składa się z dwu liczb: 16-bitowego selektora segmentu i 32-bitowego przesunięcia (offsetu).
Adres liniowy jest 32-bitową liczbą oznaczającą adres w wirtualnej przestrzeni adresowej. Przy wyłączonym stronicowaniu jest to jednocześnie adres fizyczny. W trybie rzeczywistym adres liniowy i fizyczny liczy się zgodnie ze wzorem: 16 * SEGMENT + OFFSET.
Adres fizyczny jest adresem wysyłanym z CPU przez szynę adresową magistrali procesora do modułów pamięci RAM. Jest on 32-bitowy, bo taka jest szerokość szyny adresowej.
Segmentacja i stronicowanie funkconują niezależnie od siebie.
Segmentacja
Podawany w programie adres logiczny składa się z selektora przestrzeni logicznej oraz adresu efektywnego (przesunięcia w przestrzeni logicznej). Adres logiczny składający się z selektora i przemieszczenia jest inaczej nazywany dalekim wskaźnikiem adresowym.
Selektor zazwyczaj nie jest podawany bezpośrednio w kodzie rozkazu. Jest on przechowywany w jednym z sześciu rejestrów segmentowych: CS, DS., ES, FS, GS, SS.
Przemieszczenie względem bazowego adresu segmentu jest otrzymywane w wyniku zsumowania zawartości rejestru bazowego (baza przemieszczeń), rejestru indeksowego i przemieszczenia podanego bezpośrednio w kodzie rozkazu. Adres liniowy powstaje z adresu logicznego z wykorzystaniem specjalnych tablic, w których są umieszczone deskryptory segmentów (w trybie rzeczywistym wystarczy pomnożyć selektor przez 16). W trybie wirtualnym powiązanie między selektorem a wyznaczonym przezeń segmentem jest zrealizowane przez deskryptor segmentu.
Deskryptor segmentu pamięci jest to struktura danych zajmująca w pamięci 8 bajtów, opisująca własności segmentu pamięci. Nie wdając się w szczegóły można powiedzieć iż składa się z 3 głównych częśći (przedstawiona poniżej budowa deskryptora oraz mechanizm segmentacji dotyczy procesora 80386):
adres bazowy - 32-bitowy adres liniowy początku segmentu
wielkość - 20-bitowa liczba określająca rozmiar segmentu
atrybuty - dodatkowe informacje m.in. typ segmentu i prawa dostepu, zajmuje 12 bitów
32-bitowy adres bazowy oznacza, że początek segmentu może być ustalony z dokładnością do jednego bajtu (przy założeniu 4GB ograniczenia przestrzeni wirtualnej).
20-bitowa wielkość wcale nie oznacza, że maksymalna wielkość segmentu wynosi 220 bajtów = 1 MB. Dla segmentu powyżej 1 MB wielkość podawana jest w jednostkach 4-kilobajtowych co pozwala uzyskać wielkość segmentu do 4 GB.
Deskryptory umieszczone są w pamięci kolejno tworząc tablice deskryptorów. Selektor segmentu to wskaźnik do tej tablicy, czyli wskazuje na deskryptor segmentu. Deskryptory numerowane są od zera. Zerowy deskryptor jest zarezerwowany. Podczas odwoływania się do pamięci selektor pobierany jest z rejestru segmentowego a przemieszczenie wyliczane zależnie od trybu adresownia, np. może być w rejestrze wskaźnikowym ESI.
Proces tłumaczenia adresu wirtualnego na liniowy można opisać w następujący sposób:
Kiedy proces odwołuje się do pamięci z odpowiedniego rejestru segmentowego (CS, SS, DS, ED, FS, GS) pobierany jest selektor segmentu. Na jego podstawie odnajdowany jest odpowiadający mu deskryptor: wartość w polu TI określa tablicę, numer deskryptora jest indeksem w tej tablicy, a wartość RPL musi być mniejsza lub równa od numeru uprawnienia wskazywanego przez selektor (w przeciwnym przypadku generowane jest przerwanie wewnętrzne). Z deskryptora pobierany jest adres bazowy i dodawany do przesunięcia - w ten sposób wyliczony zostasje adres liniowy.
Selektor segmentu
Do odnajdywania odpowiednich deskryptorów segmentu służy 16-bitowy selektor segmentu. Składa się on z:
13-bitowego numeru deskryptora, oznaczającego indeks w tablicy. Oznacza to, że maksymalna liczba deskryptorów w tablicy może wynosić 213 = 8192; ponieważ rozmiar deskryptora to 8 bajtów więc maksymalna wielkość tablicy deskryptorów wynosi 64 KB
1-bitowego pola TI (Table Indicator) - w związku z istnieniem dwóch tablic deskryptorów: lokalnej i globalnej - bit ten wskazuje o która tablicę chodzi: 0 - globalna, 1 - lokalna
2-bitowego pola RPL (Requestor's Privilege Level) - poziom ochrony zadania żądajacego dostępu
GDT i LDT
Jednostka segmentacji korzysta ze specjalnych struktur danych. Są to:
GDT - tablica opisu segmentów globalnych
LDT - tablice opisu segmentów lokalnych
Stosowanie dwóch tablic deskryptorów umożliwia realizację ochrony pamięci zadań w systemach wielozadaniowych. W systemie może istnieć wiele tablic LDT. Każde zadanie ma zwykle swoją własną lokalna tablicę deskryptorów. Rozmiar logicznej przestrzeni adresowej określa się jako maksymalną wielość pamięci widzianą przez pojedyncze zadanie. Program ma dostęp do tablicy GDT i jednej (własnej) tablicy LDT, może więc potencjalnie korzystać 16384 segmentów (8192 deskryptory w GDT + 8192 w LDT, razem 16K). O wielkości logicznej przestrzeni adresowej decyduje liczba segmentów i ich długość.
Położenie tablic deskryptorów GDT i aktualnie używanej LDT jest określone przez zawartość specjalnych rejestrów procesora - odpowiednio rejestru globalnej tablicy GDTR i rejestru lokalnej tablicy deskryptorów LDTR. Rejestry te pokazano poniżej. Części dostępne programowo - jawnie ładowane - oznaczono podwójną linią.
rejestr GDTR w mikroprocesorze 80386 i i486
Adres bazowy jest liczbą z zakresu 0..232 - 1
Wielkość jest liczbą z zakresu 0 .. 216 - 1
rejestr LDTR w mikroprocesorze 80386 i i486
Adres bazowy jest liczbą z zakresu 0..232 - 1
Wielkość jest liczbą z zakresu 0 .. 216 - 1
Deskryptor segmentu
Deskryptor jest ośmiobajtowym rekordem umieszczonym w tablicy GDT lub LDT. Oto jego szczegółowa budowa (w mikroprocesorze 80386):
32-bitowe pole BASE zawierające adres bazowy, który jest jednocześnie adresem liniowym początku segmentu
1-bitowe pole G (Granularity) zawierające znacznik ziarnistości; ustawiony na 1 oznacza, że "wielkość" jest wyrażona w jednostkach 4-kilobajtowych, wyzerowany - wiekość w bajtach
20-bitowe pole LIMIT zawierające długość segmentu mierzoną w bajtach, gdy G=0 lub w jednostkach 4KB-owych, gdy G=1
3-bitowe pole TYPE określające typ segmentu i prawa dostępu do niego
2-bitowe pole DPL (Descriptor Privilege Level) poziom ochrony segmentu
1-bitowe pole P (Present) zawierające znacznik obecności segmentu w pamięci; ustawiony na 1 - oznacza że segment jest obecny; ustawiany przez system na 0 gdy z braku wolnej pamięci segment zosataje tymczasowo zapisany na dysk do tzw. pamięci wirtualnej (np. pliku wymiany)
1-bitowe pole D (Default) lub B (Big) (nazwa zależy od typu segmentu) określające, czy segment jest adresowny 16, czy 32-bitowo; naczej mówiąc - domyślna długość słowa: 0 - słowo 16-bitowe, 1 - słowo 32-bitowe
1-bitowe pole S wyróżnik deskryptora segmentu programowego; 1 - deskryptor segmentu pamięci, 0 - inny deskryptor
1-bitowe pole A (Accessed) określające segment użyty
1-bitowe pole AVL (Available To Software) do dowolnego wykorzystania przez program/system
Trzy bity typu segmentu oznaczają:
000 - segment danych, tylko odczytywanie
001 - segment danych, odczytywanie i zapisywanie
010 - segment danych rozszerzalny w dół, tylko odczytywanie
011 - segment danych rozszerzalny w dół, odczytywanie i zapisywanie
100 - segment kodu, tylko wykonywanie
101 - segment kodu, wykonywanie i odczytywanie
110 - zgodny segment kodu, tylko wykonywanie
111 - zgodny segment kodu, wykonywanie i odczytywanie
Stronicowanie
Stronicowanie jest całkowicie niezależne od segmentacji. Oznacza to, że adresy tablic deskryptorów dotyczą liniowej przestrzeni adresowej. Zarówno liniowa jak i fizyczna przestrzeń adresowa obejmuje obszar 4 GB. Stronicowanie polega na podzieleniu obu tych przestrzeni na bloki (strony) po 4 KB każdy. 32-bitowy adres liniowy można więc podzielić na dwie części: bity 31..12 (20 bitów) stanowią numer strony, bity 11..0 (12 bitów) określają położenie wewnątrz strony. 20 bitów numeru strony oznacza, że cała przestrzeń adresowa zawiera 220 stron = 1 M stron.
Mechanizm stronicowania korzysta z tablic rozmieszczenia stron. Można sobie wyobrazić jedną tablicę 220 elementową, w której poszczególne elementy będą numerami stron pamięci fizycznej. Na jedną pozycję tablicy przeznaczono 32 bity ze względu na łatwość dostępu do pamieci (20 bitów to numer strony a pozostałe 12 jest wykorzystane do innych celów bądź zarezerwowane). Taka tablica wymagałaby 4 MB ciągłego obszaru pamięci fizycznej.
Niedogodność tą rozwiązano tak, że wprowadzono dwa rodzaje tablic: tablice stron i katalogi tablic stron. Wyliczanie adresu fizycznego następuje zatem dwustopniowo, a adres liniowy ma postać:
12-bitowe (bity 0-11) przesunięcie w obrębie strony
10-bitowy (bity 12-21) numer strony (indeks w tablicy stron). Dokładniej: wskazuje na element tablicy stron zawierający numer docelowej strony, tzn. tej, do której następuje odwołanie w rozkazie
10-bitowy (bity 22-31) numer tablicy stron (indeks w katalogu stron). Dokładniej: wskazuje na element katalogu tablic, który zawiera numer tablicy, w której znajduje się wskaźnik do konkretnej strony
Jako że każda z tablic zawiera 210 elementów a każdy element to 4 bajty, więc każda z tablic a także katalog tablic ma wielkość 4 kB czyli dokładnie zajmuje jedną stronę pamięci fizycznej.
Numer strony (fizyczny) zawierającej katalog stron zapisany jest w rejestrze kontrolnym CR3. Oto jego format
Każda pozycja w katalogu ston i tablicy stron ma rozmiar 32 bitów. Format elementu katalogu i tablic stron:
20-bitowy adres bazowy
1-bitowe pole P (Present) - znacznik obecności strony (tablicy) w pamięci operacyjnej
1-bitowe pole R/W (Read/Write) - znacznik zakazu zapisu dla zadań z trzeciego poziomu ochrony
1-bitowe pole U/S (User/Supervisor) - znacznik zakazu dostępu dla zadań z trzeciego poziomu ochrony (poziom użytkowy/systemowy)
1-bitowe pole A (Accessed) - znacznik użycia danej strony (tablicy)
1-bitowe pola D (Dirty) - znacznik wystąpienia oznaczającego operację zapisu do strony; bit ten nie istnieje w katalogu stron
3-bitowe pole AVL (Available To Software) - zarezerwowane dla systemu operacyjnego
1-bitowe pole PCD (Page Cache Disable) - wyłączające zapamiętywanie strony w pamięci podręcznej (i486 i nowsze)
1-bitowe pola PWT (Page Write Through) - służące do jednoczesnego zapisywania strony w zewnętrznej pamięci podręcznej i głównej (i486 i nowsze)
1-bitowe pole PS (Page Size) - znacznik długości strony mogący wystąpić tylko w katalogu stron
Adres bazowy po uzupełnieniu go o 12 zer na najmniej znaczących pozycjach jest adresem fizycznym początku ramki, w której umieszczona jest tablica stron lub strona. Do zapamiętania adresu bazowego wystarczy 20 bitów ponieważ wiadomo, że adresy fizyczne początków ramek mają 12 zer na najmniej znaczących pozycjach.
Proces tłumaczenia adresu liniowego na fizyczny można opisać w następujący sposób:
Rejestr sterujący CR3 zawiera 20-bitowy adres bazowy aktualnie używanego katalogu stron. Każdy proces posiada własną wartość rejestru CR3
Bity 31..22 adresu liniowego wskazują na element katalogu stron. Jednostka stronicowania dodaje wartość pozycji w katalogu stron przesuniętą w lewo o dwa miejsca do adresu bazowego z CR3 i uzyskuje adres fizyczny tablicy stron.
Bity 21..12 adresu liniowego wskazują na element tablicy stron. Odczytany adres dodaje się do numeru strony przesuniętego o dwa miejsca w lewo i uzyskuje adres fizyczny spod którego odczytuje adres bazowy ramki przypisanej stronie.
Numer komórki (przesunięcie) na stronie pobierany jest wprost z bitów 11..0 adresu liniowego. Wyliczony adres ramki dodaje się do przesunięcia i uzyskuje adres fizyczny komórki pamięci.
Ochrona pamięci
Jednym z założeń trybu chronionego jest ochrona pamięci przed niepożądanym zapisem, odczytem i wykonywaniem kodu. Istnieją dwa poziomy ochrony pamięci: ochrona na poziomie segmentacji i ochrona na poziomie stronicowania. Drugi poziom występuje wyłącznie w przypadku włączonego mechanizmu stronicowania, inaczej jest ignorowany.
Ochrona na poziomie segmentacji
Ochrona na tym poziomie obejmuje:
sprawdzanie typów segmentów,
sprawdzanie limitu,
ograniczenie dostępu do segmentów,
ograniczenie dostępu przestrzeni we/wy,
ograniczenie wykonywania instrukcji.
Sprawdzanie typów segmentów dotyczy praw zapisu/odczytu/uruchamiania. Ma ono znaczenie, gdy na przykład selektor segmentu kodu jest ładowany do rejestru segmentowego CS. Niektóre rejestry segmentowe mogą przechowywać selektory tylko niektórych segmentów. Wyjątek 0Dh (General Protection Fault) jest generowany podczas wykonania złej operacji na danym segmencie.
Sprawdzanie limitu występuje podczas realizowania dostępu do komórki pamięci. Jeżeli podane przemieszczenie wykracza ponad limit segmentu generowany jest wyjątek 0DH.
Ograniczenie dostępu do segmentów jest realizowane poprzez poziomy uprzywilejowania. Poziom uprzywilejowania to dwubitowa wartość, przy czym najwyższy poziom to 0, a najniższy 3. Są one przechowywane w polach CPL (Current Privillege Level), RPL (Requestor Privillege Level) i DPL (Descriptor Privillege Level). CPL jest aktualnym poziomem uprzywilejowania znajdującym się w selektorze wykonywanego kodu, RPL znajduje się w selektorze odwołującym się do danego segmentu a DPL w deskryptorze tego segmentu. Procesor bierze pod uwagę wszystkie te pola i jeżeli stwierdzi, że dany kod nie ma dostępu do segmentu, generuje wyjątek 0DH. Dany kod ma dostęp do segmentów danych na tym samym lub niższym poziomie, lecz może wykonywać skoki tylko do segmentów kodu na tym samym poziomie. Nie dotyczy to zgodnych segmentów kodu.
Aby umożliwić wykonywanie kodu na innym poziomie stworzone zostały bramki. Bramki definiują punkt wejścia do segmentów kodu. Na ich potrzeby stworzone zostały specjalne deskryptory bramek. Wyróżniamy cztery typy bramek:
bramki wywołania,
bramki pułapek,
bramki przerwań,
bramki zadań.
Deskryptory bramek wywołania mogą być umieszczone w tablicach GDT i LDT. Program na poziomie uprzywilejowania większym lub równym poziomowi bramki może za pomocą instrukcji CALL przenieść wykonywanie programu do segmentu innego poziomu (wykorzystywany jest do tego tylko selektor bramki, przemieszczenie jest pomijane). Poniżej pokazany jest deskryptor bramki wywołania:
P - (Present) bit obecności segmentu, do którego odwołuje się bramka,
DPL - (Descriptor Privillege Level) określa, najniższy poziom uprzywilejowania, jaki musi mieć kod, aby wykonać skok przez bramkę.
Selektor musi wskazywać segment, do którego ma nastąpić skok, a przemieszczenie jest adresem relatywnym w obrębie tego segmentu. Deskryptor zawiera też liczbę słów, jaka ma być skopiowana ze stosu (każdy poziom uprzywilejowania ma oddzielny stos).
Ograniczenie dostępu do przestrzeni wejścia/wyjścia jest realizowane przez wartość IOPL (Input/Output Privillege Level) w rejestrze flag oraz przez mapy portów w segmentach stanu zadań. Pole IOPL określa, jaki najniższy poziom uprzywilejowania może mieć kod, aby wykonać operację wejścia/wyjścia. Mapy bitów w segmentach TSS określają dokładnie, który z portów może używać zadanie (jedynka zabrania użycia portu). Mapy te sprawdzane są tylko w przypadku, gdy zadanie ma niższy priorytet niż określa IOPL. W wypadku wystąpienia niepożądanego dostępu do przestrzeni we/wy procesor generuje wyjątek 0DH.
Ograniczenie wykonywania instrukcji dotyczy tylko instrukcji uprzywilejowanych. W wypadku użycia ich na poziomie niższym niż 0 generowany jest wyjątek 0DH. Do instrukcji uprzywilejowanych zaliczamy:
CLTS - wyzerowanie bitu przełączenia zadania TS w rejestrze EFLAGS,
HLT - zatrzymanie pracy procesora do momentu wystąpienia przerwania,
LGDT, LIDT, LLDT - załadowanie rejestrów tablic systemowych,
LMSW - załadowanie słowa stanu procesora,
LTR - załadowanie rejestru zadania,
MOV do/z CRn, DRn, TRn - załadowanie, lub odczytanie rejestrów kontrolnych, uruchomieniowych i testowych.
Ochrona na poziomie stronicowania
Ochrona na tym poziomie obejmuje:
ograniczenie dostępu do stron,
sprawdzanie typów stron.
Ograniczenie dostępu do stron jest związane z bitem U/S wpisu w tablicy stron. Określa on dwa poziomy dostępu do stron: supervisor (poziom uprzywilejowania 0-2) i user (poziom uprzywilejowania 3). Jeżeli kod żądający dostępu do strony jest na prawach suprvisor, może on dowolnie odczytywać, lub zapisywać stronę (pomijany jest bit R/W).
Sprawdzanie typów stron dotyczy praw zapisu/odczytu (bit R/W wpisu w tablicy stron). Jeżeli nieodpowiednia operacja jest wykonana na stronie, generowany jest wyjątek.
Mechanizm wielozadaniowości
Tryb chroniony umożliwia nam zaimplementowanie mechanizmu wielozadaniowości poprzez wprowadzenie specjalnych segmentów stanu zadania TSS (Task State Segment). Selektor aktualnie wykonywanego zadania jest umieszczony w rejestrze zadania TR (Task Register). System operacyjny dokonuje przełączania zadań gdy wystąpi przerwanie zegarowe, lub gdy otrzyma od zadania sygnał bezczynności (IDLE).
Mechanizm wielozadaniowości jest ściśle powiązany z bitem NT rejestru EFLAGS i bitem TS rejestru CR0. Pierwszy z nich oznacza zagnieżdżone zadanie tzn. ustawiany jest, gdy jedno zadanie zostało przerwane przez drugie (zapobiega to zapętleniu się zadań). Drugi z bitów ustawiany jest, gdy wystąpiła operacja przełączania zadań.
Rejestr TSS przechowuje wszelkie dane dotyczące zadania, włączając adres katalogu stron (każde zadanie może mieć inne odwzorowanie adresów liniowych w fizyczne), selektor tablicy LDT oraz mapę dostępnych portów. Rozmiar segmentu TSS wynosi minimum 104 bajty, lecz może być większy w zależności od wielkości mapy portów i danych przechowywanych przez system.
T - (Trap) oznacza, czy ma być wygenerowany wyjątek 01H (Debug exceptions) przy przełączaniu do danego zadania.
Oprócz rejestrów segment TSS zawiera selektor poprzedniego segmentu stanu (uzupełniany przez procesor w wypadku przerwania działania poprzedniego zadania), oraz przemieszczenie wskazujące na mapę portów. Mapa ta jest strukturą, w której jeden bit oznacza jeden z 65536 portów (jedynka oznacza blokadę). Mapa portów nie musi uwzględniać ich wszystkich, tylko pewną ilość numerowaną kolejno od 0 do n.
Segment TSS ma swój specjalny deskryptor, który może występować jedynie w tablicy GDT. Nie może on służyć do modyfikowania zawartości segmentu (w tym celu należy stworzyć tożsamy segment danych). W procesorach 80386 ma on następującą postać:
B - (Busy) oznacza, czy zadanie jest aktualnie wykonywane (ustawiany i zerowany przez procesor).
Pozostałe bity mają takie samo znaczenie jak w innych deskryptorach.
Na potrzeby wielozadaniowości stworzona została też specjalna bramka zwana bramką zadania. Można ją umieszczać we wszystkich tablicach systemowych. Pozwala ona programom niemającym dostępu do rejestru TR na zmianę zadania. Deskryptor bramki zadania wygląda następująco:
Selektor wskazuje na segment stanu zadania. Pozostałe bity jak w przypadku bramki wywołania.