Programowanie w języku Pascal

Podstawy programowania na przerwaniach

  • 2007-09-05 16:21
  • 0 komentarzy
  • 5735 odsłon
  • Oceń ten tekst jako pierwszy
Co to jest przerwanie? Najogólniej mówiąc jest to procedura obsługi
jakiegoś zdarzenia - sprzętowego, np.: ruchu myszą, wciśnięcia klawisza,
zegara RTC, bądź programowego: sprawdzenia czy klawisz został naciśnięty,
włączenia jakiegoś trybu graficznego, odczyt/zapis z dysku itp.
Zasadnicza różnica polega na tym, czy przerwania zażądał sprzęt, czy wywołało
je oprogramowanie.
My zajmiemy się oczywiście tym drugim przypadkiem (jak ktoś chce zobaczyć
przykład działania przerwań sprzętowych, niech walnie młotkiem w napęd
CD i spróbuje coś odczytać; uwaga: nie polecam tej metody).

Przerwań jest 256, ich adresy są umieszczone w pamięci pod adresem 0:0
w tablicy o rozmiarze 1024 bajtów (1 kB). każdy adres to cztery bajty:
dwa na segment i dwa na offest adresu - czyli innymi słowy typ pointer.

Przerwanie wywołuje się pod assemblerem instrukcją int z zapodanym numerem,
wszelakie parametry przekazuje się w rejestrach (umieszczając w ax odpowiedni
numer wywołuje się jedną z funkcji danego prerwania); często zachodzi potrzeba
użycia rejestru ds, wtedy wrzuca się go na stos, a po wykonaniu przerwania
zdejmuje (instrukcje push i pop).

OK, to teraz konkrety. Na początek bardzo ogólny spis przerwań. Zainteresowanych
odsyłam do pliku int.tph [nie znam nowego adresu pliku, więc usunąłem linka - Vogel], gdzie znajduje
się spis praktycznie wszystkich przerwań i ich funkcji. Literka "h" po liczbie
oznacza zapis heksadecymalny, czyli po polskiemu szesnastkowy ;-) (ostatnia cyfra+
przedostatnia*16+przed_przedostatnia*256 itp, gdzi A=10, F=15).

# przerwaniaOpis
00hBłąd dzielenia przez zero
10hBIOS: karta graficzna:
ah=0: ustawianie rozdzielczości i trybu (np.: al=03h - tryb tekstowy 80x25,16 kolorów; al=13h - tryb graficzny 320x200, 256 kolorów); ax=4F02h: ustawianie rozdzielczości SVGA (np.: bx=101h - 640x480x8; bx=103h - 800x600x8; bx=10Fh - 320x200x24)
13hBIOS: twardy dysk - bezpośredni odczyt, zapis i formatowanie sektorów
16hBIOS: klawiatura: ax=00h - czeka na naciśnięcie klawisza, zwraca w ax jego kod i czyści bufor (readkey); ax=01h - zwraca w ax kod naciśniętego klawisza (keypressed); ax=10h - jak ax=0, tylko zwraca kod rozszerzony (wraz z F11, F12 itp); ax=11h - jak ax=1, tylko dla kodu rozszerzonego.
19hreset systemu; pod windowsem natomiast błyskawicznie zamyka program
1AhBIOS: czas systemowy - pobieranie i ustawianie
1BhPrzerwanie wywoływane przez naciśnięcie Ctrl+Break
1ChPrzerwanie wywoływane co 1/18 sekundy
21h
DOS: przerywanie działania programu, procedury związane z obsługą systemu plików (odczyt, zapis, zmiana nazw, zakładanie i kasowanie plików i katalogów itp.), wypisywanie łańcuchów znaków, czas, ładowanie programów rezydentnych, obsługa pamięci niskiej, obsługa sieci, długie nazwy plików i katalogów...</td></tr>23h</td>Przerwanie wywoływane przez naciśnięcie Ctrl+C</td></tr>25h</td>DOS: odczyt z partycji - bez podziału na Cylinder/Head/Sector</td></tr>26h</td>zapis j.w.</td></tr>28h</td>DOS: wektor wywoływany gdy DOS się nudzi (nie jest wykonywany żaden program poza COMMAND.COM)</td></tr>2Fh</td>DOS Multiplex - zestaw bardzo wielu funkcji robiących niemal wszystko (nie licząc wiązania krawata i robienia jajecznicy). Właśnie to przerwanie będzie tutaj opisywane</td></tr>31h</td>DPMI: obługa trybu chronionego</td></tr>33h</td>Mysz: pokazywanie/ukrywanie, pobieranie zdarzeń, położenia itp.</td></tr>4Bh</td>DMA: operacje na pamięci bez udziału procesora</td></tr>67h</td>Obsługa pamięci EMS (więcej w tym artykule)</td></tr>...</td>...</td></tr></table>

