22
Architektura Komputerów – Laboratorium Ćwiczenie 2–3 Instytut Informatyki, PL str. 1 Ćwiczenie 2–3 Tryb chroniony Cel: Zapoznanie się z organizacją funkcjonowania systemu komputerowego w trybie chronionym procesora, a takŜe z zasadami programowania 32-bitowego. Zadanie: Napisać program, który będzie przelączać procesor w tryb chroniony, następnie wyświetlać na ekranie napis poprzez bezpośredni zapis do segmentu obejmującego pamięć obrazu i powracać do trybu rzeczywistego. W trybie chronionym program ma korzystać z 32-bitowych segmentów. 1. Tryb rzeczywisty a tryb chroniony Procesory 8086 i 8088 firmy Intel, od których to rozpoczęla się era dynamicznego rozwoju komputerów osobistych, zostaly zaprojektowane i skonstruowane w ten sposób, iŜ realizacja podejmowanych przez nie operacji odbywala się w tzw. trybie adresowania rzeczywistego (ang. Real Address Mode), nazywanym równieŜ skrótowo trybem rzeczywistym (ang. Real Mode). Tryb ten zapewnial podstawowe mechanizmy komunikowania się z pamięcią operacyjną i ukladami wejścia-wyjścia, umoŜliwiając wykonywanie programu uŜytkownika pod kontrolą systemu operacyjnego. Niestety, nie przewidywal on Ŝadnej wspomaganej sprzętowo moŜliwości ochrony zasobów, nie dawal teŜ Ŝadnego sprzętowego wsparcia pozwalającego na równoczesne dzialanie wielu programów. Intensywny rozrost obszaru zastosowań komputerów osobistych oraz wykorzystanie ich potencjalu w coraz to nowych dziedzinach wkrótce unaocznily, jak powaŜny charakter mialy oba te ograniczenia. Po pierwsze, moŜliwość wykonywania naraz tylko jednego programu okazala się zdecydowanie niewystarczająca. Wobec tego podjęto próby programowego ominięcia tego ograniczenia. I tak dla przykladu, w systemie operacyjnym MS-DOS, który funkcjonowal w oparciu o tryb rzeczywisty, powstala koncepcja tzw. programów rezydentnych. Cechowaly się one tym, iŜ zwykle po uruchomieniu przechodzily w stan uśpienia, ale w pewnych określonych warunkach ich wykonywanie moglo znów zostać podjęte, i to niezaleŜnie od tego, czy w tym czasie pracowal juŜ inny uruchomiony program, czy nie. JednakŜe programy takie charakteryzowaly się licznymi wadami wyplywającymi z samej ich idei, wymagaly nie tylko stosowania specyficznej konstrukcji spelniającej szereg dodatkowych ograniczeń, ale równieŜ napisania sporo dodatkowego kodu w celu zabezpieczenia przed zalamaniem, co w efekcie sprawialo, Ŝe byly one bardzo niewygodne do tworzenia. Po drugie, w rezultacie braku jakichkolwiek sprzętowych mechanizmów ochrony zasobów dowolny dzialający program mial dostęp do wszystkich tych zasobów, mógl więc sprawować calkowity nadzór nad systemem komputerowym, nawet jeśli nie posiadal do tego naleŜytych uprawnień. Wykorzystując zatem brak zabezpieczeń, mógl dla przykladu bez ograniczeń ingerować w dane systemu operacyjnego lub uśpionych programów rezydentnych, a w krańcowej sytuacji doprowadzić nawet do zalamania się calego systemu. Rosnąca powszechność stosowania komputerów osobistych wymusila wprowadzenie koniecznych zmian. Dzięki temu wspólczesne, nowoczesne systemy operacyjne charakteryzują się zarówno wielodostępnością, czyli umoŜliwiają pracę wielu uŜytkownikom

Ćwiczenie 2–3 - Instytut Informatykidpuchala/LowLevelProgr/OldII/exercise_2_am_pl.pdf · Architektura Komputerów – Laboratorium Ćwiczenie 2–3 Instytut Informatyki, PŁ str

  • Upload
    hakiet

  • View
    226

  • Download
    0

Embed Size (px)

Citation preview

A r c h i t e k t u r a K o m p u t e r ó w – L a b o r a t o r i u m

Ćwiczenie 2–3

Instytut Informatyki , PŁ str. 1

Ćwiczenie 2–3 Tryb chroniony

Cel: Zapoznanie się z organizacją funkcjonowania systemu komputerowego w trybie chronionym procesora, a takŜe z zasadami programowania 32-bitowego.

Zadanie: Napisać program, który będzie przełączać procesor w tryb chroniony, następnie wyświetlać na ekranie napis poprzez bezpośredni zapis do segmentu obejmującego pamięć obrazu i powracać do trybu rzeczywistego. W trybie chronionym program ma korzystać z 32-bitowych segmentów.

1. Tryb rzeczywisty a tryb chroniony Procesory 8086 i 8088 firmy Intel, od których to rozpoczęła się era dynamicznego

rozwoju komputerów osobistych, zostały zaprojektowane i skonstruowane w ten sposób, iŜ realizacja podejmowanych przez nie operacji odbywała się w tzw. trybie adresowania rzeczywistego (ang. Real Address Mode), nazywanym równieŜ skrótowo trybem rzeczywistym (ang. Real Mode). Tryb ten zapewniał podstawowe mechanizmy komunikowania się z pamięcią operacyjną i układami wejścia-wyjścia, umoŜliwiając wykonywanie programu uŜytkownika pod kontrolą systemu operacyjnego. Niestety, nie przewidywał on Ŝadnej wspomaganej sprzętowo moŜliwości ochrony zasobów, nie dawał teŜ Ŝadnego sprzętowego wsparcia pozwalającego na równoczesne działanie wielu programów.

Intensywny rozrost obszaru zastosowań komputerów osobistych oraz wykorzystanie ich potencjału w coraz to nowych dziedzinach wkrótce unaoczniły, jak powaŜny charakter miały oba te ograniczenia.

Po pierwsze, moŜliwość wykonywania naraz tylko jednego programu okazała się zdecydowanie niewystarczająca. Wobec tego podjęto próby programowego ominięcia tego ograniczenia. I tak dla przykładu, w systemie operacyjnym MS-DOS, który funkcjonował w oparciu o tryb rzeczywisty, powstała koncepcja tzw. programów rezydentnych. Cechowały się one tym, iŜ zwykle po uruchomieniu przechodziły w stan uśpienia, ale w pewnych określonych warunkach ich wykonywanie mogło znów zostać podjęte, i to niezaleŜnie od tego, czy w tym czasie pracował juŜ inny uruchomiony program, czy nie. JednakŜe programy takie charakteryzowały się licznymi wadami wypływającymi z samej ich idei, wymagały nie tylko stosowania specyficznej konstrukcji spełniającej szereg dodatkowych ograniczeń, ale równieŜ napisania sporo dodatkowego kodu w celu zabezpieczenia przed załamaniem, co w efekcie sprawiało, Ŝe były one bardzo niewygodne do tworzenia.

Po drugie, w rezultacie braku jakichkolwiek sprzętowych mechanizmów ochrony zasobów dowolny działający program miał dostęp do wszystkich tych zasobów, mógł więc sprawować całkowity nadzór nad systemem komputerowym, nawet jeśli nie posiadał do tego naleŜytych uprawnień. Wykorzystując zatem brak zabezpieczeń, mógł dla przykładu bez ograniczeń ingerować w dane systemu operacyjnego lub uśpionych programów rezydentnych, a w krańcowej sytuacji doprowadzić nawet do załamania się całego systemu.

Rosnąca powszechność stosowania komputerów osobistych wymusiła wprowadzenie koniecznych zmian. Dzięki temu współczesne, nowoczesne systemy operacyjne charakteryzują się zarówno wielodostępnością, czyli umoŜliwiają pracę wielu uŜytkownikom

A r c h i t e k t u r a K o m p u t e r ó w – L a b o r a t o r i u m

Ćwiczenie 2–3

Instytut Informatyki , PŁ str. 2

jednocześnie, jak i wielozadaniowością, a więc pozwalają na jednoczesne bezpieczne wykonywanie się wielu programów. Wymaga to jednak zastosowania odpowiedniego podejścia w zakresie zarządzania jednocześnie działającymi programami, jak i zapewnienia odpowiedniego poziomu ochrony zasobów, tak aby Ŝaden z programów nie mógł zakłócać pracy innego, czy nawet samego systemu operacyjnego, a takŜe by niemoŜliwe było doprowadzenie przezeń do załamania systemu. W efekcie wymusiło to wprowadzenie po pierwsze mechanizmów gwarantujących istnienie naleŜytej kontroli działania poszczególnych programów, a po drugie — większego odseparowania wszystkich uruchomionych programów od siebie. Do zrealizowania tego celu konieczne okazało się zdefiniowanie nowego pojęcia — zadania.

Zadanie, zwane takŜe procesem, jest to odrębny program będący w trakcie wykonywania, który korzysta ze wspólnych z innymi działającymi programami zasobów systemu, lecz wykonywany jest od tych programów zupełnie niezaleŜnie, lub teŜ jego powiązania z tymi programami są ściśle kontrolowane.

Układy sprzętowe wspomagające realizację wielozadaniowości oraz ochrony zasobów pojawiły się w następcy procesorów 8086 i 8088, a mianowicie w procesorze 80286, oznaczanym skrótowo symbolem 286. Procesor ten mógł działać w dwóch róŜnych trybach: znanym ze swoich poprzedników trybie adresowania rzeczywistego, oraz w drugim, nowym — tzw. trybie adresowania wirtualnego z ochroną (ang. Protected Virtual Address Mode), zwanym takŜe w skrócie trybem chronionym (ang. Protected Mode).

Pierwszy z nich był w pełni zgodny z pierwotnym trybem pracy procesorów 8086 i 8088, i stanowił jedynie jego niewielkie rozszerzenie. Zastosowanie zmodyfikowanej architektury wewnątrzprocesorowej oraz lepszych technologii wytwarzania sprawiło, iŜ jednostki 286 działały w nim jako „szybsze” wersje swoich poprzedników.

Drugi z tych trybów stanowił jakościowo zupełnie inne rozwiązanie. Wprowadzał on sprzętowe mechanizmy ochrony zasobów, jak równieŜ sprzętowe wsparcie dla wielozadaniowości, a takŜe pamięć wirtualną opartą o segmentację.

W następcy procesora 286, mianowicie procesorze 80386, oznaczanym skrótowo symbolem 386, dokonano — w porównaniu z poprzednikiem — wielu zmian, zarówno w zakresie architektury samej jednostki, jak i jej moŜliwości. Modyfikacje te objęły takŜe tryb chroniony, którego sprzętowe mechanizmy uległy dalszej znaczącej rozbudowie, a ponadto wzbogacone zostały jego moŜliwości w zakresie zarządzania pamięcią. Wprowadzono mechanizm stronicowania, o który oparte zostało działanie pamięci wirtualnej, jak i sprzęg z bardzo szybkimi układami pamięci podręcznej, montowanej poza procesorem.

Procesor 386 został wyposaŜony w 32-bitowe rejestry oraz 32-bitowe magistrale stając się pierwszym przedstawicielem rodziny 32-bitowych procesorów firmy Intel. Rodzina ta, określana wspólną nazwą IA-32 (ang. Intel Architecture-32), obejmuje, obok owego procesora 386, takŜe późniejsze jednostki 486 oraz kolejne wersje procesorów Pentium. Zastosowana w nich architektura pozwala na programowanie 32-bitowe, a wiec operowanie 32-bitowymi danymi i 32-bitowymi przesunięciami.

Od czasów procesora 386 modyfikacje w zakresie zasad funkcjonowania trybu chronionego jak i układów sprzętowych stanowiących jego podstawę, a takŜe w zakresie programowania 32-bitowego, w kolejnych procesorach z rodziny IA-32 są nieznaczne.

2. Budowa procesora 80386 Procesor 386 zawiera następujące rejestry dostępne dla programisty, pogrupowane na

poniŜsze kategorie: 1. Rejestry uŜytkowe (ang. User Registers), które obejmują:

• 8 rejestrów ogólnego przeznaczenia (ang. General-Purpose Registers), • 6 rejestrów segmentowych (ang. Segment Registers),

A r c h i t e k t u r a K o m p u t e r ó w – L a b o r a t o r i u m

