Procesor Intel 8086 — model programisty
Transkrypt
Procesor Intel 8086 — model programisty
Procesor Intel 8086 — model programisty Arkadiusz Chrobot 26 września 2011 Spis treści 1 Wstęp 2 2 Rejestry procesora 8086 2 3 Adresowanie pamięci 4 4 Ważne elementy języka Pascal 8 1 1 Wstęp Głównym celem zadań, które będą realizowane przez Państwa w ramach laboratorium do przedmiotu „Systemy Operacyjne”, jest poznanie budowy i działania jednego z najprostszych systemów operacyjnych: MS-DOS. System ten był pierwotnie przeznaczony dla komputerów wyposażonych w 16-bitowe procesory firmy Intel. Współcześnie, procesor każdego komputera klasy PC umożliwia pracę w tzw. trybie rzeczywistym i trybie wirtualnym, w których jest wstecznie zgodny z tymi układami. Nawet pobieżna znajomość budowy 16-bitowych mikroprocesorów Intela znacznie ułatwi zadanie poznania funkcjonowania oprogramowania napisanego dla nich. Naszą uwagę skupimy na opisie cech procesora 8086, które mają największe znaczenie dla programisty. 2 Rejestry procesora 8086 Formalnie, przedstawienie modelu programisty danego procesora powinno się zacząć od omówienia jego listy rozkazów. Niestety objętość tej instrukcji, ani czas przeznaczony na zajęcia laboratoryjne nie pozwalają na to. Nasz opis zaczniemy więc od rejestrów, które są dostępne, bezpośrednio lub pośrednio dla programisty. Procesor 8086 wyposażony jest w osiem 16-bitowych rejestrów, które zwykło się nazywać rejestrami ogólnego przeznaczenia. Są to rejestry AX, BX, CX, DX, SI, DI, SP i BP. Rzeczywiście, każdy z nich może zostać użyty jako rejestr, w którym przechowywane są dane, jednakże niektóre z rozkazów procesora 8086 traktują te rejestry w specjalny sposób. Rejestr AX nazywany jest rejestrem akumulatora, ponieważ z niego muszą korzystać operacje arytmetyczne (konkretnie: mnożenie i dzielenie). Rejestr BX jest nazywany rejestrem bazowym, ponieważ jest wykorzystywany w niektórych trybach adresowania do przechowywania części adresu nazywanej przesunięciem lub offsetem1 . Adres ten wskazuje komórkę w obszarze pamięci nazywanym segmentem danych2 . Rejestr CX jest nazywany rejestrem licznikowym, ponieważ korzysta z niego instrukcja pętli (ang. loop) przechowując liczbę powtórzeń, które musi jeszcze wykonać. Rejestr DX może pełnić rolę rozszerzenia akumulatora w instrukcjach mnożenia i dzielenia (są w nim przechowywane starsze bity wyniku). Rejestry SI i DI pełnią rolę rejestrów indeksowych w instrukcjach łańcuchowych. SI zawiera offset źródła, a DI offset przeznaczenia. Rejestr SP jest używany przez instrukcje obsługujące sprzętowy stos i nazywany jest wskaźnikiem stosu (ang. stack pointer ). Ostatnim z grupy omawianych rejestrów jest rejestr BP, który może pełnić podobną rolę jak rejestr BX (dlatego również nazywany jest rejestrem bazowym), ale zawiera 1 2 Termin ten będzie objaśniony w rozdziale o adresowaniu pamięci. Ten termin również będzie opisany w rozdziale poświęconym adresowaniu pamięci. 2 przesunięcie względem początku segmentu stosu. Rejestry AX, BX, CX i DX odznaczają się pewną szczególną cechą. Każdy z nich jest złożony z dwóch 8-bitowych rejestrów. Ich nazwy są dwuliterowe. Nazwa rejestru 8-bitowego, który zawiera osiem starszych lub inaczej górnych bitów rejestru 16-bitowego kończy się literą H, a nazwa rejestru zawierającego osiem młodszych (dolnych) bitów kończy się na L. Pierwsza litera jest również pierwszą literą nazwy rejestru, który tworzą, np.: AX ⇒ AH i AL. Oprócz ośmiu wymieAX AL AH BX BH BL CX CH CL DX DL DH Rysunek 1: Rejestry 8-bitowe nionych rejestrów ogólnego przeznaczenia istnieje 6 innych, które mają ściśle określone role. Każdy z nich jest rejestrem 16-bitowym. Pierwszy z nich, to rejestr IP, który jest nazywany wskaźnikiem rozkazów, bowiem przechowuje offset adresu następnej instrukcji, jaką wykona procesor. Programista nie może wpływać bezpośrednio na zawartość tego rejestru. Jedynym sposobem zmiany jego zawartości jest wykonanie dowolnej instrukcji skoku. Kolejnym rejestrem jest rejestr flag. Stany poszczególnych bitów tego rejestru określają stan w jakim znajduje się procesor. Szczegółowo objaśnia to rysunek nr 2. Umieszczone w nim skróty mają następujące znaczenie: CF — znacznik przeniesienia (po wykonaniu operacji arytmetycznej nastąpiło przeniesienie jedynki na najstarszej pozycji jej wyniku), PF — znacznik parzystości (wynik ostatniej operacji logicznej lub arytmetycznej zawiera parzystą liczbę jedynek), 3 15 14 13 12 11 10 9 8 7 6 OF DF IF TF SF ZF 5 4 AF 3 2 PF 1 0 CF Rysunek 2: Rejestr flag AF — znacznik dodatkowego przeniesienia (jak CF, ale przeniesienie było na czwartym bicie), ZF — znacznik zera (wynik ostatniej operacji arytmetycznej lub logicznej był zerem), SF — znacznik znaku (określa, czy wynik ostatniej operacji arytmetycznej był dodatni, czy ujemny, TF — znacznik pułapki (określa, czy procesor pracuje w trybie debugowania), IF — znacznik przerwań (określa, czy włączone są przerwania), DF — znacznik kierunku (określa kierunek operacji łańcuchowych), OF — znacznik przepełnienia (wynik ostatniej operacji arytmetycznej nie mieści się w akumulatorze), Ostatnie cztery rejestry, to CS, DS, SS i ES. Są one nazywane rejestrami segmentowymi. Procesor 8086, jak większość innych procesorów realizuje przetwarzanie oparte o model opracowany przez Johna von Neumanna. Oznacza to, że rozkaz lub dana może zostać umieszczona w dowolnej komórce pamięci operacyjnej. Jednakże procesor musi wiedzieć, który obszar pamięci zawiera instrukcje do wykonania, a który dane, na których te instrukcje powinny zostać wykonane. Rejestry segmentowe zawierają właśnie adresy początków tych obszarów3 . Rejestr CS zawiera adres początku obszaru zawierającego instrukcje do wykonania, DS — adres początku obszaru pamięci przechowującego dane, SS — adres początku obszaru pamięci w którym jest zorganizowany stos. ES jest dodatkowym rejestrem segmentowym, który może wskazywać dowolny z wymienionych obszarów lub inny, np. pusty obszar. 3 Adresowanie pamięci Z punktu widzenia programisty komputerowego, najważniejszą cechą, jeśli chodzi o zestaw procesor-pamięć jest to, ile pamięci może procesor zaadresować. Okazuje się jednak, że procesor 8086 narzuca również pewien 3 Nie jest to dokładne wytłumaczenie. Bardziej precyzyjny opis znajduje się w następnym rozdziale. 4 szczególny sposób jej adresowania, o którym osoba pisząca program musi wiedzieć. Czynnikiem determinującym rozmiar pamięci, jaką może się posługiwać procesor, jest szerokość jego magistrali adresowej, czyli połączenia między nim a pamięcią. W przypadku procesora 8086 ta szyna jest 20-bitowa. Oznacza to, że omawiany procesor może zaadresować 220 = 1 MiB pamięci operacyjnej. Dosyć często zachodzi konieczność wykonania jakiejś operacji na adresie, np. zwiększenia go o jeden. Aby móc to zrobić procesor musi gdzieś ten adres zapamiętać. Najbardziej odpowiednim miejscem jest oczywiście któryś z rejestrów, ale one wszystkie są co najwyżej 16-bitowe. Rozwiązanie problemu jest stosunkowo proste: procesor 8086 używa dwóch rejestrów do zapamiętania adresu w pamięci. Starsze 16 bitów jest zapamiętywanych w jednym z rejestrów segmentowych, a młodsze 4 w np. rejestrze BX lub DX. Takie rozwiązanie dzieli pamięć fizyczną na obszary o wielkości 16 bajów każdy, tak jak to pokazuje rysunek 3. Te dwie części adresu będziemy nazywać odpowiednio: częścią segmentową4 i offsetem lub inaczej przesunięciem5 . Aby wskazać więc konkretną komórkę6 w pamięci, musimy podać część segmentową i offset jej adresu. Obszar o wielkości 16 bajtów będziemy nazywać segmentem fizycznym lub paragrafem. Zauważmy, że adres z liczby 20-bitowej stał się liczbą 32-bitową. Adres 20-bitowy będziemy nazywać adresem fizycznym, a 32-bitowy adresem logicznym. Przy takim rozszerzeniu adresu powstaje jednak pewien problem. Otóż adres jest traktowany tak samo, jak każda inna dana, co oznacza, że nie ma żadnego wbudowanego w procesor mechanizmu, który wymusiłby na programiście przestrzeganie podziału adresu na 16 i 4 bity. W praktyce oznacza to, że programista może użyć jako adresu dowolnej liczby 32- bitowej. Taki adres jest oczywiście podzielony na dwie 16-bitowe części. O ile rozmiar części segmentowej nie uległ zmianie, to rozmiar offsetu wzrósł o 12 bitów. Oznacza to, że segmenty generowane przez adres logiczny mają wielkość 216 = 65536 B (64 KiB). Należy również zauważyć, że 32-bitowy adres pozwala zaadresować 232 = 4 GiB pamięci. W jaki więc sposób pogodzić ze sobą te dwie wydawałoby się sprzeczne „wizje” pamięci? Rozwiązanie pokazuje rysunek 4. Segmenty logiczne „nachodzą” na siebie tak, że początek następnego segmentu znajduje się wewnątrz poprzedniego. Każdy kolejny segment logiczny rozpoczyna się 16 bajtów „dalej” od początku poprzedzającego go segmentu. Pozostaje do opisania kwestia przeliczania adresu logicznego na fizycz7 ny . Otóż adres logiczny zapisywany jest najczęściej w postaci dwóch liczb 4 Dosyć często tę część adresu nazywa się „segmentem”, ale jak wkrótce się przekonamy, termin ten jest niejednoznaczny. 5 Pełny termin brzmi „przesunięcie względem początku segmentu”. Początek segmentu wyznacza oczywiście część segmentowa adresu. 6 inaczej: konkretny bajt 7 Operacja odwrotna jest opisana wcześniej. 5 6 16 B 16 B 16 B . . . . 1 MB 16 B 16 B 16 B ? Rysunek 3: Pamięć podzielona na segmenty fizyczne. szesnastkowych rozdzielonych znakiem dwukropka, np: 7FFFh:023Ah8 . Żeby przeliczyć go na adres fizyczny wystarczy pomnożyć przez 16 wartość części segmentowej i do wyniku dodać wartość offsetu. Nasz przykładowy adres logiczny będzie więc przeliczony następująco: 7F F F h∗ 10h + 023Ah = 7F F F 0h + 023Ah = 8022Ah. Ze względu na opisany wyżej układ segmenty logiczne mają pewne obszary wspólne, co dobrze ilustruje rysunek numer 4. Przekształcenie adresu logicznego w fizyczny nie może więc być jednoznaczne, tzn. niektóre adresy logiczne, które nawet „optycznie” wydają się 8 Gwoli przypomnienia - „h” oznacza, że mamy do czynienia z liczbą szesnastkową. Jeśli część segmentowa adresu zapisana jest np. w rejestrze DS, a offset w rejestrze DX, to adres można zapisać w postaci DS:DX. 6 6 64 KB 6 16 B ? 64 KB 1 MB . . . . ? Rysunek 4: Podział pamięci fizycznej na logiczne segmenty. różne, dają po przekształceniu ten sam adres fizyczny, np.: 07C0h : 0001h = 07C01h, 0700h : 0C01h = 07C01h, 0780h : 0501h = 07C01h. Aby uniknąć takiej sytuacji zaleca się stosowanie adresów kanonicznych, czyli takich w których część segmentowa jest dowolna, natomiast wykorzystywane są tylko 4 najmłodsze bity offsetu (pozostałe są zawsze równe zero)9 . 9 Z podanych trzech adresów, adresem kanonicznym jest tylko pierwszy. 7 4 Ważne elementy języka Pascal Ten rozdział nie jest poświęcony procesorowi 8086, ale ma za zadanie przedstawienie elementów języka (Turbo) Pascal, które pozwalają na współpracę z systemem MS-DOS i mogą być przydatne podczas realizacji zajęć. Więcej informacji na temat tych elementów można uzyskać w pomocy środowiska Turbo Pascal, lub z książek wymienionych w bibliografii. 1. absolute — słowo kluczowe, które pozwala umieścić zadeklarowaną zmienną pod określonym adresem w pamięci, np.: var i v : array [ 0 . . 2 5 5 ] of p o i n t e r a b s o l u t e $0000 : $0000 ; 2. mem,memw,meml — tablice skojarzone z pamięcią komputera, które pozwalają odczytać odpowiednio: bajt, słowo i podwójne słowo z określonego adresu pamięci, np.: var x : byte ; begin x:=mem[ $0000 : $0000 ] ; end . 3. ptr — funkcja pozwalająca utworzyć wskaźnik do miejsca w pamięci komputera na podstawie części segmentowej i offsetu, np.: var x : pointer ; begin x:= p t r ( $0000 , $0000 ) ; end . 4. seg, ofs — funkcje, które zwracają część segmentową i offset adresu zmiennej, przekazanej im jako parametr wywołania, var x : byte ; s , o : word ; begin s := s e g ( x ) ; o:= o f s ( x ) ; end . 5. port,portw — tablice, które pozwalają odczytywać zawartość wybranych portów komputera. var x : byte ; 8 begin x:= p o r t [ $60 ] ; end . Literatura [1] Gary Syck, Turbo Assembler - Biblia użytkownika, LP&T, Warszawa 1994 [2] Leonid Bułhak, Ryszard Goczyński, Michał Tuszyński, DOS 5 od środka, HELP, Warszawa 1997 9