MINI KURS PISANIA PROGRAMŁW TSR W ASEMBLERZE
5. WYWOśYWANIE PRZERWAĄ DOSOWYCH W CZASIE PRACY TSR'A
Piąty odcinek kursu pisania TSRów będzie poświęcony problemom
wywoływania przerwań DOSa w trakcie działania rezydenta i sposobom radzenia
sobie z tymi trudnościami. Otóż zacznijmy od tego, że w naszym rezydencie
(nazwijmy go roboczo: "Grabber") przechwyciliśmy przerwanie klawiatury i
chcemy, aby się uaktywnił po naciśnięciu przez użytkownika kombinacji
klawiszy LewyShift+LewyCtrl+Delete, po czym zapisał do zbioru w katalogu
C:\TEMP zawartość ekranu trybu graficznego 13h. Jest to tryb o rozdzielczości
320x200 w 256 kolorach, w którym od początku segmentu A000h zapisane są w
kolejnych bajtach kolory punktów najwyższej linii ekranu (poczynając od lewej
strony), od adresu A000h:320 kolory punktów w drugiej linii itd. W ten sposób
otrzymujemy 320x200 = 64000 bajtów do zapisania w zbiorze. Do tego należy
doliczyć 768 bajtów na paletę kolorów (768=3*256, mamy 256 kolorów, każdy o
składowych: czerwonej, zielonej i niebieskiej). Aby nasze zbiory nie były
"oderwane" od rzeczywistego świata, będziemy je zapisywać w formacie .BMP -
dokładając na początku zbioru stały nagłówek (ponieważ za każdym razem
zapisujemy ekran o tej samej wielkości i liczbie kolorów) oraz zgodnie z
konwencją zapisu plików .BMP - będziemy zapisywali kolejne linie od
najniższej do najwyższej (czyli w kolejności odwrotnej, niż ich położenie w
pamięci ekranu). Kolejne pliki będą otrzymywały nazwy OBRAZ000.BMP,
OBRAZ001.BMP i tak dalej.
Tutaj zaczynają się nasze problemy - nie możemy tak poprostu bezkarnie
utworzyć nowego pliku w katalogu C:\TEMP, zapisać do niego nasze dane, po
czym go zamknąć. W momencie naciśnięcia kombinacji klawiszy uaktywniającej
naszego rezydenta będzie przecież wykonywany inny program, który może w tej
chwili sam zapisywać jakieś dane. Wtedy DOSowi zrobi się "mętlik w głowie",
co doprowadzi w najlepszym przypadku do zawieszenia komputera, a możemy też
uszkodzić system plików lub dokonać TSRem czegoś bardziej okrutnego. I
właśnie o to chodzi, aby ominąć moment, w którym inny program korzysta z
usług dosowych. Z pomocą w tej sytuacji przyjdą nam mechanizmy udostępniane
przez sam system operacyjny, a mianowicie flaga INDOS - jeden bajt pamięci,
który informuje nas, czy właśnie w tej chwili jest wykonywana jakaś funkcja
DOSa. Adres flagi INDOS możemy uzyskać poprzez odwołanie do następującej
funkcji przerwania 21h:
Nazwa: Pytanie o adres sygnalizatora pracy systemu
Wywołanie: AH=34h
Powrót: ES:BX - adres sygnalizatora pracy systemu
Opis: Funkcja zwraca adres sygnalizatora pracy systemu.
Sygnalizator ten jest ustawiony (różny od zera), gdy system
wykonuje jakąś czynność, której nie należy mu przerywać.
Sygnalizator ten jest często używany przez programy TSR,
które sprawdzają, czy mogą się uaktywnić. Sygnalizator ten
jest również ustawiony podczas czekania przez system na
naciśnięcie klawisza. W takim wypadku jest wywoływane
przerwanie 28h, które TSR może przechwycić i również w ten
sposób się uaktywniać.
Przy okazji poznaliśmy kolejny ważny aspekt programowania TSRów - pomimo
że jest wykonywane przerwanie DOSa, które oczekuje na wciśnięcie klawisza,
nie robiąc prócz tego nic pożytecznego, flaga INDOS jest zapalona. Ten fakt
jednakże możemy wykryć poprzez sprawdzenie, czy DOS wywołuje w tym czasie
przerwanie 28h (tzw. przerwanie Idle). Robimy to poprzez przechwycenie tego
przerwania i podstawienia w jego miejsce swojej własnej procedury. Kiedy
użytkownik naciśnie odpowiednią kombinację klawiszy, sprawdzamy, czy DOS jest
w tej chwili wolny - flaga INDOS=0. W przeciwnym wypadku musimy dokonać
sprawdzenia, czy jest wywoływane przerwanie 28h (w naszej procedurze obsługi
tego przerwania zapalamy odpowiednią flagę aktywności). Jeżeli nie jest ono
wywoływane, a DOS jest zajęty - nie możemy w tej chwili nic zrobić. Wtedy
mamy kilka możliwości rozwiązania tego problemu, jak np. przepisanie
zawartości ekranu (wraz z paletą) do innego bloku pamięci, który
zarezerwowaliśmy przy instalacji, a przy najbliższej okazji zapisanie tego
bloku na dysk (tutaj okazało by się pomocne przechwycenie również przerwania
zegara - INT 08h - które będzie nam dostarczało tą "najbliższą okazję"
około 18 razy na sekundę). Jednakże kto by chciał używać TSRa, który przy
instalacji zabiera nam ponad 64000 bajtów cennej pamięci ? Drugim
rozwiązaniem jest zaalokowanie pośredniego bloku w pamięci XMS lub EMS - ale
na to przyjdzie czas w kolejnych odcinkach tego cyklu. My w naszym rezydencie
wykorzystamy trzecią możliwość - po prostu nic nie zrobimy, wydając tylko
krótki dŚwięk z głośnika informujący o naszej bezradności. I jeszcze jedna
uwaga - gdy DOS czeka na naciśnięcie klawisza wywołując co chwilę przerwanie
28h, a my z tego skorzystamy, nie możemy po uaktywnieniu rezydenta korzystać
z przerwań dosowych o numerach od 00h do 0Ch.
No to mamy już ogólny zarys działania naszego TSRa: w procedurze obsługi
przerwania klawiatury sprawdzamy, czy naciśnięto kombinację klawiszy
LShift+LCtrl+Delete, a gdy miało to miejsce, przekazujemy do sterownika
klawiatury potwierdzenie odebrania znaku i w odblokowujemy kontroler przerwań
(jest to szczegółowo opisane w 4. odcinku tego kursu), po czym ustawiamy
naszą wewnętrzną flagę aktywności i włączamy przerwania instrukcją: "sti".
Kiedy teraz użytkownik znowu naciśnie tą kombinację klawiszy, a my jeszcze
nie skończyliśmy obsługi poprzedniego naciśnięcia (czyli gdy nasza wewnętrzna
flaga aktywności jest zapalona) - wtedy po prostu wychodzimy z przerwania.
Dalej należy sprawdzić flagę INDOS - gdy jest zapalona to dajemy sygnał
dŚwiękowy informujący o naszej bezradności i również wychodzimy z przerwania,
nie zapominając o zgaszeniu naszej wewnętrznej flagi aktywności. W końcu gdy
wszystko się powiodło - przystępujemy do rzeczy. Tworzymy nowy zbiór w
katalogu C:\TEMP (lub innym, każdy może wstawić sobie w kod Śródłowy to, co
chce), zapisujemy do tego zbioru stały nagłówek, czytamy paletę kolorów karty
VGA do naszego obszaru roboczego o wielkości 768 bajtów, zapisujemy ją do
pliku, dalej nagrywamy kolejne linie obrazu poczynając od najniższej (o
adresie 0A000h:0F8C0h) aż do najwyższej (o adresie 0A000h:0), zmniejszając
offset nagrywanego bloku pamięci za każdym razem o 320 bajtów (długość jednej
linii). Potem tylko zamykamy plik, gasimy wewnętrzną flagę aktywności i
powracamy z przerwania. Cały kod tej operacji wstawimy w naszego gotowego
rezydenta, korzystającego z przerwania 2Fh (Multiplex Interrupt), opisywanego
w poprzednim odcinku cyklu, pomijając tylko chwilowo nam niepotrzebną część
służącą do dezaktywowania TSRa bez usuwania go z pamięci. Nasz rezydent
będzie "wrażliwy" na numer procesu 91h podawany przy wywoływaniu przerwania
2Fh.
Teraz czas na kilka zagadnień nie dotyczących bezpośrednio programów
rezydentnych, ale bardzo nam przydatnych. Otóż musimy wiedzieć po pierwsze, w
jaki sposób sprawdzić, czy karta graficzna jest w trybie 13h. Możemy tego
dokonać wywołując bezpośrednio podfunkcję 0Fh przerwania video - INT 10h:
Nazwa: Pytanie o aktualny tryb wyświetlania
Wywołanie: AH=0Fh
Powrót: AL - tryb pracy
AH - liczba znaków w wierszu
BH - numer aktywnej strony
Jednakże możemy odczytać numer trybu również bez użycia przerwań -
szybciej i bezpieczniej (ten sam problem, co z przerwaniem dosowym - co
będzie, gdy akurat w tym momencie główny program odwołał się do przerwania
video ? Rozwiązanie problemu byłoby bardziej skomplikowane), odczytując
bezpośrednio odpowiednią wartość z obszaru zmiennych BIOSu, zawartość bajtu
spod adresu 0040h:0049h (czyli 0:0449h) również jest numerem aktualnego trybu
pracy karty graficznej. Kolejne zagadnienie to odczytanie palety kolorów
karty VGA. W przestrzeni adresowej wejścia/wyjścia (I/O) całego komputera są
wydzielone porty, z których korzysta karta VGA. Mają one adresy od 3C0h do
3DFh. Aby odczytać składowe RGB jednego koloru, należy do portu 3C7h wysłać
bajt z numerem koloru (0..255), a następnie z portu 3C9h odczytać po kolei 3
bajty ze składowymi: czerwoną, zieloną i niebieską. Licznik koloru jest
automatycznie zwiększany o 1, możemy potem od razu odczytać składowe
kolejnego koloru, już bez wpisywania jego numeru do portu 3C7h. Najszybciej
można odczytać całą paletę kolorów pod adres w ES:DI przy pomocy
następujących instrukcji:
xor al,al ; AL=0
mov dx,3c7h
out dx,al
mov dl,0c9h
mov cx,768 ; odczytujemy 256*3 = 768 bajtów
cld
rep insb ; z portu DX odczytaj kolejno CX bajtów i umieść pod ES:DI
Na nasze nieszczęście paleta jest zapisywana w zbiorach .BMP w bardzo
przedziwny sposób - każdy kolor zajmuje w niej nie 3, ale 4 bajty - i to w
kolejności: niebieski, zielony, czerwony, a 4. bajt jest równy zero. Do tego
jeszcze karta VGA zwraca nam składowe kolorów z zakresu 0..63, a w pliku .BMP
są zapisywane składowe z zakresu 0..255. Musimy to wszystko uwzględnić przy
budowie naszego rezydenta - konkretne rozwiązanie znajdziecie w kodzie
Śródłowym dołączonym do tego odcinka.
Aby przy bezradności naszego rezydenta (kiedy nie możemy wykorzystywać
przerwań DOSa) wydać sygnał dŚwiękowy nie za długi i nie za krótki,
posiłkujemy się odczytem zmiennej BIOSa zawierającą ilość taktów zegara,
zwiększanej w każdym przerwaniu zegarowym (INT 08h), czyli co około 55 ms
(18.2 raza na sekundę). Po prostu włączymy dŚwięk, odczytamy jej zawartość,
poczekamy, aż ulegnie zmianie o np. 2, po czym wyłączymy dŚwięk. Sposób
prosty i skuteczny. Należy tylko pamiętać o włączeniu przerwań już wcześniej,
aby została wykonana procedura obsługi zegara zwiększająca licznik. No i
najważniejsze: licznik mieści się w pamięci od adresu 0:046Ch i zajmuje 4
bajty, w kolejności od najmłodszego do najstarszego. W naszym przypadku
wystarczy sprawdzić, czy się zmienił ten najmniej znaczący (czyli pod
adresem, który podałem wyżej).
Operacje na plikach wykonujemy korzystając z usług dobrze już nam
znanego przerwania DOSu - 21h. Przy otwieraniu lub tworzeniu plik
identyfikowany jest przez nazwę zapisaną w ASCIIZ, natomiast przy następnych
odwołaniach do już otwartego zbioru (przy zapisywaniu do niego danych,
zamykaniu go) wykorzystujemy tzw. file handle (uchwyt, dojście), czyli liczbę
16-bitową określającą nam w sposób jednoznaczny, z jakim wcześniej otwieranym
plikiem mamy do czynienia. Oto opisy funkcji dosowych, które nam się
przydadzą:
Nazwa: Tworzenie dojścia
Wywołanie: AH=3Ch
DS:DX - adres łańcucha w kodzie ASCIIZ zawierającego nazwę
pliku
CX - atrybuty pliku
Powrót: Ustawiony znacznik C: AX - kod błędu
Nie ustawiony C: AX - numer dojścia
Opis: Funkcja tworzy plik o podanej nazwie, równocześnie definiując
doń dojście z uprawnieniami do czytania i pisania w pliku.
Nowy plik ma zerową długość i atrybuty przekazane w rejestrze
CX. Jeśli plik o podanej nazwie już instnieje to zostaje
zwolniona pamięć dyskowa mu przydzielona, nadana długość 0,
ustalone nowe atrybuty i przyporządkowane dojście z uprawn.
do czytania i pisania.
Wyjaśnienia wymaga zawartość rejestru CX ustawianego przed wywołaniem
funkcji 3Ch. Atrybuty pliku są reprezentowane przez kolejne bity w dolnej
połówce rejestru CX (czyli w CL), górną połówkę (CH) wypełniamy zerami:
bit: 7 6 5 4 3 2 1 0 r - Read Only
- - a d v s h r h - Hidden
s - System
v - Volume ID
d - Directory
a - archive
Widać, że przy pomocy tej funkcji możemy również utworzyć nowy katalog,
zapalając w CL czwarty bit, jednakże jeżeli już istnieje taki katalog, nie
ulegnie automatycznemu skasowaniu, inaczej niż to się dzieje w przypadku
plików. W naszym rezydencie nowo tworzonym plikom będziemy nadawać tylko
atrybut Archive - czyli do rejestru CX wpisywać wartość 0020h. Po utworzeniu
pliku będziemy zwiększać jego numer - 3 ostatnie cyfry nazwy stanowią
licznik. Zapisu danych do otwartego pliku dokonujemy przy pomocy funkcji 40h:
Nazwa: Pisanie przez dojście
Wywołanie: AH=40h
BX - numer dojścia
CX - liczba bajtów do zapisania
DS:DX - adres bufora
Powrót: Ustawiony znacznik C: AX - kod błędu
Nie ustawiony C: AX - liczba zapisanych bajtów
Opis: Funkcja zapisuje do pliku lub urządzenia związanego z
dojściem, którego numer jest przekazany w rejestrze BX bajty
znajdujące się w buforze, którego adres zawiera DS:DX. Liczba
bajtów do zapisania jest przekazywana w rejestrze CX. Po
zapisie wewnętrzny wskaŚnik pozycjipliku jest przesuwany tak,
aby wskazywał na bajt następny po ostatnio zapisanym. W ten
sposób możliwe jest sekwencyjne zapisywanie w pliku.
Wywołanie tej funkcji z zawartością CX równą 0 powoduje
zmianę wielkości pliku na taką, jaką aktualnie wskazuje
wskaŚnik pozycji.
Nazwa: Zamykanie dojścia
Wywołanie: AH=3Eh
BX - numer dojścia
Powrót: Ustawiony znacznik C: AX - kod błędu
Nie ustawiony C: OK.
Opis: Funkcja zamyka dojście o numerze przekazanym w AX i czyści
wszystkie bufory związane z plikiem.
No to właściwie posiadamy już całą wiedzę potrzebną do napisania
rezydenta, którym będziemy zrzucali ekran karty VGA do pliku .BMP, należy
tylko dodać, że ta metoda będzie dawała dobre rezultaty tylko w przypadku
programów korzystających z "czystego" trybu 13h - 320x200 w 256 kolorach, bez
żadnych "upiększeń" w stylu Xmode (podnoszenie rozdzielczości na standardowej
karcie VGA poprzez zmianę trybu adresowania), z czego intensywnie korzysta
większość programów demonstracyjnych i część gier. Nasze eksperymenty również
nie powiodą się, gdy program przechwytuje przerwanie klawiatury i nie zwraca
sterowanie do oryginalnej procedury obsługi. Wtedy możemy zainstalować
rezydenta w przerwaniu zegara (INT 08h) i tam sprawdzać, czy ostatnio
wciskanym klawiszem był Delete, jak również uaktualniać flagi stanu klawiszy
kontrolnych na podstawie informacji o wciśnięciach/puszczeniach Alt, Ctrl i
Shift. Ale to już będzie tematem innego odcinka. Podobnie ma się sprawa przy
naszym uproszczeniu - w przykładowym rezydencie nie sprawdzamy, czy jest
wywoływane przerwanie 28h, po stwierdzeniu zajętości DOSu (flaga INDOS<>0)
tylko dajemy dŚwięk naszej bezradności. Można też po prostu wykomentować lub
usunąć zaznaczone w kodzie linie - flaga INDOS nie będzie w ogóle sprawdzana.
To chyba już wszystko na dziś, przykładowy program jest działający i
sprawdzony tylko dla kilku programów, nie działająca reszta zawiera się w
przypadkach opisanych powyżej. Powodzenia w samodzielnym eksperymentowaniu.
Wyszukiwarka
Podobne podstrony:
kurs5 (2)Asembler w TSR KURS5kurs5więcej podobnych podstron