Ćwiczenie 2–3

Instytut Informatyki , PŁ str. 3

• 2 rejestry stanu (ang. Status Registers). 2. Rejestry systemowe (ang. System Registers), które obejmują:

• 3 rejestry sterujące (ang. Control Registers), • 6 rejestrów uruchomieniowych (ang. Debug Registers), • 2 rejestry testowe (ang. Test Registers), • 4 rejestry trybu chronionego (ang. Protected Mode Registers).

REJESTRY OGÓLNEGO PRZEZNACZENIA Procesor 386 posiada osiem 32-bitowych rejestrów ogólnego przeznaczenia. Są to rejestry: EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP. KaŜdy z nich ma wyodrębnioną młodszą 16-bitową część, do której moŜna się odwoływać, uŜywając nazwy odpowiedniego 16-bitowego rejestru występującego we wcześniejszych procesorach, np. rejestr EAX zawiera na swoich 16 najmłodszych bitach rejestr AX, EBX — BX itd. Z kolei owa młodsza 16-bitowa część dzieli się dalej na dwa 8-bitowe rejestry, starszy i młodszy, które równieŜ posiadają swoje nazwy, np. w rejestrze AX są to odpowiednio AH i AL, w rejestrze BX — BH i BL itd. Przeznaczenie poszczególnych rejestrów 32-bitowych jest zasadniczo takie same jak ich 16-bitowych odpowiedników z wcześniejszych procesorów.

REJESTRY SEGMENTOWE Procesor 386 posiada sześć 16-bitowych rejestrów segmentowych. Pierwszy z nich, rejestr CS, słuŜy do wskazywania na aktualnie przetwarzany segment kodu. Rejestry DS, ES, FS i GS słuŜą do wskazywania na segmenty danych. Spośród nich rejestr DS stosowany jest przez większość instrukcji dostępu do danych jako rejestr domyślny, toteŜ uŜycie w konkretnym rozkazie innego wymaga jego jawnego określenia, tzw. prefiksowania. Odwoływanie się do pamięci przy wykorzystaniu innego rejestru segmentowego niŜ domyślny pociąga za sobą wydłuŜenie czasu obliczenia adresu, wobec tego rejestr DS powinien wskazywać na ten segment danych, do którego występuje najwięcej odwołań. Ostatni z rejestrów segmentowych, rejestr SS, słuŜy do wskazywania na segment stosu.

REJESTRY STANU Do rejestrów stanu zaliczane są: wskaźnik instrukcji oraz rejestr znaczników. Wskaźnik instrukcji EIP jest rejestrem 32-bitowym, a jego młodsza 16-bitowa część to rejestr IP. Podobnie rejestr znaczników EFLAGS jest 32-bitowy, a na swoich najmłodszych 16 bitach zawiera rejestr FLAGS.

REJESTRY STERUJĄCE Procesor 386 posiada trzy 32-bitowe rejestry sterujące, oznaczane jako CR0, CR2, CR3. SłuŜą one do sterowania trybem pracy procesora i trybem pracy bloku stronicowania.

REJESTRY URUCHOMIENIOWE Procesor 386 posiada sześć 32-bitowych rejestrów uruchomieniowych, o symbolach DR0, DR1, DR2, DR3, DR6, DR7. Pozwalają one na sterowanie pułapkami sprzętowymi i pracą krokową, co umoŜliwia śledzenie wykonywania się programów w trybie chronionym. Tryb ten wymaga bowiem stosowania specjalnych debuggerów, a rejestry te stanowią sprzętowe wsparcie dla ich funkcjonowania, bez którego nie mogłyby one działać.

REJESTRY TESTOWE Procesor 386 posiada dwa 32-bitowe rejestry testowe, oznaczane TR6 i TR7. UŜywa ich mechanizm sterujący funkcjonowaniem stronicowania, a takŜe wykorzystywane są w czasie autotestowania procesora.

REJESTRY TRYBU CHRONIONEGO Procesor 386 posiada cztery rejestry trybu chronionego. Dwa z nich, GDTR i IDTR, są 32-bitowe, podczas gdy pozostałe dwa — LDTR i TR — 16-bitowe. Rejestry te pełnią zasadniczą rolę w funkcjonowaniu mechanizmów ochronnych trybu chronionego.

A r c h i t e k t u r a K o m p u t e r ó w – L a b o r a t o r i u m

Ćwiczenie 2–3

Instytut Informatyki , PŁ str. 4

3. Organizacja pamięci operacyjnej Odwołując się do pamięci, procesor umieszcza na swoich wyprowadzeniach

adresowych, połączonych z zewnętrzną szyną adresową, adres odpowiedniej komórki. Jest to tzw. adres fizyczny, jako Ŝe określa on faktyczne połoŜenie tej komórki w pamięci.

Zewnętrzna szyna adresowa w procesorze 8086 zawierała 20 linii, dzięki czemu był on zdolny do zaadresowania 1 MB pamięci. Zewnętrzna szyna adresowa procesora 286 zawierała juŜ 24 linie, zaś procesora 386 — 32 linie. Wynika zatem z tego, iŜ procesory 286 były w stanie zaadresować 16 MB pamięci, natomiast procesory 386 — aŜ 4 GB. Niestety, w trybie rzeczywistym, ani w pierwszym, ani w drugim przypadku niemoŜliwe jest wykorzystanie całego tego potencjalnie dostępnego obszaru. W tym trybie bowiem mechanizm adresowania musi pozostać zgodny ze swoim odpowiednikiem z procesora 8086, a zatem ogranicza się jedynie do 1 MB pamięci. Z kolei tryb chroniony nie podlega juŜ takim ograniczeniom i cała dostępna pamięć moŜe być bez przeszkód wykorzystywana.

Zastosowana w procesorze 8086 i wszystkich jego następcach organizacja pamięci logicznej nazywana jest segmentacją. Oznacza to, Ŝe dostęp do konkretnej komórki w pamięci odbywa się poprzez odwołanie do odpowiedniego segmentu, stanowiącego pewien wyodrębniony i skończony obszar pamięci, i podanie przesunięcia w tym segmencie, a więc odległości tejŜe komórki pamięci od początku segmentu. Tak wyspecyfikowany adres logiczny zostaje następnie przetłumaczony przez procesor w procesie zwanym translacją na adres fizyczny.

W trybie rzeczywistym segment charakteryzowany jest przez dwie wielkości: adres swojego początku oraz swój rozmiar, czyli długość. Jeśli chodzi o pierwszą z tych cech, musi ona spełniać jedną podstawową regułę, a mianowicie początek segmentu nie moŜe znajdować się w dowolnym miejscu pamięci, lecz musi leŜeć na granicy tzw. paragrafu, którego odpowiadający mu 16-bitowy numer paragrafu jest zapamiętywany w rejestrze segmentowym. Z kolei długość segmentu, a więc druga cecha go opisująca, moŜe stanowić wartość nie przekraczającą 65536. Wynika z tego, Ŝe maksymalne przemieszczenie w obrębie segmentu o największym dostępnym rozmiarze równieŜ moŜe zostać zapisane jako liczba 16-bitowa. Adres fizyczny uzyskuje się mnoŜąc numer paragrafu znajdujący się w rejestrze segmentowym przez 16 (efektywnie: przesuwając go o 4 miejsca w lewo) i dodając do otrzymanego iloczynu przesunięcie.

NaleŜy przy tym podkreślić, Ŝe tryb rzeczywisty nie zapewnia Ŝadnej ochrony pamięci, toteŜ nie przeprowadza się jakiejkolwiek kontroli uzyskanej w wyniku translacji wartości. Jeśli długość segmentu jest krótsza niŜ podane przesunięcie, adres fizyczny, mimo Ŝe w istocie leŜący juŜ poza danym segmentem, zostanie zgodnie z powyŜszą procedurą wyznaczony, a dostęp do pamięci — zrealizowany.

W trybie chronionym równieŜ obowiązuje segmentacja, toteŜ aby odwołać się do określonej komórki w pamięci, trzeba znać, analogicznie jak w trybie rzeczywistym, adres początku segmentu, w którym się ona mieści, oraz przemieszczenie w obrębie tegoŜ segmentu, pod którym faktycznie się ona znajduje. Wprowadzenie po raz pierwszy w procesorze 386 mechanizmu stronicowania sprawiło, Ŝe adres powstający w wyniku złoŜenia adresu segmentu i przemieszczenia niekoniecznie musi stanowić adres fizyczny. Wobec tego, dla odróŜnienia, został on nazwany adresem liniowym. Z kolei adres fizyczny moŜe zostać wyznaczony dopiero po określeniu owego adresu liniowego. Ten etap translacji dokonywany jest przez specjalne mechanizmy sprzętowe odpowiedzialne za realizację stronicowania. W przypadku jednak, gdy mechanizm stronicowania jest wyłączony, adres fizyczny jest taki sam jak liniowy i Ŝadna dodatkowa konwersja nie zachodzi. NaleŜy przy tym zaznaczyć, Ŝe w procesorze 386, jako Ŝe dysponuje on 32 liniami adresowymi, oba adresy — liniowy i fizyczny — są 32-bitowe.

A r c h i t e k t u r a K o m p u t e r ó w – L a b o r a t o r i u m

Ćwiczenie 2–3

Instytut Informatyki , PŁ str. 5

4. Mechanizmy ochrony zasobów w systemach wielozadaniowych W systemach wielozadaniowych w celu ochrony zasobów powszechnie

wykorzystywane są dwa podstawowe mechanizmy: • separacja zadań, • poziomy uprzywilejowania. SEPARACJA ZADAŃ

Zanim jakikolwiek program zacznie się wykonywać, musi najpierw zostać umieszczony w pamięci. Zasadniczą kwestią staje się zatem separacja przydzielonych obszarów pamięci poszczególnym zadaniom tak, aby nie mogły one nawzajem zakłócać swojej pracy. Separacja taka jest łatwa do zrealizowania za pomocą tzw. koncepcji rejestru bazowego i granicznego. Rejestr bazowy zawiera najniŜszy adres obszaru pamięci dostępnego dla danego zadania, zaś rejestr graniczny — adres najwyŜszy. Zadanie ma prawo odwoływać się jedynie do owego przydzielonego mu obszaru pamięci, a więc o adresach z przedziału pomiędzy wartościami umieszczonymi w obu tych rejestrach. W momencie, gdy wymagany jest dostęp do konkretnej komórki pamięci, jej adres podlega najpierw sprzętowej kontroli, polegającej na porównaniu go z wartościami z owych rejestrów. JeŜeli adres ten jest mniejszy od adresu znajdującego się w rejestrze bazowym bądź większy od tego z rejestru granicznego, dostęp nie jest realizowany, a w zamian zgłaszany jest wyjątek informujący o próbie niedozwolonego dostępu do pamięci. Pewną odmianą przy tej koncepcji jest sytuacja, w której w rejestrze granicznym przechowuje się nie tyle najwyŜszy adres dostępny dla zadania, ale maksymalne przesunięcie względem adresu najmniejszego przechowywanego w rejestrze bazowym. W przypadku korzystania z segmentacji koncepcję rejestru bazowego i granicznego stosuje się nie tyle do całych programów naraz, ale osobno do poszczególnych ich segmentów. KaŜdy segment znajdujący się w programie posiada więc adres swojego początku, czyli adres bazowy, a takŜe swój rozmiar, określany przez programistę, który stanowi podstawę do wyznaczenia granicy segmentu. Opisy wszystkich segmentów uŜywanych przez program trzymane są razem w specjalnej tablicy zwanej tablicą deskryptorów segmentów.

POZIOMY UPRZYWILEJOWANIA Drugi mechanizm ochrony, stosowany równolegle obok separacji obszarów pamięci przydzielonych poszczególnym zadaniom, wiąŜe się z tym, iŜ wykonywanie niektórych operacji zarezerwowane jest wyłącznie dla systemu operacyjnego, zaś zwykłe programy uŜytkowników nie powinny mieć moŜliwości ich zrealizowania. Pociąga to za sobą konieczność rozróŜnienia przynajmniej dwóch oddzielnych trybów wykonywania rozkazów: trybu uprzywilejowanego, nazywanego takŜe trybem systemu, w którym pracuje jedynie system operacyjny, mający uprawnienia do wykonania dowolnej instrukcji, oraz trybu uŜytkownika, gdzie wykonanie pewnych instrukcji, zwanych instrukcjami uprzywilejowanymi, pozostaje niedostępne. Do instrukcji uprzywilejowanych naleŜą rozkazy zmieniające zawartość niektórych rejestrów procesora stanowiących podstawę mechanizmu ochrony, jak równieŜ zmieniające poziom ochrony bądź dokonujących pewnych operacji wejścia-wyjścia. Wykrycie przez procesor próby zrealizowania przez program uŜytkownika instrukcji uprzywilejowanej powoduje, analogicznie jak poprzednio, natychmiastowe zgłoszenie odpowiedniego wyjątku.

