Mini kurs pisania programów TSR w asemblerze
Mini kurs pisania programów TSR w asemblerze
Przerwania w programach TSR, pamięć i zegar CMOS
W poprzednich odcinkach kursu dowiedzieliśmy się, co to jest TSR i jak się go
instaluje w pamięci. Przyszedł czas na zaprzęgnięcie naszego rezydenta do
bardziej konkretnych zadań, dobrym przykładem niech będzie napisanie prostego
programu instalującego się w pamięci i pokazującego aktualną sekundę, taka
mała wprawka przed pełnym zegarem, który każdy z was będzie mógł spokojnie
sam napisać po przeczytaniu tego odcinka.
Co nam tym razem będzie potrzebne ? Oczywiście, przerwanie zegara, wykonywane
z częstotliwością 18.2 Hz (czyli około 18 razy na sekundę), a dokładnie:
1193181/65536 Hz. Możemy "przechwycić" to przerwanie, czyli podstawić swoją
własną procedurę, którą komputer będzie wywoływać ze wspomnianą
częstotliwością. W naszej procedurze będziemy pobierać z komputera aktualny
czas i wyświetlać liczbę sekund w lewym górnym rogu ekranu. Pojawia się tylko
pytanie - po co sprawdzać czas aż 18 razy na sekundę, jeżeli mamy
wyświetlać tylko sekundy, które się będą zmieniać co 18 przerwań ?
Najprostszym rozwiązaniem na oszczędzenie czasu procesora jest sprawdzanie
aktualnego czasu tylko co 18 wywołanie naszej procedury. Jednakże możemy
postąpić jeszcze inaczej - wyświetlać sekundnik na ekranie tylko wtedy, gdy
jego wskazanie jest różne od poprzedniego. To nam oszczędzi mocy procesora
traconej za przez każdą sekundę na wyświetlaniu tej samej liczby 18 razy.
My jednak w programie przykładowym zrezygnujemy z takiej optymalizacji, aby
nie zaciemniać kodu, każdy może to sam poćwiczyć. Jeszcze jedna dygresja - po
dokonaniu swoich działań nasza procedura musi zwracać sterowanie do
oryginalnej (czyli pod adres, który odczytamy w czasie instalowania się
naszego TSRa, dla skrócenia opisu nazywa się często ten adres "wektorem
przerwania").
Teraz opis dwóch przydatnych funkcji, które nam udostępnia DOS (czyli
przerwanie 21h):
Funkcja 25h
Nazwa: Ustalanie adresu kodu obsługi przerwania
Wywołanie: AH=25h
AL - numer przerwania
DS:DX - adres procedury obsługującej przerwanie
Powrót: Brak
Opis: Funkcja ustawia nową procedurę obsługi przerwania o numerze
podanym w AL. Adres procedury obsługi przerwania powinien być
przekazany w DS:DX.
Funkcja 35h
Nazwa: Pytanie o adres kodu obsługi przerwania
Wywołanie: AH=35h
AL - numer przerwania
Powrót: ES:BX - adres procedury obsługi przerwania
Opis: Funkcja zwraca adres procedury obsługi przerwania o numerze
podanym w AL.
Dobra, mamy już wiadomości o tym, jak przechwytywać przerwanie po
zapamiętaniu adresu oryginalnej procedury obsługi. Pytanie: no to które to
właściwie jest przerwanie zegarowe ? Otóż jest to przerwanie nr 8, czyli
IRQ0. Należy się jednak drobne wyjaśnienie: IRQ0 oznacza, że do kontrolera
przerwań (a są takie dwa układy na płycie głównej komputera) do linii nr 0
przychodzą informacje od układu zegarowego, który na tą linię wystawia sygnał
żądania przerwania właśnie 18 razy na sekundę. Podobnie do IRQ0 podłączona
jest klawiatura, IRQ5 często karta muzyczna i tak dalej. Numer przerwania
obsługującego linię IRQx to x+8, czyli przerwanie zegarowe ma numer 8,
przerwanie klawiatury - nr 9 i tak dalej. Drugim kontrolerem nie będziemy się
na razie zajmować, zaznaczę tylko, że obsługuje on przerwania IRQ8 do IRQ15,
a numery przerwań od drugiego kontrolera zaczynają się dla zmyłki od 40h.
Kolejna sprawa: jak odczytać aktualną sekundę ? Jest kilka sposobów, my
skorzystamy z bezpośredniego dostępu do zegara CMOS umieszczonego na płycie
głównej komputera. Jest on widziany w przestrzeni adresowej jako dwa kolejne
porty: o numerze 70h oraz 71h, dostępne dla programisty poprzez instrukcje:
out i in. Instrukcja 'out' służy do wysyłania danych do portu, instrukcja
'in' do czytania z portu. W naszym przypadku będą to instrukcje: out 70h,al
oraz in al,71h. Pierwszą z nich wyślemy do zegara CMOS numer komórki, która
nas interesuje (o tym dalej), a drugą odczytamy jej zawartość. Cały fragment
kodu czytający aktualną sekundę będzie w związku z tym wyglądał tak:
xor al,al
out 70h,al
jmp $+2
in al,71h
Instrukcja jmp $+2 powoduje drobne opóźnienie wymagane do poprawnej
współpracy z zegarem CMOS, natomiast xor al,al jest równoważne mov al,0 -
czyli po prostu do rejestru AL wpisuje zero. Po wykonaniu wyżej podanego
bloku 4 rozkazów otrzymamy aktualną sekundę w AL w kodzie BCD, który należy
jeszcze przekonwertować na kody dwóch znaków liczby. Jak to jest zrobione w
praktyce ujrzycie za chwilę w listingu rezydenta. Jeszcze tylko trochę więcej
informacji o układzie CMOS, w którym oprócz zegara zawarta jest też pamięć
przechowująca najważniejsze ustawienia naszych komputerów (czyli całą
zawartość SETUPu). Oto adresy i funkcje kolejnych komórek, do których możemy
się odwoływać (po opisy szczegółowe odsyłam do książek):
0 aktualna sekunda zegara czasu rzeczywistego (RTC) w kodzie BCD
1 sekunda ustawienia budzika w kodzie BCD
2 aktualna minuta w BCD
3 minuta ustawienia budzika w BCD
4 aktualna godzina RTC w BCD
5 godzina ustawienia budzika w BCD
6 dzień tygodnia (1=niedziela,2=poniedziałek itd.)
7 dzień miesiąca w BCD
8 miesiąc w BCD
9 rok w BCD (ostatnie dwie cyfry)
0ah RTC rejestr stanu A
0bh RTC rejestr stanu B
0ch RTC rejestr stanu C
0dh RTC rejestr stanu D
0eh bajt stanu ustawiany przez POST
0fh powód wyłączenia
10h typ stacji dysków w systemie
11h zarezerwowane
12h typ twardego dysku
13h zarezerwowane
14h bajt wyposażenia komputera
I tak dalej. Jest tych komórek 256 i kogo bardziej interesują, może zawsze
zajrzeć do literatury (np. podanej już wcześniej książki: "Jak pisać
wirusy"). Kolejna sprawa: jak wypisać wartość na ekranie nie używając do tego
przerwania DOSu (używanie przerwań w naszej procedurze rezydentnej jest
bardzo ryzykowne, o tym będzie powiedziane dokładniej w dalszych częściach
kursu) ? Otóż jest sposób, należy kody znaków do wypisania "wcisnąć"
bezpośrednio w obszar pamięci ekranu, na kartach VGA, CGA, EGA itp. zaczyna
się ona od początku segmentu B800h, natomiast na karcie Hercules (HGC) od
B000h. Pod tymi adresami mamy dostęp do kodu pierwszego znaku na ekranie
(czyli tego w lewym górnym rogu), w następnym bajcie leży atrybut tego znaku,
dalej kod drugiego znaku, jego atrybut itd. Kolory znaków możemy obliczyć
podstawiając odpowiednie bity w bajcie atrybutów:
nr bitu: 7 6 5 4 3 2 1 0
znaczenie: K R G B i r g b
K - to blink, czyli migotanie znaku (znak miga gdy bit K=1), i to intensity -
jasność znaku (0=ciemniejszy, 1=jaśniejszy), RGB to kolejne składowe kolorów
tła, natomiast rgb to składowe kolorów znaku. Przykład: potrzebujemy bajt
atrybutu oznaczający jasnoczerwone znaki na czarnym tle, nie migające:
nr bitu: 7 6 5 4 3 2 1 0
znaczenie: K R G B i r g b
wartość: 0 0 0 0 1 1 0 0
| ^^|^^ | ^^^^^-czerwony
znak nie ---+ | +jasny
miga tło czarne
Czyli wychodzi na to, że poszukiwany atrybut znaku to 0ch. Można wpisać go w
pamięć ekranu oddzielnie, po wpisaniu kodu znaku, jednak my te dwie rzeczy
zrobimy jednocześnie - wpisując od razu całe słowo 16-bitowe rozkazem stosw,
umieszczającym wartość rejestru AX pod adresem ES:DI i zwiększającym DI o 2 -
tak, że wskazuje od razu na następny znak. Po uruchomieniu programu będziecie
mogli się przekonać, że czas zawarty w zegarze CMOS spieszy się nieznacznie
względem czasu DOSowego (np. pokazywanego przez Dos Navigatora, Nortona
Commandera itp.), ponieważ przy uruchamianiu komputera DOS odczytuje
zawartość CMOSa i trochę czasu mu zajmuje ustawienie swojego zegara - przez
to się spóźnia. Natomiast po wyłączeniu komputera zegar CMOS chodzi sobie
jakby nigdy nic - jego zasilanie jest podtrzymywane bateryjnie. Ale dość
ględzenia, przyszedł czas na listing:
.model tiny
.code
.386
org 100h
Start:
jmp Instaluj
; tutaj będą nasze zmienne:
staraproc dd 0 ; dd oznacza 4 bajty (tutaj o wartości 0)
NaszaProc:
push ax ; zapamiętujemy wartości używanych rejestrów
push bx
push di
push es
mov ax,0b800h ; B800h - segment pamięci ekranu karty VGA
mov es,ax
xor di,di ; zerujemy DI - adres w pamięci ekranu
xor al,al ; AL=0 - komórka z aktualną sekundą w BCD
out 70h,al ; wysyłamy do zegara CMOS
jmp $+2 ; małe opóźnienie
in al,71h ; odczytujemy wynik z zegara CMOS
mov bl,al
and bl,0fh ; prawa połówka bajtu - prawa cyfra w BCD
add bl,'0' ; do tego dodajemy kod zera
shr al,4 ; lewa połówka bajtu - lewa cyfra w BCD
add al,'0' ; do tego też dodajemy kod '0'
mov ah,0ch ; atrybut napisu - jasnoczerwony na czarnym tle
stosw ; i rzucamy na ekran pierwszą cyfrę
mov al,bl
stosw ; potem drugą
pop es
pop di
pop bx
pop ax
jmp dword ptr cs:[staraproc] ; skok do oryginalnej procedury
; koniec części rezydentnej
Instaluj:
mov ax,3508h ; 35h: pobranie wektora przerwania
int 21h ; wynik wpadł do ES:BX
mov word ptr cs:[staraproc],bx ; trzeba jeszcze go gdzies zapamietac
mov word ptr cs:[staraproc +2],es
mov ax,2508h ; 25h: ustawienie wektora przerwania
mov dx,offset NaszaProc ; DS:DX - wektor naszej procedury
int 21h
mov ah,9 ; 09h: wydruk napisu na ekran
mov dx,offset Napis
int 21h
mov dx,offset Instaluj ; do DX wpisujemy adres pierwszego bajtu,
int 27h ; który ma być zwolniony, wcześniejsze
; zostają w pamięci na stałe
Napis db 'Program zainstalowany w pamięci.',13,10,'$'
end Start
W następnym odcinku dowiemy się, jak naszego rezydenta wyrzucić z pamięci i
do tego jeszcze kilka innych przydatnych rzeczy.
Powrót na główną stronę kursu.
Wyszukiwarka
Podobne podstrony:
Asembler w TSR KURS2KURS2kurs2 (2)kurs2kurs2więcej podobnych podstron