AKO Lab2007 cw4 7


Katedra Architektury Systemów Komputerowych

Laboratorium

Architektury Komputerów

Materiały pomocnicze do ćwiczeń laboratoryjnych cz. II

S p i s t r e ś c i

  1. Programowanie mieszane

  2. Operacje na liczbach zmiennoprzecinkowych

  3. Specyfika kodowania programów w systemie Windows i Linux

  4. Obsługa przerwań sprzętowych

Opracował dr inż. Andrzej Jędruch

Gdańsk 2007

Laboratorium Architektury Komputerów

Ćwiczenie 4

Programowanie mieszane

Wprowadzenie

Współcześnie, bardziej złożone oprogramowanie tworzone jest przez zespoły kilku lub kilkunastu programistów. Zazwyczaj każdy z nich koduje (programuje) jeden lub więcej modułów funkcjonalnych, realizujących wyraźnie wyodrębnione operacje tworzonej aplikacji. W tego rodzaju pracach pożądane jest, by każdy z programistów miał szeroką swobodę działania, ograniczoną jedynie przez te elementy oprogramowania, które wiążą ze sobą poszczególne moduły funkcjonalne.

Powyższy postulat realizuje się poprzez kodowanie oprogramowania w postaci wielu oddzielnych plików, zawierających kod źródłowy programu. Zazwyczaj pojedynczy programista tworzy kilka takich plików. Są one następnie tłumaczone na kod zrozumiały przez procesor i scalane (konsolidowane), tworząc kompletny, gotowy program zapisany w pliku .EXE (system Windows) czy .out (system Linux).

W przypadku tworzenia oprogramowania współpracującego z urządzeniami niestandardowymi, zadaniem jednego z modułów funkcjonalnych jest organizowanie współpracy z tym urządzeniem. W wielu przypadkach, ze względu na specyficzne wymagania urządzenia, taki moduł musi być kodowany w asemblerze, niekiedy przez konstruktora urządzenia. W rozpatrywanej sytuacji kod asemblerowy musi być przystosowany do współdziałania z pozostałym oprogramowaniem, kodowanym zazwyczaj w języku wysokiego poziomu (np. C/C++).

Niniejsze opracowanie przybliża zagadnienia związane z współdziałaniem kodu napisanego w asemblerze z kodem w języku C (i po pewnych rozszerzeniach C++), w środowisku 32-bitowym systemu Windows. Bardzo podobne, lub identyczne mechanizmy stosowane są w innych systemach operacyjnych.

Kompilacja, linkowanie i ładowanie

W wielu środowiskach programowania wytworzenie programu wynikowego wykonywane jest w dwóch etapach. Najpierw kod źródłowy każdego modułu programu zostaje poddany kompilacji (jeśli moduł napisany jest w języku wysokiego poziomu) lub asemblacji (jeśli moduł napisany jest w asemblerze). W obu tych przypadkach uzyskuje się plik w języku pośrednim (rozszerzenie .OBJ). Następnie uzyskane pliki .OBJ poddaje się konsolidacji czyli linkowaniu. W trakcie linkowania dołączane są także wszystkie niezbędne programy biblioteczne. W rezultacie zostaje wygenerowany plik zawierający program wynikowy z rozszerzeniem .EXE. Plik ten zawiera kod programu w języku maszynowym (czyli zrozumiałym przez procesor), aczkolwiek niektóre jego elementy wymagają korekcji uzależnionej od środowiska, w którym program będzie wykonany. Korekcja ta następuje w trakcie ładowania programu.

Niektóre programy biblioteczne mają charakter uniwersalny i są wykorzystywane przez wiele programów użytkowych. Wygodniej byłoby więc dołączać te programy dopiero w trakcie wykonywania programu, co pozwoliłoby na zmniejszenie rozmiaru pliku .EXE. W takim przypadku mówimy, że program korzysta z biblioteki dynamicznej (zapisanej w pliku z rozszerzeniem DLL). Omawiane fazy translacji pokazane są na poniższym rysunku.

0x01 graphic

Pliki .OBJ generowane przez różne kompilatory (w danym środowisku) zawierają kod w tym samym języku, który możemy uważać za język pośredni, stanowiący jak gdyby "wspólny mianownik" dla różnych języków programowania.

Podprogramy w technice programowania mieszanego

Problem tworzenia programu, którego fragmenty napisane są w różnych językach programowania wymaga m.in. ustalenia sposobu komunikowania się poszczególnych fragmentów ze sobą. Komunikacja taka staje się stosunkowo łatwa do zrealizowania, jeśli poszczególne fragmenty programu mają postać podprogramów (procedur). Podprogramy stanowią, ze swej natury, w pewien sposób wyizolowaną część programu, a komunikacja z nimi odbywa się wg ściśle ustalonego protokołu, określającego formaty danych i wzajemne obowiązki programu wywołującego i wywoływanego podprogramu. Protokół ten nazywany jest także opisem interfejsu podprogramu (procedury). W ten sposób, w trakcie wykonywania programu, wywoływanie fragmentów napisanych w różnych językach programowania, sprowadza się do wywoływania odpowiednich podprogramów. W przypadku języka C wywołanie podprogramu oznacza po prostu wywołanie funkcji języka C, której kod został zdefiniowany w innym pliku, niekoniecznie napisanym w języku C.

Powyższe rozważania wskazują, że interfejs do podprogramów musi być jasno i przejrzyście zdefiniowany, a zarazem musi być na tyle uniwersalny, by mógł być implementowany przez kompilatory różnych języków programowania. Z tego powodu producenci oprogramowania (m.in. firma Microsoft) ustalają pewne niskopoziomowe protokoły wywoływania podprogramów, przeznaczone dla wytwarzanych przez nich kompilatorów języków programowania.

Obecnie, w oprogramowaniu komputerów osobistych rodziny PC, wyłoniły się trzy typy interfejsu procedur. Jeden z nich, używany jest przez translator języka C, drugi przez translator Pascala, a trzeci StdCall stanowi połączenie dwóch poprzednich.

Konwencje wywoływania procedur stosowane przez kompilatory języka C

1. Parametry podprogramu przekazywane są przez stos. Parametry ładowane są na stos w kolejności odwrotnej w stosunku do tej w jakiej podane są w kodzie źródłowym, np. wywołanie funkcji calc (a,b) powoduje załadowanie na stos wartości b a następnie a.

2. Jeśli parametr ma postać pojedynczego bajtu, to na stos ładowane jest podwójne słowo (32 bity), którego młodszą część stanowi podany bajt.

3. Jeśli parametrem jest liczba składająca się z kilku bajtów, to najpierw na stos ładowana jest najstarsza część liczby i kolejno coraz młodsze. Taki schemat ładowania stosowany jest w komputerach, w których liczby przechowywane są w standardzie mniejsze niżej, i wynika z faktu, że stos rośnie w kierunku malejących adresów.

4. Obowiązek zdjęcia parametrów ze stosu po wykonaniu podprogramu należy do programu wywołującego.

5. Kompilatory języka C stosują dwa typowe sposoby przekazywania parametrów: przez wartość i przez adres. Jeśli parametrem funkcji jest nazwa tablicy, to na stos ładowany jest adres tej tablicy; wszystkie inne obiekty, które nie zostały jawnie zadeklarowane jako tablice, przekazywane są "przez wartość".

6. Wyniki podprogramu przekazywane są przez rejestry:

wynik 1-bajtowy przez AL,

wynik 2-bajtowy przez AX,

wynik 4-bajtowy przez EAX.

Jeśli wynikiem podprogramu jest adres (wskaźnik), to przekazywany jest także przez rejestry.

7. Jeśli podprogram zmienia zawartość rejestrów EBX, EBP, ESI, EDI, SS, DS, to powinien w początkowej części zapamiętać je na stosie i odtworzyć bezpośrednio przed zakończeniem. Pozostałe rejestry robocze mogą być używane bez konieczności zapamiętywania i odtwarzania ich zawartości.

Elementy procedur asemblerowych wywoływanych z poziomu języka C

Podprogram w asemblerze przystosowany do wywoływania z poziomu języka C musi być skonstruowany dokładnie wg tych samych zasad co funkcje w języku C. Wynika to z faktu, że program w języku C będzie wywoływał podprogram w taki sam sposób w jaki wywołuje inne funkcje w języku C.

Wszystkie nazwy globalne zdefiniowane w treści podprogramu muszą być wymienione na liście dyrektywy PUBLIC. Jednocześnie nazwy innych używanych zmiennych globalnych i funkcji muszą być zadeklarowane na liście dyrektywy EXTRN.

Ze względu na konwencję nazw stosowaną przez kompilatory języka C, każdą nazwę o zasięgu globalnym wewnątrz podprogramu asemblerowego należy poprzedzić znakiem podkreślenia _ (nie dotyczy to konwencji StdCall). Niektóre kompilatory C rozróżniają tylko 8 pierwszych znaków nazwy, co należy brać pod uwagę przy tworzeniu nazw globalnych.

Należy też pamiętać, że w języku C małe i wielkie litery nie są utożsamiane. Asembler odróżnia małe i wielkie litery tylko wówczas, jeśli w linii wywołania asemblera podano odpowiednią opcję:

-Cp dla asemblera MASM (ml.exe),

-ml dla asemblera TASM.

Program przykładowy: szukanie największej liczby w tablicy

Część programu w języku C (plik oblicz.c)