5. Deskryptory segmentów W przeciwieństwie do trybu rzeczywistego, gdzie segment charakteryzowany był

jedynie poprzez adres swojego początku oraz swój rozmiar, który to jednak na etapie wykonywania programu nie był w ogóle brany pod uwagę, w trybie chronionym charakterystyka segmentu jest znacznie szersza i obejmuje, oprócz dwóch powyŜszych, szereg dodatkowych istotnych właściwości. Dopiero wszystkie te informacje wspólnie tworzą

A r c h i t e k t u r a K o m p u t e r ó w – L a b o r a t o r i u m

Ćwiczenie 2–3

Instytut Informatyki , PŁ str. 6

kompletny opis segmentu. Opis ten przechowywany jest w specjalnej strukturze zwanej deskryptorem segmentu, która to struktura zawiera następujące dane: • adres bazowy segmentu, • rozmiar segmentu, • atrybuty określające szereg istotnych cech segmentu, w tym między innymi prawa

dostępu do segmentu, takie jak na przykład moŜliwość zapisu lub odczytu komórek pamięci w jego obrębie.

RozróŜnia się następujące rodzaje deskryptorów segmentów: • deskryptory segmentów pamięci, • deskryptory systemowe. DESKRYPTORY SEGMENTÓW PAMIĘCI

Deskryptory te opisują segmenty znajdujące się w programach uŜytkownika. WyróŜnione zostały dwa typy takich deskryptorów: 1. deskryptor segmentu danych, 2. deskryptor segmentu kodu. Segment stosu stanowi specyficzny segment danych, zatem deskryptor go opisujący naleŜy do kategorii deskryptorów segmentów danych.

DESKRYPTORY SYSTEMOWE Deskryptory te mają szczególne zastosowanie z punktu widzenia systemu. WyróŜnione zostały ich następujące typy: 1. deskryptor segmentu zajmowanego przez lokalną tablicę deskryptorów, 2. deskryptor segmentu stanu zadania, 3. deskryptor furtki wywołania, 4. deskryptor furtki przerwania, 5. deskryptor furtki potrzasku, 6. deskryptor furtki zadania. Deskryptory te nie będą omawiane.

Schemat deskryptora segmentu pamięci dla procesora 386 zawiera ryc. 1. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0

63 Adres bazowy 31..24 G D/B 0 AVL Wielkość 19..16 48

47 P DPL S Typ A Adres bazowy 23..16 32

31 Adres bazowy 15..0 16

15 Wielkość 15..0 0

Ryc. 1. Format deskryptora segmentu dla procesora 386.

Znaczenie poszczególnych pól zawartych w deskryptorze segmentu jest następujące: ADRES BAZOWY (ANG. BASE ADDRESS)

Adres bazowy segmentu stanowi 32-bitową wartość określającą adres początku segmentu. PoniewaŜ wartość ta zapisywana jest na 32 bitach, oznacza to, iŜ adres początku segmentu musi on być pamiętany inaczej niŜ w trybie rzeczywistym. I tak jest w istocie, jako Ŝe o ile w trybie rzeczywistym przechowywany był on w postaci 16-bitowego numeru paragrafu, od którego rozpoczynał się dany segment, o tyle tu, w trybie chronionym, przyjmuje formę pełnego 32-bitowego adresu liniowego. Niesie to dwie powaŜne konsekwencje. Po pierwsze, początek segmentu nie musi juŜ leŜeć dokładnie na granicy paragrafu, lecz moŜe znajdować się w dowolnym miejscu w pamięci. Po drugie zaś, wyznaczenie adresu fizycznego konkretnej komórki pamięci, do której nastąpiło odwołanie, odbywa się nieco inaczej. W przypadku procesora 386, o ile wyłączony został mechanizm stronicowania, dokonuje się tego jedynie poprzez dodanie Ŝądanego przemieszczenia do owego adresu bazowego pamiętanego w sposób bezpośredni, zatem juŜ bez wymnaŜania czegokolwiek przez 16. Zanim jednak do tego dojdzie, najpierw przeprowadzone zostaje sprawdzenie,

A r c h i t e k t u r a K o m p u t e r ó w – L a b o r a t o r i u m

Ćwiczenie 2–3

Instytut Informatyki , PŁ str. 7

czy owe przemieszczenie nie wykracza poza dopuszczalną wielkość segmentu. JeŜeli okazałoby się, Ŝe tak — wówczas realizacja dostępu nie nastąpi, natomiast procesor generuje wyjątek GP (ang. General Protection Fault — ogólne naruszenie mechanizmu ochrony).

ZAKRES (ANG. LIMIT) Zakres jest jednym z dwóch pól, które determinują rozmiar segmentu. Przechowuje on 20-bitową wartość określającą górną granicę wielkości przesunięcia w ramach segmentu.

ZIARNISTOŚĆ (ANG. GRANULARITY) — G Ziarnistość jest drugim z pól, które determinują rozmiar segmentu, pozwala ona bowiem na poprawną interpretację zapamiętanej jako zakres wielkości. Jej zadaniem jest przekazanie informacji, w jakich jednostkach wyraŜona jest ta wielkość. Wartość 0 oznacza, iŜ jednostką długości jest 1 bajt. Maksymalny rozmiar segmentu w takim przypadku wynosić moŜe 1 MB. Wartość 1 oznacza, iŜ jednostką długości są 4 KB. Maksymalny rozmiar segmentu jest wówczas równy 4 GB. Wyznaczenie rzeczywistego rozmiaru segmentu musi zatem odbywać się w oparciu zarówno o zakres, jak i o ziarnistość. Przykładowo, jeŜeli w polu zakres zapisana zostanie wartość 1023, rozmiar segmentu wynosi 1 KB, jeŜeli G=0, albo 4 MB, jeŜeli G=1. NaleŜy przy tym zauwaŜyć, Ŝe skoro pole zakres wyraŜa nie tyle rozmiar segmentu, ile górną granicę wielkości przesunięcia w jego obrębie, więc dla zakresu równego 0 i G=0 rozmiar segmentu wynosi nie 0, lecz 1 bajt (górna granica — 0, ilość danych, jaką moŜna tam przechować — 1 bajt, stąd maksymalne przesunięcie — 0h i rozmiar segmentu — 1 bajt), natomiast dla G=1 rozmiar ten wynosi 4096 bajtów (górna granica — 0, ilość danych, jaką moŜna tam przechować — 4 KB, stąd maksymalne przesunięcie — 0FFFh i rozmiar segmentu — 4096 bajtów).

DŁUGOŚĆ SŁOWA (ANG. DEFAULT OPERATION SIZE) — D/B Pole D/B określa, czy segment ma charakter 16-bitowy czy teŜ 32-bitowy. Wartość 0 oznacza segment 16-bitowy, a wartość 1 — segment 32-bitowy. Przekłada się to bezpośrednio na interpretowanie przesunięć przy odwoływaniu się do tego segmentu: w pierwszym przypadku przesunięcia są wartościami 16-bitowymi, zaś w drugim — 32-bitowymi.

RODZAJ DESKRYPTORA (ANG. DESCRIPTOR TYPE) — S Rodzaj deskryptora stwierdza, czy jest to deskryptor pamięci, czy systemowy. Wartość 0 oznacza segment systemowy, a wartość 1 — segment pamięci.

TYP (ANG. TYPE) W przypadku deskryptora segmentu pamięci, opisywany przezeń segment, jak zostało to juŜ wcześniej wspomniane, moŜe być albo segmentem kodu, albo segmentem danych. Pole typ słuŜy do wyznaczenia, który z powyŜszych wariantów zachodzi, jak i równieŜ do określenia pewnych praw związanych z dostępem do segmentu. Wartości tego pola przedstawione zostały w poniŜszej tabeli:

Wartość Znaczenie 0 segment danych, tylko odczytywanie 1 segment danych, odczytywanie i zapisywanie 2 segment danych rozszerzalny w dół, tylko odczytywanie 3 segment danych rozszerzalny w dół, odczytywanie i zapisywanie 4 segment kodu, tylko wykonywanie 5 segment kodu, wykonywanie i odczytywanie 6 zgodny segment kodu, tylko wykonywanie 7 zgodny segment kodu, wykonywanie i odczytywanie

Segmenty kodu przechowują instrukcje realizowane przez program, muszą zatem mieć nadane prawo pozwalające na ich wykonywanie. JeŜeli z segmentów tych mają być oprócz

A r c h i t e k t u r a K o m p u t e r ó w – L a b o r a t o r i u m

Ćwiczenie 2–3

Instytut Informatyki , PŁ str. 8

tego odczytywane umieszczone tam dodatkowo dane, musi ponadto zostać nadane zezwolenie na odczyt. Nie ma jednak typu, który pozwalałby na przyznanie prawa zapisu do takich segmentów, a więc dane takie nie mogą być modyfikowane w trakcie pracy programu. Segmenty danych mogą być albo zwykłe, albo rozszerzalne w dół. RozróŜnienie to ma istotne znaczenie, gdy zachodzi konieczność powiększenia takiego segmentu. Przy zwykłym segmencie danych w takiej sytuacji wystarczy odpowiednio zwiększyć wartość pola zakres. Inaczej jednak sprawa wygląda w przypadku stosów programów, równieŜ będących segmentami danych, tyle Ŝe dosyć specyficznymi. OtóŜ dane umieszczane są w takim segmencie w miejscu wskazywanym przez wierzchołek stosu, a wartość tego wierzchołka po kaŜdym zapisie nowej danej ulega dekrementacji. MoŜna zatem powiedzieć, Ŝe stos „rośnie” w kierunku malejących adresów. Powiększając segment stosu naleŜałoby zatem nie tyle przydzielić mu dodatkową przestrzeń za jego końcem, ale raczej — przed jego początkiem. Wymagałoby to jednak zmiany dwóch właściwości segmentu, mianowicie zakresu oraz adresu bazowego, a takŜe jednoczesnego zmodyfikowania wartości wierzchołka stosu. Aby tego uniknąć, wprowadzony został specjalny typ segmentu danych, jakim jest właśnie segment rozszerzalny w dół. Typ ten charakteryzuje się odwrotną interpretacją pola zakres. OtóŜ w zwykłym segmencie wartość tego pola oznacza górną granicę wielkości przesunięcia w ramach segmentu, natomiast w odniesieniu do segmentu rozszerzalnego w dół — granicę dolną. W tym ostatnim przypadku wszystkie przemieszczenia mniejsze lub równe wyznaczonemu na podstawie tejŜe granicy przemieszczeniu traktowane są jako spoza segmentu, natomiast większe — jako leŜące w jego obrębie. W tej sytuacji rozszerzenie takiego segmentu sprowadza się jedynie do zmniejszenia wartości pola zakres. Zgodne segmenty kodu wiąŜą się z poziomami ochrony i zostaną omówione w dalszej części.

POZIOM OCHRONY (ANG. DESCRIPTOR PRIVILEGE LEVEL) — DPL Poziom ochrony segmentu wiąŜe się z poziomami ochrony i zostanie omówiony w dalszej części.

SEGMENT OBECNY (ANG. PRESENT) — P Pole to wykorzystywane jest przez mechanizm pamięci wirtualnej i nie będzie

omawiane. SEGMENT UśYTY (ANG. ACCESSED) — A

Pole to wykorzystywane przez mechanizm pamięci wirtualnej i nie będzie omawiane. POLE DOSTĘPNE DLA PROGRAMU (ANG. AVAILABLE TO SOFTWARE) — AVL

Pole to nie jest uŜywane przez procesor. Zostało utworzone z myślą o wykorzystaniu przez system operacyjny.

W celu zobrazowania postaci konkretnego deskryptora, posłuŜyć się moŜna następującym przykładem:

Descriptor DW 0F9Fh, 8000h, 920Bh, 0000h Deskryptor ten opisuje segment, który obejmuje swym obszarem całą pamięć obrazu w