..itp itd.

Małe wyjaśnienie na dzień dobry: moduł dos udostępnia coś takiego jak typ
TRegisters oraz procedury Intr i MsDos; są one jednak tragicznie powolne w
porównaniu z gołym assemblerem, ponadto dorzucają dodatkowy kod do pliku exe,
czyli w zasadzie niepotrzebnie zwiększają jego rozmiar.

Jeśli jednak przeraża Cię assembler, możesz posługiwać się typem TRegisters,
w sumie chodzi o to, żeby coś zrobić i żeby ewentualnie było to szybkie,
nie odwrotnie. Po co komu szybki program, który nic nie robi, bo jego autor
zrezygnował z połowy jego funkcji na rzecz szybkości?

Więc zacznijmy od czegoś miłego i łatwego z cyklu int2Fh:


function DOS7 : boolean; assembler;
asmend;


W wyniku funkcja zwróci true jeśli masz DOSa w wersji conajmniej 7.0, co oznacza,
że masz zainstalowanego minimum Win95. Jak zauważyłeś, funkcja ma w nagłówku dyrektywe assembler - oznacza to, że w nim jest cała zrobiona i jako taka nie potrzebuje
kilku dodatków niezbędnych zwykłym procedurom. Dzięki temu wywołuje się szybciej.

Jeśli funkcja zwraca coś o wielkości bajta lub worda, to jest to wartość umieszczona w rejestrze ax.

To skoro potrafimy sprawdzić wersję DOSu, to pora na wersję Windowsa. Z pomocą
znowu przychodzi multipleks 2Fh. Przy okazji można sprawdzić, czy program dosowy
został uruchomiony z Windowsem działającym w tle (uwaga, we właściwościach skrótu
do dosowego pliku exe można ustawić wyłączenie tej funkcji -
właściwości->program->zaawansowane->zapobiegaj wykrywaniu Windows... )


function WinVer : word;  assembler;

asmend;


Proste, prawda? BTW: Multipleks 2Fh do społu z 21h obsługują 99% wszelkich
odwołań systemowych (tzn. takich, gdzie poprzez przerwanie wywoływana jest
funkcja - coś jak wywoływanie funkcji po indeksie z bibliotek dll - ale to
tak nawiasem mówiąc ;)).

Teraz kolej na procesor (niechcący przerwanie 15h, ale to wyjątek który potwierdzi regułę):


Function CPUVer : byte; assembler;
asmend;




Aha - ta funkcja oszukuje, tzn. na procesorach powyżej 486 nie zwraca prawidłowych wartości.

Sprawdźmy jeszcze, co tam u myszy słychać - zwierzę to siedzi na przerwaniu 33h,
niezależnie od fizycznego podłączenia myszy (tzn. jest to przerwanie sterownika,
a nie samej myszy).


function MouseVersion : word; assembler;
asmend;


Zero - mysz nie podłączona, każdy inny wynik zwraca wersję sterownika.
Dotatkowo można pokusić się o doklejenie kodu, który będzie wykorzystywać
poniższe informacje:
  CH = typ: 1=bus, 2=serial, 3=InPort, 4=PS/2, 5=HP;

  CL = przerwanie: 0=PS/2, 2=IRQ2, 3=IRQ3, ..., 7=IRQ7;

albo zrobienie oddzielnych funkcji.
To może teraz konsekwentnie więcej o myszy? Jedziemy!

Najpierw pobieranie i zmienianie ustawień tego zwierzaczka.


procedure GetMouseSenst(var h,v,d : word); assembler;
asmend;


Pierwszy parametr zwraca przyspieszenie wskaźnika w poziomie, drugi w pionie,
a trzeci odstęp czasu w milisekundach który musi upłynąć, aby dwa kliknięcia
nie były uważane za podwójne kliknięcie. Procedura jest trochę połatana (trzy
instrukcje les), ale działa znakomicie.

Teraz przydałoby się ustawić jakieś zmiany. Oto Wielce Skomplikowana I Jakże
Trudna Procedura Do Robienia Zmian.


procedure SetMouseSenst(h,v,d : word); assembler;
asmend;