/* Poszukiwanie największego elementu w tablicy liczb

całkowitych za pomoca funkcji (podprogramu)

szukaj_max, ktora zostala zakodowana w asemblerze.*/#include <stdio.h>extern int szukaj_max (int * tablica, int n);

int main(){ int wyniki [12] = {456, -15, 4000000, -345678, 88046592, 2297645,

-1, 444, 7867023, -19000444, 31, -12};

int wartosc_max; wartosc_max = szukaj_max(wyniki, 12); printf("\nNajwiekszy element tablicy jest = %d", wartosc_max); return 0;

}

Część programu w asemblerze (plik podaj_m.asm)

; Podprogram _szukaj_max poszukuje największej liczby w tablicy.

; Podprogram jest przystosowany do wywoływania z poziomu języka C.

.386

PUBLIC _szukaj_max_TEXT SEGMENT dword public 'CODE' use32

ASSUME cs:_TEXT

_szukaj_max PROC near

push ebp mov ebp, esp

push ebx

push esi

mov ecx, [ebp+12] ; liczba elementow tablicy

mov ebx, [ebp+8] ; adres tablicy

mov esi, 0

mov eax, [ebx+esi*4] ; pierwszy element tablicy

dec ecx ; ilość porównań jest mniejsza o 1

; od ilości elementów tablicy

ptl: inc esi

cmp eax, [ebx+esi*4] ; porównanie z kolejnym

; elementem tablicy

jge dalej

mov eax, [ebx+esi*4]

dalej: loop ptl

; obliczona wartość maksymalna pozostaje w rejestrze EAX i będzie

; wykorzystana przez kod programu napisany w języku C

pop esi

pop ebx pop ebp ret

_szukaj_max ENDP_TEXT ENDS

END

Tworzenie programu wynikowego w formacie EXE

W celu uzyskania pliku w formacie .EXE, który zawiera kod zrozumiały dla procesora, należy najpierw poddać kompilacji (lub asemblacji) pliki z kodem źródłowym, a następnie przeprowadzić scalanie uzyskanych plików .OBJ poprzez konsolidację (linkowanie). Działania te można wykonać w okienku konsoli poprzez kolejne uruchamianie potrzebnych kompilatorów, a w końcu konsolidatora (linkera). Istnieje też możliwość wykonania powyższych zadań w środowisku zintegrowanym, zawierającym połączony edytor, kompilator, linker, debugger i inne narzędzia. Do najbardziej znanych środowisk zintegrowanych należy system Borland C/C++ czy Microsoft Visual Studio.

Realizacja zadań w środowisku zintegrowanym ma szereg zalet, jak m.in. bardzo przejrzysta sygnalizacja błędów w programie, łatwe dostępne opisy funkcji systemowych, możliwość bezpośredniego użycia debuggera i wiele innych. Są to jednak systemy bardzo złożone, zawierające wiele opcji, często nieistotnych z punktu widzenia budowy mniejszych systemów. Złożoność ta jest szczególnie widoczna w przypadku środowiska Microsoft Visual Studio. Jednym z warunków efektywnego użycia takich środowisk jest posiadanie nowoczesnego komputera, z szybkim procesorem i dużą pamięcią operacyjną.

Z powyższych względów, w niniejszym opracowaniu skupimy uwagę przede wszystkim na tworzeniu programów wykonywalnych za pomocą kompilatorów zewnętrznych, nie używając środowiska zintegrowanego. Co więcej, wydaje się że użycie kompilatorów zewnętrznych stanowi metodę bardziej uniwersalną, która może być względnie łatwo przeniesiona do tworzenia oprogramowania na inne typy procesorów.

Istnieje wiele kompilatorów języka C/C++, a wśród nich najbardziej znane są kompilatory CL.EXE firmy Microsoft i BCC32.EXE firmy Borland. Pierwszy z nich dostępny jest dla studentów Wydziału ETI PG (jako element Microsoft Visual Studio) w ramach programu Academic Alliance, a drugi stanowi element systemu C++ Builder, a ponadto można go uzyskać na stronie WWW firmy Borland po uprzednim wypełnieniu krótkiej ankiety. Prócz tego istnieje wiele innych kompilatorów języka C/C++, które można bez ograniczeń kopiować ze stron WWW.

Poniżej opisano najważniejsze parametry precyzujące działanie kompilatorów firmy Microsoft, a także parametry typowych programów konsolidujących (linkerów). Wszystkie te programy wywołujemy w okienku konsoli, podając nazwę pliku EXE zawierającego kompilator (lub asembler lub linker), potrzebne opcje dodatkowe i nazwę kompilowanego pliku, np.

ml -c -Cp -omf podaj_m.asm

Każda opcja musi być poprzedzona znakiem lub /, przy czym opcji nie można łączyć, np. opcje "H  u" nie są równoważne opcji "Hu". Opcje winny być rozdzielone co najmniej jedną spacją. Wyłączenie opcji następuje (zazwyczaj) poprzez umieszczenie dodatkowego znaku, np. u. Małe i wielkie litery opcji są rozróżniane. Omyłkowe zastąpienie, np. opcji -c przez -C powoduje często wystąpienie trudnych do wykrycia błędów.

Poniżej przedstawiono najważniejsze opcje dla kompilatorów, asemblerów i linkerów. Listę dostępnych opcji można zazwyczaj wyświetlić poprzez napisanie nazwy pliku i „-h” lub „/?”.

1. Asembler ML.EXE firmy Microsoft (wersja 8.0)

-omf tworzenie pliku OBJ w formacie OMF

-coff tworzenie pliku OBJ w formacie COFF

-c asemblacja programu bez automatycznego uruchamiania linkera

-Cp rozróżnianie małych i wielkich liter

-Fl tworzenie sprawozdania (listingu) z przebiegu asemblacji

-Zi dodanie do pliku informacji potrzebnych do debuggowania programu

2. Kompilator CL.EXE firmy Microsoft

-Zi dodanie do pliku informacji potrzebnych do debuggowania programu

-c kompilacja programu bez automatycznego uruchamiania linkera

3. Konsolidator (32-bitowy) LINK.EXE firmy Microsoft (wersja 7.10)

-debug dodanie do pliku informacji potrzebnych do debuggowania programu

-defaultlib: ścieżka dostępu do standardowych plików bibliotecznych (lib)

-libpth: ścieżka dostępu do innych plików bibliotecznych (lib)

-entry: punkt wejścia do programu

-out: nazwa pliku wynikowego

-subsystem: przeznaczenie programu (console, Windows, ...)

Kompilacja i konsolidacja (linkowanie) programu przykładowego

Przypomnijmy, że omawiany wcześniej program został umieszczony w plikach: oblicz.c, (kod w języku C) oraz podaj_m.asm (kod w asemblerze).Rozpatrzymy tworzenie programu w formacie EXE za pomocą oprogramowania dostępnego w laboratoriach MKZL na Wydziale ETI PG.

Potrzebne oprogramowanie firmy Microsoft zainstalowane jest laboratoriach MKZL, natomiast studenci mogą je uzyskać w ramach programu Microsoft Academic Alliance (bliższe informacje podane są na stronie internetowej Wydziału ETI PG).

Operacje asemblacji, kompilacji i linkowania należy poprzedzić wywołaniem pliku wsadowego VCVARS32.BAT. Plik ten powinien być wywołany z poziomu katalogu bieżącego, w którym znajdują się pliki źródłowe programu. Po uruchomieniu tego pliku, kompilatory (asemblery) i linkery można wywoływać bez podawania ścieżek dostępu. W laboratoriach MKZL ścieżka dostępu do pliku VCVARS32.BAT zależy od wersji MS Visual Studio i tak dla:

  1. MS Visual Studio 2005

″c:\Program Files\Microsoft Visual Studio (x86)\VC\bin\VCVARS32.BAT″

  1. MS Visual Studio .Net (wpisać w jednym wierszu)

″c:\Program Files\Microsoft Visual Studio .Net 2003\VC7\

bin\VCVARS32.BAT″

Kompilację i asemblację przeprowadza się następująco:

cl -c oblicz.c

ml -c -coff -Cp -Fl podaj_m.asm

zaś do linkowania użyjemy polecenia

link -subsystem:console -out:oblicz.exe podaj_m.obj oblicz.obj

Jeśli program był poprawny, uzyskamy plik oblicz.exe.

Omawiane tu polecenia wygodnie jest umieścić razem w pliku wsadowym z rozszerzeniem .BAT. Wówczas, poprzez podanie nazwy pliku wsadowego automatycznie zostanie przeprowadzona kompilacja, asemblacja i linkowanie.

M.BAT

@rem Programowanie mieszane

@echo Wcześniej trzeba uruchomić plik wsadowy VCVARS32.BAT

cl -c oblicz.c

ml -c -coff -Cp -Fl podaj_m.asm

link -subsystem:console -out:oblicz.exe oblicz.obj podaj_m.obj

Znacznie większe możliwości automatyzacji procesu przetwarzania ma program narzędziowy make (Microsoft: nmake), który dostępny jest na wielu platformach. Poniżej podano dwie wersje pliku makefile, odpowiednio dla kompilatorów Microsoft i Borland. Przeprowadzenie kompilacji, asemblacji i linkowania wymaga tylko uruchomienia programu make (Borland) lub nmake (Microsoft) — program make (nmake) odszukuje plik makefile i wykonuje wszystkie zawarte w nim polecenia. Wykonywane są jednak tylko te operacje, które są niezbędne dla wytworzenia nowego kodu wynikowego. W szczególności oznacza to, że kompilacja wykonywana jest tylko wówczas, jeśli nastąpiła zmiana pliku źródłowego.