trybie 03h karty grafiki, a więc w trybie tekstowym o 80 kolumnach i 25 wierszach. Wyświetlany obraz zapisywany jest w tej pamięci w ten sposób, iŜ następującym po sobie pozycjom na ekranie, zaczynając od lewego górnego naroŜnika i przebiegając kolejno wzdłuŜ wszystkich wierszy, przyporządkowane są dwa bajty: pierwszy przechowuje kod ASCII wyświetlanego w danym miejscu znaku, a drugi — jego atrybuty, takie jak kolor czcionki czy tła. Pamięć ta ma zatem charakter dwuwymiarowej tablicy, a jej rozmiar określić moŜna wymnaŜając 80 kolumn przez 25 wierszy i przez 2 bajty, co daje razem 4000 bajtów. Dodatkowo, początek tej pamięci umiejscowiony jest pod ściśle określonym adresem, a mianowicie rozpoczyna się ona od paragrafu o numerze 0B800h.

A r c h i t e k t u r a K o m p u t e r ó w – L a b o r a t o r i u m

Ćwiczenie 2–3

Instytut Informatyki , PŁ str. 9

W przedstawionym powyŜej deskryptorze adres bazowy segmentu (zapisany na bitach 16–39 i 56–63) po scaleniu do właściwej postaci 32-bitowej przyjmuje wartość 000B8000h, która to dokładnie odpowiada początkowi wspomnianego paragrafu. Ziarnistość ustawiona na 0 (bit 55 — G=0) oznacza, Ŝe zakres wyraŜony jest w bajtach, natomiast sam zakres (bity 0–15 oraz 48–51) zapisany jako liczba 20-bitowa wynosi 00F9Fh. Po przeliczeniu daje to więc maksymalne przesunięcie w ramach segmentu wynoszące 0F9Fh i rozmiar segmentu równy dokładnie 4000 bajtów. Rozpisanie wartości 92h (bity 40–47) dostarcza informacji o tym, Ŝe segment jest obecny w pamięci (bit 47 — P=1), ma najwyŜszy poziom ochrony (bity 45 i 46 — DPL=0), stanowi segment pamięci (bit 44 — S=1) oraz przechowuje dane, które moŜna zarówno odczytywać, jak i zapisywać (bity 41–43 — Typ=1). Wiadomo ponadto, iŜ segment ten jest 16-bitowy (bit 54 — D/B=0). Pola A (bit 40) i AVL (bit 52) pozostają wyzerowane.

6. Rodzaje tablic deskryptorów Deskryptory segmentów przechowywane są razem w tzw. tablicy deskryptorów. Istnieją

trzy rodzaje takich tablic: • Globalna tablica deskryptorów (ang. Global Descriptor Table), oznaczana w skrócie

GDT — zawiera deskryptory segmentów, w których mieszczą się głównie dane i kod systemu operacyjnego, ale takŜe pewne ogólnie dostępne zasoby, jak na przykład pamięć obrazu.

• Lokalna tablica deskryptorów (ang. Local Descriptor Table), oznaczana w skrócie LDT — zawiera deskryptory segmentów naleŜących do określonego zadania. KaŜdy z programów posiada zatem swoją własną lokalną tablicę deskryptorów, opisującą segmenty, z których się składa.

• Tablica deskryptorów przerwań (ang. Interrupt Descriptor Table), oznaczana w skrócie IDT — zawiera deskryptory furtek przechowujących odwołania do tych wszystkich segmentów, gdzie umieszczone są procedury obsługi przerwań.

Program uŜytkownika moŜe korzystać zarówno z segmentów opisanych w swojej LDT, jak i w GDT.

7. Selektory Komunikowanie się z pamięcią wymaga kaŜdorazowego wyspecyfikowania fizycznego

adresu konkretnej jej komórki, do której ma być zrealizowany dostęp. Adres ten wyznaczany jest na podstawie adresu początku segmentu oraz przemieszczenia. O ile jednak w trybie rzeczywistym adres początku segmentu, w postaci numeru paragrafu, przechowywany jest w odpowiednim rejestrze segmentowym, o tyle w trybie chronionym wygląda to juŜ inaczej, jako Ŝe rejestr taki pełni w nim odmienną rolę. Zamiast numeru paragrafu znajduje się tam tzw. selektor segmentu. Selektor ten stanowi odwołanie do odpowiedniego deskryptora segmentu w wybranej tablicy deskryptorów, który to dopiero deskryptor zawiera wszystkie niezbędne informacje na temat lokalizacji tegoŜ segmentu. Schemat selektora segmentu zawiera ryc. 2.

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0

15 Numer deskryptora TI RPL 0

Ryc. 2. Format selektora segmentu.

Znaczenie poszczególnych pól selektora segmentu jest następujące: NUMER DESKRYPTORA (ANG. INDEX)

Numer deskryptora zawiera indeks deskryptora w wybranej tablicy, przy czym dozwolone jest odwoływanie się tylko do tablicy GDT albo LDT. W kaŜdej z nich deskryptory ponumerowane są zgodnie z kolejnością, w jakiej występują. Tablice te zawierać mogą

A r c h i t e k t u r a K o m p u t e r ó w – L a b o r a t o r i u m

Ćwiczenie 2–3

Instytut Informatyki , PŁ str. 10

maksymalnie po 8192 deskryptory, które indeksowane są od 0 do 8191. Numer deskryptora odpowiada więc indeksowi tablicy, pod którym mieści się Ŝądany deskryptor.

WYRÓśNIK TABLICY (ANG. TABLE INDICATOR) — TI Pole TI mówi, w jakiej tablicy poszukiwać naleŜy Ŝądanego deskryptora: wartość 0 w tym miejscu oznacza, Ŝe deskryptor znajduje się w GDT, zaś wartość 1 wskazuje na LDT.

POZIOM OCHRONY ZADANIA (KODU) śĄDAJĄCEGO DOSTĘPU (ANG. REQUESTOR’S PRIVILEGE

LEVEL) — RPL Pole to wiąŜe się z poziomami ochrony, jednak jego wykorzystanie jest specyficzne i

nie będzie omawiane. Przykładowo, selektor moŜe przyjmować następującą postać: Selector DW 08h PowyŜszy selektor odwołuje się do tablicy GDT (bit 2 — TI=0), w której

odpowiadający mu deskryptor znajduje się pod indeksem 1 (bity 3–15), czyli tak naprawdę jest drugim deskryptorem w tej tablicy, poniewaŜ pierwszy ma przyporządkowany indeks 0. Ponadto selektor ten dostarcza informacji, iŜ program Ŝądający dostępu do wskazywanego segmentu działa na najwyŜszym poziomie uprzywilejowania (bity 0–1 — RPL=0).

Selektor, w którym numer deskryptora jest równy 0 oraz TI=0, ma znaczenie specjalne. Jest to tzw. selektor zerowy. Powinien być on umieszczany w tych rejestrach segmentowych, których zawartość jest nieistotna, czyli które aktualnie nie odwołują się do Ŝadnego konkretnego deskryptora. Dokładnie rzecz ujmując, selektorowi temu odpowiada deskryptor znajdujący się w tablicy GDT pod indeksem 0, jednakŜe deskryptor ten nigdy nie jest uŜywany. Próba dostępu do pamięci przy uŜyciu rejestru segmentowego załadowanego takim selektorem spowoduje wygenerowanie przez procesor wspomnianego juŜ wcześniej wyjątku GP. Jedyny rejestr segmentowy, w którym selektor zerowy nie moŜe zostać umieszczony, to rejestr SS. Procesor nie zezwoli na taka operację, równieŜ zgłaszając wyjątek GP.

Jakakolwiek odwołanie się do zawartości dowolnego segmentu, zanim zostanie zrealizowane, wymusza najpierw na sprzętowych mechanizmach ochrony przeprowadzenie kompletnej kontroli jego legalności, a więc sprawdzenie, czy na przykład mieści się ono w obrębie wskazanego segmentu, czy nie narusza praw dostępu do niego, choćby chcąc dokonać zapisu w segmencie przeznaczonym tylko do odczytu itp. Aby za kaŜdym razem nie trzeba było odczytywać deskryptora owego segmentu z pamięci, co niepotrzebnie wydłuŜałoby czas dostępu, procesor zaopatrzony został w specjalne rejestry deskryptorów stowarzyszone z odpowiednimi rejestrami segmentowymi. Załadowanie selektora do rejestru segmentowego powoduje automatyczne załadowanie wskazywanego przezeń deskryptora do właściwego rejestru deskryptora. Owe rejestry deskryptorów mają jednak ukryty charakter, a więc są niewidoczne i niedostępne dla programisty. Schemat powiązania rejestrów segmentowych i skojarzonych z nimi ukrytych rejestrów deskryptorów zawiera ryc. 3.

Rejestry segmentowe (widoczne) Rejestry deskryptorów (ukryte) CS Adres bazowy Zakres Atrybuty DS Adres bazowy Zakres Atrybuty ES Adres bazowy Zakres Atrybuty FS Adres bazowy Zakres Atrybuty GS Adres bazowy Zakres Atrybuty SS Adres bazowy Zakres Atrybuty

Ryc. 3. Powiązanie rejestrów segmentowych z odpowiadającymi im ukrytymi rejestrami deskryptorów.

Zanim dany selektor zostanie faktycznie umieszczony w rejestrze segmentowym, wpierw poddawany jest on niezbędnym testom, których zadaniem jest wykrycie jakiejkolwiek próby naruszenia zasad ochrony. Warunków do spełnienia jest tu kilka. Przede wszystkim wartość selektora musi być wartością poprawną, ma więc wskazywać na rzeczywiście istniejący deskryptor segmentu, bądź stanowić selektor zerowy. Samo zadanie z kolei musi

A r c h i t e k t u r a K o m p u t e r ó w – L a b o r a t o r i u m

Ćwiczenie 2–3

Instytut Informatyki , PŁ str. 11

znajdować się na odpowiednim poziomie uprzywilejowania, tak aby nie naruszyć poziomu ochrony tego segmentu, który to poziom zapisany jest w jego deskryptorze. Ponadto zawarte tamŜe atrybuty owego segmentu spełniać muszą pewne załoŜenia. Mianowicie dla rejestrów segmentowych danych (DS, CS, ES, FS i GS) segment musi mieć zezwolenie do odczytu. Dla rejestru segmentowego stosu (SS) musi mieć on zezwolenie zarówno do odczytu jak i do zapisu, przy czym moŜe, choć nie musi, być segmentem rozszerzalnym w dół. Dla rejestru segmentowego kodu (CS) musi mieć atrybut wykonywalności. Dopiero przejście całej tej procedury i stwierdzenie, Ŝe Ŝaden z tych warunków nie uległ naruszeniu, pozwala na wpisanie selektora do rejestru segmentowego i automatycznie powoduje takŜe umieszczenie odpowiadającego mu deskryptora w stowarzyszonym rejestrze ukrytym. Jakiekolwiek naruszenie zasad ochrony z kolei doprowadzi do wygenerowania przez procesor wyjątku GP.

W razie potrzeby, by uchronić się przed zajściem tej ostatniej sytuacji, moŜna samodzielnie zweryfikować poprawność selektora i nadane mu prawa. SłuŜą do tego trzy instrukcje. Pierwsza z nich, instrukcja LAR, sprawdza poprawność podanego selektora i, jeŜeli jest prawidłowy, zwraca atrybuty wskazywanego przezeń segmentu. Druga, rozkaz VERW, kontroluje, czy selektor jest poprawny i czy odpowiadający mu segment ma prawo do zapisu, natomiast trzecia, VERR, na podobnej zasadzie weryfikuje, czy segment ma prawo do odczytu.

Po umieszczeniu selektora w rejestrze segmentowym moŜna juŜ odwołać się do konkretnej komórki pamięci w danym segmencie. I tu równieŜ przed przeprowadzeniem kaŜdej tego typu operacji włączają się mechanizmy ochrony, które sprawdzają legalność podjętego działania, zaś w przypadku stwierdzenia nieprawidłowości spowodują wygenerowanie wyjątku GP. Pojawi się on na przykład przy próbie zapisu do segmentu, który nie ma nadanego prawa do zapisu, albo przy próbie odczytu bądź zapisu danych poza granicą segmentu. Tej ostatniej sytuacji moŜna zapobiec, uprzednio sprawdzając długość segmentu za pomocą rozkazu LSL.