To tyle o myszy. W następnym odcinku pomęczymy przewanie 21h (tzw. DOS multiplex).

Następny odcinek ;-)

Oto jest w DOSie taka funkcja o bardzo długiej nazwie, służąca do odczytywania
danych z tablicy partycji dysków. Aż prosi się o to, żeby z niej skorzystać - więc
skorzystamy. Najpierw definicja typu:


PDPBRec = ^DPBRec;
DPBRec  = record
  num               : byte;  
  unit_nun          : byte;  
  bytes_per_sector  : word;  
  highsec           : byte;  
  reserved1         : byte;  
  reserv_sectors    : word;  
  num_of_FATs       : byte;  
  root_entries      : word;  
  first_user_sector : word;  
  highest_cluster   : word;  
  sectors_per_FAT   : byte;  
  first_dir_sector  : word;  
  device_driver     : pointer;  
  media_byte        : byte;  
  access            : byte;  
  next_DPB          : PDPBRec;  
  writing_clust     : word;  
  free_clusters     : word; 
end;


Jak widzisz jest tu mnóstwo ciekawych rzeczy, co prawda dotyczących fatu 16,
ale dobre i to na start. Numery tabel, podane w komentarzach, odnoszą się do
pliku pomocy int.tph.

Zapewne przyda się procedura, która wypełni ten rekord odpowiednimi danymi?...
I oto jest; zwraca true dla pomyślnie wykonanej operacji.


function GetDPB(num : byte;var data : PDPBRec) : boolean; assembler;
asmend;


To może teraz coś z innej bajki (wracamy do przerwania 2Fh) - pobieranie
i ustawianie strony kodowej (oczywiście DOSowej):


procedure SET_ACTIVE_CODE_PAGE(page : word); assembler;
asmend;

function GET_ACTIVE_CODE_PAGE : word; assembler;
asmend;


I znowu inna bajka: parkowanie dysków (tzn. ustawianie głowic w miejscach,
gdzie nie uszkodzą dysku przy jego potrząsaniu).


function ParkDisk(num : byte) : boolean; assembler;
asmend;


Te i dużo innych funkcji znajduje się w module int2f, który możesz ściągnąć
tutaj. Plik pomocy Turbo Pascala, na
podstawie którego powstał ninijeszy artykuł, jest tu.

Z tych przerwań które zostały, ważnym i dość często używanym, choć przeważnie
nie bezpośrednio, jest 10h. Rzut oka na tabelę wyżej i już wiesz, że chodzi o
kartę graficzną. Dokładnie. Więc teraz szybki kurs programowania w trybie 13h
(rozdzielczość 320x200, 256 kolorów) - tzw. tryb X.

Najpierw włączanie i wyłączanie trybu graficznego: zapodajesz w rejestrze al numer
trybu, ustawiasz ah = 0 i wywołujesz przerwanie 10h. Czyli:


procedure SetVGAMode(mode : byte); assembler;
asmend;


Przerwanie 10h lubi jednak czasem psuć kilka potrzebnych rejestrów,
więc do powyższej procedury jest potrzebna mała modyfikacja:


procedure Int10h; assembler;
asmend;

procedure SetVGAMode(mode : byte); assembler;
asmend;


I już nic się nie popsuje.

Więc teraz kombinujesz co tam podać jako parametr? Więc dwie podstawowe wartości:
03h to tryb tekstowy 80x25x16 kolorów, 13h (19d) to już wspomniany tryb graficzny X:
320x200x8bpp. Jak się bazgrze po ekranie w takim trybie?

Otóż 320*200 = 64000 bajtów. Jeden segment - w sam raz na możliwości TP.
Pamięć trybu graficznego jest umieszczona pod adresem 0A000h:0. Na tej podstawie
możesz sobie zrobić procedurki putpixel i getpixel:


procedure PutPixel(x,y,color : byte);
begin
  mem[:word(y)*+word(x)] := color
end;

function GetPixel(x,y,color : byte) : byte;
begin
  GetPixel := mem[:word(y)*+word(x)]
end;


albo w wersji assemblerowej:


procedure PutPixel(x,y,color : byte); assembler;
asmend;

function GetPixel(x,y : byte) : byte; assembler;
asmend;


W sumie prosta sprawa. Ale można tu robić takie sztuczki, że głowa mała.

Zapraszam do ściągnięcia biblioteki xvga,
w której jest gigantyczna ilość procedur do obróbki grafiki trójwymiarowej
wyświetlanej właśnie w trybie X.