# Programowanie mieszane (wersja dla Microsoft)

oblicz.exe : oblicz.obj podaj_m.obj

link -subsystem:console -out:oblicz.exe oblicz.obj podaj_m.obj

oblicz.obj : oblicz.c

cl -c oblicz.c

podaj_m.obj : podaj_m.asm

ml -c -coff -Cp -Fl podaj_m.asm

Laboratorium Architektury Komputerów

Ćwiczenie 5

Operacje na liczbach zmiennoprzecinkowych

Wprowadzenie

Liczby zmiennoprzecinkowe (zmiennopozycyjne) zostały wprowadzone do techniki komputerowej w celu usunięcia wad zapisu stałoprzecinkowego. Wady te są wyraźnie widoczne w przypadku, gdy w trakcie obliczeń wykonywane są działania na liczbach bardzo dużych i bardzo małych. Warto dodać, że format zmiennoprzecinkowy dziesiętny stosowany jest od dawna w praktyce obliczeń (nie tylko komputerowych) i polega na przedstawieniu liczby w postaci iloczynu pewnej wartości (zwykle normalizowanej do przedziału <1, 10) i potęgi o podstawie 10, np. 0x01 graphic
. Dane w tym formacie wprowadzane do komputera zapisuje się zazwyczaj za pomocą litery e, np. 3.37e6.

W komputerach używane są binarne formaty liczb zmiennoprzecinkowych, które od około dwudziestu lat są znormalizowane i opisane w amerykańskim standardzie IEEE 754. Wszystkie współczesne procesory, w tym koprocesor arytmetyczny w architekturze IA-32, spełniają wymagania tego standardu.

Ponieważ działania na liczbach zmiennoprzecinkowych są dość złożone, zwykle realizowane są przez odrębny procesor zwany koprocesorem arytmetycznym. Koprocesor arytmetyczny jest umieszczony w jednej obudowie z głównym procesorem, chociaż funkcjonalnie stanowi on oddzielną jednostkę, która może wykonywać obliczenia niezależnie od głównego procesora. Koprocesor arytmetyczny oferuje bogatą listę rozkazów wykonujących działania na liczbach zmiennoprzecinkowych, w tym działania arytmetyczne, obliczanie wartości funkcji (trygonometrycznych, logarytmicznych, itp.) i wiele innych.

Architektura koprocesora arytmetycznego

Koprocesor arytmetyczny stanowi odrębny procesor, współdziałający z procesorem głównym, i znajdujący się w tej samej obudowie. Liczby, na których wykonywane są obliczenia, składowane są w 8 rejestrach 80-bitowych tworzących stos. Rozkazy koprocesora adresują rejestry stosu nie bezpośrednio, ale względem wierzchołka stosu. W kodzie asemblerowym rejestr znajdujący się na wierzchołku stosu oznaczany jest ST(0) lub ST, a dalsze ST(1), ST(2),..., ST(7).

Z każdym rejestrem stosu koprocesora związany jest 2-bitowy rejestr pomocniczy (nazywany czasami polem stanu rejestru), w którym podane są informacje o zawartości odpowiedniego rejestru stosu. Ponadto aktualny stan koprocesora jest reprezentowany przez bity tworzące 16-bitowy rejestr stanu koprocesora. W rejestrze tym m.in. zawarte są informacje o zdarzeniach w trakcie obliczeń (tzw. wyjątki), które mogą, opcjonalnie, powodować zakończenie wykonywania programu lub nie.

Z kolei również 16-bitowy rejestr sterujący pozwala wpływać na pracę koprocesora, m.in. możliwe jest wybranie jednego z czterech dostępnych sposobów zaokrąglania.

Koprocesor oferuje bogatą listę rozkazów. Na poziomie asemblera mnemoniki koprocesora zaczynają się od litery F. Stosowane są te same tryby adresowania co w procesorze, a w polu operandu mogą występować obiekty o długości 32, 64 lub 80 bitów. Przykładowo instrukcja

fadd ST(0), ST(3)

powoduje dodanie do zawartości rejestru ST(0) zawartości rejestru ST(3). Rejestr ST(0) jest wierzchołkiem stosu, natomiast rejestr ST(3) jest rejestrem oddalonym od wierzchołka o trzy pozycje. Warto dodać, że niektóre instrukcje nie mają jawnego operandu, np. "fabs" zastępuje liczbę na wierzchołku stosu przez jej wartość bezwzględną.

Do przesyłania danych używane są przede wszystkim instrukcje FLD i FST. Instrukcja FLD ładuje na wierzchołek stosu koprocesora liczbę zmiennoprzecinkową pobranej z lokacji pamięci lub ze stosu koprocesora. Instrukcja FST powoduje przesłanie zawartości wierzchołka stosu do lokacji pamięci lub do innego rejestru stosu koprocesora. Obie te instrukcje mają kilka odmian, co pozwala m.in. na odczytywanie z pamięci liczb całkowitych z jednoczesną konwersją na format zmiennoprzecinkowy. Dostępne są też instrukcje wpisujące na wierzchołek stosu niektóre stałe matematyczne, np. FLDPI.

Warto zwrócić uwagę, że załadowanie wartości na wierzchołek stosu powoduje, że wartości wcześniej zapisane dostępne są poprzez indeksy większe o 1, np. wartość ST(3) będzie dostępna jako ST(4); z tych powodów poniższa sekwencja instrukcji jest błędna:

FST ST(7)

FLD xvar ; błąd! — ST(7) staje się ST(8), a takiego rejestru nie ma

W obliczeniach zmiennoprzecinkowych porównania występuje znacznie rzadziej w zwykłym procesorze. Dostępnych jest kilka instrukcji porównujących wartości zmiennoprzecinkowe, przy czym wynik porównania wpisywany jest do ustalonych bitów rejestru stanu koprocesora. M.in, instrukcja FCOM x porównuje ST(0) z operandem x i ustawia bity C3 i C0 w rejestrze stanu koprocesora: C3=C0=0, gdy ST(0) > x albo C3=0, C0=1 w gdy ST(0) < x. Jeśli porównywane wartości są równe, to C3=1, C0=0. Stan C3=C0=1 oznacza, że porównanie nie mogło być przeprowadzone.

Bity w rejestrze stanu koprocesora określające wynik porównania zostały umieszczone na pozycjach odpowiadających znaczników w rejestrze procesora - pozwala to na wykorzystanie zwykłych instrukcji skoków warunkowych (dla liczb bez znaku). Przedtem trzeba jednak przepisać starszą część rejestru stanu koprocesora do młodszej części rejestru znaczników procesora. Ilustruje to podana niżej sekwencja rozkazów.

15

14

13

12

11

10

9

8

B

C3

ST

C2

C1

C0

starsze bity rejestru stanu koprocesora

7

6

5

4

3

2

1

0

SF

ZF

AF

PF

CF

młodsze bity rejestru znaczników procesora

FCOM ST(1) ; porównanie ST(0) i ST(1)

FSTSW AX ; zapamiętanie rejestru stanu koprocesora w AX

SAHF ; przepisanie AH do rejestru znaczników

JZ ROWNE

JA WIEKSZE

Począwszy od procesora Pentium Pro dostępny jest także rozkaz FCOMI, który wpisuje wynik porównania od razu do rejestru znaczników procesora. Stan znaczników procesora (ZF, PF, CF) po wykonaniu rozkazu FCOMI podano w poniższej tabeli.

ZF

PF

CF

ST(0) > x

0

0

0

ST(0) < x

0

0

1

ST(0) = x

1

0

0

niezdefiniowane

1

1

1

Koprocesor arytmetyczny może wykonywać obliczenia jednocześnie z głównym procesorem — wyłania się więc problem synchronizacji. Stosowana zasada jest następująca: w chwili, gdy procesor napotka instrukcję koprocesora, to czeka na zakończenie wykonywania poprzedniej instrukcji koprocesora, po czym zezwala na rozpoczęcie wykonywania nowej i przechodzi do wykonywania kolejnej instrukcji (zwykłej). W sytuacji kiedy dalsze obliczenia procesora zależą od wyniku działania koprocesora należy zatrzymać pracę procesora za pomocą instrukcji FWAIT. Procesor wznowi pracę po zakończeniu wykonywania rozkazu przez koprocesor arytmetyczny.

Przykłady obliczeń

Obliczenia realizowane za pomocą koprocesora arytmetycznego wymagają dość często dostosowania formuł obliczeniowych do specyfiki koprocesora. Przykładowo, obliczenie wartości funkcji ex wymaga użycia rozkazów

F2XM1 obliczenie ST(0) ← (2ST(0) − 1), przy czym ST(0) ∈ <−1, +1>

FSCALE obliczenie ST(0) ← ST(0) ∗ 2ST(1) , przy czym ST(1) jest wartością całkowitą

FLDL2E wpisanie na wierzchołek stosu koprocesora wartości log2 e

FRNDINT zaokrąglenie zawartości wierzchołka stosu do liczby całkowitej

Podane dalej symbole [ ]c i [ ]u oznaczają, odpowiednio, część całkowitą i ułamkową wartości podanej w nawiasach.

W obliczeniach wykorzystuje się zależność ab = 2 b log2 a, skąd wynikają podane niżej przekształcenia

0x08 graphic

fldl2e ; log 2 e

