Asembler w TSR KURS2


MINI KURS PISANIA PROGRAMÓW TSR W ASEMBLERZE

2. 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:

----------> Obciąć <----------
.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
----------> Obciąć <----------

W następnym odcinku dowiemy się, jak naszego rezydenta wyrzucić z
pamięci i do tego jeszcze kilka innych przydatnych rzeczy.


Wyszukiwarka

Podobne podstrony:
Asembler w TSR KURS0
Asembler w TSR KURS5
Asembler w TSR KURS3
Asembler w TSR KURS4
Asembler w TSR SPIS TRE
Asembler w TSR KURS1
Asembler linux
TASM operacje asembler
KURS2
21 Pisanie i uruchamianie programów w asemblerze
Procedury arytmetyczne w języku Asembler ST7
kurs2 (2)
OAK W11 Asembler Pentium
asembler
Asembler Podstawy programowania w Windows

więcej podobnych podstron