8. Poziomy ochrony Zapewnienie bezpiecznego funkcjonowania systemu komputerowego wymaga, oprócz

zastosowania odpowiedniej separacji zadań za pomocą ochrony zajmowanej przez nie pamięci, takŜe uniemoŜliwienia wykonywania pewnych newralgicznych z punktu widzenia owego bezpieczeństwa operacji przez programy do tego nieuprawnione. Pociąga to za sobą konieczność wyodrębnienia róŜnych poziomów uprzywilejowania, które determinować będą dostępność realizacji tego typu operacji. W trybie chronionym istnieją cztery takie poziomy. Trzy najbardziej uprzywilejowane z reguły przeznaczone są dla systemu operacyjnego, zaś dopiero na czwartym, najniŜszym, działają programy uŜytkownika. Ale takŜe w ramach poziomów przypisanych systemowi operacyjnemu obowiązuje ściśle określona hierarchia. Poziom najwyŜszy przeznaczony jest dla jądra systemu operacyjnego, poziomy drugi i trzeci wykorzystywane są zaś przez usługi systemowe, sterowniki urządzeń itp. Poziom trzeci moŜe równieŜ zostać udostępniony dla programów uŜytkowych wymagających szczególnej ochrony, takich jak na przykład systemy zarządzania bazami danych.

Zastosowanie róŜnych poziomów uprzywilejowania wymaga, aby w kaŜdym programie wszystkie segmenty, bez względu na to, czy zawierają kod, dane, czy teŜ są segmentami stosu, miały określony właściwy sobie poziom ochrony. Informacja ta jest zapisywana w polu DPL deskryptora segmentu. NajwyŜszemu, czyli najbardziej uprzywilejowanemu poziomowi, odpowiada wartość 0 tego pola, zaś poziomowi najniŜszemu, a więc najmniej uprzywilejowanemu — wartość 3.

W systemie kaŜdy z programów wykonuje się na ściśle wyznaczonym poziomie ochrony. PoniewaŜ jednak naraz moŜe być uruchomionych wiele zadań, konieczne staje się zdefiniowanie tzw. bieŜącego poziomu ochrony. OtóŜ bieŜący poziom ochrony (ang. Current

A r c h i t e k t u r a K o m p u t e r ó w – L a b o r a t o r i u m

Ćwiczenie 2–3

Instytut Informatyki , PŁ str. 12

Privilege Level), oznaczany w skrócie CPL, jest to ten poziom uprzywilejowania, jaki ma zadanie, któremu aktualnie przydzielony został czas procesora, a precyzyjniej — aktualnie przetwarzany segment kodu tego zadania. CPL pamiętany jest na dwóch najmłodszych bitach rejestru CS, czyli w polu RPL znajdującego się tam selektora.

Poziom ochrony zadania determinuje moŜliwości wykonania przez to zadanie instrukcji uprzywilejowanych, które niewłaściwie uŜyte mogą wszak zagrozić bezpiecznemu funkcjonowaniu systemu. NaleŜą do nich między innymi rozkazy modyfikujące pewne kluczowe struktury systemowe, na przykład tablice deskryptorów. Tylko zadania na najwyŜszym poziomie uprzywilejowania mają prawo wykonywać takie instrukcje.

Poziom ochrony zadania wyznacza takŜe moŜliwości dostępu przez to zadanie do innych segmentów, tak kodu jak i danych. Oba te przypadki rządzą się jednak odmiennymi prawami.

Przy dostępie do danych dopuszczalne jest odwołanie się do segmentu będącego na tym samym lub niŜszym poziomie ochrony w stosunku do CPL. Wynika to z przyjętej zasady, Ŝe nie moŜna zezwolić zadaniu działającemu na określonym poziomie uprzywilejowania na odczytywanie bądź modyfikowanie danych bardziej chronionych. JeŜeli stwierdzona zostanie próba naruszenia tej reguły, procesor wygeneruje wyjątek GP. Z drugiej strony, dostęp do danych będących na takim samym poziomie ochrony lub chronionych słabiej odbywać się moŜe bez przeszkód.

Z kolei w przypadku, gdy dostęp ma mieć miejsce do innego segmentu kodu, co moŜe zostać zrealizowane poprzez wykonanie skoku do tego segmentu lub przez wywołanie procedury tam się znajdującej, operacja taka dozwolona jest wtedy, gdy poziom ochrony segmentu docelowego jest równy CPL. W przeciwnym razie nastąpi wygenerowanie wyjątku GP. Istnieje jednak odstępstwo od tej reguły. W określonych sytuacjach moŜliwa jest realizacja dostępu do segmentu kodu takŜe wówczas, kiedy segment ten ma wyŜszy niŜ CPL poziom ochrony. Ma to miejsce na przykład wtedy, gdy program wywołuje którąś z funkcji udostępnianych aplikacjom przez system operacyjny. A zatem, moŜna uznać, iŜ program ma prawo odwołać się do segmentu kodu znajdującego się na tym samym poziomie ochrony bądź — jeśli spełnione są określone warunki — na poziomie wyŜszym. Nie ma jednak prawa odwoływać się do segmentu znajdującego się na poziomie niŜszym. Uzasadnione jest to faktem, Ŝe realizacja zadania działającego na określonym poziomie uprzywilejowania nie moŜe opierać się na wykorzystaniu procedur niejako mniej „godnych zaufania”.

Dostęp do segmentu kodu na wyŜszym poziomie ochrony jest moŜliwy tylko w dwóch sytuacjach. Pierwsza z nich zachodzi wówczas, gdy segment docelowy jest segmentem wykonywalnym o typie zgodnym. W przeciwieństwie do tych segmentów, które nie są oznaczone jako zgodne, segment zgodny umoŜliwia wejście doń nie tylko z segmentu o takim samym poziomie ochrony, ale takŜe o poziomie niŜszym. Tylko w przypadku próby dostępu z segmentu znajdującego się na wyŜszym poziomie uprzywilejowania nastąpi wówczas wygenerowanie wyjątku GP. Co więcej, przejście do owego segmentu o wyŜszym poziomie ochrony nie powoduje zmiany CPL. MoŜna więc powiedzieć, Ŝe segment zgodny ma taki sam bieŜący poziom ochrony, który miał podprogram wywołujący. Drugi przypadek natomiast wymaga zastosowania specjalnego mechanizmu, jakim jest skorzystanie z tzw. furtki wywołania, który to mechanizm jednak nie będzie omawiany.

9. Budowa tablic deskryptorów Jak zostało to juŜ wspomniane wcześniej, deskryptory segmentów trzymane są razem w

specjalnych tablicach, zwanych tablicami deskryptorów. WyróŜnia się trzy rodzaje takich tablic: globalną tablicę deskryptorów (GDT), lokalną tablicę deskryptorów (LDT) oraz tablicę deskryptorów przerwań (IDT). O ile jednak w systemie istnieje tylko jedna tablica GDT i jedna tablica IDT, o tyle kaŜde zadanie dysponuje własną tablicą LDT.

A r c h i t e k t u r a K o m p u t e r ó w – L a b o r a t o r i u m

Ćwiczenie 2–3

Instytut Informatyki , PŁ str. 13

PoniewaŜ wszystkie te tablice znajdują się w pamięci operacyjnej, siłą rzeczy muszą one równieŜ zostać umieszczone w pewnych segmentach, a te z kolei muszą posiadać odpowiednie opisy. W zaleŜności od rodzaju tablicy opisy te wyglądają nieco inaczej i przechowywane są w innych miejscach. TABLICA GDT

Tablica GDT moŜe zawierać do 8192 deskryptorów. Jako Ŝe pojedynczy deskryptor zapisywany jest na 8 bajtach, oznacza to, Ŝe jej długość nie moŜe przekraczać 64 KB. Segment, w którym się ona znajduje, opisywany jest za pomocą tzw. pseudodeskryptora segmentu, tym róŜniącego się od pełnego deskryptora, Ŝe nie zawiera on Ŝadnych informacji na temat atrybutów, lecz jedynie 32-bitowy adres bazowy i 16-bitowy zakres, wyraŜający rozmiar tego segmentu w bajtach. Aby umoŜliwi ć efektywne odwoływanie się do tej tablicy, pseudodeskryptor ów trzymany jest w specjalnym rejestrze procesora — GDTR (ang. Global Descriptor Table Register). Cechą charakterystyczną tablicy GDT jest to, Ŝe jej pierwszy deskryptor, znajdujący się pod indeksem 0, nie jest nigdy przez system uŜywany.

TABLICA LDT Tablica LDT podlega takiemu samemu ograniczeniu jak tablica GDT: moŜe ona zawierać do 8192 deskryptorów, numerowanych od indeksu 0, a zatem jej rozmiar takŜe nie moŜe przekraczać 64 KB. Tablica ta umieszczana jest w odrębnym segmencie, który jednakowoŜ musi posiadać swój pełny deskryptor, do tego znajdujący się obowiązkowo w GDT. Deskryptor ten ma specjalne znaczenie, toteŜ naleŜy do grupy deskryptorów systemowych. Aby jednak przy kaŜdym odwołaniu do tablicy LDT nie zachodziła konieczność odczytywania go z pamięci w celu zlokalizowania tejŜe tablicy, procesor wyposaŜony został w specjalny rejestr — LDTR (ang. Local Descriptor Table Register), funkcjonujący na podobnej zasadzie jak rejestry segmentowe, a więc posiadający stowarzyszony ukryty rejestr deskryptora. W samym rejestrze LDTR przechowany jest jedynie selektor, który wskazuje na ten deskryptor w tablicy GDT, który z kolei opisuje segment, gdzie znajduje się tablica LDT. Załadowanie rejestru LDTR tym selektorem powoduje automatyczne umieszczenie wskazywanego przezeń deskryptora w owym ukrytym rejestrze. KaŜde z zadań posiada swoją własną LDT, co oznacza, Ŝe w momencie przełączania zadania wartość rejestru LDTR musi ulec odpowiedniej zmianie.

TABLICA IDT Tablica IDT, w przeciwieństwie do dwóch poprzednich, wykorzystywana jest przez procesor 386 zarówno w trybie chronionym, jak i rzeczywistym. W trybie rzeczywistym, wbrew nazwie, przechowuje ona nie deskryptory, lecz wektory, zawierające dalekie adresy procedur obsługi przerwań, stanowiąc tym samym tablicę wektorów przerwań. W tym przypadku tablica ta moŜe mieć maksymalny rozmiar równy 1 KB. W trybie chronionym umieszczone są w niej deskryptory furtek, zaś maksymalny rozmiar moŜe wynosić 2 KB. W obu trybach tablica ta znajduje się w osobnym segmencie, który moŜe być zlokalizowany w dowolnym miejscu pamięci. Segment ten, podobnie jak to miało miejsce przy tablicy GDT, opisywany jest za pomocą pseudodeskryptora, zawierającego 32-bitowy adres bazowy oraz 16-bitowy zakres wyraŜony w bajtach, aczkolwiek sens ma tylko umieszczenie tam wartości nie przekraczającej 11 bitów, jako Ŝe — zgodnie z tym, co zostało powiedziane wyŜej — tablica ta nie moŜe być dłuŜsza niŜ 2 KB. W przeciwieństwie jednak do tablicy GDT, jej pierwszy element, znajdujący się pod indeksem 0, moŜe stanowić normalnie uŜywany deskryptor. Analogicznie jak poprzednio, w celu przyspieszenia odwoływania się do tej tablicy, jej pseudodeskryptor przechowywany jest w specjalnym rejestrze procesora — IDTR (ang. Interrupt Descriptor Table Register).

A r c h i t e k t u r a K o m p u t e r ó w – L a b o r a t o r i u m

Ćwiczenie 2–3

Instytut Informatyki , PŁ str. 14

Aby zapewnić zgodność z procesorami 8086, w trybie rzeczywistym rejestr IDTR zawiera normalnie adres bazowy równy 0, zaś zakres przyjmuje wartość 3FFh. W tej sytuacji tablica wektorów przerwań mieści się na początku przestrzeni adresowej i ma długość 1 KB, czyli wygląda dokładnie tak, jak ma to miejsce w przypadku procesora 8086, który rejestru IDTR wszak nie posiada. Co prawda w procesorze 386 podczas pracy w trybie rzeczywistym dozwolone jest, poprzez odpowiednią modyfikację zawartości rejestru IDTR, przemieszenie tej tablicy w inne miejsce pamięci bądź zmienienie jej długości, lecz ze wspomnianego wyŜej względu się tego nie praktykuje.