fmulp st(1), st(0) ; obliczenie x * log 2 e

fst st(1) ; kopiowanie obliczonej wartości do ST(1)

frndint ; zaokrąglenie do wartości całkowitej

fsub st(1), st(0) ; obliczenie części ułamkowej

fxch

; po zamianie: ST(0) - część ułamkowa, ST(1) - część całkowita

f2xm1 ; obliczenie wartości funkcji wykładniczej

; dla części ułamkowej wykładnika

fld1

faddp st(1), st(0) ; dodanie 1 do wyniku

fscale ; mnożenie przez 2^(część całkowita)

fstp st(1)

; przesłanie wyniku do ST(1) i usunięcie wartości z wierzchołka

; w rezultacie wynik znajduje się w ST(0)

Fragment programu wyznaczający pierwiastki równania kwadratowego

Poniżej podano fragment programu, w którym rozwiązywane jest równanie kwadratowe 0x01 graphic
, przy czym wiadomo, że równanie ma dwa pierwiastki rzeczywiste różne. Współczynniki równania a = 2, b = -1, c = -15 podane są w segmencie danych w postaci 32-bitowych liczb zmiennoprzecinkowych (format float). Fragment programu nie zawiera rozkazów wyświetlających pierwiastki równania (x1 = -2.5, x2 = 3) na ekranie — działanie programu można sprawdzić posługując się debuggerem.

.686

dane SEGMENT use16

; 2x^2 - x - 15 = 0

wsp_a dd +2.0

wsp_b dd -1.0

wsp_c dd -15.0

dwa dd 2.0

cztery dd 4.0

x1 dd ?

x2 dd ?

dane ENDS

— — — — — — — — — —

— — — — — — — — — —

mov ax, SEG dane

mov ds, ax

finit

fld wsp_a ; załadowanie współczynnika a

fld wsp_b ; załadowanie współczynnika b

fst st(2) ; kopiowanie b

; sytuacja na stosie: ST(0) = b, ST(1) = a, ST(2) = b

fmul st(0),st(0) ; obliczenie b^2

fld cztery

; sytuacja na stosie: ST(0) = 4.0, ST(1) = b^2, ST(2) = a, ST(3) = b

fmul st(0), st(2) ; obliczenie 4 * a

fmul wsp_c ; obliczenie 4 * a * c

fsubp st(1), st(0) ; obliczenie b^2 - 4 * a * c

; sytuacja na stosie: ST(0) = b^2 - 4 * a * c, ST(1) = a, ST(2) = b

fldz ; zaladowanie 0

; sytuacja na stosie: ST(0) = 0, ST(1) = b^2 - 4 * a * c,

; ST(2) = a, ST(3) = b

; rozkaz FCOMI jest akceptowany przez asembler tylko przy dyrektywie .686

; oba porównywane operandy musza być podane na stosie koprocesora

fcomi st(0),st(1)

fstp st(0) ; usuniecie zera z wierzchołka stosu

ja delta_ujemna ; skok, gdy delta ujemna

; w przykładzie nie wyodrębnia się przypadku delta = 0

; sytuacja na stosie: ST(0) = b^2 - 4 * a * c, ST(1) = a, ST(2) = b

fxch st(1)

; sytuacja na stosie: ST(0) = a, ST(1) = b^2 - 4 * a * c, ST(2) = b

fadd st(0), st(0) ; ; obliczenie 2 * a

fstp st(3)

; sytuacja na stosie: ST(0) = b^2 - 4 * a * c, ST(1) = b, ST(2) = 2 * a

fsqrt ; pierwiastek z delty

fst st(3) ; przechowanie obliczonej wartości

; sytuacja na stosie: ST(0) = sqrt(b^2 - 4 * a * c), ST(1) = b,

; ST(2) = 2 * a, ST(3) = sqrt(b^2 - 4 * a * c)

fchs ; zmiana znaku

fsub st(0), st(1); obliczenie -b - sqrt(delta)

fdiv st(0), st(2); obliczenie x1

fstp x1 ; zapisanie x1 w pamięci

; sytuacja na stosie: ST(0) = b, ST(1) = 2 * a, ST(2) = sqrt(b^2 - 4 * a * c)

fchs

fadd st(0), st(2)

fdiv st(0), st(1)

fstp x2

fstp st(0) ; oczyszczenie stosu

fstp st(0)

Rozkazy dla zastosowań multimedialnych

Zauważono pewną specyfikę programów wykonujących operacje na obrazach i dźwiękach: występują tam fragmenty kodu, które wykonują wielokrotnie powtarzające się działania arytmetyczne na liczbach całkowitych, często 8- i 16-bitowych. W architekturze IA-32 wprowadzono specjalne grupy rozkazów MMX i SSE przeznaczone do wykonywania ww. operacji. Rozkazy te wykonują równoległe operacje na kilku danych. Rozkazy grupy MMX wykonują działania na liczbach stałoprzecinkowych, natomiast rozkazy grupy SSE (ang. Streaming SIMD Extensions) na liczbach zmiennoprzecinkowych.

Rozkazy grupy SSE wykonują równoległe operacje na czterech 32-bitowych liczbach zmiennoprzecinkowych. Wprowadzone rozkazy przeznaczone są głównie do zastosowań w zakresie grafiki komputerowej, gdzie występują operacje przetwarzania dużych zbiorów liczb zmiennoprzecinkowych (mnożenie macierzy, transpozycja macierzy, itd.).

Dla SSE zdefiniowano 8 nowych rejestrów: każdy rejestr ma 128 bitów i zawiera 4 liczby zmiennoprzecinkowe; rejestry oznaczone są symbolami xmm0 ÷ xmm7.

0x01 graphic

W praktyce poprzez zastosowanie SSE uzyskuje się podwojenie prędkości przetwarzania (chociaż działania wykonywane są równolegle na czterech liczbach). Zestaw rozkazów SSE jest ciągle rozszerzany (SSE 2, SSE 3), ale podstawowy zestaw obejmuje 70 rozkazów, w tym:

50 rozkazów operacji zmiennoprzecinkowych,

12 rozkazów operacji stałoprzecinkowych

8 rozkazów pomocniczych;

Rozkazy mogą wykonywać działania na danych:

0x01 graphic

0x01 graphic

; Program przykładowy ilustrujący operacje SSE procesora

; Poniższy podprogram jest przystosowany do wywoływania

; z poziomu języka C (program arytmc_SSE.c)

.686

.XMM

public _dodaj_SSE, _pierwiastek_SSE, _odwrotnosc_SSE

_TEXT SEGMENT dword public 'CODE' use32

ASSUME cs:_TEXT

_dodaj_SSE PROC

push ebp

mov ebp, esp

push ebx

push esi

push edi

mov esi, [ebp+8] ; adres pierwszej tablicy

mov edi, [ebp+12] ; adres drugiej tablicy

mov ebx, [ebp+16] ; adres tablicy wynikowej

; ladowanie do rejestru xmm5 czterech liczb zmiennoprzecinkowych

; 32-bitowych - liczby zostaja pobrane z tablicy, ktorej adres

; poczatkowy podany jest w rejestrze ESI

; mnemonik "movups" : mov, u - unaligned (adres obszaru nie jest

; podzielny przez 16), p - packed (do rejestru ladowane sa od razu

; cztery liczby), s - short (inaczej float, liczby zmiennoprzecinkowe

; 32-bitowe)

movups xmm5, [esi]

movups xmm6, [edi]

; sumowanie czterech liczb zmiennoprzecinkowych zawartych

; w rejestrach xmm5 i xmm6

addps xmm5, xmm6

; zapisanie wyniku sumowania w tablicy w pamieci

movups [ebx], xmm5

pop edi

pop esi

pop ebx

pop ebp

ret

_dodaj_SSE ENDP

;=========================================================

_pierwiastek_SSE PROC

push ebp

mov ebp, esp

push ebx

push esi

mov esi, [ebp+8] ; adres pierwszej tablicy

mov ebx, [ebp+12] ; adres tablicy wynikowej

; ladowanie do rejestru xmm5 czterech liczb zmiennoprzecinkowych

; 32-bitowych - liczby zostaja pobrane z tablicy, ktorej adres

; poczatkowy podany jest w rejestrze ESI

; mnemonik "movups" : mov, u - unaligned (adres obszaru nie jest

; podzielny przez 16), p - packed (do rejestru ladowane sa od razu

; cztery liczby), s - short (inaczej float, liczby zmiennoprzecinkowe

; 32-bitowe)

movups xmm6, [esi]

; obliczanie pierwiastka z czterech liczb zmiennoprzecinkowych

; znajdujacych sie w rejestrze xmm6 - wynik wpisywany jest do xmm5

sqrtps xmm5, xmm6

; zapisanie wyniku sumowania w tablicy w pamieci

movups [ebx], xmm5

pop esi

pop ebx

pop ebp

ret

_pierwiastek_SSE ENDP

;=========================================================

; rozkaz RCPPS wykonuje obliczenia na 12-bitowej mantysie

; (a nie na typowej 24-bitowej) - obliczenia wykonywane sa

; szybciej, ale sa mniej dokladne

_odwrotnosc_SSE PROC

push ebp

mov ebp, esp

push ebx

push esi

mov esi, [ebp+8] ; adres pierwszej tablicy

mov ebx, [ebp+12] ; adres tablicy wynikowej

; ladowanie do rejestru xmm5 czterech liczb zmiennoprzecinkowych