Z przerwania 10h można wyciągnąć znacznie więcej niż tylko tryb 13h. Spójrz
na tą tabelkę. Sporo tego jest... Ale nie
wszsytko jest do osiągnięcia na Twojej karcie graficznej; jeśli chcesz się
przekonać co konkretnie możesz uzyskać u siebie, ściągnij ten programik.

Co jeszcze można wyczarować na przerwaniach? Ano - może być obsługa klawiatury.
Wreszcie nie trzeba będzie korzystać z modułu crt... Popatrzmy: klawiatura wisi
na przerwaniu 16h, można z tego skorzystać i zrobić własne readkey i keypressed.
Oto nadchodzi:


var
  ScanCode : byte;

function ReadKey : char; assembler;
asmend;

function KeyPressed : boolean; assembler;
asmend;


Obie funkcje są całkowicie kompatybilne z tymi z modułu crt, zwracają przy tym
wszystkie rozszerzone kody - F11, F12, Alt+(Spacja, kursory, Enter) itp.

A jak zrobić własną funkcję obsługującą przerwania? Więc po pierwsze: da się.
Po drugie: robisz zupełnie normalną procedurę (jedna rzecz jest wymagana: musi ona
być jak najszybsza, żeby nie blokowała komputera kiedy będzie zbyt często wywoływana,
lub musi byc zabezpieczona przed powielaniem samej siebie, tzn. wywołaniem przerwania
zanim skończy się obsługa wywołania poprzedniego). Więc tworzysz normalną procedurę,
jako parametry podajesz (Flags, CS, IP, AX, BX, CX, DX, SI, DI, DS, ES, BP: Word)
a nastepnie w nagłówku dodajesz słowo interrupt. Ustawiasz ją zamiast jakiegoś
przerwania procedurą SetIntVec z modułu dos. Ale uwaga! Musisz zapamiętać adres
procedury, która wcześniej obsługiwała dane przerwanie, i przy zakończeniu programu
przywrócić ją; inaczej wywołanie przerwania będzie trafiało w miejsce, w którym kiedyś
była Twoja procedura, a teraz mogą być śmiecie. Jak się zapewne domyślasz do pobrania
adresu kodu obsługującego przerwanie służy procedura GetIntVec.

Ponadto jeśli parametry nie należą do dziedziny obsługiwanej przez Twoją procedurę,
należy również wywołać jej poprzednika (którego to wcześniej na szczęście zachowałeś).
W ten sposób tworzy się łańcuszek procedur, każda sprawdza, czy np.: w ax jest
podany numer ich funkcji, a jeśli nie, to żądanie przerwania podają dalej.
Tak właśnie działają opisywane wyżej multipleksy 21h i 2Fh.

Jak wykorzystać powyższe informacje? Otóż możesz na przykład zablokować kombinację
klawiszy Ctrl-Break, która zamyka każdy standardowy program zrobiony pod TP.

Wystarczy oprogramować przewanie 1Bh. Prosta sprawa.


uses dos, crt;
var
  OldProc : pointer; 

procedure nic; interrupt; 
begin
end;

begin
  GetIntVec(,OldProc);
  SetIntVec(,@nic);

  repeat 
    
  until keypressed; 

  SetIntVec(,OldProc);
end.


W ten sam spoób możesz oprogramować przerwanie 1Ch, wywoływane 18.2 raza
na sekundę (65536 razy na godzinę), podklejając pod nie zegarek, a następnie
możesz zakończyć program rezydentnie. Co to takiego? Najprościej: kończysz program,
a on zostaje w pamięci i może sobie działać dalej, sterowany właśnie przerwaniami.
Taka pseudowielozadaniowość. Bez wywłaszczania ~;-)
Służy do tego procedura Keep, analogiczna do Halt. Tylko musisz uważać, żeby
Twój program załadowany rezydentnie nie zajmował całej dostępnej pamięci,
bo inaczej przerąbane - DOS się wykrzacza i o ile nie działasz pod Windozą
(a pewnie nie działasz, bo programy rezydentne pod Windą to totalna porażka,
po prostu nie mają sensu) system się wiesza. Pamięć przydzielasz dyrektywą
kompilatora $M albo w oknie Options->Memory sizes... Przydzielasz jej tak
mało, żeby tylko program się nie wywalał przy włączonej opcji Stack checking
(Options->Compiler). przy okazji zyskasz na tym, że program będzie o dwa grosze
szybszy (mniejszy stos do zarządzania).