Pobieranie informacji na temat połoŜenia wszystkich trzech rodzajów tablic, jak i zmiana tego połoŜenia, moŜliwa jest przy uŜyciu specjalnych instrukcji, które jednakŜe w trybie chronionym naleŜą do instrukcji uprzywilejowanych, a zatem mogą być wykonane jedynie przez programy operujące na najwyŜszym poziomie uprzywilejowania.

Do pobrania danych opisujących tablicę GDT, a więc jej adresu bazowego i długości, słuŜy rozkaz SGDT. Z kolei zmiana połoŜenia bądź rozmiaru tej tablicy musi zostać niezwłocznie zgłoszona procesorowi, czego dokonuje się za pomocą rozkazu LGTD. Obie instrukcje mają ten sam argument, a mianowicie wskaźnik na 6-bajtowy obszar pamięci, którego pierwsze dwa bajty zawierają długość tablicy, a pozostałe cztery — jej adres bazowy, przy czym w pierwszym przypadku instrukcja umieszcza tam odpowiednie dane, zaś w drugim — stamtąd pobiera.

Analogicznie realizowane jest pobieranie bądź modyfikowanie informacji o połoŜeniu i wielkości tablicy IDT. SłuŜą do tego odpowiednio rozkazy SIDT i LIDT , o takim samym jak powyŜej argumencie.

Ustalenie i zapisanie połoŜenia bieŜącej tablicy LDT odbywa się za pomocą instrukcji SLDT i LLDT. Argumentem obu z nich jest selektor odpowiedniego deskryptora znajdującego się w tablicy GDT.

Wszystkie powyŜsze rozkazy mogą równieŜ zostać uŜyte w trybie rzeczywistym. Dzięki temu moŜliwe jest na przykład wcześniejsze uprzednie przygotowanie tablic GDT oraz IDT przed dokonaniem przełączenia do trybu chronionego.

10. Przejście do trybu chronionego Podanie sygnału na wejście RESET procesora 386 powoduje jego wyzerowanie i

inicjalizację, która to wprowadza go w tryb adresowania rzeczywistego. W jej trakcie do rejestru IDTR załadowany zostaje pseudodeskryptor o adresie bazowym mającym wartość 0 oraz o zakresie równym 3FFh. Dzięki temu następuje takie ustalenie lokalizacji i rozmiaru tablicy wektorów przerwań, aby zachodziła pełna zgodność z procesorem 8086. Tablica wektorów przerwań mieści się zatem na początku pamięci operacyjnej i zawiera miejsce na 256 wektorów. MoŜna więc w pewnym sensie powiedzieć, Ŝe po inicjalizacji procesor 386 działa jako szybszy odpowiednik jednostki 8086.

Po zakończeniu tego etapu następuje skok do programu znajdującego się w pamięci EPROM, który zawiera kod inicjalizacji systemu komputerowego. Program ten, po wykonaniu odpowiednich testów, przystępuje do wczytania z dysku systemu operacyjnego.

Przejście do trybu chronionego wiąŜe się ze zmianą odpowiedniego bitu w jednym z rejestrów sterujących procesora.

Aby przejść do trybu chronionego, naleŜy ustawić flagę PE (ang. Protected Mode Enable), czyli najmniej znaczący bit rejestru CR0. PoniewaŜ nie istnieje instrukcja procesora bezpośrednio zmieniająca wartość tego bitu, operację tę trzeba wykonać trzyetapowo: najpierw naleŜy pobrać zawartość całego rejestru CR0 do jakiegoś 32-bitowego rejestru ogólnego przeznaczenia (np. EAX) za pomocą rozkazu MOV, następnie ustawić ten bit uŜywając instrukcji OR i na koniec tę zmodyfikowaną wartość zapisać ponownie rozkazem MOV do rejestru CR0.

A r c h i t e k t u r a K o m p u t e r ó w – L a b o r a t o r i u m

Ćwiczenie 2–3

Instytut Informatyki , PŁ str. 15

Zachowanie zgodności z procesorem 286 przy przechodzeniu między trybami rzeczywistym a chronionym i z powrotem pociągnęło za sobą pewne istotne konsekwencje. OtóŜ o ile procesor 386 pozwala w sposób programowy wrócić do trybu rzeczywistego, o tyle w jego poprzedniku włączenie trybu chronionego uniemoŜliwiało powrót do trybu rzeczywistego inaczej niŜ przez podanie procesorowi sygnału na wejście RESET. Ponadto, w procesorze 286 nie istniał 32-bitowy rejestr CR0 — w zamian znajdował się tam 16-bitowy rejestr MSW (ang. Machine Status Word), w którym to umieszczona była flaga PE. Do operowania na rejestrze MSW uŜywało się rozkazu SMSW — w celu pobrania jego zawartości do jakiegoś 16-bitowego rejestru ogólnego przeznaczenia, oraz LMSW — do przepisania jej z owego 16-bitowego rejestru do rejestru MSW. A zatem, o ile moŜna było rozkazem LMSW wpisać do tego rejestru nową wartość z ustawionym bitem PE, by przejść z trybu rzeczywistego do chronionego, o tyle nie moŜna było tym rozkazem w trybie chronionym załadować wartości przywracającej tryb rzeczywisty. W procesorze 386 rejestr MSW został włączony do rejestru CR0 jako jego mniej znacząca połowa. Wobec tego tu równieŜ wolno korzystać z rozkazów SMSW i LMSW. JednakŜe wspomniana zgodność z procesorem 286 utrzymuje powyŜsze ograniczenie w mocy: za pomocą rozkazu LMSW moŜna wprowadzić do rejestru MSW wartość włączającą tryb chroniony, natomiast nie moŜna odtworzyć trybu rzeczywistego, mimo iŜ procesor 386 na to pozwala. W tej sytuacji w przypadku tego procesora zamiast korzystać z rozkazów SMSW i LMSW operujących na rejestrze MSW, naleŜy uŜyć rozkazów MOV do operowania na całym rejestrze CR0, co umoŜliwi zarówno ustawienie jak i skasowanie flagi PE.

Zanim jednak ustawi się tę flagę wprowadzając tym samym procesor w tryb chroniony, naleŜy wcześniej, jeszcze w trybie rzeczywistym, odpowiednio zainicjować tablice GDT i IDT oraz załadować rejestry GDTR i IDTR. Dopiero potem moŜna zmienić wartość bitu PE. NaleŜy jednak zwrócić uwagę, Ŝe zmiana tej wartości i załadowanie rejestru IDTR są to dwie róŜne instrukcje, które nie mogą być wszakŜe wykonane jednocześnie. Oznacza to, iŜ po wpisaniu do rejestru IDTR pseudoeskryptora tablicy przerwań trybu chronionego system jeszcze przez chwilę pozostanie w trybie rzeczywistym. Pojawienie się w tym momencie przerwania spowoduje błąd, gdyŜ procesor nie będzie miał juŜ dostępu do tablicy wektorów przerwań trybu rzeczywistego, jako Ŝe — jak zostało to wspomniane wcześniej — do lokalizacji tej tablicy takŜe i w tym trybie wykorzystywany jest rejestr IDTR, teraz załadowany wszak wartościami przystosowanymi do trybu chronionego. Dlatego zmianę zawartości rejestru IDTR oraz przełączenie trybu naleŜy dokonywać obowiązkowo przy zablokowanych przerwaniach. Ten fragment kodu musi być ponadto napisany wyjątkowo starannie, aby nie doszło takŜe do wygenerowania Ŝadnego wyjątku.

Samo ustawienie flagi PE nie oznacza jeszcze końca inicjalizacji trybu chronionego. W trybie tym bowiem rejestry segmentowe zawierają selektory odpowiednich segmentów, podczas gdy bezpośrednio po przełączeniu jest w nich nadal to, co znajdowało się tam w trybie rzeczywistym, czyli numery początkowych paragrafów tychŜe segmentów. Wobec tego w pierwszej kolejności po zmianie bitu PE naleŜy zaktualizować zawartości rejestrów segmentowych. W przypadku rejestru CS ma to takŜe dodatkowe znaczenie. OtóŜ procesor wykonując program wczytuje na zapas rozkazy i je wstępnie dekoduje umieszczając w specjalnej kolejce, a dopiero z tej kolejki pobiera do realizacji. Przed włączeniem trybu chronionego część instrukcji — która tak naprawdę miała się wykonać juŜ w tym trybie — została wczytana i zinterpretowana jeszcze w trybie rzeczywistym. Instrukcje te naleŜy więc wczytać ponownie i zdekodować zgodnie z nowym obowiązującym trybem, co ma szczególne znaczenie przy włączonym stronicowaniu, gdzie adresy liniowe i fizyczne nie muszą być zgodne. Wyczyszczenie kolejki rozkazów i załadowanie rejestru CS odpowiednim selektorem moŜna wykonać naraz za pomocą rozkazu dalekiego skoku do następnej instrukcji. Niestety, niektóre asemblery (np. Turbo Assembler) przy natrafieniu na tego typu

A r c h i t e k t u r a K o m p u t e r ó w – L a b o r a t o r i u m

Ćwiczenie 2–3

Instytut Informatyki , PŁ str. 16

sytuację uznają, Ŝe skok do następnej instrukcji moŜe być z powodzeniem wykonany jako bliski — co w normalnych warunkach spowoduje zoptymalizowanie kodu, ale w tym konkretnym przypadku uniemoŜliwi osiągnięcie zamierzonego celu — i automatycznie go na taką postać przekształcą. Aby do tego nie dopuścić, dobrze jest wpisać rozkaz dalekiego skoku bezpośrednio w języku maszynowym, czyli nie przy uŜyciu mnemonika JMP, lecz poprzez umieszczenie kodu operacyjnego i argumentów skoku (czyli przesunięcia i selektora segmentu kodu) za pomocą dyrektyw DB i DW:

DB 0EAh ;kod operacyjny rozkazu JMP FAR DW OFFSET NextInstr ;przesuni ęcie nast ępnej ;instrukcji DW Selector ;warto ść selektora (np. 08h w przypadku ;odwołania si ę do deskryptora pod indeksem 1 ;w tablicy GDT) NextInstr: Ten sposób zagwarantuje poŜądane zaktualizowanie rejestru CS przy jednoczesnym

opróŜnieniu kolejki rozkazów. Następnie za pomocą rozkazów MOV naleŜy dokonać aktualizacji pozostałych rejestrów

segmentowych, ładując je poprawnymi selektorami. Dopiero po wykonaniu tych wszystkich czynności moŜna uznać, Ŝe tryb chroniony

został w pełni zainicjowany. W przypadku systemu operacyjnego MS-DOS naleŜy zwrócić uwagę na jeszcze jedną

kwestię. OtóŜ zazwyczaj podczas startu system ten uruchamia specjalne sterowniki, takie jak HIMEM.SYS czy EMM386.EXE, które pozwalają mu korzystać z pamięci powyŜej 1 MB. Aby jednak móc z niej skorzystać, sterowniki te włączają pewne mechanizmy trybu chronionego. Oznacza to, Ŝe działający w takim środowisku program uŜytkownika znajduje się na zbyt niskim poziomie uprzywilejowania, by móc modyfikować połoŜenie tablic deskryptorów czy rejestrów sterujących. A zatem, Ŝeby poprawnie dokonać przełączenia procesora z trybu rzeczywistego w tryb chroniony pod systemem MS-DOS, naleŜy system ten uruchomić bez tych sterowników.

11. Powrót do trybu rzeczywistego W celu opuszczenia trybu chronionego i powrócenia do trybu rzeczywistego naleŜy