; 32-bitowych - liczby zostaja pobrane z tablicy, ktorej adres

; poczatkowy podany jest w rejestrze ESI

; mnemonik "movups" : mov, u - unaligned (adres obszaru nie jest

; podzielny przez 16), p - packed (do rejestru ladowane sa od razu

; cztery liczby), s - short (inaczej float, liczby zmiennoprzecinkowe

; 32-bitowe)

movups xmm5, [esi]

; obliczanie odwrotnosci czterech liczb zmiennoprzecinkowych

; znajdujacych sie w rejestrze xmm6 - wynik wpisywany jest do xmm5

rcpps xmm5, xmm6

; zapisanie wyniku sumowania w tablicy w pamieci

movups [ebx], xmm5

pop esi

pop ebx

pop ebp

ret

_odwrotnosc_SSE ENDP

_TEXT ENDS

END

=====================

/* Program przykladowy ilustrujacy operacje SSE procesora

styczen 2007

Ponizszy podprogram jest przystosowany do wywolywania

do wspolpracy z podprogramem zakodowanym w asemblerze

(plik arytm_SSE.asm)

*/

#include <stdio.h>

void dodaj_SSE (float *, float *, float *);

void pierwiastek_SSE (float *, float *);

void odwrotnosc_SSE (float *, float *);

int main()

{

float p[4] = {1.0, 1.5, 2.0, 2.5};

float q[4] = {0.25, -0.5, 1.0, -1.75};

float r[4];

dodaj_SSE (p, q, r);

printf ("\n%f %f %f %f", p[0], p[1], p[2], p[3]);

printf ("\n%f %f %f %f", q[0], q[1], q[2], q[3]);

printf ("\n%f %f %f %f", r[0], r[1], r[2], r[3]);

printf("\n\nObliczanie pierwiastka");

pierwiastek_SSE (p, r);

printf ("\n%f %f %f %f", p[0], p[1], p[2], p[3]);

printf ("\n%f %f %f %f", r[0], r[1], r[2], r[3]);

printf("\n\nObliczanie odwrotnosci - ze wzgledu na stosowanie");

printf("\n12-bitowej mantysy obliczenia sa malo dokladne");

odwrotnosc_SSE (p, r);

printf ("\n%f %f %f %f", p[0], p[1], p[2], p[3]);

printf ("\n%f %f %f %f", r[0], r[1], r[2], r[3]);

return 0;

}

Laboratorium Architektury Komputerów

Ćwiczenie 6

Specyfika kodowania programów

w systemie Windows i Linux

Asembler w systemie Linux

Dla systemu Linux istnieje przynajmniej kilka asemblerów o różniącej się (istotnie) składni. Jednym z bardziej “przyjaznych”, przypominającym składnią asemblery stosowane w systemie Windows jest NASM.

Poniżej przedstawiony jest program wyświetlający napis powitalny. Program można przeredagować za pomocą dowolnego edytora dostępnego w Linuksie, np. w edytora programie Midnight Commander (mc). Program źródłowy można skompilować i uruchomić w systemie Linux za pomocą następujących poleceń:

- kompilacja programu do postaci półskompilowanej (.o)

nasm -f elf program.asm

- tworzenie wersji wykonywalnej programu o nazwie program.out

ld -s -o program.out program.o

Składnia asemblera NASM jest podobna składni asemblera MASM (ml.exe) lub TASM. Kilka charakterystycznych przykładów różnic w składni podano w poniższej tablicy.

MASM (ML), TASM

NASM

v1 db ?

v1 resb 1

v2 db 12 dup (?)

v2 resb 12

v3 dw ?

v3 resw 1

v4 db 21 dup ('A')

v4 TIMES 21 db 'A'

extrn

extern

public

global

Odpowiednikiem funkcji usługowych wywoływanych za pomocą rozkazu INT  21H w systemie Windows/DOS są funkcje wywoływane za pomocą INT  80H w Linuksie. Funkcje te wykonują jednak inne operacje (które są zgodne z funkcjami oferowanymi w bibliotece języka C). Wybrane funkcje systemowe wywoływane za pomocą INT 80H podane są w poniższej tablicy. Numer funkcji należy podać w rejestrze EAX.

1

Zakończenie wykonywania procesu; kod powrotu należy wpisać do rejestru EBX (zwykle 0)

3

Odczyt pliku; EBX - uchwyt pliku, ECX - adres bufora docelowego, EDX - liczba bajtów do przeczytania. W przypadku EBX = 0 dane odczytywane są z klawiatury. Po odczycie w EAX podana jest liczba odczytanych bajtów albo kod błędu.

4

Zapis do pliku; EBX - uchwyt pliku, ECX - adres bufora źródłowego, EDX - liczba bajtów do zapisania. W przypadku EBX = 1 dane wysyłane są na ekran monitora. Po odczycie w EAX podana jest liczba zapisanych bajtów albo kod błędu.

5

Otwarcie pliku; EBX - adres obszaru, w którym zapisana jest nazwa pliku zakończona bajtem zerowym; ECX - bity dostępu (często = 0); EDX - prawa dostępu (często = 0). Po wykonaniu funkcji EAX zawiera uchwyt pliku albo kod błędu.

6

Zamknięcie pliku (nie używa się dla uchwytów 0, 1, 2); EBX - uchwyt zamykanego pliku.

; program przykładowy przystosowany do asemblera NASM

; nasm -f elf pierwszy.asm (asemblacja)

; ld -o hello pierwszy.o (konsolidacja)

section .text ; początek sekcji rozkazów programu

global _start ; deklaracja symbolu globalnego

_start: ; etykieta wskazująca pierwszy rozkaz programu

mov eax, 4 ; numer funkcji

; systemowej 'sys_write' (zapis do pliku)

mov ebx, 1 ; identyfikator pliku lub urządzenia (1 oznacza

; ekran)

mov ecx, tekst ; adres obszaru pamięci, w którym znajduje się

; wyświetlany tekst

mov edx, dlugosc ; liczba znaków tekstu

int 80h ; wywołanie funkcji systemowej

; zakończenie programu - wywołanie funkcji systemowej sys_exit

mov eax, 1 ; numer funkcji sys_exit

int 80h ; wywołanie funkcji

section .data ; początek sekcji danych programu

tekst db 10, 'Witam!', 10 ; wyświetlany tekst

dlugosc EQU $ - tekst ; długość napisu

Kodowanie programów dla systemu Windows

Przygotowanie programów dostosowanych do pracy w środowisku Windows, prócz pewnego doświadczenia w programowaniu, wymaga też zaakceptowania odmiennego sposobu funkcjonowania programów. Zazwyczaj programy tego typu tworzy się w językach wysokiego poziomu jak C/C++ czy Pascal, często wykorzystując narzędzia szybkiego programowania jakimi są np. Borland Delphi czy MS Visual Studio. Kodowanie w asemblerze, choć bardziej złożone, pozwala jednak na dokładniejsze poznanie mechanizmów wywoływania funkcji i przesyłania komunikatów.

Spośród wielu elementów programowania właściwych dla systemu Windows, jednym z najważniejszych jest system komunikatów. Można powiedzieć, że działanie programu w środowisku Windows sprowadza się do obsługi komunikatów. Po uruchomieniu programu i wyświetleniu związanego z nim okna, program oczekuje na komunikaty (zdefiniowano około 140 komunikatów). Ich źródłem mogą być naciśnięcia klawiszy na klawiaturze, kliknięcie myszy, zmiana rozmiarów okna, i wiele innych zdarzeń. Program po otrzymaniu komunikatu interpretuje go i podejmuje odpowiednie działania.

W celu uproszczenia omawianych zagadnień, prezentowany dalej program przykładowy ma postać okna dialogowego, na którym rozmieszczone są przyciski sterujące i okienka do wprowadzania i wyświetlania tekstu.

Zasoby programu

Programy przygotowywane dla środowiska Windows posiadają możliwość odrębnego zapisania kodu zawierającego właściwy algorytm oraz kodu stanowiącego opis wyglądu zewnętrznego programu. W ten sposób pewne cechy zewnętrzne programu mogą być łatwo modyfikowane bez konieczności korygowania całego programu. Wygląd zewnętrzny programów zakodowany jest w tzw. zasobach aplikacji. W zasobach mogą być zawarte okienka, obrazki, ikony, kursory, opisy menu, itp. Najważniejszą cechą zasobów jest to, iż mogą zostać utworzone niezależnie od reszty programu, a później dołączone do niego poprzez specyficzne linkowanie.

Opis zasobów umieszcza się zazwyczaj w pliku z rozszerzeniem .RC. Plik ten zostaje następnie przekształcony za pomocą kompilatora zasobów (rc.exe) do postaci zakodowanej RES, która jest scalana (linkowana) z programem EXE.

Edycję pliku .RC można wykonywać za pomocą dowolnego edytora tekstowego, np. za pomocą Notatnika. Zazwyczaj wygodniej jest korzystać z edytorów zasobów, które stanowią integralną część wielu środowisk programistycznych (np. MS Visual Studio).

Treść pliku zasobów zapisywana jest w stylu zbliżonym do języka C. Każdy element zasobów identyfikowany jest przez numer lub nazwę, np. 123 ICON jakas_ikona.ico . Oznacza to, że pewien zasób ma numer 123, typ ICON, a dane pobierane są z pliku jakas_ikona.ico . Typy zasobów skojarzone z numerami 1 ÷ 255 są zarezerwowane.

Plik ZadWin.rc

