Przerwanie (ang. interrupt) lub żądanie przerwania (IRQ - Interrupt ReQuest) - sygnał powodujący zmianę przepływu sterowania, niezależnie od aktualnie wykonywanego programu. Pojawienie się przerwania powoduje wstrzymanie aktualnie wykonywanego programu i wykonanie przez procesor kodu procedury obsługi przerwania (ang. interrupt handler).
Rodzaje przerwań
Przerwania dzielą się na dwie grupy:
Sprzętowe:
Zewnętrzne - sygnał przerwania pochodzi z zewnętrznego układu obsługującego przerwania sprzętowe; przerwania te służą do komunikacji z urządzeniami zewnętrznymi, np. z klawiaturą, napędami dysków itp.
faults (niepowodzenie) - sytuacje, w których aktualnie wykonywana instrukcja powoduje błąd; gdy procesor powraca do wykonywania przerwanego kodu wykonuje następną, po tej która wywołała wyjątek, instrukcję;
traps (pułapki) - sytuacja, która nie jest błędem, jej wystąpienie ma na celu wykonanie określonego kodu; wykorzystywane przede wszystkim w debugerach; gdy procesor powraca do wykonywania przerwanego kodu tę samą instrukcję która wywołała wyjątek;
aborts - błędy, których nie można naprawić.
Programowe - z kodu programu wywoływana jest procedura obsługi przerwania; najczęściej wykorzystywane do komunikacji z systemem operacyjnym, który w procedurze obsługi przerwania (np. w DOS 21h, 2fh, Windows 2fh, Linux x86 przerwanie 80h) umieszcza kod wywołujący odpowiednie funkcje systemowe w zależności od zawartości rejestrów ustawionych przez program wywołujący, lub oprogramowaniem wbudowanym jak procedury BIOS lub firmware.
Producenci procesorów część pozycji w tablicy wektorów przerwań rezerwują dla przerwań wewnętrznych. Pozostałe numery przerwań mogą być dowolnie wykorzystane przez producentów systemów komputerowych i oprogramowania. Obsługiwanie większości przerwań (wszystkich lub wybranych numerów) można wstrzymać lub zablokować, wyjątkiem są przerwania niemaskowalne.
Opis procesorów rodziny x86
W trybie rzeczywistym (ang. real) pracy procesora adres procedury obsługi przerwania jest zapisany w tablicy wektorów przerwań. Tablica wektorów przerwań przechowuje adresy poszczególnych procedur obsługi przerwań; przerwania identyfikowane są przez numer (wektor przerwania) i w przypadku procesorów serii x86 jest możliwych do 256 przerwań.
Tablica wektorów przerwań znajduje się w pierwszych 1024 (256 4 Bajtowych adresów procedur obsługi przerwań) komórkach pamięci operacyjnej.
W komputerach PC jest zazwyczaj 16 różnych sygnałów IRQ (ang. interrupt request) - IRQ0 do IRQ15. Często mówiąc o IRQ ma się na myśli sam numer przerwania, jako zasób udostępniany przez procesor. Jako, że jest ich tylko 16, bywają problemy z przydzieleniem osobnego przerwania każdemu z urządzeń, które go potrzebuje, może to powodować przydzielenie tego samego przerwania dwóm urządzeniom. Mówi się wtedy o konflikcie przerwań, gdyż najczęściej dwa urządzenia nie mogą współdzielić jednego.
W trybie chronionym (ang. protected) pracy procesora x86 (od procesora i386) mamy do czynienia z tablicą deskryptorów przerwań (ang. Interrupt Descriptor Table, IDT) łączącą każdy wektor wyjątku lub przerwania z deskryptorem bramy (deskryptory bram to deskryptory pozwalające na kontrolowany dostęp do segmentów kodu o różnych stopniach uprzywilejowana) dla procedury lub zadania (ang. task) obsługującym dany wyjątek lub przerwanie.
Położenie IDT jest zapisane w rejestrze tablicy deskryptorów przerwań (ang. Interrupt Descriptor Table Register, IDTR). IDT zawiera do 256 wpisów zwanych deskryptorami. Rozmiar IDT to 256*8B (8 Bajtów to rozmiar pojedynczego deskryptora); w przypadku mniejszej ilości deskryptorów (obsługiwanych przerwań) niż maksymalne 256, puste sloty (czyli w rzeczywistości nieważne deskryptory) powinny zawierać flagę dostępności segmentu (ang. Segment Present Flag, P) ustawiona na 0 (zobacz budowa deskryptora).
IDT może zawierać trzy różne rodzaje deskryptorów bram:
deskryptor bramy zadania (ang. Task-Gate Descriptor)
deskryptor bramy przerwania (ang. Interrupt-Gate Descriptor)
deskryptor bramy pułapki (ang. Trap-Gate Descriptor)
Kontroler przerwań 8259A.
Kontroler 8259A ma osiem linii wejściowych, które przyjmują sygnały zgłoszeń. Układy 8259A mogą być łączone w kaskady, powielając tym samym liczbę dostępnych wejść. Istnieje również (nie wykorzystywana w architekturze PC) możliwość sterowania zewnętrznym buforem pośredniczącym między układem, a silnie obciążonymi magistralami systemowymi.
Rysunek przedstawia schemat wyprowadzeń tego układu.
Wyprowadzenia układu 8259A:
IRO-IR7 (Interrupt Request) - każde z tych wyprowadzeń może być połączone z co najwyżej jednym urządzeniem, które - wymuszając wysoki poziom logiczny na przypisanym sobie wejściu - zgłasza żądanie obsługi przerwania, wejście IRO posiada najwyższy, a IR7 najniższy priorytet;
~CS (Chip Select) - wejście umożliwiające procesorowi wprowadzenie układu w tryb programowania;
~WR (Write) - procesor wymusza niski poziom logiczny na tym wejściu, chcąc zapisywać dane do wewnętrznych rejestrów układu;
~RD (Read) - procesor wymusza niski poziom logiczny na tym wejściu, chcąc odczytywać dane z wewnętrznych rejestrów układu;
D7-DO - dwukierunkowa magistrala danych, służąca do komunikacji między procesorem i kontrolerem (zapis i odczyt rejestrów wewnętrznych) oraz przekazywania procesorowi numeru przerwania;
CASO-CAS2 (Cascade) - lokalna magistrala adresowa używana przy kaskadowym połączeniu układów 8259A, umożliwia ona zaadresowanie do 8 takich układów;
~SP/EN (Slave ProgramlEnable Buffer) - w buforowanym trybie pracy pełni rolę wyjścia (EN) sterującego buforem zewnętrznym. W trybie nie buforowanym jest to wejście (~SP) konfigurujące układ jako Master (~SP=1) lub Slave (~SP=0);
INT (Interrupt) - wyjście to połączone jest bezpośrednio z wejściem INTR procesora, jeżeli układ 8259A pracuje jako Master, układ pracujący w trybie Slave połączony jest swoim wyjściem INT z jednym z wejść IRn kontrolera Master;
~INTA (Interrupt Acknowledge) - wejście to połączone jest z wyjściem INTA procesora, przyjmuje ono sygnał potwierdzenia przyjęcia przerwania przez CPU;
AO - wejście istotne jedynie w trybie programowania, służące do rozróżniania rozkazów;
VCC - wejście napięcia zasilającego (+5V);
GND - masa zasilania.
Cykl przyjęcia zgłoszenia.
Układ 8259A dysponuje trzema 8-bitowymi rejestrami: IMR, IRR i ISR. Pozycja bitowa n każdego z nich odpowiada jednemu z wejść IRn.
Schematycznie przedstawia to poniższy rysunek:
Urządzenie połączone z wejściem o numerze n zgłasza przerwanie wymuszając wysoki poziom logiczny na linii IRn. Ustawiając bit n w rejestrze IMR można spowodować ignorowanie wszelkich przerwań nadchodzących tą linią
Jeżeli wspomniany bit jest wyzerowany, informacja o przerwaniu przekazywana jest dalej i powoduje ustawienie bitu n w rejestrze IRR. Priorytet zgłoszeń na wejściach IRO-IR7 nie jest jednakowy i maleje ze wzrostem numeru wejścia. Ponieważ jednocześnie mogą nadchodzić zgłoszenia z wielu linii, układ oceny priorytetu wybiera jednorazowo tylko jeden z nich - ten o najwyższym priorytecie. Jednocześnie aktywowany jest sygnał INT, informujący procesor o konieczności obsługi przerwania. Jeżeli obsługa przerwań nie jest programowo zablokowana, ten ostatni reaguje sygnałem potwierdzenia na wyjściu ~INTA, które jest połączone z wejściem ~INTA układu 8259A. Wybrany przez układ oceny priorytetu bit rejestru IRR przekazywany jest teraz do rejestru ISR. Oznacza to, że w tym momencie obsługiwane jest przerwanie z linii o numerze odpowiadającym ustawionej pozycji bitowej rejestru ISR.
Procesor wysyła teraz drugi impuls ~INTA. Kontroler przerwań odpowiada na niego wystawieniem na szynę danych D7-DO wektora przerwań o następującej budowie:
bit 7 |
bit 6 |
bit 5 |
bit 4 |
bit 3 |
bit 2 |
bit 1 |
bit 0 |
bity 7 - 3 -> stanowią offset, można ustalić na etapie programowania kontrolera;
bity 2 - 0 -> stanowią numer ustawionego w rejestrze ISR bitu.
Procesor traktuje ten bajt jako numer procedury obsługi przerwania, do wykonania której teraz przejdzie.
Jeżeli kontroler pracuje w tzw. trybie automatycznym, w momencie przejścia do wykonywania procedury obsługi przerwania zerowany jest odpowiedni bit rejestru ISR. W przeciwnym wypadku (tryb E01) wyzerowania tego bitu dokonuje sama procedura obsługi przerwania wysyłając rozkaz EOI do kontrolera. Tryb pracy kontrolera (AEOI/EOI) określany jest w fazie programowania.
Niezależnie od trybu pracy, moment wyzerowania bitu rejestru ISR odpowiadającego aktualnie obsługiwanemu kanałowi oznacza gotowość kontrolera do przyjęcia następnego zgłoszenia. Należy tu jeszcze raz podkreślić, że zgłoszenia nie są obsługiwane w miarę nadchodzenia, istotny natomiast jest ich priorytet. Oznacza to, że przy pełnej kolejce oczekujących na liniach IR1-7 zgłoszeń (bity 7-1 rejestru IRR ustawione) zarejestrowanie zgłoszenia na linii IRO spowoduje zrealizowanie go w pierwszej kolejności, tj. po zakończeniu aktualnie wykonywanego cyklu obsługi.
Programowanie kontrolera przerwań.
Tryb pracy układu 8259A ustalany jest programowo. Układ otrzymuje w fazie programowania 2-4 bajty konfiguracyjne ICWI-ICW4 (Initialization Command Word), które decydują o późniejszym zachowaniu systemu obsługi przerwań. Programując układy 8259A, procesor komunikuje się z nimi przez następujące porty.
Model AT
Adres portu |
Dostępne dane |
Tryb dostępu |
020h dla linii |
IRR, ISR, wektor przerwań |
do odczytu |
0A0h dla linii |
IRR, ISR, wektor przerwań |
do odczytu |
021h dla linii |
IMR |
do odczytu |
0A1h dla linii |
IMR |
do odczytu |
Inicjowanie pracy układu.
Procesor przekazuje przez port 020h Master lub 0A0h Slave pierwszy bajt inicjujący ICW1.
Bajt ICW1:
0 |
0 |
0 |
1 |
bit 3 |
0 |
bit 1 |
bit 0 |
bity 7 - 4 -> wartość 0001 stanowi sygnaturę rozkazu;
bit 3 -> wartość 1 oznacza wyzwalanie wejść IRO poziomem napięcia, 0 - wyzwalanie zboczem.
bit 2 -> wartość 0
bit 1 -> wartość 1 oznacza pojedynczy układ (XT), 0 - obecność kaskady układów;
bit 0 -> wartość 1 oznacza, że będzie wysłany bajt ICW4, 0 oznacza brak bajtu ICW4.
Procesor przekazuje przez port 021h Master lub 0A1h Slave drugi bajt inicjujący ICW2.
Bajt ICW2:
bit 7 |
bit 6 |
bit 5 |
bit 4 |
bit 3 |
0 |
0 |
0 |
bity 7 - 3 -> przemieszczenie (offset) wektora przerwań;
bity 2 - 0 -> 000;
Jeżeli w systemie obecne są dwa układy 8259A (decyduje o tym bit 1 bajtu ICW1), procesor wysyła trzeci bajt inicjujący ICW3. Budowa tego bajtu jest różna dla układu Master i Slave.
Bajt ICW3 (Master):
bit 7 |
bit 6 |
bit 5 |
bit 4 |
bit 3 |
bit 2 |
bit 1 |
bit 0 |
bity 7 - 0 -> jedynka na pozycji n oznacza, że wejście n połączone jest z wyjściem INT układu Slave. Zero na tej pozycji oznacza, że wejście n jest wolne lub połączone z obsługiwanym urządzeniem.
Bajt ICW3 (Slave):
0 |
0 |
0 |
0 |
0 |
bit 2 |
bit 1 |
bit 0 |
bity 7 - 3 -> wartość 00000;
bity 2 - 0 -> zakodowany dwójkowo numer wejścia układu Master, które jest połączone z wyjściem INT układu Slave. Nadaje to układowi Slave numer identyfikacyjny, który jest porównywany z adresem ustawianym przez układ Master na liniach CASO-CAS2. Uaktywnia się więc tylko adresowany układ. Jest to rozwiązanie ogólne, stosowane w dużych systemach.
Kontroler oczekuje teraz bajtu ICW4, jeżeli zostało to zaanonsowane w bajcie ICW1 (bit 0).
Bajt ICW4:
0 |
0 |
0 |
bit 4 |
bit 3 |
bit 2 |
bit 1 |
bit 0 |
bity 7 - 5 -> wartość 000;
bit 4 -> w komputerach PC/XT/AT i PS/2 zawsze zero. Jedynka na tej pozycji modyfikuje sposób klasyfikacji priorytetów, co ma zastosowanie w wielokrotnych kaskadowych połączeniach kontrolerów, spotykanych w dużych systemach;
bit 3 -> wartość 1 oznacza buforowany tryb pracy - końcówka ~SP/EN układu 8259A przestaje być wtedy wejściem przełączającym tryb pracy Master/Slave i staje się wyjściem sterującym zewnętrznym buforem szyn systemowych. Również i ta możliwość znajduje zastosowanie wyłącznie w dużych systemach z silnie obciążonymi magistralami systemowymi;
bit 2 -> wartośc 1 oznacza urządzenie Master, a 0 urządzenie Slave. Bit ten używany jest do programowego konfigurowania danego układu jako Master lub Slave w sytuacji gdy ustawiony jest bit 3 i nie ma możliwości używania do tego celu końcówki ~SP/EN;
bit 1 -> jedynka oznacza tryb AEOI, tj. automatyczne zerowanie odpowiedniego bitu rejestru ISR w momencie przekazania sterowania do procedury obsługi przerwania. Wartość 0 oznacza, że procedura obsługi przerwania sama musi się zatroszczyć o wyzerowanie tego bitu wysyłając rozkaz EOI do kontrolera (lub kontrolerów, jeżeli przerwanie pochodzi od układu Slave);
bit 0 -> wartość 1 oznacza normalny tryb pracy, dostosowany do procesorów 8086/88; po drugim impulsie ~INTA procesorowi przekazywany jest 8-bitowy wektor przerwań. Zero na tym bicie przełącza układ w tryb 16-bitowy, tj. 8259A wysyła bezpośrednio 16-bitowy adres procedury obsługi przerwania (a nie 8-bitowy numer, który jest dopiero indeksem w tablicy adresów) w porcjach po osiem bitów - wystawienie drugiej połowy adresu wyzwalane jest wtedy trzecim impulsem ~INTA pochodzącym od CPU.
Opisane powyżej polecenia zawarte w bajtach ICW1 - ICW4 zapewniają konfigurowanie układu jeszcze przed rozpoczęciem obsługi zgłoszeń.
Istnieją też rozkazy, które można przekazywać kontrolerowi podczas jego pracy modyfikując tym samym dynamicznie system obsługi przerwań stosownie do bieżących potrzeb. Układ 8259A rozpoznaje trzy rozkazy OCW1 - OCW3 (Operation Confrol Word).
Rozkaz OCW1 podawany jest przez port 021h dla układu Master lub 0A1h dla układu Slave:
bit 7 |
bit 6 |
bit 5 |
bit 4 |
bit 3 |
bit 2 |
bit 1 |
bit 0 |
bity 7 - 0 -> 1 na pozycji bitowej n maskuje (blokuje) linię zgłoszeń n.
Rozkaz OCW2 podawany jest przez port 020h dla układu Master lub 0A0h dla układu Slave:
bit 7 |
bit 6 |
bit 5 |
0 |
0 |
bit 2 |
bit 1 |
bit 0 |
bity 7 - 5 -> zawartość 001 stanowi nie adresowany rozkaz E0I, a 011 adesowany rozkaz EOI; powoduje wyzerowanie w rejestrze ISR pozycji bitowej adresowanej bitami 2 - 0, pozostałe kombinacje bitów 7 - 5 maja zastosowanie w dużych systemach, gdzie obsługiwanych jest wiele urządzeń o jednakowym priorytecie(decyduje wówczas kolejność zgłoszeń);
bity 4 - 3 -> wartość 00 stanowi sygnaturę rozkazu;
bity 2 - 0 stanowią zakodowany dwójkowo numer linii, której dotyczy polecenie EOI.
Rozkaz OCW3 podawany jest przez port 020h układu Master lub 0A0h układu Slave:
0 |
bit 6 |
bit 5 |
0 |
1 |
bit 2 |
bit 1 |
bit 0 |
bit 7 -> wartość 0;
bity 6 - 5 -> 10 - wyzerowanie maski specjalnej, 11 - ustawienie maski specjalnej;
bity 4-3-> 01 stanowi sygnaturę rozkazu;
bit 2 -> wartość 1 oznacza polling;
bity 1 - 0 -> 10 oznacza rozkaz odczytu rejestru IRR - kontroler wystawia do portu 020h dla układu Master lub 0 do portu A0h dla układu Slave zawartość rejestru IRR, 11 - rozkaz odczytu rejestru ISR - kontroler wystawia do portu 020h dla układu Master lub do portu 0A0h dla układu Slave zawartość rejestru ISR, pozostałe kombinacje bitów 1 i 0 są ignorowane.
Wyjaśnienia wymaga pojęcie maski specjalnej. Bit rejestru ISR aktualnie obsługiwanego kanału jest po przekazaniu CPU wektora przerwań zerowany automatycznie tylko w trybie AEOI. W trybie EOI ten sam bit może być wyzerowany, dopiero przez sama procedurę obsługi przerwania, nie jest jednak wcale określone, kiedy to nastąpi. W przedziale czasowym, w którym układ kontrolera przerwań oczekuje na jawny rozkaz E0I, mogą być obsługiwane tylko zgłoszenia o priorytecie wyższym od aktualnie obsługiwanego. Zgłoszenia na liniach o niższym priorytecie są ignorowane.
Maska specjalna powoduje interpretację zawartości rejestru IMR w specyficzny sposób: ustawiony na pozycji n bit tego rejestru blokuje wprawdzie wszelkie zgłoszenia na linii n, ale jednocześnie oznacza, że wszystkie zgłoszenia na liniach o priorytecie wyższym i niższym od n będą mogły być obsługiwane w określonym powyżej przedziale czasu.
Polling.
Istnieje też inna forma przekazywania informacji pomiędzy kontrolerem przerwań, a procesorem. Jest to tzw. polling. Końcówka ~INTA układu 8259A nie może być wtedy połączona z wyjściem ~INTA procesora (dokładniej, kontrolera magistrali 8288), można więc to wejście wykorzystać inaczej. Wysłanie przez procesor rozkazu OCW3 z ustawionym bitem 2 oznacza skierowanie zapytania do kontrolera (polling). Układ 8259A odpowiada wystawieniem do tego samego portu, z którego otrzymał rozkaz OCW3, bajtu danych o następującej strukturze:
bit 7 |
x |
x |
x |
x |
bit 2 |
bit 1 |
bit 0 |
bit 7 -> wartość 1 oznacza, że istnieje zgłoszenie oczekujące na obsługę, 0 oznacza brak zgłoszeń;
bity 6 - 3 -> nie są używane;
bity 2 - 0 -> oznaczają zakodowany binarnie numer linii, na której oczekuje zgłoszenie.
1. Wstęp - rodzaje przerwań
W przypadku, gdy pewne zdarzenia wewnętrzne wymagają natychmiastowej obsługi przez procesor powiadamia o tym jednostkę centralną wysyłając odpowiedni sygnał. Cykl przyjęcia zgłoszenia przerwania wygląda następująco:
Procesor po uzyskaniu zgłoszenia przerwania identyfikuje jego numer,
Odkłada na stos rejestr flagowy, adres powrotny, zeruje flagę IF,
Ustala adres procedury obsługi przerwania, ładuje go do pary rejestrów CS:IP, a tym samym wykonuje skok pod podany adres,
Procedura obsługi programu musi się kończyć instrukcją IRET, wskazującą na powrót do dawniej wykonywanego zadania. Ów proces nazywamy obsługą przerwania.
W systemie 80x86 występuje kilka rodzajów przerwań:
przerwania programowe,
przerwania sprzętowe,
przerwania specjalne.
1.1. Przerwania programowe
Procesor 80x86 wykonuje program instrukcja po instrukcji. Kolejność ta może zostać zaburzona rozkazami CALL lub JMP, które przekazują sterowanie w inne miejsce pamięci. Takie przerwania kodu programu określone przez użytkownika, są generowane przez dwubajtowy rozkaz INT. Pierwszy bajt jest kodem, operacji, a drugi - zawiera numer przerwania, które będzie obsługiwane. Rozkaz INT nie jest maskowany znacznikiem odblokowania przerwań I. Jest używany do przenoszenia sterowania do dynamicznie przemieszczanego podprogramu oraz takiego, którego miejsce w pamięci nie jest znane. Program może symulować pojawienie się przerwania za pomocą instrukcji INT. Jej Argumentem jest liczba, określająca, które z przerwań należy obsłużyć. [1]
Ten typ przerwań jest używany przez programy, które musza być dostępne z innych programów. Numer przerwania może bowiem pozostać niezmieniony, mimo że procedura obsługująca go może znajdować się pod różnymi adresami.
Dobrym przykładem w ten sposób wywoływanego programu jest MS-DOS.
MS-DOS udostępnia wiele funkcji, które mogą być wykorzystane w programach użytkowych. By z nich skorzystać należy w rejestrze AH umieścić numer żądanej funkcji, a następnie wywołać przerwanie 21 h. Na przykład, by wyświetlić na ekranie pojedynczy znak, należy jego kod przesłać do rejestru DL i wywołać funkcję o numerze 2:
mov dl, 'A' ;Umieść znak w rejestrze DL
mov ah, 2 ;Numer funkcji w AL
int 2lh ;Wywołaj MS-DOS
W przerwaniach tego typu wartości znaczników z przerwanego programu są przekładane na stos przed przeniesieniem sterowania do innego miejsca. Wywołany podprogram w zakończeniu musi zawierać rozkaz IRET, który pobiera znaczniki ze stosu i przesyła je do rejestru znaczników.
Wszystkie przerwania wprowadzane przez program nie są maskowane znacznikiem I. Przerwanie programowe wywołuje odpowiedni program obsługi z tablicy przerwań. Nie występuje tutaj cykl potwierdzenia przerwania. Przerwania te blokują następne przerwania maskowane. Kasowany jest również znacznik T. Wektor przerwania dla przerwań tego typu jest albo przypisany rozkazowi, albo określony przez sam rozkaz. [1]
1.2. Przerwania sprzętowe
Przerwania te są wysyłane przez urządzenia systemowe. Sygnał przerywający jest doprowadzany do wejścia INTR mikroprocesora. Są one maskowane znacznikiem I. Podczas trwania ostatniego cyklu zegarowego w każdym rozkazie jest próbkowany stan wejścia INTR. Mikroprocesor pomija tę czynność, jeżeli był wykonywany rozkaz MOV lub POP związany z rejestrem segmentowym. Wejście INTR nie będzie wówczas próbkowane dopóty, dopóki nie zakończy się wykonanie następnego rozkazu. Dzięki temu 32-bitowy wskaźnik będzie załadowany do rejestrów SS i SP bez niebezpieczeństwa pojawienia się przerwy między tymi załadowaniami. Innym wyjątkiem jest rozkaz WAIT, który powoduje, że mikroprocesor czeka na pojawienie się niskiego poziomu logicznego na wejściu TEST. Podczas tego oczekiwania również jest próbkowany stan wejścia INTR i gdy pojawi się przerwanie, będzie obsłużone. Jednak po obsłudze mikroprocesor kontynuuje rozkaz WAIT. Przerwanie pochodzące od sprzętu jest wyzwalane wysokim poziomem logicznym na wejściu INTR. Jest ono wewnętrznie synchronizowane podczas każdego cyklu zegarowego przy narastającym zboczu CLK; aby mogło być przyjęte, na wejściu INTR (podczas cyklu zegarowego poprzedzającego koniec rozkazu łańcuchowego) musi pojawić się wysoki poziom logiczny. [2]
Podczas obsługiwania tego przerwania inne przerwania są zablokowane. Kasowany jest wówczas znacznik I, jako część reakcji na dowolne żądanie przerwania (INTR, NMI, przerwania sprzętowe, pojedynczego kroku). Rejestr znaczników jest automatycznie przekładany na stos. Podczas gdy poprzednia zawartość rejestru znaczników jest przechowywana na stosie, znacznik I jest kasowany, chyba że będzie ustawiony w programie obsługi przerwania specjalnym rozkazem. Jako reakcja na przyjęcie przerwania mikroprocesor realizuje dwa kolejne cykle potwierdzenia przerwania.
1.2.1. Kontroler obsługi przerwań sprzętowych 8259A - wiadomości ogólne
Przerwania są generowane przez różne urządzenia, które znajdują się na płycie głównej, komputera lub są podłączone do jego magistrali. Poprzez wysłanie przerwania każde z urządzeń próbuje zwrócić na siebie uwagę procesora. W komputerach IBM PC obsługą, nadchodzących do systemu przerwań, zajmuje się układ sterownika przerwań 8259. Do jego zadań należy zapewnienie, by żadne z przerwań o niskim priorytecie nie mogło mieć wpływu na przerwania o priorytecie wysokim oraz, by żadne z przerwań pojawiające się w systemie jednocześnie, nie zostało zignorowane. Zawiera on tablicę przerwań sprzętowych uporządkowaną względem ich priorytetów.
Gdy pojawi się przerwanie, to układ 8259 blokuje wszystkie pozostałe przerwania o niższym priorytecie, dopóki procedura jego obsługi nie wyśle sygnału, który oznacza jej zakończenie.
Tabela poniżej zamieszcza przerwania obsługiwane przez układ 8259:
Kanał |
Opis |
Numer przerwania |
IRQ0 |
Układ czasowy |
08h |
IRQ1 |
Klawiatura |
09h |
IRQ2 |
Drugi układ 8259 (tylko komputery AT) |
0Ah |
IRQ8 |
Zegar czasu rzeczywistego |
70h |
IRQ9 |
Symulowanie IRQ2 |
71h |
IRQ10 |
Zarezerwowane |
72h |
IRQ11 |
Zarezerwowane |
73h |
IRQ12 |
Mysz |
74h |
IRQ13 |
Wyjątek koprocesora |
75h |
IRQ14 |
Sterownik dysku stałego |
76h |
IRQ15 |
Zarezerwowane |
77h |
IRQ3 |
Szeregowy port 2 |
0Bh |
IRQ4 |
Szeregowy port 1 |
0Ch |
IRQ5 |
Sterownik dysku stałego |
0Dh |
IRQ6 |
Sterownik dysków elastycznych |
0Eh |
IRQ7 |
Port równoległy |
0Fh |
Jeśli pojawi się któreś z nich, to układ ten sprawdza, czy aktualnie nie jest obsługiwane przerwanie o wyższym priorytecie. Jeśli nie, wówczas sygnał przerwania, oraz jego numer; jest przekazywany do procesora. Jeśli znacznik zezwolenia na przerwanie jest ustawiony, to CPU kończy wykonywanie bieżącej instrukcji, a następnie wywołuje procedurę obsługi zgłoszonego przerwania. Adresy tych procedur są umieszczone w tablicy wektorów przerwań, która znajduje się w pamięci pod adresem 0000:0000. Każdy jej element zawiera segment i przemieszczenie procedury ISR dla każdego z 256 możliwych przerwań. Układ 8259 musi zostać poinformowany o zakończeniu procedury obsługi przerwania. Dopiero wtedy może on obsłużyć następne. Zadnie wysłania do portu 20h (podstawowy 8259) lub 0A0h (dodatkowy 8259) tego układu komendy EOI (End Of Interrupt) spoczywa na procedurze ISR. Polecenie E01 nie musi być wysłane na samym końcu tej procedury. Gdy w procedurze tej zaistnieje możliwość zezwolenia na przyjmowanie innych przerwań, to komenda EOI może zostać wysłana do układu 8259. Kod, który realizuje tę operację wygląda następująco:
mov al, 20h ;Prześlij do AL komendę EOI
out 20h, al. ;Wyślij ją do podstawowego układu 8259
Układ 8259 oprócz rozpoznawania priorytetów i numerów przerwań, może również blokować przyjmowanie określonych przerwań. Dla każdego przerwania istnieje bowiem specjalny znacznik zwany maska przerwania, który oznacza, czy powinno ono zostać przez sterownik zignorowane, czy też nie. Przy instalacji procedury obsługi należy się upewnić, czy bit, odpowiadający danemu przerwaniu, jest wyzerowany tak, aby przerwania rzeczywiście mogły być przyjmowane. [3]
Podprogramy, które wymagają częstej obsługi ze strony procesora, mogą użyć tych bitów do zablokowania części przerwań (na przykład, gdy port szeregowy odbiera dane z prędkością 115200 bodów). Na obsłużenie wszystkich przerwań, pochodzących od portu szeregowego, zegara i dysku. może bowiem nie starczyć czasu procesora. Poniższy kod blokuje wszystkie przerwania oprócz przerwania, które pochodzi od portu szeregowego. Na większości komputerów PC umożliwia to przeprowadzenie takiej transmisji:
Mov al, 11101111b ;Wartość 0 zezwala na przerwania związane z kanałem
;IRQ4 (COMI)
out 21, al ;Wysłanie maski do układu 8259
Oczywiście pozostawienie komputera w takim stanie nie jest dobrym rozwiązaniem. Przecież przerwania od wszystkich pozostałych urządzeń, jak klawiatury, twardego dysku czy zegara systemowego, nie będą przyjmowane. Dlatego też trik ten powinien być stosowany na możliwie krótki czas.
Przed zmiana maski można z portu 21 h pobrać jej stara wartość, a następnie, po wykonaniu krytycznego odcinka programu, wartość tę przywrócić:
.DATa
OldMask DB ? ;Poprzednia wartość maski
.CODE
in al, 2lh ;Pobranie bieżącej wartości maski
mov 0ldMask, a1 ;zapamiętanie jej
mov al, lll0llllb ;Nowa wartość maski
out 21h, a1
.
. ;Krytyczna część programu
.
mov al, OldMask ;Pobranie starej wartości maski
out 21h, al ;Wysłanie jej do układu 8259
1.2.2. Układ scalony 8259A
Kontroler 8259A ma osiem linii wejściowych, które przyjmują sygnały zgłoszeń. Układy 8259A mogą być łączone w kaskady, powielając tym samym liczbę dostępnych wejść. Istnieje również (nie wykorzystywana w architekturze PC) możliwość sterowania zewnętrznym buforem pośredniczącym między układem, a silnie obciążonymi magistralami systemowymi. Warto też wspomnieć, że w modelach IBM PC/XT i AT wejścia IRn reagują na zbocze impulsu przerwania, co stwarza niebezpieczeństwo interpretacji impulsu zakłócającego jako zgłoszenia przerwania. Inaczej zagadnienie to rozwiązano w architekturach EISA i MCA, gdzie wyzwalanie przerwań odbywa się poziomem napięcia. Możliwe jest tutaj dzielenie jednej linii zgłoszeń przez kilka urządzeń (ang. shared interrupt). System EISA rezygnuje dzięki temu z drugiego kontrolera 8259A. [2]
Niżej przedstawiony rysunek przedstawia schemat wyprowadzeń tego układu.
Wyprowadzenia układu 8259A opisano poniżej:
IRO-IR7 (Interrupt Request) - każde z tych wyprowadzeń może być połączone z co najwyżej jednym urządzeniem, które - wymuszając wysoki poziom logiczny na przypisanym sobie wejściu - zgłasza żądanie obsługi przerwania-Wejście IRO posiada najwyższy, a IR7 najniższy priorytet;
~CS (Chip Select) - wejście umożliwiające procesorowi wprowadzenie układu w tryb programowania;
~WR (Write) - procesor wymusza niski poziom logiczny na tym wejściu, chcąc zapisywać dane do wewnętrznych rejestrów układu;
~RD (Read) - procesor wymusza niski poziom logiczny na tym wejściu, chcąc odczytywać dane z wewnętrznych rejestrów układu;
D7-DO Dwukierunkowa magistrala danych, służąca do komunikacji między procesorem i kontrolerem (zapis i odczyt rejestrów wewnętrznych) oraz przekazywania procesorowi numeru przerwania;
CASO-CAS2 (Cascade) - lokalna magistrala adresowa, używana przy kaskadowym Połączeniu układów 8259A. Umożliwia ona zaadresowanie do 8 takich układów;
~SP/EN (Slave ProgramlEnable Buffer) - w buforowanym trybie pracy pełni rolę wyjścia (EN) sterującego buforem zewnętrznym. W trybie nie buforowanym jest to wejście (~SP) konfigurujące układ jako Master (~SP=I) lub Slave (~SP=0);
INT (Interrupt)-wyjście to połączone jest bezpośrednio z wejściem INTR procesora, jeżeli układ 8259A pracuje jako Master. Układ pracujący w trybie Slave połączony jest swoim wyjściem INT z jednym z wejść IRn kontrolera Master;
~INTA (Interrupt Acknowledge) - wejście to połączone jest z wyjściem INTA procesora. Przyjmuje ono sygnał potwierdzenia przyjęcia przerwania przez CPU;
AO wejście istotne jedynie w trybie programowania, służące do rozróżniania rozkazów;
VCC wejście napięcia zasilającego (+5V);
GND Masa zasilania.
1.2.3. Cykl przyjęcia zgłoszenia [2]
Układ 8259A dysponuje trzema 8-bitowymi rejestrami: IMR, IRR i ISR. Pozycja bitowa n każdego z nich odpowiada jednemu z wejść IRn.
Schematycznie przedstawia to poniższy rysunek:
Urządzenie połączone z wejściem o numerze n zgłasza przerwanie wymuszając wysoki poziom logiczny na linii IRn. Ustawiając bit n w rejestrze IMR można spowodować ignorowanie wszelkich przerwań nadchodzących ta linia.
Jeżeli wspomniany bit jest wyzerowany, informacja o przerwaniu przekazywana jest dalej i powoduje ustawienie bitu n w rejestrze IRR. Jak już wspomniano wcześniej, priorytet zgłoszeń na wejściach IRO-IR7 nie jest jednakowy i maleje ze wzrostem numeru wejścia. Ponieważ jednocześnie mogą nadchodzić zgłoszenia z wielu linii, układ oceny priorytetu wybiera jednorazowo tylko jeden z nich - ten o najwyższym priorytecie. Jednocześnie aktywowany jest sygnał INT, informujący procesor o konieczności obsługi przerwania. Jeżeli obsługa przerwań nie jest programowo zablokowana (bit IE rejestru stanu procesora), ten ostatni reaguje sygnałem potwierdzenia na wyjściu ~INTA, które jest połączone z wejściem
~INTA układu 8259A.
Wybrany przez układ oceny priorytetu bit rejestru IRR przekazywany jest teraz do rejestru ISR. Oznacza to, że w tym momencie obsługiwane jest przerwanie z linii o numerze odpowiadającym ustawionej pozycji bitowej rejestru ISR.
Procesor wysyła teraz drugi impuls ~INTA. Kontroler przerwań odpowiada na niego wystawieniem na szynę danych D7-DO wektora przerwań o następującej budowie:
bit 7 |
bit 6 |
bit 5 |
bit 4 |
bit 3 |
bit 2 |
bit 1 |
bit 0 |
bity 7- 3....... stanowią tzw. offset, można ustalić na etapie programowania kontrolera;
bity 2- 0....... numer ustawionego w rejestrze ISR bitu.
Procesor traktuje ten bajt jako numer procedury obsługi przerwania, do wykonania której teraz przejdzie.
Jeżeli kontroler pracuje w tzw. trybie automatycznym, w momencie przejścia do wykonywania procedury obsługi przerwania zerowany jest odpowiedni bit rejestru ISR. W przeciwnym wypadku (tryb E01) wyzerowania tego bitu dokonuje sama procedura obsługi przerwania wysyłając rozkaz EOI do kontrolera. Tryb pracy kontrolera (AEOI/EOI) określany jest w fazie programowania.
Niezależnie od trybu pracy, moment wyzerowania bitu rejestru ISR odpowiadającego aktualnie obsługiwanemu kanałowi oznacza gotowość kontrolera do przyjęcia następnego zgłoszenia. Należy tu jeszcze raz podkreślić, że zgłoszenia nie są obsługiwane w miarę nadchodzenia, istotny natomiast jest ich priorytet. Oznacza to, że przy pełnej kolejce oczekujących na liniach IR1-7 zgłoszeń (bity 7-1 rejestru IRR ustawione) zarejestrowanie zgłoszenia na linii IRO spowoduje zrealizowanie go w pierwszej kolejności, tj. po zakończeniu aktualnie wykonywanego cyklu obsługi.
1.2.4. Programowanie kontrolera przerwań [2]
Tryb pracy układu 8259A ustalany jest programowo. Układ otrzymuje w fazie programowania 2-4 bajty konfiguracyjne ICWI-ICW4 (Initialization Command Word), które decydują o późniejszym zachowaniu systemu obsługi przerwań. Programując układy 8259A, procesor komunikuje się z nimi przez następujące porty.
Model AT
Adres portu |
Dostępne dane |
Tryb dostępu |
020h dla linii |
IRR, ISR, wektor przerwań |
do odczytu |
0A0h dla linii |
IRR, ISR, wektor przerwań |
do odczytu |
021h dla linii |
IMR |
do odczytu |
0A1h dla linii |
LMR |
do odczytu |
Inicjowanie pracy układu
Procesor przekazuje przez port 020h (M) lub 0A0h (S) pierwszy bajt inicjujący ICW1 .
Bajt ICW1:
0 |
0 |
0 |
1 |
bit 3 |
0 |
bit 1 |
bit 0 |
bity 7-4......... 0001 , sygnatura rozkazu;
bit 3 ............. 1 - oznacza wyzwalanie wejść IRO poziomem napięcia,
0 - wyzwalanie zboczem.
bit 2 ............. 0;
bit 1 ..............1 oznacza pojedynczy układ (XT), 0 - obecność kaskady układów;
bit 0 ..............1 oznacza, że będzie wysłany bajt ICW4, 0 oznacza brak bajtu ICW4.
Procesor przekazuje przez port 021 h (Master) lub 0A1 h (Slave) drugi bajt inicjujący ICW2.
Bajt ICW2:
bit 7 |
bit 6 |
bit 5 |
bit 4 |
bit 3 |
0 |
0 |
0 |
bity 7-3........ przemieszczenie (offset) wektora przerwań;
bity 2-0........ 000.
Jeżeli w systemie obecne są dwa układy 8259A (decyduje o tym bit 1 bajtu ICWI), procesor wysyła trzeci bajt inicjujący ICW3. Budowa tego bajtu jest różna dla układu Master i Slave.
Bajt ICW3 (Master):
bit 7 |
bit 6 |
bit 5 |
bit 4 |
bit 3 |
0 |
0 |
0 |
I bity 7-0........jedynka na pozycji n oznacza, że wejście n połączone jest z wyjściem INT I układu Slave. Zero na tej pozycji oznacza, że wejście n jest wolne lub połączone z obsługiwanym urządzeniem.
Bajt ICW3 (Slave):
0 |
0 |
0 |
0 |
0 |
bit 2 |
bit 1 |
bit 0 |
bity 7-3........ 00000;
bity 2-0........ zakodowany dwójkowo numer wejścia układu Master, które jest połączone z wyjściem INT układu Slave. Nadaje to układowi Slave numer identyfikacyjny, który jest porównywany z adresem ustawianym przez układ Master na liniach CASO-CAS2. Uaktywnia się więc tylko adresowany układ. Jest to rozwiązanie ogólne stosowane w dużych systemach; w PC adresowany jest i tak tylko jeden układ Slave.
Kontroler oczekuje teraz bajtu ICW4, jeżeli zostało to zaanonsowane w bajcie ICW1 (bit0).
Bajt ICW4:
0 |
0 |
0 |
bit 4 |
bit 3 |
bit 2 |
bit 1 |
bit 0 |
bity 7-5........ 000;
bit 4 ............. w komputerach PC/XT/AT i PS/2 zawsze zero. Jedynka na tej pozycji modyfikuje sposób klasyfikacji priorytetów, co ma zastosowanie w wielokrotnych kaskadowych połączeniach kontrolerów, spotykanych w dużych systemach;
bit 3 ............. wartość 1 oznacza buforowany tryb pracy; końcówka ~SP/EN układu 8259A przestaje być wtedy wejściem przełączającym tryb pracy Master/Slave i staje się wyjściem sterującym zewnętrznym buforem szyn systemowych. Również i ta możliwość znajduje zastosowanie wyłącznie w dużych systemach z silnie obciążonymi magistralami systemowymi;
bit 2 ............. 1 - Master 0 - Slave. Bit ten używany jest do programowego konfigurowania danego układu jako Master lub Slave w sytuacji gdy ustawiony jest bit 3 (patrz powyżej) i nie ma możliwości używania do tego celu końcówki ~SP/EN;
bit 1................jedynka oznacza tryb AEOI, tj. automatyczne zerowanie odpowiedniego bitu rejestru ISR w momencie przekazania sterowania do procedury obsługi prze rwania. Wartość 0 oznacza, że procedura obsługi przerwania sama musi się zatroszczyć o wyzerowanie tego bitu wysyłając rozkaz EOI do kontrolera (lub kontrolerów, jeżeli przerwanie pochodzi od układu Slave);
bit 0 .............jedynka oznacza normalny tryb pracy, dostosowany do procesorów 8086/88;
po drugim impulsie ~INTA procesorowi przekazywany jest 8-bitowy wektor przerwań. Zero na tym bicie przełącza układ w tryb 16-bitowy, tj. 8259A wysyła bezpośrednio 16-bitowy adres procedury obsługi przerwania (a nie 8-bitowy numer, który jest dopiero indeksem w tablicy adresów) w porcjach po osiem bitów - wystawienie drugiej połowy adresu wyzwalane jest wtedy trzecim impulsem ~INTA pochodzącym od CPU.
Opisane powyżej polecenia zawarte w bajtach ICW1-ICW4 zapewniają konfigurowanie układu jeszcze przed rozpoczęciem obsługi zgłoszeń. Konfigurację kontrolera przerwań przeprowadzają procedury inicjalizacyjne BIOS-u wykonywane po włączeniu komputera jeszcze przed załadowaniem systemu operacyjnego.
Istnieją też rozkazy, które można przekazywać kontrolerowi podczas jego pracy modyfikując tym samym dynamicznie system obsługi przerwań stosownie do bieżących potrzeb. Układ 8259A rozpoznaje trzy rozkazy OCW1-OCW3 (Operation Confrol Word), które zostaną pokrótce omówione poniżej.
Rozkaz OCW1 podawany jest przez port 021 h (dla układu Master) lub 0A1 h (Slave):
bit 7 |
bit 6 |
bit 5 |
bit 4 |
bit 3 |
bit 2 |
bit 1 |
bit 0 |
bity 7-0........jedynka na pozycji bitowej n maskuje (blokuje) linię zgłoszeń n.
Rozkaz OCW2 podawany jest przez port 020h (dla układu Master) lub 0A0h (Slave):
bit 7 |
bit 6 |
bit 5 |
0 |
0 |
bit 2 |
bit 1 |
bit 0 |
bity 7-5........ 001 - nie adresowany rozkaz E01 ,
011 - adresowany rozkaz EOI; powoduje wyzerowanie w rejestrze ISR pozycji bitowej adresowanej bitami 2-0, pozostałe kombinacje bitów 7-5 maja zastosowanie w dużych systemach, gdzie obsługiwanych jest wiele urządzeń o jednakowym priorytecie(decyduje wówczas kolejność zgłoszeń). Znaczenie tych kombinacji nie będzie omawiane;
bity 4-3........ 00, sygnatura rozkazu;
bity 2-0........ zakodowany dwójkowo numer linii, której dotyczy polecenie EOI.
Rozkaz OCW3 podawany jest przez port 020h (układ Master) lub 0A0h (Slave)
0 |
bit 6 |
bit 5 |
0 |
bit 2 |
bit 1 |
bit 0 |
|
bit 7 .............. 0,
bity 6-5........ 10 - wyzerowanie maski specjalnej,
11 - ustawienie maski specjalnej,
bity 4-3........ 01 - sygnatura rozkazu;
bit 2 ...............1 - polling;
bity 1-0........ 10 - rozkaz odczytu rejestru (RR; kontroler wystawia do portu 020h (Master) lub 0A0h (Slave) zawartość rejestru IRR,
11 - rozkaz odczytu rejestru ISR; kontroler wystawia do portu 020h (Master) lub
0A0h (Slave) zawartość rejestru ISR, pozostałe kombinacje bitów 1 0 są ignorowane.
Wyjaśnienia wymaga pojęcie maski specjalnej. Jak wiemy, bit rejestru ISR aktualnie obsługiwanego kanału jest po przekazaniu CPU wektora przerwań zerowany automatycznie tylko w trybie AEOI. W trybie EOI ten sam bit może być wyzerowany, dopiero przez sama procedurę obsługi przerwania, nie jest jednak wcale określone, kiedy to nastąpi. W przedziale czasowym, w którym układ kontrolera przerwań oczekuje na jawny rozkaz E01 , mogą być obsługiwane tylko zgłoszenia o priorytecie wyższym od aktualnie obsługiwanego. Zgłoszenia na liniach o niższym priorytecie są ignorowane.
Maska specjalna powoduje interpretację zawartości rejestru IMR w specyficzny sposób: ustawiony na pozycji n bit tego rejestru blokuje wprawdzie wszelkie zgłoszenia na linii n, ale jednocześnie oznacza, że wszystkie zgłoszenia na liniach o priorytecie wyższym i niższym od n będą mogły być obsługiwane w określonym powyżej przedziale czasu.
1.2.5. Polling
Gwoli ścisłości należy nadmienić, że istnieje też inna forma przekazywania informacji pomiędzy kontrolerem przerwań a procesorem - tzw. polling. Końcówka ~INTA układu 8259A nie może być wtedy połączona z wyjściem ~INTA procesora (ściślej, kontrolera magistrali 8288), można więc to wejście wykorzystać inaczej. Wysłanie przez procesor rozkazu OCW3 z ustawionym bitem 2 oznacza skierowanie zapytania do kontrolera (polling). Układ 8259A odpowiada wystawieniem do tego samego portu, z którego otrzymał rozkaz OCW3,
bajtu danych o następującej strukturze:
bit 7 |
x |
x |
x |
x |
bit 2 |
bit 1 |
bit 0 |
bit 7 ............. 1 - istnieje zgłoszenie oczekujące na obsługę,
0 - brak zgłoszeń;
bity 6-3........ nie używane;
bity 2-0........ zakodowany binarnie numer linii, na której oczekuje zgłoszenie.
1.2.6. Przerwanie niemaskowalne (NMI)
Przerwanie to również zaliczane jest do grupy przerwań sprzętowych, bowiem nie można go wygenerować instrukcja kodu maszynowego. W przeciwieństwie do przerwań odbieranych liniami IRQ, które obsługiwane są przez dedykowany do tego celu kontroler 8259A, sygnał przerwania NMI kierowany jest bezpośrednio do końcówki NMI procesora. Wysoki sygnał logiczny na tym wejściu prowadzi, natychmiast po zakończeniu wykonywania bieżącego rozkazu, do wywołania procedury INT 2. Przerwanie takie nie może być zamaskowane ani rozkazem cl i procesora, ani przez operacje na rejestrze IMR kontrolera 8259A. Ma ono najwyższy priorytet ze wszystkich przerwań sprzętowych.
Wywołanie przerwania NMI w przypadku komputerów PC ma miejsce po stwierdzeniu błędu parzystości pamięci operacyjnej, co należy uznać za sytuację uniemożliwiającą dalsze użytkowanie komputera. Procedura obsługi INT 2 wyprowadza na monitor stosowny komunikat (Parrity error at...) i wstrzymuje pracę systemu. Jedynym wyjściem w tym momencie jest ponowny restart systemu w nadziei, że błąd parzystości spowodowany został na przykład chwilowa utrata styku lub przegrzaniem modułów RAM, a nie ich trwałym uszkodzeniem. Źródłem tego przerwania może też być sygnał generowany przez uszkodzony koprocesor arytmetyczny. [4]
Przerwanie niemaskowalne, nie jest jednak absolutnie nie do zamaskowania. W momencie włączenia komputera następuje uruchomienie procedur inicjalizujących BIOS-u. Jednym z ich zadań jest budowa tablicy wektorów przerwań, tj. stałego miejsca w "niskim" obszarze pamięci operacyjnej, w którym umieszczane są czterobajtowe adresy punktów wejścia do procedur obsługi wszystkich przerwań. Jednocześnie inicjalizowane są wszystkie inne rejestry systemu. Własna inicjalizację przeprowadzają układy obsługi pamięci dynamicznej. Może się zdarzyć, że układ kontroli parzystości RAM stwierdzi błąd i wywoła tym samym procedurę obsługi przerwania INT 2. Adres punktu wejścia do tej procedury może jednak nie być jeszcze ustawiony i wskazywać przypadkowe miejsce w pamięci. Procesor, podejmując wykonanie programu od tego miejsca, najprawdopodobniej zawiesi się. [4]
Jedna z pierwszych operacji, jakie musi wykonać BIOS uruchamiając komputer, jest więc zamaskowanie przerwania NMI. Odpowiada to ustawieniu bitu 7 portu o adresie 0A0h (w XT) lub 070h (w AT). Manipulując samodzielnie tym bitem należy zachować szczególna ostrożność - port ten bowiem steruje również praca pamięci konfiguracji CMOS-RAM.
Przerwania
Przerwanie jest to zatrzymanie wykonującego się w danej chwili programu i przekazanie sterowania do procedury, która to przerwanie obsługuje. Po zakończeniu się tej procedury następuje powrót do wcześniej wykonywanego programu. Istnieją dwa rodzaje przerwań: przerwania sprzętowe i systemowe. Przerwania sprzętowe wywoływane są w krytycznych sytuacjach (na żądanie urządzeń zewnętrznych), np. gdy naciśnięto klawisz Ctrl+Break na klawiaturze, lub gdy jest błąd parzystości pamięci. Ze względu na to, że w jednej chwili może zgłosić kilka urządzeń żądanie o przerwanie, a procesor w danej chwili może obsłużyć tylko jedno z nich, każdemu przerwaniu sprzętowemu przyporządkowano pewien numer (priorytet). Numer ten mówi w jakiej kolejności należy wykonywać przerwania, np. jeśli w tej samej chwili zgłosi żądanie o przerwanie klawiatura i zegar, procesor najpierw wykona przerwanie zegarowe, ponieważ ma większy priorytet (0), a dopiero później przerwanie klawiatury (priorytet 1). Przerwania sprzętowe oznacza się jako IRQx, gdzie x jest numerem priorytetu. Przerwanie o najwyższym priorytecie (IRQ0) ma numer 8 i jest to przerwanie zegarowe. Tabela części przerwań sprzętowych znajduje się na końcu artykułu. Przerwania sprzętowe mogą być maskowalne lub nie. Przerwania maskowalne nie muszą być przekazywane do procesora w przeciwieństwie do przerwań niemaskowalnych. W komputerach IBM PC istnieje przerwanie maskowalne NMI (ang. Non Maskable Interrupt). Jest ono generowane w sytuacjach krytycznych, np. przy błędzie parzystości pamięci. Przerwania systemowe mogą być wywoływane tylko w sposób programowy.W procesorach rodziny 8086 adresy przerwań przechowywane są w tzw. tablicy wektorów przerwań. Tablica ta zajmuje 1024 bajty pamięci i rozpoczyna się od adresu 0000:0000, czyli na samym początku pamięci komputera. Adresy procedur przerwań są 32-bitowe, a więc adres do jednego przerwania zapisany jest w czterech bajtach. W dwóch pierwszych bajtach zapisany jest offset, a w następnych dwóch segment. Np. offset przerwania 5 zapisany jest pod adresem: 0000:0014H, a segment pod adresem 0000:0016H. Do szybkiego znajdywania adresu przerwania można posłużyć się wzorem:
offset=4*NR segment=4*NR+2
, gdzie NR - numer przerwania.
Jeśli chcemy np. zmienić adres przerwania zegarowego 1CH, tak aby wskazywał na naszą procedurę, zakładając, że nasza procedura zaczyna się etykietą PRZERWANIE napiszemy ciąg instrukcji:
MOV AX,0 MOV ES:AX
MOV ES:70H,OFFSET PRZERWANIE MOV ES:72H,SEG PRZERWANIE
Można również to uczynić za pomocą przerwania 21H i funkcji 25H. W rejestrze AL podajemy numer przerwania, natomiast adres przekazujemy w DS:DX. Ten sam przypadek co wyżej uzyskany za pomocą przerwań będzie wyglądać następująco:
MOV AX,SEG PRZERWANIE MOV DS,AX MOV
DX,OFFSET PRZERWANIE MOV AL,01CH MOV AH,25H INT
21H
Za pomocą przerwania 21H i funkcji 35H możemy uzyskać informacje o adresie procedury obsługi danego przerwania. W rejestrze AL podajemy wówczas numer interesującego nas przerwania, a w odpowiedzi dostaniemy w ES:BX adres tego przerwania.
Każda procedura obsługi przerwania musi być zakończona instrukcją powrotu IRET. Działa ona na wszystkich procesorach. Powoduje zdjęcie ze stosu rejestrów IP, CS oraz rejestru znaczników. Na procesorach 80386 można użyć instrukcji IRETD.
Przerwania sprzętowe
Przerwanie |
IRQ |
Komputer PC i PC XT |
Komputer AT, 386, 486 i Pentium |
08h |
IRQ0 |
Czasomierz systemowy |
Czasomierz systemowy |
09h |
IRQ1 |
Kontroler klawiatury |
Kontroler klawiatury |
0Ah |
IRQ2 |
Nieprzypisana |
Przyłączona do linii IRQ od 8 do 15 |
0Bh |
IRQ3 |
Port COM2: 2F8h:2FFh |
Port COM2: 2F8h:2FFh |
0Ch |
IRQ4 |
Port COM1: 3F8h-3FFh |
Port COM1: 3F8h-3FFh |
0Dh |
IRQ5 |
Kontroler dysku twardego XT |
Port LPT2: 378h lub 278h |
0Eh |
IRQ6 |
Kontroler dyskietek |
Kontroler dyskietek |
0Fh |
IRQ7 |
Port LPT1: 3BCh lub 378h |
Port LPT1: 3BCh lub 378h |
010h |
IRQ8 |
Niedostępna |
Zegar czasu rzeczywistego |
011h |
IRQ9 |
Niedostępna |
Zastępuje linię IRQ 2 |
012h |
IRQ10 |
Niedostępna |
Nieprzypisana |
013h |
IRQ11 |
Niedostępna |
Nieprzypisana |
014h |
IRQ12 |
Niedostępna |
Port myszy komputera PS/2 |
015h |
IRQ13 |
Niedostępna |
NPU (jednostka przetwarzania numerycznego) |
016h |
IRQ14 |
Niedostępna |
Dysk twardy |
017h |
IRQ15 |
Niedostępna |
Karta drugiego dysku twardego (nowsze komputery) |
Wprowadzenie do trybu chronionego
Dotychczas wszystkie nasze wysiłki programotwórcze były skupione na trybie rzeczywistym procesora. Mieliśmy w nim znane i jakże nielubiane 64-kilobajtowe segmenty oraz uciążliwe ograniczenie rozmiaru pamięci do 1M (a właściwie 640 kB bo skorzystanie z pozostałych 384 kB było w dużej części niemożliwe). Mieliśmy jednakże do naszej dyspozycji BIOS i DOS oraz ich usługi, które uwalniały nas od żmudnego kodowania osobnej procedury dla każdej czynności jaką chcieliśmy wykonać. Dziś tryb rzeczywisty zszedł na dalszy plan i wykorzystywany jest w zasadzie jedynie przy starcie PeCeta (choć moim skromnym zdaniem, przy odrobinie chęci, da się w nim zrobić całkiem dużo).
W dalszej części tego tekstu tryb rzeczywisty będziemy określać skrótem RM (Real Mode) zaś tryb chroniony PM (Protected Mode).
Z praktycznego punktu widzenia praca w trybie chronionym jest wydajniejsza i łatwiejsza ... oczywiście dopiero po załatwieniu wszystkich trudnych sraw :-) Spójrzmy zatem na poniższą tabelę.
. |
RM |
PM 16-bit |
PM 32-bit |
Adres bazowy segmentu |
16-bit (1M) = rejestr seg. * 16 |
24-bit (16M) z deskryptora |
32-bit (4G) z deskryptora |
Rozmiar segmentu (limit) |
16-bit, 64k (stały) |
16-bit, 1k - 64k |
20-bit, 1b-1M lub 4k-1G |
Ochrona |
nie |
tak |
tak |
Rejestr segmentowy |
adres bazowy / 16 |
selektor |
selektor |
Przewaga trybu chronionego nad rzeczywistym nie podlega chyba dyskusji. I nie chodzi tu tylko o powyższe liczby. Nowoczesne procesory po prostu lepiej pracują w trybie chronionym bo z myślą o nim zostały zaprojektowane.
Pojęcia z powyższej tabeli staną się jasne w dalszej części tego artykułu. Dla ścisłości dodam, że 16-bitowym trybem chronionym nie będziemy się zajmować ponieważ jest to przeżytek z epoki procesora 286. Całą uwagę skupimy na PM 32-bitowym. Ale po kolei ...
Adresowanie pamięci
We wszystkich procesorach serii 80x86 wszystkie odwołania do pamięci są niczym innym jak przesunięciami względem adresu bazowego niezależnie od trybu (RM czy PM) zaś zawartość rejestru segmentowego nazywana jest selektorem (ang. selector).
Rejestry segmentowe CS, DS, ES, SS, FS i GS używane są do określania segmentów scharakteryzowanych jako "kod", "dane" i "stos" (ang. "code", "data", "stack"). Rejestr CS wskazuje na segment zawierający aktualnie wykonywany kod. Wskaźnik instrukcji (EIP, ang. instruction pointer) jest przesunięciem względem początku segmentu kodu do miejsca, w którym znajduje się następna instrukcja do wykonania. Międzysegmentowe transfery kontroli (skoki, powroty, przerwania i wyjątki) zmieniają wartości rejestru CS. Wszystkie operacje dotyczące stosu korzystają z rejestru SS do zlokalizowania właściwego segmentu. Rejestry DS, ES, FS i GS umożliwiają dostęp aż do czterech różnych obszarów danych (FS i GS dostępne są począwszy od procesorów 386).
W trybie RM wartość selektora (wpisanego do rejestru segmentowego) określa 16 starszych bitów z 20-bitowego adresu liniowego. W trybie PM selektor służy jako indeks w tablicy deksryptorów (ang. descriptor table). Deskryptor wskazany wartością selektora zawiera pełny 32-bitowy adres (adres bazowy) jak również inne informacje. Dopóki dany rejestr segmentowy nie zostanie zmieniony, wszystkie odwołania do pamięci z użyciem tego segmentu będą powodować dodanie 32-bitowego przesunięcia do adresu bazowego z danego deskryptora w celu otrzymania aktualnego adresu liniowego (ang. linear address). Jeśli stronicowanie (ang. paging) jest włączone to adres liniowy podlega translacji na adres fizyczny (ang. physical address). Jeśli stronicowanie jest wyłączone to adres liniowy jest ostatecznym adresem fizycznym. Segmenty w trybie PM nie są ograniczone do 64k lecz mogą rosnąć aż do 4G (4G = 4096M = 4194304k = 4294967296b).
Deskryptory
Istnieją dwa rodzaje deskryptorów (ang. descriptor) : systemowe i segmentowe. Do adresowania pamięci wystarczą nam tylko deskryptory segmentowe - deskryptorami systemowymi zajmiemy się później. Wszystkie deskryptory mają długość 8 bajtów i przechowywane są w jednej z tablic deskryptorów. Deskryptor segmentowy określa atrybuty danego segmentu tj. jego początek inaczej adres bazowy, rozmiar, typ oraz prawa dostępu.
Powyższy rysunek przedstawia ogólny format deskryptora segmentowego. Znaczenie poszczególnych bitów wyjaśnia poniższa tabela.
Bit |
Znaczenie |
G |
Ziarnistość (ang Granularity), określa czy limit jest sprawdzany w bajtach czy jednostkach 4k. |
D/B |
Domyślny/Duży (ang. Default/Big). Dla segmentów kodu określa domyślny rozmiar dla tzw. operandu instrukcji (16-bit lub 32-bit). Dla segmentów danych typu expand-down wpływa na sposób kontroli limitu. |
AVL |
Dostępny (ang. Available) dla projektanta systemu operacyjnego do wykorzystania w dowolnym celu. |
P |
Obecny (ang. Present). Wskazuje czy dany segment jest obecny w pamięci. Może być pomocny w implementacji pamięci wirtualnej. |
DPL |
Poziom Uprzywilejowania Deskryptora (ang. Descriptor Privilege Level). To 2-bitowe pole jest wykorzystywane przez mechanizmy ochrony. |
DT |
Typ Deskryptora (ang. Descriptor Type). DT=0 oznacza deskryptor systemowy, DT=1 deksryptor segmentowy (kod lub dane). |
Co/Da |
Określa czy segment zawiera kod (ang Code, bit=1) czy dane (ang. Data, bit=0). Jego wartość decyduje czy dwa kolejne bity są interpretowane jako C/R (ustawiony) czy E/W (wyzerowany). |
E |
Dotyczy segmentów danych. Jeśli E=1 to segment jest typu expand-down tzn. może się rozrastać 'w dół'. Zazwyczaj tak zorganizowane są segmenty stosu. |
W |
Dotyczy segmentów danych. Jeśli W=1 to segment można zapisywać (ang. write) i odczytywać (ang. read) zaś w przeciwnym wypadku segment jest tylko do odczytu. |
C |
Dotyczy segmentów kodu. Jeśli C=1 to segment jest typu conforming (ze względu na funkcję trudno jest tutaj znaleźć właściwe tłumaczenie). Dla C=0 segment ma typ non-conforming. |
R |
Dotyczy segmentów kodu. Jeśli R=1 to segment kodu może być swobodnie odczytywany (ang. read). Gdy R=0 to zawartość segmentu (kod) może być tylko wykonywana (możliwy jest tylko odczyt instrukcji do wykonania przez sam procesor). |
A |
Procesor automatycznie ustawia ten bit gdy miało miejsce odwołanie do tego deskryptora (ang. Accessed). |
Tablice deskryptorów
Mamy trzy rodzaje tablic: globalną (GDT, ang. Global Descriptor Table), lokalną (LDT, ang. Local Descriptor Table) i przerwań (IDT, ang. Interrupt Descriptor Table). Tablica GDT (wskazywana przez rejestr GDTR) przechwuje do 8191 deskryptorów (deskryptor zerowy jest zarezerwowany), które są dostępne dla wszystkich zadań (ang. tasks). Tablica LDT (wskazywana przez rejestr LDTR) przechowuje do 8192 deskryptorów (zerowy nie jest zarezerwowany), które są widziane tylko przez dane zadanie. Tablica IDT (oczywiście wskazywana rejestrem IDTR) przechowuje do 256 bramek (ang. gate). Podobnie do tablicy wektorów z trybu RM, tablica IDT zawiera dane wskazujące położenie kodu obsługi przerwań. Każde przerwanie, sprzętowe czy programowe, ma przypisany numer wektora. Jest on niczym innym jak indeksem w tablicy IDT. Wszystkie trzy tablice są tworzone przez oprogramowanie i przechowywane w pamięci.
Każdy rejestr segmentowy posiada przypisany deskryptor umieszczony wewnątrz procesora. Przy wpisaniu do rejestru segmentowego nowego selektora procesor odczytuje z tablicy deskryptor po czym umieszcza go w swoim wewnętrznym odpowiedniku, skąd zawsze ma do niego swobodny dostęp.
Dodatkowo procesor posiada trzy rejestry zawierające faktyczne adresy tablic deskryptorów, które służą mu do ich lokalizowania i odczytu. Są to GDTR, LDTR oraz IDTR (odpowiednio ang. Global Descriptor Table Register, Local Descriptor Table Register oraz Interrupt Descriptor Table Register). Ich zapis i odczyt umożliwiają specjalne instrukcje.
Selektory
Jak już wspomnieliśmy, selektor oznacza zawartość rejestru segmentowego lub wartość, która ma zostać do niego wpisana. Sam selektor składa się z trzech części: indeksu, wskaźnika tablicy TI (ang. Table Indicator) oraz żądanego poziomu uprzywilejowania RPL (ang. Requested Privilege Level). Indeks mówi nam, który deskryptor z tablicy ma być pobrany. Wskaźnik określa, w której tablicy ma być on szukany (0 - GDT, 1 - LDT). Zagadnienie poziomów uprzywilejowania poruszymy później, przy omawianiu mechanizmów ochrony. Poniższy rysunek przedstawia strukturę selektora.
Znów o adresowaniu
Skoro już wiemy jakie trzy elementy biorą udział w określaniu adresu omówmy sobie jak się to odbywa (problemy związane z ochroną odłóżmy na chwilę na bok). Do rejestru segmentowego wpisana zostaje wartość selektora. Następnie wartość ta jest rozdzielana na trzy cześci (indeks, TI oraz RPL). Procesor pobiera z właściwej tablicy wskazany deksryptor (RPL też jest teraz sprawdzany ale zajmiemy się tym później). Odczytane dane trafiają do wspomnianego wcześniej wewnętrznego deskryptora odpowiadającego modyfikowanemu rejestrowi segmentowemu. Od tej pory wszystkie odwołania do pamięci korzystające z tego rejestru segmentowego dodają podane w kodzie instrukcji przesunięcie do adresu bazowego umieszczonego w wewnętrznym deskryptorze.
Obejrzyjmy to może na przykładzie. Najpierw fragment kodu.
mov ax, 20h ; selektor 20h = 0000000000100 0 00
mov ds, ax ; ładujemy do DS
mov ebx, 100h
mov eax, [ebx] ; odwołanie do pamięci poprzez DS
Zapis wartości do rejstru segmentowego mamy już omówiony. W tym przykładzie selektor ma wartość 20h co oznacza indeks = 4, TI = 0 (tablica GDT), RPL = 0. Co więc się dzieje w momencie odwołania do pamięci poprzez ten rejestr? Otóż z wewnętrznego deskryptora pobrany zostaje adres bazowy. Następnie procesor dodaje do niego wartość przesunięcia podaną w kodzie instrukcji (w naszym przykładzie jest to wartość EBX czyli 100h). Wynikiem jest adres liniowy (adres bazowy + 100h). Jak już wiemy w przypadku włączonego mechanizmu stronicowania adres ten podlega odpowiedniej translacji na fizyczny jeśli zaś stronicowanie jest zablokowane to jest on równy adresowi fizycznemu.
Ochrona
Podstawowym powodem włączenia mechanizmów ochronnych do architektury x86 było (i jest) ułatwienie wykrywania i identyfikacji błędów oraz umożliwienie projektowania bezpiecznych i stabilnych systemów operacyjnych. Jednocześnie, korzystanie z trybu PM wcale nie zmusza nas do stosowania funkcji ochronnych, a przynajmniej nie wszystkich.
Jak już wiemy każdy deskryptor, poza adresem bazowym, zawiera także dodatkowe informacje dotyczące systemu ochrony takie jak :
- sposób dostępu do różnego typu segmentów,
- zapewnienie dostępu w granicach limitu,
- zarządzanie poziomami uprzywilejowania (czyli kto i do czego ma dostęp),
- kontrola nad uprzywilejowanymi instrukcjami.
Kontrola typu
Kontrola typu segmentu ma za zadanie zapewnić, że dany segment jest używany we właściwy sposób. Stąd też wynikaja podstawowe i oczywiste reguły jak np.:
- rejestr CS może zawierać tylko selektor wskazujący na segment kodu wykonywalnego,
- rejestry DS, ES, FS i GS nie mogą być załadowane selektorami wskazującymi na segmenty nie przeznaczone do odczytu,
- tylko selektory wskazujące na segmenty zapisywalne mogą być załadowane do SS,
- żadna instrukcja nie może zapisywać do segmentu kodu wykonywalnego,
- żadna instrukcja nie może zapisywać do segmentu danych jeśli bit W deskryptora nie jest ustawiony,
- żadna instrukcja nie może odczytywać segmentu kodu wykonywalnego jeśli bit R nie jest ustawiony.
Powyższe zasady pozwalają w prosty sposób na zapewnienie poprawnego i zgodnego z przeznaczeniem użycia segmentów. Kontrola typu jest przeprowadzana przy każdym załadowaniu nowej wartości do rejestru segmentowego. Jeżeli któraś z tych reguł zostanie naruszona przez aplikację to wygenerowany będzie wyjątek GPF (ang. General Protection Fault czyli Ogólne Naruszenie Ochrony).
Dla przykładu, mamy dwa zadania (Z1 i Z2) wymagające dostępu do tego samego obszaru pamięci ale chcemy aby tylko jedno z nich (Z1) mogło dokonywać zapisów i odczytów w tym obszarze a drugie (Z2) tylko odczytywać. W trybie RM (lub ogólnie na starszych procesorach) zadanie Z1 mogłoby przekazywać do Z2 kopię wspólnego obszaru danych lub wskaźnik do nich. Problem z kopią polega na tym, iż może ona być już nieaktualna. Z kolei wskaźnik umożliwiłby zadaniu Z2 przypadkowe zmodyfikowanie danych. Rozwiązanie polega na zastosowaniu trybu PM i mechanizmów ochrony. Zadanie Z1 powinno utworzyć dwa deskryptory o tym samym adresie bazowym (wspólne dane), jeden dla siebie z ustawionymi atrybutami odczytu i zapisu, drugi dla Z2 tylko z możliwością odczytu. W ten sposób Z2 miałoby dostęp do danych ale nie mogłoby ich zmodyfikować. Dodatkowe zabezpieczenie mogłaby stanowić kontrola limitu, mająca na celu np. udostępnienie zadaniu Z2 tylko pewnej części danych.
Kontrola limitu
Koncepcja sprawdzania limitu jest bardzo prosta. Kontrola limitu uniemożliwia programowi dostęp do obszarów poza granicami segmentu. Procesor przeprowadza ją automatycznie przy każdym odwołaniu do pamięci tak przy segmentach kodu jak i danych. Jeżeli dany segment jest określony jako 512-bajtowy to dostęp do 513-tego bajtu i dalszych jest zabroniony - właśnie za to odpowiada kontrola limitu. Jak wiemy rozmiar pola limitu w deskryptorze to 20 bitów, stąd maksymalny limit to wartość 1048575. I tu do gry wchodzi bit ziarnistości G. Otóż jeśli bit G jest wyzerowany, limit jest sprawdzany co do bajtu i maksymalny rozmiar segmentu wynosi właśnie 1M. Z kolei ustawienie bitu G powoduje zmianę ziarnistości na 4k dając w efekcie maksymalny rozmiar segmentu 4k * 1M = 4G.
Dla pełnej jasności przedstawmy dwa szczególne przypadki gdy limit jest równy 0FFFFFh :
- gdy G=0 to maksymalny dostępny adres to 0FFFFFh (1048575 = 1M)
- gdy G=1 to maksymalny dostępny adres to 0FFFFFFFFh (4294967295 = 4G)
Z powyższego wynika również, że mając G=0 możemy ustalać rozmiar segmentu z dokładnością do bajta zaś przy G=1 z dokładnością do 4-kilobajtowej strony.
Z punktu widzenia programu dostęp nie jest możliwy do:
- bajtu (8-bit), jeśli adres jest większy od limitu (ofs > limit),
- słowa (16-bit), jeśli adres jest większy od limitu pomniejszonego o1 (ofs > limit - 1),
- podwójnego słowa (32-bit), jeśli adres jest większy od limitu pomniejszonego o 3 (ofs > limit - 3),
- poczwórnego słowa (64-bit), jeśli adres jest większy od limitu pomniejszonego o 7 (ofs > limit - 7).
Przekroczenie powyższych adresów wygeneruje GPF.
Inaczej wygląda sprawa limitu przy segmentach typu expand-down (ustawiony bit E w atrybutach deskryptora), będących zazwyczaj segmentami stosu. W tym przypadku dostęp jest możliwy dopóki przesunięcie jest większe od limitu. Dostępne adresy zaczynają się od wartości limit + 1 a kończą na 64k lub 4G w zależności od bitu B. Wynika z tego, że przesunięcie 0 będzie określało adres równy bazowemu zaś przesunięcie 0FFFFFFFFh adres o 1 mniejszy niż adres bazowy. Stąd tez w segmentach stosu z ustawionym bitem E początkowa wartość ESP wynosi 0 lub 0FFFFFFFCh. Oczywiście korzystanie z tego typu segmentów nie jest obowiązkowe.
W przypadku przekroczenia limitu w segmencie stosu wygenerowany zostanie wyjątek SF (ang. Stack Fault czyli Błąd Stosu). Kontrola limitu może być w praktyce zablokowana poprzez ustawienie bitu G i limitu do maksymalnej wartości co spowoduje udostępnienie całych 4G. Dla segmentów expand-down maksymalny limit wynosi 0.
Poziomy uprzywilejowania
Nowoczesna architektura x86 daje nam do dyspozycji sposób na kontrolowanie jakie rodzaje aplikacji mają dostęp do systemu, segmentów oraz sprzętu z obszarem I/O zmapowanym do pamięci RAM. Odbywa się to za pomocą poziomów uprzywilejowania, ponumerowanych od 0 do 3. Mniejszy numer oznacza wyższy poziom (musimy pamiętać o tej swoistej 'odwrotności' gdy będziemy mówić o poziomach niższych i wyższych). W zasadzie nie ma bezpośredniej możliwości ominięcia tego mechanizmu ochrony, może to być jedynie dokonane poprzez przypisanie poziomu 0 wszystkim obiektom i segmentom w systemie.
Korzystanie z różnych poziomów uprzywilejowania jest kluczowe dla zapewnienia stabilności systemu, zwłaszcza w sytuacji gdy ma on obsługiwać programy tworzone przez użytkownika/programistę nie związanego z samym systemem. Dla przykładu nadanie systemowi operacyjnemu poziomu 0 a programom użytkownika poziomu 3 chroni obszary sytemowe przed ingerencją ze strony aplikacji. Oczywiście jeśli system może ufać wszystkim uruchamianym zadaniom i aplikacjom poziom uprzywilejowania może być na stałe ustawiony na 0, w praktyce eliminując tego typu ochronę (stanowi to jednak potencjalne niebezpieczeństwo).
Istnieją trzy struktury, w których określany jest poziom uprzywilejowania :
- CPL (ang. Current Privilege Level), aktualny poziom uprzywilejowania zapisany w dwóch najmłodszych bitach rejestru CS czyli poziom aktualnie wykonywanego kodu,
- DPL (ang. Descriptor Privilege Level), poziom uprzywilejowania deskryptora,
- RPL (ang. Requested Privilege Level), żądany poziom uprzywilejowania dla ładowanego selektora.
Te trzy struktury są sprawdzane przy każdorazowym załadowaniu nowej wartości do rejestru segmentowego. Kontrola poziomu jest przeprowadzana inaczej dla kodu, danych i stosu.
Zanim zajmiemy się dokładniej poziomami musimy sobie coś wyjaśnić. Mówiąc, że poziom A jest wyższy od B zapisujemy to liczbowo jako A < B zaś jeśli A jest niższy od B to zapis liczbowy ma postac A > B. Wynika to ze wspomnianego już faktu odwrotnej numeracji poziomów. Zrozumienie tej zależności jest kluczowe dla uniknięcia pomyłki przy omawianiu niektórych zagadnień w dalszej części tego artykułu.
Ograniczanie dostępu do danych
Aktualnie wykonywany kod ma dostęp do danych o poziomie niższym (liczbowo większym) lub równym własnemu. Dostęp do danych o wyższym poziomie jest niemożliwy. Inaczej mówiąc dostęp do danych jest dozwolony jeśli spełniony jest warunek DPL >= CPL i DPL >= RPL. Załadowanie segmentu danych selektorem wskazującym na segment kodu jest dozwolone o ile ów segment kodu ma ustawiony bit R (readable). Wyjątkiem jest tutaj stos, który jest inny dla każdego poziomu uprzylejowania. Podczas zapisywania do rejestru SS musi byc spełniony warunek CPL = DPL = RPL.
Tak dla scisłości, mówiąc tutaj o DPL mamy na myśli poziom deskryptora, który chcemy załadować. Oczywiście, jeśli żaden z powyższych warunków nie zostanie spełniony procesor wygeneruje wyjątek GPF.
Poziomy w transferach kontroli
Pod określeniem transferu kontroli będziemy rozumieć przejście od aktualnie wykonywanego kodu do innego. Transfery międzysegmentowe dokonywane instrukcjami CALL, JMP, RET, INT lub IRET stanowią przedmiot szczegółowej kontroli jeśli chodzi o poziomy uprzywilejowania. Podczas takich transferów selektor docelowy musi wskazywać na deskryptor będący :
- innym segmentem kodu,
- bramką (ang. gate) - wywołanie (ang. call), przerwanie lub pułapka (ang. trap),
- segmentem TSS (ang. Taks State Segment czyli Segment Stanu Zadania).
Sposób kontroli poziomu uprzywilejowania różni się w zależności od typu celu transferu i użytej instrukcji. Najpierw omówimy transfery międzysegmentowe (instrukcje JMP, CALL i RET) oraz transfery poprzez bramki (tylko instrukcje CALL i JMP). Pozostałymi rodzajami transferów zajmiemy się w dalszej części tekstu.
Confoming kontra Non-conforming
Przekazanie kontroli do innego segmentu kodu instrukcjami CALL i JMP objęte jest jedynie dwiema regułami. Wynika to z faktu, iż mamy doczynienia z dwoma rodzajami segmentów kodu : conforming i non-conforming. Gdy docelowy segment jest typu non-conforming (inaczej mówiąc 'normalny') to musi mieć on taki sam poziom jak segment, z którego nastąpiło wywołanie (DPL = CPL, dodatkowo RPL <= CPL), w przeciwnym wypadku generowany jest wyjątek GPF.
Segmenty typu conforming używane są gdy chcemy udostępnić kod aplikacjom o różnych poziomach uprzywilejowania. W momencie przekazania kontroli do takiego segmentu jego CPL przejmuje wartość z segmentu, z którego dane wywołanie nastąpiło. Wobec tego poziom uprzywilejowania nie ulega zmianie. Aby wywołanie takie było możliwe musi być spełniony warunek DPL <= CPL (jest to sytuacja odwrotna niż przy dostępie do danych). Jest to jedyny przypadek gdy CPL może być różne od DPL w segmencie aktualnie wykonywanego kodu. Sama wartość DPL wskazuje najwyższy poziom z jakiego możliwy jest transfer do tego segmentu. Np. dla DPL = 2 wywołanie może nastąpić wyłącznie z segmentu o CPL 2 lub 3. Mówiąc inaczej segmenty typu conforming pozwalają na wywoływanie kodu bardziej uprzywilejowanego przez kod mniej uprzywilejowany. Oczywiście, skoro CPL pozostaje bez zmian to w dalszym ciągu zachowane są wszystkie zasady bezpieczeństwa.
Bramki
Bramka (ang. gate lub call gate) pozwala na przekazanie kontroli z segmentu o niższym poziomie do segmentu o wyższym poziomie uprzywilejowania. Każda bramka posiada swój deskryptor składający się z pięciu pól: selektora docelowego, 32-bitowego przesunięcia, atrybutów, licznika parametrów (ang. DWord Count, dokładnie chodzi o licznik podwójnych słów) oraz DPL (zob. poniższy diagram). Selektor docelowy wskazuje segment kodu wywoływanego poprzez daną bramkę instrukcją CALL lub JMP. Przesunięcie jest adresem kodu w segmencie docelowym (określa bezpośrednio nową wartość EIP). Licznik parametrów określa rozmiar danych, które mają być skopiowane z mniej uprzywilejowanego stosu na stos należący do poziomu kodu wywoływanego. DPL bramki określa które poziomy mają do niej dostęp. Obowiązuje warunek DPL <= CPL (oczywiście chodzi tu o DPL bramki). Jeśli nie zostanie spełniony to efektem będzie GPF.
Samych bramek mamy kilka typów. Procesor odróżnia je po polu Type w atrybutach deskryptora (zob. niżej). W tej chwili nie będziemy omawiać ich wszystkich. Wystarczy wiedzieć, że korzystają one z podobnego mechanizmu przy transferze kontroli do innego segmentu.
Podczas zapisu do rejestru CS selektora wskazującego na deskryptor bramki (deskryptor systemowy, nie segmentowy) muszą być spełnione warunki : DPL bramki <= (CPL i RPL) oraz CPL >= DPL segmentu docelowego. Inaczej mówiąc procesor najpierw sprawdza czy segment źródłowy może skorzystać z bramki (DPL bramki <= CPL) a następnie czy może skorzystać z segmentu docelowego (CPL >= DPL celu). Chodzi tu o umożliwienie segmentom z niższym poziomem dostępu do kodu umieszczonego w segmentach bardziej uprzywilejowanych (przykładem może być korzystanie z funkcji systemu operacyjnego przez programy użytkownika).
Instrukcja RET
W zasadzie powrót instrukcją RET powinien odbywać się bez problemów jednakże zawsze istnieje możliwość, że wywołany kod zmienił zawartość stosu a tym samym wartość selektora powrotnego. Dlatego też przy każdej międzysegmentowej instrukcji RET procesor dokonuje kontroli poziomów. Po wczytaniu ze stosu powrotnej wartości CS sprawdzane jest jego RPL. Jeśli RPL > CPL to nastepuje powrót międzypoziomowy. Jeżeli RPL = CPL to mamy zwykły powrót na tym samym poziomie. W trzecim przypadku, gdy RPL < CPL, procesor wygeneruje GPF gdyż nie jest możliwe wykonanie instrukcją RET powrotu do segmentu bardziej uprzywilejowanego.
Ochrona instrukcji
Mechanizm ochrony zapobiega wykonywaniu instrukcji nazwijmy to 'delikatnych' z punktu widzenia bezpieczeństwa systemu operacyjnego na poziomach innych niż te 'zaufane'. Niektóre z instrukcji są możliwe do wykonania tylko na najwyższym poziomie, inne reagują na tzw. poziom uprzylejowania I/O.
Poziomy uprzywilejowania I/O
Dwa bity w rejestrze EFLAGS określają aktualny poziom I/O dla każdego zadania (IOPL, ang. Input-Output Privilege Level). Instrukcje I/O są związane z komunikacją ze sprzętem. Sa to : IN, INS, OUT, OUTS, CLI i STI (obsługa portów I/O oraz blokowanie/odblokowywanie przerwań). Gdy procesor znajduje się w trybie V-86 (o nim później) do powyższego zestawu dochodzą : INT n, IRET, PUSHf oraz POPF. Pozwalają one na lepszą emulację 8086 w trybie chronionym. Poziom I/O określa jaki minimalny poziom uprzywilejowania segmentu jest wymagany do wykonania powyższych instrukcji. Wartość IOPL może być zmieniona przez kod wykonywany na najwyższym poziomie (CPL=0). Próba zmiany IOPL przez kod o niższym poziomie jest ignorowana (IOPL pozostaje taki sam) bez generowania żadnych wyjątków.
Jak już wspomnieliśmy każde zadanie posiada własną kopię EFLAGS stąd też może mieć nadany inny poziom IOPL. Dodatkowo, każde zadanie posiada własną mapę bitową odzwierciedlającą wszystkie porty I/O. Pozwala to na kontrolę dostępu do każdego portu niezależnie od ustawionego IOPL.
Posiadanie instrukcji reagujących na IOPL umożliwia stworzenie bezpiecznego systemu operacyjnego, w którym dostęp do sprzetu jest pod pełną kontrolą. Jeżeli bowiem wszystkie aplikacje użytkowe wykonywane są na poziomie 3 przy IOPL = 0 to są one zmuszone do korzystania z funkcji systemu aby uzyskać (pośredni) dostęp do sprzętu. Bitowa mapa portów może służyć do udostępniania (lub nie) urzadzen różnym zadaniom. To samo dotyczy sprzętu, którego obszar I/O jest zmapowany w pamięci RAM. W tej sytuacji system może utworzyć dodatkowe deskryptory z odpowiednimi prawami udostępniając je w razie potrzeby.
Stronicowanie
Moduł stronicowania (ang. Paging Unit) ma za zadanie umożliwić realizację systemu pamięci wirtualnej oraz uruchamianie wielu zadań typu V-86 jednocześnie. Oczywiście do projektanta systemu należy wybór czy skorzystać ze stronicowania czy nie, jako że nie musi być ono włączone aby możliwa była praca w trybie PM.
Mechanizm stronicowania jest włączany poprzez ustawienie bitu PG w rejestrze kontrolnym CR0 (rejestr ten omówimy w swoim czasie). Jeśli bit ten jest ustawiony to każdy adres liniowy jest przesyłany do modułu stronicowania gdzie podlega odpowiedniej translacji. Wynikiem jest adres fizyczny. W przypadku gdy bit PG jest wyzerowany, adres liniowy jest równy adresowi fizycznemu o czym już wspominaliśmy.
Moduł stronicowania dzieli pamięć na odcinki długości 4 kB nazywane stronami. Aby ustalić która strona odpowiada danemu odwołaniu do pamięci procesor dzieli 32-bitowy adres liniowy na trzy cześci: 10-bitowy indeks w katalogu stron (ang. page directory), 10-bitowy indeks w talicy stron (ang. page table) oraz 12-bitowe przesunięcie wewnątrz strony. Mamy tu więc dwie tablice używane do zaadresowania strony pamięci. Najpierw brany jest pod uwagę indeks katalogu stron. Pozwala on zaadresować do 1k tablic stron. Dalej jest indeks w tablicy stron wskazanej przez odpowiednią wartość z katalogu. Każda tablica może zaadresować do 1K stron. Mamy stąd całkowitą liczbę stron 1k * 1k = 1M. Ponieważ rozmiar pojedynczej strony to 4k daje nam to możliwość pokrycia całej przestrzeni adresowej (czyli 4G). Adres fizyczny aktualnego katalogu jest przechowywany w rejestrze kontrolnym CR3 (zajmiemy się nim w swoim czasie). Aktualnie używaną tablicę stron procesor przechowuje w swojej podręcznej pamięci. Jest ona przeładowywana tylko podczas zapisu nowej wartości do CR3 lub przełączenia zadań.
Każdy rekord w katalogu stron zawiera adres fizyczny odpowiedniej tablicy stron w 20 starszych bitach (wszystkie strony umieszczone są na granicy 4k). Uzyskany z adresu liniowego drugi indeks wskazuje odpowiedni rekord w danej tablicy. Zawiera on również w 20 starszych bitach adres fizyczny odpowiadającej mu strony. Do tego adresu dodawane jest przesunięcie (12 młodszych bitów adresu liniowego).
Rekordy obu tablic mają taki sam format i rozmiar 4 bajtów (zob. poniższy rysunek).
Wyjaśnijmy teraz poszczególne pola.
Bit |
Znaczenie |
AVAIL |
Te trzy bity są dostępne (ang. Available) dla projektanta systemu operacyjnego do dowolnego wykorzystania. |
D |
Tzw. 'Brudny' bit (ang. Dirty). Ustawiany każdorazowo przy dokonaniu zapisu do danej strony. Używany w systemach pamięci wirtualnej. |
A |
Procesor ustawia ten bit gdy nastąpił zapis/odczyt do/z danej strony (inaczej mówiąc miało miejsce odwołanie (ang. Accessed) do strony). Również stosowany przy obsłudze pamięci wirtulnej. |
U/S |
Bit ten jest wykorzystywany jako dodatkowy sposob ochrony na poziomie poszczególnych stron. Wyzerowany oznacza, że strona jest dostępna dla systemu operacyjnego (ang. Supervisor) a konkretnie dla CPL równego 0, 1 lub 2. Z kolei gdy jest on ustawiony to strona dostępna jest także dla aplikacji użytkownika (ang. User) czyli dla CPL=3. Innymi słowy dostęp do stron z ustawionym bitem U/S jest możliwy ze wszystkich poziomów (dowolne CPL) zaś strony z bitem U/S wyzerowanym nie są dostępne jeśli CPL=3. |
R/W |
Wyzerowanie tego bitu powoduje, że strona jest tylko do odczytu. Jeśli bit ten zostanie ustawiony to strona może być odczytywana (ang. read) i zapisywana (ang. write). |
P |
Obecny (ang. Present). Wskazuje czy dana strona znajduje się w pamięci. Również stosowany w systemach pamięci wirtualnej. |
Jak widać zawartość rekordów obu tablic stanowi dobry punkt wyjścia do implementacji systemu pamięci wirtualnej oraz stworzenia dodatkowego zabezpieczenia przed dostępem niepowołanych programów do krytycznych obszarów kodu i danych. Warto wiedzieć, że artybuty zapisane w rekordach katalogu i tablicy dotyczące tej samej strony mogą się różnić. Dopiero na podstawie obu tych zestawów procesor określa bieżące parametry dla mechanizmu ochrony.
Zastosowanie stronicowania w implementacji systemu pamięci wirtualnej
W każdym rekordzie zarówno katalogu jak i tablic stron znajdują się bity przeznaczone do wspomagania mechanizmu pamięci wirtualnej - są to bity D, A i P. Bit P jest ustawiany bądź zerowany przez system operacyjny w celu określenia czy dana strona została usunięta z pamięci i zapisana na dysk. Przy wystąpieniu operacji zapisu/odczytu do/z takiej strony procesor wygeneruje wyjątek PF (ang. Page Fault czyli błąd strony). Jeśli cała pamięć jest używana to system (a w zasadzie procedura obsługi wyjątku PF) może skorzystać z bitów D i A do wyznaczenia innej strony, którą można tymczasowo usunąć z pamięci i na jej miejsce załaduje tą, do której wystąpiło odwołanie. Korzystając z funkcji modułu stronicowania możemy usunąć z pamięci praktycznie wszystkie strony, jednak zalecane jest aby strony zawierające kod i dane systemu nie podlegały tej operacji (również strony do których odwołania występują bardzo często). Oczywiście logiczne jest, że strony zawierające bieżący katalog i tablice stron oraz tablice deskryptorów nie mogą być usunięte.
Warto zauważyć, że przypisanie stron do rekordów w tablicy nie musi być liniowe albowiem do każdego rekordu możemy przypisać dowolną stronę.
Stronicowanie i tryb V-86
Inną kluczową funkcją mechanizmu stronicowania jest umożliwienie jednoczesnego wykonywania wielu zadan pracujących w trybie V-86. Każde takie zadanie posiada zakres adresów ograniczony do 20 bitów a tym samym do pierwszego megabajta pamięci. Z użyciem stronicowania możemy ten megabajt przesunąć w dowolne miejsce przestrzeni adresowej co w efekcie umożliwia wykonywanie wielu zadań V-86 w równocześnie. Odbywa się to poprzez przypisanie każdemu zadaniu osobnego katalogu i tablicy stron. Ponieważ katalog jest wskazywany zawartością CR3 przełączenie pomiędzy dwoma zadaniami V-86 wymaga (poza innymi dodatkowymi czynnościami) jedynie zmiany tego rejestru tak aby wskazywał odpowiedni katalog stron.
Wielozadaniowość
Wspomaganie wielozadaniowości (ang. multi-tasking) jest mechanizmem wbudowanym w architekturę x86, który może aczkolwiek nie musi być używany przez system operacyjny. Wiele systemów świadomie nie korzysta ze sprzętowych mechanizmów obsługujących wielozadaniowość. Przyczyną tego stanu jest fakt, iz sprzętowe przełączanie zadań może być także realizowane całkowicie programowo. Ponieważ w wielu systemach przełączanie zadań i tak wiąże się z dodatkowymi czynnościami, wykonanie pewnej nadmiarowej pracy związanej z samym przełączeniem nie stanowi problemu. Ponadto programowe przełączanie zadań może być szybsze ze względu na rodzaj i ilośc informacji niezbędnych do zapamiętania podczas przełączenia. Korzystanie z wbudowanych mechanizmów procesora zawsze wymaga przesłania tej samej (dość dużej) ilości informacji, które w pewnej części nie są istotne. Stąd też zastosowanie programowej metody pozwala skrócić czas samego przełączenia.
Sprzętowe przełączanie zadań zależy od poniższych struktur systemowych :
- segmentu TSS (służy do zapisywania i odtwarzania stanu procesora),
- deskryptora segmentu TSS (musi się znajdować w tablicy GDT),
- rejestru TR (ang. Task Register czyli Rejestr Zadania, przechowuje selektor TSS bieżącego zadania),
- deskryptora bramki zadania (ang. task gate descriptor).
TSS, jak wszystkie segmenty, posiada swój deskryptor, zawsze umieszczany w GDT. Procesor odróżnia go po wartości pola Type równej 010x1b (gdzie x określa stan 'zajęty' zadania). Deskryptory segmentów TSS wyglądają tak samo jak deskryptory segmentów kodu za wyjątkiem bitu D i pola Type.
Segment TSS jest wykorzystywany do przechowywania kompletnego stanu procesora wraz z mapą bitową I/O, które to informacje będą zapisywane/odczytywane podczas przełączenia. Dodatkowo TSS może też zawierać inne informacje, nie związane z operacją przełączania zadań.
Sprzętowe przełączenie zadania ma miejsce gdy :
- wykonany zostanie skok CALL lub JMP do segmentu TSS lub bramki zadania,
- wygenerowane przerwanie lub wyjątek wskaże na bramkę zadania w IDT,
- bieżące zadanie wykona rozkaz IRET gdy ustawiony jest bit NT (ang. Nested Task czyli Zadanie Zagnieżdżone).
Deskryptor bramki zadania wygląda dokładnie tak samo jak deskryptor przerwania lub bramki-pułapki (ang. trap gate) za wyjątkiem pola Type i działa też w ten sam sposób (czym za chwilę się zajmiemy).
Przerwania
Proces obsługi przerwań w trybie chronionym wygląda podobnie jak w 8086, z tym że zamiast tablicy wartości CS:IP mamy tablicę bramek zadań, przerwań i pułapek (ang. task gate, interrupt gate i trap gate) określaną jako IDT (czyli znana nam tablica deskryptorów przerwań). Ponieważ przerwania mogą wystąpić w dowolnej chwili procesor musi korzystać z bramek do zapewnienia możliwości wywołania odpowiedniego kodu niezależnie od aktualnego CPL. Bramki przerwań i pułapek działają w ten sam sposób jak wspomniane już bramki wywołań (ang. call gate), z tym że nie przekazują żadnych parametrów ze stosu jednego poziomu na inny. Bramki te działają tak samo za wyjątkiem wpływu na flagę IF. Bramki przerwań zerują flagę IF (blokując przerwania) zaś bramki pułapek pozostawiają ją bez zmian. Ponieważ rejestr flag EFLAGS jest zawsze przesyłany na stos podczas obsługi procesu przerwania, rozkaz IRET przywróci oryginalną wartość flagi IF.
W trybie PM źródłem przerwania może być zewnętrzne zdarzenie, wyjątek lub wykonanie instrukcji INTO, INT n, INT3 lub BOUND. Informacja odkładana na stosie dla przerwań spowodowanych zdarzeniami zewnętrznymi lub instrukcjami jest taka sama. Dla wyjątków dodatkowa informacja (kod błędu) również może zostać wpisana na stos.
Prześledźmy w skrócie co się dzieje podczas wystąpienia przerwania. Jako pierwszy obliczany jest adres deskryptora przerwania w tablicy IDT. Następnie pobierane są selektor i przesunięcie stanowiące adres docelowy. Ostatnim krokiem jest pobranie bazy z deskryptora określonego selektorem z IDT i wyznaczenie docelowego adresu liniowego. Teraz procesor może dokonać transferu kontroli do procedury obsługi przerwania.
Tryb V-86
Wspomniany już tryb V-86 (ang. Virtual-86) wspomaga wykonywanie jednego lub więcej programów przeznaczonych dla trybu RM procesora 8086/80186 w środowisku chronionym. Praktycznym przykładem wykorzystania tego trybu jest tzw. okno DOS-a w 32-bitowych systemach operacyjnych. Podczas pracy w trybie V-86 adres jest obliczany w ten sam sposób co w procesorach 8086/80186 w trybie rzeczywistym. Oznacza to, że rejestry segmentowe zostają załadowane 16-bitową bazą, która pomnożona zostaje przez 16. Do wyniku tej operacji dodawane jest 16-bitowe przesunięcie tworząc 20-bitowy adres liniowy. Różnica polega na tym, że program pracujący w trybie V-86 działa w otoczeniu chronionym i ma przypisany najniższy poziom uprzywilejowania. Podlega on więc wszystkim restrykcjom wynikającym z tego faktu, o których wcześniej mówiliśmy (dotyczy to również poziomu IOPL i instrukcji uprzywilejowanych) co stanowi przeciwieństwo faktycznego trybu RM. W momencie wystąpienia wyjątku lub przerwania procesor powraca do trybu PM do odpowiedniej procedury obsługi.
Stronicowanie w połączeniu z trybem V-86 umożliwia systemowi operacyjnemu :
- ochronę przed niepowołanym dostępem do rejestrów procesora, sprzętu i struktur systemowych,
- łatwiejszą koordynację zasobów dzielonych pomiędzy zadania V-86 i inne,
- emulację funkcji systemu 8086/80186.
Korzystanie z modułu stronicowania nie jest konieczne jeśli wykonywane jest tylko jedno zadanie V-86. Jednak gdy jest ich więcej stronicowanie musi zostać użyte. W takiej sytuacji 20-bitowy adres liniowy zamieniony zostanie na adres fizyczny różny dla każdego zadania V-86.
Podsumowanie
W tym tekście poruszyliśmy dość ogólnie zagadnienia związane z działaniem procesora x86 w trybie PM. Cóż, w końcu jest to dopiero wprowadzenie. Niemniej jednak zdobyta wiedza pozwala nam spokojnie przejść do programowania pod DPMI, gdzie dokładna znajomość szczegółów nie jest konieczna. Oczywiście kolejne części cyklu o PM bardziej szczegółowo będą omawiać aspekty programowania w trybie chronionym.