wyzerować flagę PE. JednakŜe, aŜeby powrót ten wykonał się poprawnie, muszą zostać spełnione pewne określone warunki. Wynika to z faktu, iŜ tak naprawdę w trybie rzeczywistym procesor 386 korzysta z tych samych sprzętowych mechanizmów adresowania, które uŜywane są w trybie chronionym, to znaczy równieŜ posługuje się ukrytymi rejestrami deskryptorów stowarzyszonymi z rejestrami segmentowymi. Jako Ŝe jednak mechanizmy ochrony pozostają tu wyłączone, a ponadto zakres oraz atrybuty są dla wszystkich segmentów w tym trybie takie same, załadowanie nowej wartości do rejestru segmentowego powoduje jedynie zmianę adresu bazowego w skojarzonym z nim rejestrze ukrytym. Odbywa się to poprzez zapisanie na tym polu numeru paragrafu umieszczonego w rejestrze segmentowym pomnoŜonego przez 16. Inne pola deskryptora natomiast nie podlegają w tej sytuacji jakimkolwiek modyfikacjom. Oznacza to, Ŝe przed wyjściem z trybu chronionego naleŜy zadbać, aby w rejestrach ukrytych atrybuty oraz zakres segmentu przyjęły takie wartości, jakie mają w trybie rzeczywistym. W przeciwnym bowiem razie system nie będzie mógł poprawnie działać, a procesor sam odpowiedniej korekty nie dokona. PoniewaŜ jednak program nie moŜe bezpośrednio operować na rejestrach ukrytych, jedyną moŜliwość zapisania tam odpowiednich wartości stanowi umieszczenie w rejestrach segmentowych selektorów wskazujących na poprawne z punktu widzenia trybu rzeczywistego deskryptory segmentów. Tylko pole określające adres bazowy nie musi mieć w tym momencie zmienianej wartości, jako Ŝe będzie ją moŜna zawsze zaktualizować juŜ po przejściu do trybu

A r c h i t e k t u r a K o m p u t e r ó w – L a b o r a t o r i u m

Ćwiczenie 2–3

Instytut Informatyki , PŁ str. 17

rzeczywistego, jeszcze raz przeładowując rejestry segmentowe adresami poszczególnych segmentów.

Zatem przełączanie trybów wymaga wykonania określonych czynności w pewnej ustalonej sekwencji. Po pierwsze, jeŜeli w trybie chronionym włączone było stronicowanie, naleŜy je wyłączyć, postępując zgodnie z odpowiednią procedurą. Po drugie, naleŜy przekazać sterownie do segmentu o wielkości 64 KB, co spowoduje załadowanie do ukrytego rejestru deskryptora stowarzyszonego z rejestrem CS właściwych dla trybu rzeczywistego wartości. Tego ostatniego kroku nie trzeba wykonywać, jeŜeli aktualny segment kodu spełnia owe wymagania. Po trzecie, naleŜy załadować do pozostałych rejestrów segmentowych selektory wskazujące na deskryptory wypełnione takŜe wartościami wymaganymi w tym trybie (zakres segmentu wynoszący 0FFFFh, G=0, P=1, Typ=1 — segment danych, zapisywanie i odczytywanie). Następnie trzeba zablokować przerwania, po czym moŜna wyzerować bit PE. Zanim dokona się odblokowania przerwań juŜ w trybie rzeczywistym, naleŜy pamiętać o odpowiedniej zmianie rejestru IDTR, aby wskazywał na tablicę wektorów przerwań właściwą dla tego trybu.

Przed odblokowaniem przerwań pozostaje do wykonania ostatni krok, jakim jest ponowne załadowanie rejestrów segmentowych. Inaczej, zamiast numerów początkowych paragrafów segmentów znajdować się tam będą dalej umieszczone jeszcze w trybie chronionym selektory. Przy okazji operacja ta spowoduje zaktualizowanie adresu bazowego w ukrytych rejestrach deskryptorów.

12. Programowanie 32-bitowe Procesory 8086 i 8088 pozwalały na wykonywanie programów 16-bitowych.

MoŜliwość wykonywania 32-bitowych programów pojawiła się w procesorze 386 w wyniku wyposaŜenia go zarówno w rejestry o rozmiarze 32 bitów, jak i w 32-bitowe magistrale.

Pisanie programów 16-bitowych, nazywane programowaniem 16-bitowym, charakteryzuje się pewnymi róŜnicami w stosunku do pisania programów 32-bitowych, nazywanego programowaniem 32-bitowym. PROGRAMOWANIE 16-BITOWE

Programy 16-bitowe cechują się tym, Ŝe: • operują głównie na danych 16-bitowych bądź 8-bitowych; • posługują się 16-bitowymi przesunięciami; • maksymalny rozmiar pojedynczego segmentu wynosi 64 KB, co wynika z liczby bitów

przeznaczonych na przesunięcie. PROGRAMOWANIE 32-BITOWE

Programy 32-bitowe cechują się tym, Ŝe: • operują zasadniczo na danych 32-bitowych bądź 8-bitowych; • posługują się 32-bitowymi przesunięciami; • maksymalny rozmiar pojedynczego segmentu wynosi 4 GB, co wynika z liczby bitów

przeznaczonych na przesunięcie. Programowanie 32-bitowe pozwala zatem na uŜywanie 32-bitowych danych i

32-bitowych przesunięć, niedostępnych w programowaniu 16-bitowym. 32-BITOWE DANE

Operowanie na danych 32-bitowych umoŜliwiają 32-bitowe rejestry ogólnego przeznaczenia oraz 32-bitowa magistrala danych. Z programistycznego punktu widzenia operowanie na takich danych wymaga posługiwania się 32-bitowymi rejestrami ogólnego przeznaczenia oraz stosowania 32-bitowych zmiennych, w których dane te będą zapamiętywane.

A r c h i t e k t u r a K o m p u t e r ó w – L a b o r a t o r i u m

Ćwiczenie 2–3

Instytut Informatyki , PŁ str. 18

32-BITOWE PRZESUNIĘCIA Posługiwanie się 32-bitowymi przesunięciami moŜliwe jest dzięki wykorzystaniu 32-bitowych rejestrów oraz istnieniu 32-bitowej magistrali adresowej. Z programistycznego punktu widzenia posługiwanie się 32-bitowymi przesunięciami wymaga uŜywania 32-bitowych rejestrów, w których te przesunięcia będą umieszczane, jak i 32-bitowych zmiennych, w których ewentualnie będą one zapamiętywane. Oznacza to, Ŝe do adresowania kolejnej instrukcji do wykonania słuŜy nie rejestr IP, lecz EIP, wierzchołek stosu wskazywany jest nie przez rejestr SP, lecz ESP. Podobnie, przy adresowaniu pośrednim wykorzystuje się na przykład jako rejestr bazowy nie rejestr BX, a EBX, a do operowania w obrębie ramki stosu — nie rejestr BP, lecz EBP. Operacje na blokach pamięci, zamiast rejestrami SI i DI, posługują się analogicznie rejestrami ESI i EDI. Z kolei zmienne przechowujące adres bliski mają rozmiar nie 16 bitów, ale 32, natomiast przechowujące adres daleki — nie 32 bitów, ale 48.

Korzystanie z 32-bitowych rejestrów oznacza równieŜ, iŜ w określonych sytuacjach zachodzi konieczność uŜycia rozkazów stanowiących 32-bitowe odpowiedniki instrukcji 16-bitowych. Przykładem takiej sytuacji jest operacja odłoŜenia na stos wszystkich rejestrów ogólnego przeznaczenia: zamiast rozkazu POPA, odkładającego jedynie zawartość rejestrów 16-bitowych, naleŜy uŜyć rozkazu POPAD, który odłoŜy pełną ich zawartość 32-bitową.

Procesor wykonując program musi wiedzieć, czy program ma charakter 16-bitowy czy 32-bitowy. Odpowiednią interpretację determinuje aktualnie przetwarzany segment kodu. JeŜeli segment ten jest 16-bitowy, zgodnie z zasadami programowania 16-bitowego przyjmuje się, Ŝe dane mają rozmiar 16-bitowy bądź 8-bitowy, a przesunięcia — 16-bitowy. Analogicznie, jeśli segment ten jest 32-bitowy, zakłada się, Ŝe dane mają rozmiar 32-bitowy bądź 8-bitowy, natomiast przesunięcia — 32-bitowy.

Czasem jednak zachodzi konieczność odwołania się do danej 16-bitowej mimo, Ŝe aktualnie przetwarzany segment kodu jest 32-bitowy. Podobnie, podczas przetwarzania 16-bitowego segmentu kodu moŜe zajść konieczność posłuŜenia się 32-bitową daną, co procesor 386 jak najbardziej umoŜliwia. Aby zasygnalizować procesorowi zmianę domyślnego rozmiaru danych przed kodem operacyjnym rozkazu umieszczany jest dodatkowy bajt, tak zwany przedrostek rozmiaru argumentu, o wartości 66h. Oznacza on w przypadku kodu 16-bitowego, Ŝe występujący za nim rozkaz, zamiast daną 16-bitową posłuŜy się 32-bitową, natomiast w przypadku kodu 32-bitowego, Ŝe zamiast daną 32-bitową posłuŜy się 16-bitową. Jak widać, posługiwanie się danymi 8-bitowymi w jednym ani w drugim przypadku nie wymaga stosowania przedrostka.

Podobny przypadek ma miejsce, gdy zachodzi konieczność uŜycia przesunięcia o rozmiarze niezgodnym z domyślnym dla aktualnie przetwarzanego segmentu kodu. MoŜe to zachodzić na przykład wówczas, gdy aktualny segment kodu jest 16-bitowy, a potrzeba pobrać daną z segmentu danych, którego rozmiar jest większy niŜ 64 KB. Wówczas umieszczany jest przedrostek rozmiaru adresu, o wartości 67h. Podobnie jak poprzednio, oznacza on, iŜ w przypadku kodu 16-bitowego występujący za nim rozkaz, zamiast 16-bitowym, posługuje się 32-bitowym przesunięciem, natomiast w przypadku kodu 32-bitowego — Ŝe zamiast przesunięcia 32-bitowego występuje 16-bitowe.

Programowanie 32-bitowe moŜe być stosowane zarówno w trybie chronionym, jak i w rzeczywistym, jednak w tym drugim ma ono ograniczony charakter. TRYB CHRONIONY

W trybie chronionym moŜna korzystać ze wszystkich moŜliwości programowania 32-bitowego. O tym, czy segment ma charakter 16-bitowy czy 32-bitowy, decyduje zawartość bitu D/B w deskryptorze tego segmentu.

A r c h i t e k t u r a K o m p u t e r ó w – L a b o r a t o r i u m

Ćwiczenie 2–3

Instytut Informatyki , PŁ str. 19

TRYB RZECZYWISTY W trybie rzeczywistym do opisu segmentów nie są wykorzystywane deskryptory, toteŜ domyślnym typem jest zawsze segment 16-bitowy. Posługiwanie się zatem 32-bitowymi danymi bądź 32-bitowymi przesunięciami wymaga kaŜdorazowo stosowania przedrostków. Ponadto nałoŜone jest dodatkowe ograniczenie na przesunięcia: nawet jeśli mają one rozmiar 32-bitowy, ich wartość i tak nie moŜe przekraczać 0FFFFh, czyli maksymalnej wartości moŜliwej do uzyskania na 16 bitach.

Podczas asemblacji kodu źródłowego asembler musi wiedzieć, z jakim typem segmentu — 16-bitowym bądź 32-bitowym — ma do czynienia, aby ewentualnie w odpowiednich miejscach poumieszczać stosowne przedrostki. Aby przekazać asemblerowi tę informację, naleŜy wyspecyfikować za dyrektywą SEGMENT argument określający rozmiar. MoŜe on przyjmować wartość USE16 i wówczas oznacza, Ŝe dany segment ma charakter 16-bitowy, bądź USE32 i wtedy określa dany segment jako 32-bitowy. W przypadku segmentów, które będą uŜytkowane w trybie chronionym procesora, wartość tego argumentu musi być zgodna z wartością bitu D/B w deskryptorze tego segmentu.

Ponadto, aby dokonać poprawnej konsolidacji programu zawierającego 32-bitowe segmenty, konieczne jest wyspecyfikowanie w wywołaniu programu TLINK opcji /3.

W programowaniu 32-bitowym występują następujące tryby adresowania: 1. Domyślny — dana zawarta jest w rejestrze, na którym instrukcja zawsze operuje w sposób

domyślny, przy czym rejestr ten nie jest jawnie specyfikowany w samym rozkazie, np.: mul ebx ;pomnó Ŝ zawarto ść rejestru EAX przez ;EBX

2. Natychmiastowy — dana zawarta się w samym rozkazie, np.: mov eax, 12345678h ;umie ść w rejestrze EAX warto ść ;12345678h mov al, 12h ;umie ść w rejestrze AL warto ść 12h