#include <windows.h>

#define ID_OKNO_DIALOGOWE 23453

#define RAMKA_ARGUMENT 23454

#define RAMKA_WYNIK 23455

ID_OKNO_DIALOGOWE DIALOG DISCARDABLE 0, 0, 230, 146

STYLE DS_MODALFRAME | DS_SETFOREGROUND | DS_3DLOOK | DS_CENTER |

WS_POPUP|WS_VISIBLE|WS_CAPTION|WS_SYSMENU

CAPTION "Program Windows zakodowany w asemblerze"

FONT 18, "MS Sans Serif"

{

CONTROL "Argument:", -1, "STATIC", SS_LEFT | WS_CHILD |

WS_VISIBLE | WS_GROUP, 5, 12, 40, 8

CONTROL "", RAMKA_ARGUMENT, "EDIT", ES_LEFT |

ES_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | WS_BORDER |

WS_TABSTOP, 50, 10, 105, 12

CONTROL "Wynik:", -1, "STATIC", SS_LEFT | WS_CHILD |

WS_VISIBLE | WS_GROUP, 5, 32, 35, 8

// opis okienka dla tekstu wynikowego

// ES_CENTER - wyswietlany tekst bedzie centrowany

CONTROL "Tu będzie wpisany wynik programu",

RAMKA_WYNIK, "EDIT", ES_CENTER |

ES_AUTOHSCROLL | WS_CHILD | WS_VISIBLE |

WS_BORDER | WS_TABSTOP, 50, 30, 120, 12

/* =========================================================

Opisy przyciskow OK i Cancel

BS_DEFPUSHBUTTON - ten przycisk moze zostac uaktywniony takze

przez nacisniecie klawisza ENTER

WS_VISIBLE - okno jest inicjalnie widoczne

WS_TABSTOP - pozwala przechodzić przez elementy okna

za pomoca klawisza TAB

*/

CONTROL "OK", 1, "BUTTON", BS_DEFPUSHBUTTON | WS_CHILD |

WS_VISIBLE | WS_TABSTOP, 20, 50, 30, 14

CONTROL "Cancel", 2, "BUTTON", BS_PUSHBUTTON | WS_CHILD |

WS_VISIBLE | WS_TABSTOP, 105, 50, 30, 14

}

MENU MainMenu

BEGIN

END

Program główny

W zasobach omawianego programu przykładowego zdefiniowano obiekt typu DIALOG, który zawiera opisy okienek sterujących. W działaniach związanych z tymi zasobami używane są funkcje DialogBoxParam i EndDialog. Pierwsza z tych funkcji tworzy okno dialogowe wg opisu podanego w pliku zasobów — stała ID_OKNO_DIALOGOWE (drugi parametr) jest identyfikatorem wskazującym na opis okna dialogowego, który zawarty jest w pliku zasobów (.rc). Czwarty parametr Oblicz_DialogProc jest wskaźnikiem do funkcji obsługującej komunikaty kierowane do okna. Z kolei funkcja EndDialog powoduje zamknięcie okna dialogowego.

Procedura obsługi zdarzeń (funkcja Oblicz_DialogProc) powinna zwracać wartość FALSE, jeśli zdarzenie nie zostało obsłużone, albo wartość TRUE, jeśli zdarzenie zostało obsłużone (nie potrzeba wywoływać funkcji DefWindowProc). Jeśli potrzebny jest uchwyt okna (handle), to trzeba umieścić odpowiedni kod w części obsługującej komunikat WM_INITDIALOG.

Podany tu program przykładowy jest ograniczony do okna dialogowego. Typowe programy w systemie Windows mają jednak budowę nieco bardziej złożoną, a głównym elementem takiego programu jest pętla komunikatów - krótki (kilka wierszy) fragment programu, przez który przesyłane są komunikaty sterujące wykonywaniem programu. Właściwy kod, zawierający operacje obsługi komunikatów realizuje odrębna funkcja nazywana funkcją okienkową.

Plik ZadWin.asm

; Okno dialogowe w systemie Windows

; program przykładowy w asemblerze (styczeń 2007)

.686

extrn _ExitProcess@4 : near

extrn _GetModuleHandleA@4 : near

extrn _DialogBoxParamA@20 : near

extrn _MessageBoxA@16 : near

extrn _EndDialog@8 : near

extrn _GetWindowTextA@12 : near

extrn _SetWindowTextA@8 : near

extrn _GetDlgItem@8 : near

public _WinMain@16

IDOK EQU 1

IDCANCEL EQU 2

WM_INITDIALOG EQU 0110H

WM_COMMAND EQU 0111H

WM_CLOSE EQU 0010H

TRUE EQU 1

FALSE EQU 0

ID_OKNO_DIALOGOWE EQU 23453

RAMKA_ARGUMENT EQU 23454

RAMKA_WYNIK EQU 23455

_DATA SEGMENT dword public 'DATA' use32

hInstance dd ?

napis db 'Wyjście z programu',0

napis2 db 'Naciśnieto Sprawdź',0

argument db 32 dup (?)

wynik db 32 dup (?)

napis3 db 'Tu wpisz argument',0

_DATA ENDS

_TEXT SEGMENT dword public 'CODE' use32

ASSUME cs:_TEXT, ds:_DATA

;---------------------------------------------------------------

; Funkcja obslugujaca komunikaty kierowane do okna dialogowego

; BOOL CALLBACK Oblicz_DialogProc

; (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

$Oblicz_DialogProc STRUC

dd ?, ? ; EBP, slad

hWnd dd ?

uMsg dd ? ; kod komunikatu

wParam dd ?

lParam dd ?

$Oblicz_DialogProc ENDS

Oblicz_DialogProc PROC

push ebp

mov ebp, esp

cmp ($Oblicz_DialogProc PTR [ebp]).uMsg, WM_CLOSE

jnz nieWmClose ; skok gdy inny komunikat

; obsluga komunikatu WM_CLOSE

ZamknijDialog:

;EndDialog(hWnd, 0);

; return TRUE;

push 0

push ($Oblicz_DialogProc PTR [ebp]).hWnd

call _EndDialog@8 ; zamkniecie okna dialogowego

mov eax, TRUE

pop ebp

ret

;------------------------------------------------------------------

nieWmClose: cmp ($Oblicz_DialogProc PTR [ebp]).uMsg, WM_INITDIALOG

jne nieWmInitdialog ; skok, gdy inny komunikat

; komunikat WM_INITDIALOG generowany jest w chwili tworzenia okna

; SetWindowText(GetDlgItem(hWnd,RAMKA_ARGUMENT),(LPCSTR)"Tu wpisz argument");

; return TRUE;

push RAMKA_ARGUMENT ; wartosc stalej

push ($Oblicz_DialogProc PTR [ebp]).hWnd

call _GetDlgItem@8 ; funkcja zwraca wartosc przez EAX

; wpisanie tekstu poczatkowego do okna dialogowego

push OFFSET napis3

push eax

call _SetWindowTextA@8

mov eax,TRUE

pop ebp

ret

;------------------------------------------------------------------

nieWmInitdialog:

cmp ($Oblicz_DialogProc PTR [ebp]).uMsg, WM_COMMAND

jne nieWmCommand

cmp ($Oblicz_DialogProc PTR [ebp]).wParam, IDOK

jne nieIdOK

; pobieranie tekstu z okienka RAMKA_ARGUMENT

; GetWindowText(GetDlgItem(hWnd,RAMKA_ARGUMENT),(LPSTR)argument,30);

push RAMKA_ARGUMENT ; identyfikator okienka

push ($Oblicz_DialogProc PTR [ebp]).hWnd

call _GetDlgItem@8 ; funkcja zwraca wartosc przez EAX

push 30

push OFFSET argument

push eax

call _GetWindowTextA@12

; kopiowanie 7 znakow do drugiego okna

mov ecx, 7

push esi

push edi

mov esi, OFFSET argument

mov edi, OFFSET wynik

kopiowanie: mov al, [esi]

mov [edi], al

inc esi

inc edi

loop kopiowanie

pop edi

pop esi

;----------------------------------------------------------------

; wpisywanie tekstu do okienka RAMKA_WYNIK

; SetWindowText(GetDlgItem(hWnd,RAMKA_WYNIK), wynik);

push RAMKA_WYNIK ; wartosc stalej

push ($Oblicz_DialogProc PTR [ebp]).hWnd

call _GetDlgItem@8 ; funkcja zwraca wartosc przez EAX

push OFFSET wynik

push eax

call _SetWindowTextA@8

mov eax, FALSE

pop ebp

ret

nieIdOK: cmp ($Oblicz_DialogProc PTR [ebp]).wParam, IDCANCEL

jne nieIdCancel

push IDCANCEL

push ($Oblicz_DialogProc PTR [ebp]).hWnd

call _EndDialog@8 ; zamkniecie okna dialogowego

mov eax,TRUE

pop ebp

ret

nieIdCancel:

nieWmCommand:

mov eax, FALSE

pop ebp

ret

Oblicz_DialogProc ENDP

;====================================================================

_WinMain@16:

; hInstance = GetModuleHandle (NULL);

push 0

call _GetModuleHandleA@4

mov hInstance,eax

Comment |

Funkcja DialogBoxParam tworzy okno dialogowe

odp = DialogBoxParam(hInstance, (LPCSTR)ID_OKNO_DIALOGOWE, 0,

Oblicz_DialogProc, (LPARAM) NULL);

hInstance - uchwyt do instancji aplikacji

ID_OKNO_DIALOGOWE - identfikator do opisu okna w pliku zasobow .RC

Oblicz_DialogProc - wskaznik do funkcji obslugujacej

komunikaty kierowane do okna

|

push 0

push offset Oblicz_DialogProc

push 0

push ID_OKNO_DIALOGOWE

push hInstance

call _DialogBoxParamA@20

; zakonczenie programu

push 0

call _ExitProcess@4 ; zakonczenie programu

_TEXT ENDS

END

Kompilacja i konsolidacja programu

W rozpatrywanym przykładzie program główny, będący aplikacją okienkową systemu Windows, zakodowany jest w asemblerze (plik ZadWin.asm). Zasoby programu zawarte są w pliku ZadWin.rc . W celu utworzenia programu wynikowego w pliku z rozszerzeniem .exe należy wykonać niżej podane operacje (wcześniej należy uruchomić plik wsadowy VCVARS32.BAT - zob. opis ćw. 1):

ml -c -Cp -coff -Fl ZadWin.asm

rc ZadWin.rc

(zapisać w jednym wierszu) link -subsystem:windows -out:ZadWin.exe ZadWin.obj

libcmt.lib user32.lib kernel32.lib ZadWin.res

Laboratorium Architektury Komputerów

Ćwiczenie 7

Obsługa przerwań sprzętowych

Wprowadzenie

Współczesne, wielozadaniowe systemy operacyjne starają się izolować programy użytkowe od sprzętu komputerowego. Programy mogą się komunikować z urządzeniami wyłącznie za pośrednictwem usług oferowanych przez system operacyjny. W tej sytuacji przeprowadzenie jakichkolwiek eksperymentów ilustrujących zasady sterowania urządzeniami komputerowymi nie może być zrealizowane na poziomie zwykłej aplikacji. Powyższe uwagi odnoszą się także do mechanizmów obsługi przerwań sprzętowych, które są tematem niniejszego ćwiczenia.

W tej sytuacji jedynym rozwiązaniem jest przeprowadzanie eksperymentów w zakresie sprzętu na komputerze pozbawionym systemu operacyjnego, którego rolę (w szczątkowym zakresie) przejmuje uruchomiony program - takie podejście jest stosowane w projektach wykonywanych w sem. 4 w ramach przedmiotu „Oprogramowanie systemowe”. Jednak taki mini-system operacyjny, nawet w najprostszej postaci jest dość złożony i trudny do analizy.

W ramach niniejszego ćwiczenia spróbujemy zrealizować przedstawione zamierzenia wykorzystując środowisko systemu DOS. System ten już od wielu lat nie jest używany, ale jego funkcje zostały przejęte przez system Windows, który jeszcze do niedawna oferował możliwość wykonywania programów przeznaczonych do wykonywania w środowisku systemu DOS.

System operacyjny DOS powstał w początkowym okresie rozwoju komputerów PC i założenia był systemem jednozadaniowym, przystosowanym do pracy przy dość ubogich zasobach sprzętowych (np. pamięć operacyjna 640 KB, procesor z zegarem 8 MHz). System DOS nie posiada żadnych mechanizmów ochrony systemu operacyjnego i pozwala w szczególności na ingerencję (oczywiście w przemyślany sposób) w mechanizmy obsługi przerwań. Stanowi to podstawę do ilustracji mechanizmów obsługi przerwań w ramach niniejszego ćwiczenia.

Przypomnijmy, że procesory zgodne z architekturą IA-32 mogą pracować w dwóch trybach pracy:

rzeczywistym, który naśladuje i pewnym stopniu rozszerza funkcje procesora 8086;

chronionym, nazywanym też wirtualnym, w którym dostępne są mechanizmy wielozadaniowości i stosowane są odmienne niż w trybie rzeczywistym sposoby adresowania i ochrony.

W ramach trybu chronionego wprowadzono także (począwszy od procesora 386) specjalny podtryb, określany jako tryb V86 (tryb wirtualny 8086), w którym, z punktu widzenia wykonywanych programów, procesor działa prawie dokładnie tak samo jak w trybie rzeczywistym.

Programy napisane dla systemu DOS mogą być wykonywane w trybie rzeczywistym lub w trybie V86. W systemie Windows możliwość wykonywania programów w trybie rzeczywistym istniała tylko w starszych wersjach systemu. Z kolei w trybie V86, który jest odmianą trybu chronionego, ze względu na stabilność systemu, nie jest możliwy bezpośredni dostęp do sprzętu. W tej sytuacji, konstruktorzy systemu Windows stworzyli warunki do wykonywania programów napisanych dla systemu DOS poprzez symulowanie sprzętu. Tak więc aplikacja DOSowa działająca w trybie V86 może bez ograniczeń wykonywać rozkazy działające na sprzęcie, przy czym nie są one wykonywane na rzeczywistym sprzęcie, ale dokładnie symulowane.

Sytuacja komplikuje się jeszcze bardziej w odniesieniu do komputerów, w których zainstalowane są 64-bitowe systemy operacyjne, np. Windows XP 64. Systemy tego typu nie akceptują programów dla systemu DOS i można je uruchamiać jedynie za pomocą maszyny wirtualnej, np. DOSBox, która opisana jest poniżej.

Maszyna wirtualna DOSBox

0x08 graphic
W laboratoriach komputerowych MKZL, w celu uruchomienia maszyny wirtualnej DOSBox należy uruchomić program c:\programy\DOSBox—0.63\dosbox.exe. Ponadto, wersja instalacyjna tego programu w postaci pliku DOSBox0.63-win32-installer2.exe dostępna jest na wielu stronach internetowych.

Po uruchomieniu ma­szyny wirtualnej na ekranie pojawi się pokazane obok okno.

Do asemblacji programów będziemy używać asemblera MASM, a do konsolidacji programu LINK (wersja 16-bitowa) W laboratoriach komputerowych MKZL programy MASM i LINK umieszczone są w katalogu c:\programy\masm, natomiast wszystkie pliki źródłowe i pliki tworzone podczas asemblacji i linkowania należy przechowywać w katalogu d:\studenci (lub w jednym z jego podkatalogów).

Dla podanych lokalizacji wskazane jest utworzenie dwóch dysków wirtualnych skojarzonych z ww. katalogami. W tym celu w okienku konsoli maszyny wirtualnej trzeba wprowadzić polecenia:

mount t c:\programy\masm

mount d d:\studenci

Po ustawieniu bieżacego napędu dyskowego (wirtualnego) na d: można przeprowadzić asemblację i linkowanie programu przykładowego:

t:\masm .......asm,,,;

t:\link ......obj;

W rezultacie powstanie plik ...exe, który można uruchomić w okienku maszyny wirtualnej.

Obsługa przerwań sprzętowych

Przerwania sprzętowe są pewnymi zdarzeniami zachodzącymi w urządzeniach komputera, które wymagają podjęcia niezwłocznej obsługi. W takim urządzenie wysyła do procesora sygnał przerwania, który powoduje, że procesor przerywa wykonywanie bieżącego programu i rozpoczyna wykonywanie innego programu, zazwyczaj stanowiącego część systemu operacyjnego. Zadaniem tego programu zbadanie przyczyn nadejścia sygnału przerwania i podjęcie odpowiedniej akcji, np. poinformowanie użytkownika, że kopiowanie pliku z Internetu zostało zakończone, albo też że operacja drukowania została zatrzymana ze względu na brak papieru. Po wykonaniu wszystkich czynności związanych z obsługą przerwania system operacyjny wznawia wykonywanie przerwanego programu.

Tak więc procesor, oprócz wykonywania rozkazów programu, procesor musi być przygotowany do obsługi przerwań, które pojawiają się asynchronicznie. Zazwyczaj procesor podejmuje obsługę przerwania po zakończeniu aktualnie wykonywanego rozkazu. Następnie zapisuje położenie w pamięci (adres) kolejnego rozkazu do wykonania, który został by wykonany, gdyby nie nadeszło przerwanie. Zazwyczaj położenie to zapisywane jest na stosie.

Po zakończeniu obsługi przerwania musi nastąpić wznowienie wykonywania programu głównego. Obsługa przerwanie nie może mieć żadnego wpływu na wykonywanie programu głównego, w szczególności nie mogą nastąpić jakiekolwiek zmiany zawartości rejestrów i znaczników. Ponieważ rejestry i znaczniki będą używane w trakcie obsługi przerwania, trzeba je więc od razu zapamiętać i odtworzyć bezpośrednio przez zakończeniem obsługi. Działania te wykonywane są zazwyczaj programowo, z częściowym wspomaganiem sprzętowym. Przykładowo, w procesorach zgodnych z architekturą IA-32 automatycznie zapamiętywany jest tylko rejestr znaczników, inne rejestry muszą być zapamiętane przez program obsługi.

Omówimy teraz technikę obsługi przerwań sprzętowych stosowanych w komputerach PC z procesorem o architekturze IA-32. Warunkiem przyjęcia przerwania sprzętowego (generowanego przez urządzenie zewnętrzne) jest stan znacznika IF = 1. Znacznik IF (ang. interrupt flag) w rejestrze znaczników (bit nr 9) określa zezwolenie na przyjmowanie przerwań: procesor może przyjmować przerwania tylko wówczas, gdy IF=1. Znacznik IF jest automatycznie zerowany w chwili przyjęcia przerwania;.

0x01 graphic

Możliwe jest zablokowanie przyjmowania przerwań poprzez wyzerowanie znacznika IF. W programie, do zmiany stanu znacznika IF można zastosować rozkazy CLI (IF ← 0) lub STI (IF ← 1).

W omawianych procesorach po wystąpieniu przerwania sprzętowego, bezpośrednio przed uruchomieniem programu obsługi przerwania na stosie zapisywany jest ślad, który umożliwia powrót do przerwanego programu. Struktura śladu jest identyczna jak w przypadku rozkazu INT.

0x01 graphic

Obsługa przerwań jest ściśle związana z tablicą wektorów przerwań. Przypomnijmy, że w trybie rzeczywistym tablica wektorów przerwań zawiera 256 adresów, z których każdy zajmuje 4 bajty. Adresy kodowane są w postaci segment:offset. Tablica umieszczona jest w pamięci począwszy od adresu fizycznego 0 (aczkolwiek jej położenie może zostać zmienione).

Po zapisaniu śladu na stosie procesor odszukuje w tablicy wektorów przerwań adres procedury obsługi przerwania i rozpoczyna ją wykonywać. Numer wektora przerwania, w którym zawarty jest adres procedury obsługi zależy w ustalony sposób od numeru linii IRQ, poprzez którą nadszedł sygnał przerwania. W przypadku programów 16-bitowych wykonywanych w systemie Windows/DOS numer wektora stanowi powiększony o 8 numer linii IRQ (dla linii IRQ 8 ÷ IRQ 15 numer powiększany jest o 104).

Podprogram obsługi przerwania kończy rozkaz IRET, która powoduje wznowienie wykonywania przerwanego programu poprzez odtworzenie rejestrów (E)IP, CS i (E)FLAGS, na podstawie śladu zapamiętanego na stosie.

Sterownik przerwań

Zazwyczaj każde urządzenie dołączone do komputera jest w stanie generować sygnały przerwań. Wymaga to odpowiedniego zorganizowania systemu przerwań, tak poszczególne przerwania były przyjmowane wg ustalonej hierarchii. Na ogół procesor nie jest przygotowany do bezpośredniej obsługi przerwań, zwłaszcza jeśli jest zainstalowanych dużo urządzeń. Stosowane są różne systemy obsługi przerwań; niekiedy zainstalowana jest wspólna linia przerwań dla wszystkich urządzeń — po nadejściu przerwania procesor sprawdza stany poszczególnych urządzeń identyfikując urządzenie, które wysłało przerwanie (metoda odpytywania). W innych systemach linia przerwań przechodzi przez wszystkie zainstalowane urządzenia ustawione wg priorytetów.

Aktualnie w komputerach PC system przerwań obsługiwany jest przez układ APIC (dawniej używano dwóch układów typu 8259), który pełni rolę "sekretarki" procesora. W trybie rzeczywistym procesora układ APIC pracuje w trybie konwencjonalnym naśladując pracę swoich poprzedników. Możemy zatem odnieść nasze rozważania do układów 8259, co pozwoli na dokładniejsze wyjaśnienie techniki obsługi przerwań. Układy te, pracujące w konfiguracji kaskadowej, mogą obsługiwać do 15 źródeł przerwań. Sygnały przerwań z poszczególnych urządzenia kierowane są do układów 8259 poprzez linie oznaczone symbolami IRQ 0 - IRQ 15.

0x01 graphic

Z każdą linią IRQ (ang. interrupt request) skojarzony jest wektor przerwania w tablicy wektorów (deskryptorów) przerwań. Skojarzenie to wykonywane poprzez odpowiednie zaprogramowanie układu 8259 — wykonuje to system operacyjny podczas inicjalizacji. Typowe przyporządkowanie stosowane w systemie DOS podane jest poniższej tabeli. W systemie Windows używane są deskryptory (wektory) 50H - 5FH, zaś w systemie Linux 20H - 2FH.

IRQ

Urządzenie

Nr wek-tora

IRQ

Urządzenie

Nr wek-tora

0

zegar systemowy, przerwanie wysyłane przez układ 8254 (w systemie DOS około 18 razy/s)

8

8

zegar czasu rzeczywistego, przerwanie generowane ustalonym czasie (budzenie)

112

1

klawiatura, przerwanie wysyłane po naciśnięciu lub zwolnieniu klawisza

9

9

113

2

połaczone z drugim układem 8259

10

114

3

łącze szeregowe COM2

11

11

115

4

łącze szeregowe COM1

12

12

116

5

łącze równoległe LPT2

13

13

koprocesor arytmetyczny

117

6

sterownik dyskietek

14

14

sterownik dysku twardego

118

7

łącze równoległe LPT1

15

15

119

Zatem, nadejście sygnału IRQ, np. IRQ 1 powoduje przerwanie i uruchomienie podprogramu obsługi przerwania, którego adres znajduje się w wektorze: 9 (DOS), 51H (Windows), 21H (Linux).

Współadresowalne układy wejścia/wyjścia

Typowym przykładem wykorzystania techniki układów współadresowalnych jest pamięć ekranu w komputerach PC. W trybie tekstowym sterownika graficznego znaki wyświetlane na ekranie stanowią odwzorowanie zawartości obszaru pamięci od adresu fizycznego B8000H. Pamięć ta należy do przestrzeni adresowej procesora, ale zainstalowana jest na karcie sterownika graficznego.

0x01 graphic

Każdy znak wyświetlany na ekranie jest opisywany przez dwa bajty w pamięci ekranu: bajt o adresie parzystym zawiera kod ASCII znaku, natomiast następny bajt zawiera opis sposobu wyświetlania, nazywany atrybutem znaku. Kolejne bajty omawianego obszaru odwzorowywane są w znaki na ekranie począwszy od pierwszego wiersza od lewej do prawej, potem drugiego wiersza, itd. tak jak przy czytaniu zwykłego tekstu.

Przykład obsługi przerwania zegarowego

W celu zilustrowania mechanizmu przerwań rozpatrzymy przykład obsługi przerwania zegarowego, które w systemi Windows/DOS generowane jest co ok. 55 ms. Podany niżej program wyświetla znaki * w takt przerwań zegarowych. Naciśnięcie dowolnego klawisza powoduje zakończenie wykonywania programu. W podanej wersji programu po wyświetleniu gwiazdki sterowanie przekazywane jest do oryginalnej procedury BIOSu (która obsługuje przerwanie zegarowe).

.386

rozkazy SEGMENT use16

ASSUME CS:rozkazy

obsluga_zegara PROC ; procedura obsługi przerwań zegarowych

push ax ; przechowanie używanych rejestrów

push es

push bx

mov ax,0b800h ;adres pamięci ekranu

mov es,ax

mov al,'*'

mov bx,cs:licznik ;załadowanie adresu kolejnego znaku

mov es:[bx],al ;wyświetlenie '*'

mov al,7 ;kolor biały na czarnym tle

mov es:[bx+1],al

add bx,2

cmp bx,4000 ;sprawdzenie czy cały ekran

jb wysw_dalej ;skok gdy koniec ekranu

mov bx,0 ;wyzerowanie gdy ekran zapisany

wysw_dalej:

mov cs:licznik,bx ;zapisanie rejestru bx w zmiennej licznik

pop bx

pop es

pop ax

jmp dword PTR cs:wektor8

; dane programu ze względu na specyfikę obsługi przerwań umieszczone

; są w segmencie kodu

licznik dw 320

wektor8 dd 0

obsluga_zegara ENDP

zacznij:

mov al,0 ; ustalenie strony graficznej nr 0

mov ah,5

int 10h

mov ax, 0

mov ds,ax

mov eax,ds:[32] ;odczytanie wektora 8 do eax

mov cs:wektor8,eax ;odesłanie oryginalnego adresu

; procedury obsługi przerwania

; wpisanie do wektora 8 adresu własnej procedury

mov ax,seg obsluga_zegara

mov bx, offset obsluga_zegara

cli ;zablokowanie przerwań

mov ds:[32],bx ;zapisanie adresu naszej procedury

mov ds:[34],ax

sti ;odblokowanie przerwań

petla_oczekiwania:

mov ah,1 ;oczekiwanie na naciśniecie klawisza

int 16h

jz petla_oczekiwania

; odtworzenie wektora 8

mov eax,CS:wektor8

cli

mov ds:[32],eax ;odesłanie wektora 8

sti

mov al,0

; zakończenie programu

mov ah,4ch

int 21h

rozkazy ENDS

nasz_stos SEGMENT stack

db 128 dup (?)

nasz_stos ENDS

end zacznij

32

0x01 graphic

0x01 graphic



Wyszukiwarka

Podobne podstrony:
AKO lab2012 cw4 id 53975 Nieznany (2)
AKO lab2010 cw4, Studia - informatyka, materialy, Architektura komputerów
AKO Lab2007 cw1 3 (2)
cw4 Zespół Klinefeltera
OS gr03 cw4 id 340946 Nieznany
cw4 badanie drgan skretnych
fizyka lab20090221 00006
crossgosp, Skrypty, UR - materiały ze studiów, studia, studia, Bastek, Studia, Rok 3, SEMESTR V, Woi
postępowanie cywilne-ćw4, pomoce naukowe ;), Postępowanie cywilne
PTK cw4, WAT, SEMESTR II, PTK
ćw4 8 11
cw4 protokol
cw4 telex cz1 id 123468 Nieznany
cw4
inventor cw4 zespol
Cw4 tow
CW4 doc

więcej podobnych podstron