3. Bezpośredni — adres, pod którym znajduje się dana, zawarty jest w samym rozkazie, np.: mov Zmienna, eax ;umie ść zawarto ść rejestru EAX ;w zmiennej pod adresem Zmienna mov al, [40h] ;umie ść zawarto ść spod adresu 40h ;w rejestrze AL

4. Bazowy — adres, pod którym znajduje się dana, zawarty jest w dowolnym 32-bitowym rejestrze ogólnego przeznaczenia, np.:

mov [ecx], al ;umie ść zawarto ść rejestru AL pod ;adresem zapisanym w rejestrze ECX mov eax, [ebp] ;umie ść w rejestrze EAX zawarto ść spod ;adresu zapisanego w rejestrze EBP

5. Bazowy z przemieszczeniem — adres, pod którym znajduje się dana, stanowi suma zawartości dowolnego 32-bitowego rejestru ogólnego przeznaczenia oraz przemieszczenia, które umieszczone jest w rozkazie, np.:

mov [edx+2], eax ;umie ść zawarto ść rejestru EAX pod ;adresem uzyskanym przez zwi ększenie ;o 2 adresu zapisanego w rejestrze EDX mov al, [esi+Tablica] ;umie ść w rejestrze AL zawarto ść ;spod adresu uzyskanego przez ;dodanie do adresu zmiennej ;Tablica warto ści zapisanej ;w rejestrze ESI mov al, Tablica[esi] ;inny zapis poprzedniego rozka zu

6. Skalowany indeksowy — adres, pod którym znajduje się dana, stanowi zawartość dowolnego 32-bitowego rejestru ogólnego przeznaczenia z wyjątkiem rejestru ESP pomnoŜona przez współczynnik 2, 4 lub 8, np.:

A r c h i t e k t u r a K o m p u t e r ó w – L a b o r a t o r i u m

Ćwiczenie 2–3

Instytut Informatyki , PŁ str. 20

mov al, [ecx*2] ;umie ść w rejestrze AL zawarto ść spod ;adresu uzyskanego przez pomno Ŝenie ;przez 2 adresu zapisanego w rejestrze ;ECX mov [ebx*4], eax ;umie ść zawarto ść rejestru EAX pod ;adresem uzyskanym przez pomno Ŝenie ;przez 4 adresu zapisanego w rejestrze ;EBX

7. Skalowany indeksowy z przemieszczeniem — adres, pod którym znajduje się dana, stanowi suma zawartości dowolnego 32-bitowego rejestru ogólnego przeznaczenia z wyjątkiem rejestru ESP pomnoŜonej przez współczynnik 2, 4 lub 8, oraz przemieszczenia, które umieszczone jest w rozkazie, np.:

mov [edx*2+4], al ;umie ść zawarto ść rejestru AL pod ;adresem uzyskanym przez pomno Ŝenie ;przez 2 adresu zapisanego w rejestrze ;EDX i zwi ększenie go o 4 mov eax, [ecx*8+Tablica] ;umie ść w rejestrze EAX zawarto ść ;spod adresu uzyskanego przez ;dodanie do adresu zmiennej ;Tablica zapisanej w rejestrze ECX ;warto ści pomno Ŝonej przez 8 mov eax, Tablica[ecx*8] ;inny zapis poprzedniego ro zkazu

8. Bazowo-indeksowy — adres, pod którym znajduje się dana, stanowi suma zawartości dowolnego 32-bitowego rejestru ogólnego przeznaczenia oraz zawartości innego dowolnego 32-bitowego rejestru ogólnego przeznaczenia z wyjątkiem rejestru ESP, np.:

mov eax, [ebx+edx] ;umie ść w rejestrze EAX zawarto ść spod ;adresu uzyskanego przez dodanie ;adresów zapisanych w rejestrach EBX ;i EDX mov [ecx+esi], al ;umie ść zawarto ść rejestru AL pod ;adresem uzyskanym przez dodanie ;adresów zapisanych w rejestrach ECX ;i ESI

9. Bazowo-indeksowy z przemieszczeniem — adres, pod którym znajduje się dana, stanowi suma zawartości dowolnego 32-bitowego rejestru ogólnego przeznaczenia oraz zawartości innego dowolnego 32-bitowego rejestru ogólnego przeznaczenia z wyjątkiem rejestru ESP, a takŜe przemieszczenia, które umieszczone jest w rozkazie, np.:

mov [ebp+edi+10], eax ;umie ść zawarto ść rejestru EAX pod ;adresem uzyskanym przez zwi ększenie ;o 10 sumy adresów zapisanych ;w rejestrach EBP i EDI mov al, [ecx+esi+Tablica] ;umie ść w rejestrze AL zawarto ść ;spod adresu uzyskanego przez ;dodanie do adresu zmiennej ;Tablica sumy adresów zapisanych ;w rejestrze ECX i ESI mov al, Tablica[ecx+esi] ;inny zapis poprzedniego r ozkazu

10. Skalowany bazowo-indeksowy — adres, pod którym znajduje się dana, stanowi suma zawartości dowolnego 32-bitowego rejestru ogólnego przeznaczenia oraz zawartości innego dowolnego 32-bitowego rejestru ogólnego przeznaczenia z wyjątkiem rejestru ESP pomnoŜonej przez współczynnik 2, 4 lub 8, np.:

mov al, [ecx+esi*4] ;umie ść w rejestrze AL zawarto ść spod ;adresu uzyskanego dodanie adresu ;zapisanego w rejestrze ECX i

A r c h i t e k t u r a K o m p u t e r ó w – L a b o r a t o r i u m

Ćwiczenie 2–3

Instytut Informatyki , PŁ str. 21

;pomno Ŝonego przez 4 adresu zapisanego ;w rejestrze ESI mov [ebx+edx*2], eax ;umie ść zawarto ść rejestru EAX pod ;adresem uzyskanym przez dodanie adresu ;zapisanego w rejestrze EBX i ;pomno Ŝonego przez 2 adresu zapisanego ;w rejestrze EDX

11. Skalowany bazowo-indeksowy z przemieszczeniem — adres, pod którym znajduje się dana, stanowi suma zawartości dowolnego 32-bitowego rejestru ogólnego przeznaczenia oraz zawartości innego dowolnego 32-bitowego rejestru ogólnego przeznaczenia z wyjątkiem rejestru ESP pomnoŜonej przez współczynnik 2, 4 lub 8, a takŜe przemieszczenia, które umieszczone jest w rozkazie, np.:

mov [ebx+edi*2+8], al mov eax, [edx+ecx*4+Tablica] mov eax, Tablica[edx+ecx*4] Jako rejestr bazowy moŜe zatem występować dowolny z rejestrów ogólnego

przeznaczenia. Jako rejestr indeksowy moŜe natomiast występować dowolny z rejestrów ogólnego przeznaczenia z wyjątkiem rejestru ESP.

JeŜeli w trybach: bazowym, bazowym z przemieszczeniem, bazowo-indeksowym, bazowo-indeksowym z przemieszczeniem, skalowanym bazowo-indeksowym oraz skalowanym bazowo-indeksowym z przemieszczeniem rolę rejestru bazowego pełni rejestr EBP lub ESP, domyślnym rejestrem segmentowym jest wówczas rejestr SS. W przeciwnym przypadku jest to rejestr DS. Analogicznie, jeśli w trybach: skalowanym indeksowym oraz skalowanym indeksowym z przemieszczeniem rolę rejestru indeksowego pełni rejestr EBP, domyślnym rejestrem segmentowym jest rejestr SS, natomiast w pozostałych przypadkach — rejestr DS.

13. Specyfikacja programu 1. Program po uruchomieniu w trybie rzeczywistym ma przełączyć procesor w tryb

chroniony, zademonstrować swoje poprawne działanie w tym trybie poprzez wyświetlenie na ekranie dowolnego napisu, po czym ponownie przywrócić tryb rzeczywisty.

2. Program ma być typu .EXE z niezaleŜnymi segmentami danych, kodu i stosu. Zastosowanie modelu TINY nie będzie akceptowane.

3. Segmenty wykorzystywane przez program w trybie rzeczywistym powinny być 16-bitowe. Z kolei segmenty wykorzystywane przez program w trybie chronionym powinny być 32-bitowe. W szczególności oznacza to, Ŝe zaraz po przełączeniu do trybu chronionego program powinien przekazać sterowanie do 32-bitowego segmentu kodu, natomiast tuŜ przed powrotem do trybu rzeczywistego ponownie przekazać sterownie do 16-bitowego segmentu kodu.

4. Przed przełączeniem procesora w tryb chroniony naleŜy odpowiednio zainicjować globalną tablicę deskryptorów, w której muszą znaleźć się deskryptory segmentów: kodu, danych, stosu i pamięci obrazu. NaleŜy takŜe odpowiednio zainicjować rejestr GDTR.

5. Segment obejmujący pamięć obrazu musi pokrywać ją w całości, ale wyłącznie, tzn. jego adres bazowy musi być identyczny z początkiem tej pamięci, a rozmiar — identyczny z jej rozmiarem.

6. Program ma nie korzystać ani z lokalnej tablicy deskryptorów, ani z tablicy deskryptorów przerwań. Aby w tej sytuacji nie dopuścić do zgłaszania przerwań w trakcie pracy w trybie chronionym, tuŜ przed przełączeniem do tego trybu trzeba przerwania zablokować. Przywrócić moŜliwość ich zgłaszania naleŜy dopiero po powrocie do trybu rzeczywistego.

7. W trybie chronionym program ma działać na najwyŜszym poziomie uprzywilejowania.

A r c h i t e k t u r a K o m p u t e r ó w – L a b o r a t o r i u m

Ćwiczenie 2–3

Instytut Informatyki , PŁ str. 22

8. Po przełączeniu w tryb chroniony naleŜy pamiętać o odpowiednim przeładowaniu rejestrów segmentowych, aby zawierały poprawne selektory.

9. Wyświetlany napis musi początkowo znajdować się w segmencie danych. Samo wyświetlenie tego napisu odbywać się ma poprzez przekopiowanie jego łańcucha z segmentu danych do segmentu obejmującego pamięć obrazu.

10. Przed opuszczeniem trybu chronionego naleŜy pamiętać o odpowiednim przeładowaniu rejestrów segmentowych, aby w skojarzonych z nimi rejestrach ukrytych zostały umieszczone wartości właściwe dla trybu rzeczywistego.

14. Pytania kontrolne 1. Jakie są podstawowe mechanizmy ochrony zasobów w systemach wielozadaniowych? 2. Jakie rozkazy obejmują instrukcje uprzywilejowane? 3. Jakie informacje zawarte są w deskryptorze segmentu pamięci? 4. Jakie są rodzaje tablic deskryptorów i ile takich tablic znajduje się w systemie? 5. Jak wyglądają i do czego słuŜą selektory segmentów? 6. Jakie jest zastosowanie selektora zerowego? 7. Co to są ukryte rejestry deskryptorów i jaką pełnią rolę? 8. Jakim procedurom kontroli podlega odwołanie się do pamięci? 9. Ile jest poziomów ochrony i do czego przypisany jest kaŜdy z nich? 10. Jakie ograniczenia wiąŜą się z dostępem do segmentów znajdujących się na innych poziomach

ochrony? 11. Jakie instrukcje uprzywilejowane operują na tablicach deskryptorów i do czego konkretnie one

słuŜą? 12. Jak wygląda procedura przejścia do trybu chronionego? 13. Jak wygląda procedura powrotu do trybu rzeczywistego? 14. Czym charakteryzuje się programowanie 32-bitowe?

15. Literatura: Tryb chroniony:

R. GOCZYŃSKI, M. TUSZYŃSKI: Mikroprocesory 80286, 80386 i i486. Komputerowa Oficyna Wydawnicza HELP, Warszawa, 1991, str.34–43, 50–57, 59–61, 69–77, 79–85, 101–105, 128–130.

S. KRUK: Procesor Pentium. Wydawnictwo PLJ, Warszawa, 1998, str.112–135, 142–148, 156–159, 168–185.

G. SYCK: Turbo Assembler. Biblia uŜytkownika. Wydawnictwo LT&P, Warszawa, 1994, str.239–251.

Programowanie 32-bitowe:

R. GOCZYŃSKI, M. TUSZYŃSKI: Mikroprocesory 80286, 80386 i i486. Komputerowa Oficyna Wydawnicza HELP, Warszawa, 1991, str.147–152.

S. KRUK: Procesor Pentium. Wydawnictwo PLJ, Warszawa, 1998, str.186–188.