Pojemność dysku ? bajtów
-- Sebastian Pawlak, PCKurier 26/1996.
Lawinowy rozwój techniki, upowszechnienie komputerów we wszystkich dziedzinach życia, rodzi zapotrzebowanie na coraz to lepsze i nowocześniejsze oprogramowanie. Niestety, czym programy mają więcej funkcji, mogą realizować więcej zadań, tym szybciej na dysku twardym kończy się miejsce. Bardzo pomocny, był mi zakup nośnika o pojemności 1,2 GB. Jednak moja radość została zmącona jednym mało znaczącym, ale skutecznie utrudniającym życie, faktem. Jako jeden z wielu używam nakładki na system operacyjny, Norton Commander, dlatego chcąc sprawdzić autentyczną wielkość nośnika wcisnąłem standartowo ctrl+L. Jak zazwyczaj pojawiła się tabela informacyjna: wersja Nortona, ilość pamięci operacyjnej i nagle:
? total bytes on drive C: ? bytes free on drive C:
Skonfrontowałem te dziwne dane o stanie dysku ze znajomymi, okazało się, że mają podobny problem. Po prostu Norton, najprawdopodobniej z powodu przekroczenia zakresu zmiennej odpowiedzialnej za informacje o dysku, przy rozmiarze lub ilości miejsca wolnego większej niż ok. 655 MB, wyświetla "?". Problem ten został zlikwidowany dopiero w wersji 5.0 Norton Commander`a, ale nie każdy chce albo może zainsta- lować tę wersję nakładki. Postanowiłem napisać program, który na przerwaniu (wybrałem 10h) w odpowiednim momencie sam będzie odczytywał z dysku jego pojemność i ilość wolnego miejsca, a później dane te wyświetli w panelach mojego Nortona.
A, oto jak działa program:
1. Na początku program sprawdza czy właściwa procedura jest
już w pamięci. Jeśli nie to wykonywane są następujące
czynności.
2. Podpina starą procedurę przerwania 10h (operacje video)
na inny wolny numer np. 0A0h.
3. Gdy mamy już kopię przerwania 10h, możemy podłączyć pod
nr 10h naszą procedurę, która będzie wyświetlać dane o
stanie dysku w panelach Norton Commander`a.
4. Teraz program pozostaje rezydentnym i następuje wyjście
do DOS`a.
Tak działa właściwa procedura wyświetlająca dane na ekranie:
1. Na początku wywołuje oryginalną procedurę obsługi
przerwania 10h (znajduje się ona pod przerwaniem 0A0h).
2. Sprawdza czy któryś z paneli Nortona jest w trybie
"Info" -odbywa się to przez odnajdywanie, w odpowiednich
miejscach paneli, znaków rozpoznawczych ("I" od napisu
Info i "?"). Jeśli, któryś panel "Info" jest aktywny to:
3. Odczytuje z dysku dane o jego pojemności i ilości
wolnego miejsca.
4. Zamienia zmienną z pojemnością dysku na odpowiadający
łańcuch tekstowy. Wynik operacji pokazuje na ekranie.
5. Sprawdza czy ma też wyświetlić ilość wolnego miejsca
na dysku. Jeśli tak to...
6. Konwertuje zmienną z informacją o ilości wolnego
miejsca na łańcuch tekstowy i wyświetla go na ekranie.
Najbardziej złożone z całego programu jest przekształcenie zmiennej liczbowej na łańcuch tekstowy w celu wyświetlenia informacji na ekranie. Dodatkowym problemem jest też, to że algorytm będzie operował na zmiennych 32 bitowych, co stwarza konieczność stosowania rozszerzonych rejestów, a tym samym wyklucza kompilację programu na komputerach starszych niż 386.
Podstawowy algorytm zamiany liczby na tekst przedstawię na
przykładzie liczby 125:
1. Dzieli liczbę przez wielokrotność 10, tak aby otrzymać
wartość pierwszej cyfry tej liczby. W tym wypadku 125
dzielimy przez 100, w wyniku otrzymamy 1 z resztą.
125 / 100 = 1
2. Otrzymaną w punkcie 1 liczbę zapisujemy jako kolejny
znak do łańcucha.
3. Wartość z punktu 1 mnożymy przez liczbe, przez którą
poprzednio dzieliliśmy.
1 * 100 = 100
4. Od wartości wejściowej dla punktu 1 odejmujemy liczbę
wyliczoną w punkcie 3.
125 - 100 = 25
W ten sposób, otrzymana liczba jest "tą samą" co w
punkcie 1 ale bez pierwszej cyfry.
5. Teraz należy wyciągnąć kolejną cyfre z liczby 125 (w tym
wypadku będzie to 2) powtarzając operację od punktu 1.
Oczywiście wartość wejściowa dla punktu 1 będzie tą
wyliczoną w punkcie 4 (czyli 25).
Poniżej przedstawiam całkowity rozkład liczby 125:
125/100 = 1 <--- cyfra 1
1*100 = 100
125-100 = 25
25/10 = 2 <--- cyfra 2
2*10 = 20
25-20 = 5
5/1 = 5 <--- cyfra 3
5*1 = 5
5-5 = 0 (koniec)
Teraz pora na przedstawienie kompletnego i gotowego do uruchomienia programu.
Zasady asemblacji:
TASM sddnc.asm
TLINK /t sddnc.obj
Zawartość pliku "SDDNC.ASM"
Wydruk 1
; *******************************************
; * Stan Dysku Do Norton Commander`a, wer.1 *
; * autor: Sebastian Pawlak, czerwiec 1996 *
; *******************************************
; Program wyświetla w panelu "Info"
; Norton Commander`a (wersje starsze niż 5.0)
; dane o pojemności i ilości wolnego miejsca
; na dysku dla rozmiarów większych niż 655 MB.
Code SEGMENT
ASSUME cs:Code
.386 ; w programie będą wykonywane operacje na
; rejestrach 32 bitowych
ORG 100h
Start:
jmp Instalacja
; *********************************
; * Zmienne i stałe dla programu. *
; *********************************
Info1 DB 'Program jest juz w pamieci!',13,10,'$'
Info2 DB 'SDdNC wer.1 Sebastian Pawlak, PCkurier',13,10
DB 'Program zainstalowany.',13,10,'$'
; Norton Commander może wyświetlać panel "Info" w dwóch
; trybach (dochodzi jeszcze trzeci "Niepełny Ekran", ale tu
; rozmieszczenie znaków rozpoznawczych jest takie jak w
; trybie standartowym), standartowym i wtedy gdy ustawiona
; jest opcja "Menu bar always visible" (wtedy panele
; przesunięte są o jeden wiersz w dół). Dwie poniższe
; stałe zawierają informacje o rozmieszczeniach znaków
; rozpoznawczych w dwóch trybach wyświetlania paneli.
ZnakiRozpoznawczePaneli DB 'I','?' , 'I','?' ; lewy
DB 'I','?' , 'I','?' ; prawy
OffsetyZnakowRozpoznawczychPaneli DW 36,976, 36+160,976+160
DW 116,1056, 116+160,1056+160
InformacjaDyskuWzorzec DB 32,',',32,32,32,',',32,32,32
DB ',',32,32,32
TablicaOffsetowNapisowNaEkranie DW 964,964+160,1044,1044+160
TablicaDzielnikow DD 1000000000,100000000,10000000,1000000
DD 100000,10000,1000,100,10,1
Dzielnik DD ?
PojemnoscDysku DD ?
WolnyDysk DD ?
PojemnoscDyskuNapis DB 13 DUP (?)
WolnyDyskNapis DB 13 DUP (?)
KolorNapisu DB 1*16+14
OffsetNapisuNaEkranie DW ?
Pozycja_W_Napisie DB 0
StanPisaka DB 0 ; Zmienna odpowiedzialna za to, żeby
; na początku tekstu nie występowały
; zera. Przykłądowo w 0,067,433,222
; nie zostaną wyświetlone 2 pierwsze
; zera.
NrCyfry DB 0
;*****************
;* Kod programu. *
;*****************
; Procedura wyświetla w podanym miejscu na ekranie tekst.
; Parametry wejściowe: OffsetNapisuNaEkranie,
; cx - długość napisu,
; si - offset tekstu w pamięci.
WydrukujNapisNaEkranie PROC
mov ax,02h ; na czas wyświetlania napisu
int 33h ; chowany jest kursor myszki
mov di,OffsetNapisuNaEkranie
KolejnyZnakNaEkran:
mov al,ds:[si]
mov ah,KolorNapisu
inc si
mov es:[di],ax
inc di
inc di
loop KolejnyZnakNaEkran
mov ax,01h ; wyświetla kursor myszki
int 33h
ret
WydrukujNapisNaEkranie ENDP
NowaProceduraPrzerwania10h:
jmp Kod
SymbolProgramu DB 'SDdNC' ; na podstawie tego napisu będzie
; sprawdzane czy program jest już
; w pamięci
Kod:
int 0A0h ; na początku odwołanie do oryginalnej
; procedury obsługi przerwania 10h
pushfd ; znaczniki na stos
pushad ; rejestry na stos
push ds
push es
push ss
push cs
pop ds
; Sprawdza, w którym panelu ma wyświetlać informacje.
mov ax,0B800h
mov es,ax
mov bx,offset OffsetyZnakowRozpoznawczychPaneli
mov si,offset ZnakiRozpoznawczePaneli
mov dl,0
mov cx,8
SprawdzNastepnyZnakRozpoznawczy:
mov di,ds:[bx]
inc bx
inc bx
mov al,es:di
mov ah,ds:si
inc si
cmp al,ah
jne NieZnalazlemZnakuRozpoznawczego
inc dl
cmp dl,2 ; jeśli znalazł napis "Info" i "?"
je KtorysPanelAktywny ; to kończy szukanie
mov dh,cl
NieZnalazlemZnakuRozpoznawczego:
loop SprawdzNastepnyZnakRozpoznawczy
jmp Koniec
KtorysPanelAktywny:
; Tu następuje sprawdzanie czy pętla szukająca na pewno
; znalazła "I" i "?" dla jednego możliwego stanu panela,
; a nie np. literę "I" z panela lewego bez opcji menu bar
; i "?" od tego samego okna z opcją menu bar.
sub dh,cl
cmp dh,1
je InformacjePoprawne
jmp Koniec
InformacjePoprawne:
; Teraz na podstawie rejestru cx, zawierającego informację,
; w którym momencie zostało przerwane szukanie aktywnego
; panela, ustawiana jest zmienna OffsetNapisuNaEkranie.
mov ax,7
sub al,cl
mov si,offset TablicaOffsetowNapisowNaEkranie
add si,ax
mov ax,ds:si
mov OffsetNapisuNaEkranie,ax
; Przepisanie wzorców napisów informacyjnych
; do zmiennych roboczych.
mov si,offset InformacjaDyskuWzorzec
mov di,offset PojemnoscDyskuNapis
mov bx,offset WolnyDyskNapis
mov cx,13
KopiujKolejnyZnakWzorca:
mov al,ds:si
mov ds:[di],al
mov ds:[bx],al
inc bx
inc si
inc di
loop KopiujKolejnyZnakWzorca
; *************************************************
; * Odczyt pojemności i wolnego miejsca na dysku. *
; *************************************************
mov ah,36h ; funkcja 36h przerwania 21h zwraca parametry
mov dl,0 ; niezbędne do obliczenia pojemności i ilości
int 21h ; wolnego miejsca na dysku
cmp ax,0FFFFh ; czy parametry odczytane poprawnie?
jne ParametryDyskuPoprawne
jmp Koniec ; błąd odczytu parametrów
ParametryDyskuPoprawne:
mov di,dx
mul cx ; ax (liczba sektorów) * cx (rozmiar sektora)
push ax ; parametr ax (liczba sektorów * rozmiar sektora)
; będzie jeszcze potrzebny przy wyliczaniu
; wolnego miejsca, dlatego odkładamy go na stos
; Obiczanie pojemności dysku.
mul di ; pojemność = ax (obliczone wcześniej) * dx
; gdzie dx - całkowita liczba bloków na dysku
; Zapisanie obliczonej pojemności dysku do zmiennej
mov si,offset PojemnoscDysku
mov ds:si,ax
inc si
inc si
mov ds:si,dx
pop ax ; podniesienie ze stosu odłożonego wcześniej
; parametru (liczba sektorów * rozmiar sektora)
; Obliczanie wolnego miejsca na dysku.
mov dx,0
mul bx ; wolne = ax (podniesiony wyżej ze stosu) * bx
; gdzie bx - liczba wolnych bloków
; Zapisanie obliczonego wolnego miejsca do zmiennej.
mov si,offset WolnyDysk
mov ds:si,ax
inc si
inc si
mov ds:si,dx
mov edx,0
; *******************************************************
; * Tworzenie łańcucha znaków z informacją o pojemności *
; * dysku w przykładowej postaci: 1,257,300,127 *
; *******************************************************
mov Pozycja_W_Napisie,0
mov StanPisaka,0
mov NrCyfry,0
mov eax,PojemnoscDysku
_PokazNastepnaCyfre:
; Pobranie z tablicy dzielnika.
mov bl,NrCyfry
mov bh,0
shl bx,1
shl bx,1
mov si,offset TablicaDzielnikow
add si,bx
mov ecx,ds:si
mov Dzielnik,ecx
; Dzieli pojemność dysku (ax) przez wyznaczony dzielnik
; (wielokrotność 10), aby uzyskać pierwszą cyfre
; dzielonej liczby. Przykładowo 1257300127/1000000000
; wynosi 1 i reszte.
div ecx
mov dx,0
push ax
; Umieszczenie cyfry w łańcuchu znaków
; tak aby co trzy znaki występował przecinek
; (1,257,300,127), na początku nigdy nie
; występowało zero (0,857,300,127,
; pierwsze zero nie zostanie wyświetlone).
add al,48
cmp StanPisaka,1
je _ZapiszZnakDoLancucha
cmp al,'0'
jne _WlaczPisak
mov si,offset PojemnoscDyskuNapis
mov bl,Pozycja_W_Napisie
mov bh,0
add si,bx
inc si
mov ah,ds:si
cmp ah,','
jne _NieZapisujDoLancucha
mov ah,32
mov ds:si,ah
inc Pozycja_W_Napisie
jmp _NieZapisujDoLancucha
_WlaczPisak:
mov StanPisaka,1
_ZapiszZnakDoLancucha:
mov si,offset PojemnoscDyskuNapis
mov bl,Pozycja_W_Napisie
mov bh,0
add si,bx
mov ah,ds:si
cmp ah,','
jne _UmiescZnak
inc Pozycja_W_Napisie
inc si
_UmiescZnak:
mov ds:si,al
_NieZapisujDoLancucha:
inc Pozycja_W_Napisie
pop ax
; Monożenie uzyskanej w procesie dzielenia liczby
; przez Dzielnik (ten sam co wyżej w dzieleniu).
; Przykładowo 1 * 1000000000 wynosi 1000000000.
mov ebx,Dzielnik
mul ebx
; Odjęcie PojemnosciDysku od obliczonej niedawno
; pierwszej cyfry * Dzielnik. W wyniku tego działania
; "obcinana" jest pierwsza cyfra zmiennej PojemnoscDysku
; dzięki czemu powtarzając obliczenia można uzyskać
; wartość kolejnej cyfry.
; Przykładowo 1257300127 - 1000000000 wynosi 257300127.
mov ebx,eax
mov eax,PojemnoscDysku
sub eax,ebx
mov PojemnoscDysku,eax
inc NrCyfry
cmp NrCyfry,9
ja _KoniecPisaniaPojemnosciDysku
jmp _PokazNastepnaCyfre
_KoniecPisaniaPojemnosciDysku:
mov cx,13
mov si,offset PojemnoscDyskuNapis
call WydrukujNapisNaEkranie
; ********************************************************
; * Tworzenie łańcucha znaków z informacją o wolnym *
; * miejscu na dysku w przykładowej postaci: 857,120,147 *
; ********************************************************
; Sprawdza czy ma wyświetlać w panelu ilość wolnego miejsca
; na dysku.
mov di,OffsetNapisuNaEkranie
add di,172
mov al,es:di
cmp al,'?' ; '?' z napisu '? bytes free ...'
je WyswietlanieWolnegoMiejsca
jmp Koniec
WyswietlanieWolnegoMiejsca:
mov Pozycja_W_Napisie,0
mov StanPisaka,0
mov NrCyfry,0
mov eax,WolnyDysk
PokazNastepnaCyfre:
; Pobranie z tablicy dzielnika.
mov bl,NrCyfry
mov bh,0
shl bx,1
shl bx,1
mov si,offset TablicaDzielnikow
add si,bx
mov ecx,ds:si
mov Dzielnik,ecx
; Dzieli ilość wolnego miejsca (ax) przez wyznaczony
; Dzielnik (wielokrotność 10), aby uzyskać pierwszą
; cyfre dzielonej liczby. Przykładowo 857120147/100000000
; wynosi 8 i resztę.
div ecx
mov dx,0
push ax
; Umieszczanie cyfry w łańcuchu znaków
; tak aby co trzy cyfry występował przecinek
; (857,120,147), na początku nigdy nie
; występowało zero (0,857,120,147,
; pierwsze zero nie zostanie wyświetlone).
add al,48
cmp StanPisaka,1
je ZapiszZnakDoLancucha
cmp al,'0'
jne WlaczPisak
mov si,offset WolnyDyskNapis
mov bl,Pozycja_W_Napisie
mov bh,0
add si,bx
inc si
mov ah,ds:si
cmp ah,','
jne NieZapisujDoLancucha
mov ah,32
mov ds:si,ah
inc Pozycja_W_Napisie
jmp NieZapisujDoLancucha
WlaczPisak:
mov StanPisaka,1
ZapiszZnakDoLancucha:
mov si,offset WolnyDyskNapis
mov bl,Pozycja_W_Napisie
mov bh,0
add si,bx
mov ah,ds:si
cmp ah,','
jne UmiescZnak
inc Pozycja_W_Napisie
inc si
UmiescZnak:
mov ds:si,al
NieZapisujDoLancucha:
inc Pozycja_W_Napisie
pop ax
; Monożenie uzyskanej w procesie dzielenia liczby
; przez Dzielnik (ten sam co wyżej w dzieleniu).
; Przykładowo 8 * 100000000 wynosi 800000000.
mov ebx,Dzielnik
mul ebx
; Odjęcie WolnegoDysku od obliczonej niedawno
; (pierwszej cyfry * Dzielnik). W wyniku tego działania
; "obcinana" jest pierwsza cyfra zmiennej WolnyDysk
; dzięki czemu powtarzając obliczenia można uzyskać
; wartość kolejnej cyfry.
; Przykładowo 857120147 - 800000000 wynosi 57120147.
mov ebx,eax
mov eax,WolnyDysk
sub eax,ebx
mov WolnyDysk,eax
inc NrCyfry
cmp NrCyfry,9
ja KoniecPisaniaMiejscaWolnego
jmp PokazNastepnaCyfre
KoniecPisaniaMiejscaWolnego:
mov cx,13
mov si,offset WolnyDyskNapis
add OffsetNapisuNaEkranie,160
call WydrukujNapisNaEkranie
Koniec:
pop ss ; rejestry ze stosu
pop es
pop ds
popad
popfd ; znaczniki ze stosu
iret ; wyjście z procedury obsługi przerwania
KoniecKodu:
; Instalacja procedury obsługi przerwania w pamięci.
Instalacja:
mov ax,3510h ; pobiera adres procedury obsługi
int 21h ; przerwania 10h
mov di,bx
add di,3 ; rejestr di wskazuje na symbol programu
; ('SDdNC') ; di zwiększany jest o 3 żeby
; ominąć pierwszy rozkaz (jmp Kod)
; Sprawdza zgodność symbolu ze zmiennej SymbolProgramu
; ('SDdNC') z symbolem na początku programu obsługi
; przerwania.
mov si,offset SymbolProgramu
mov cx,5
PorownajSymbole:
cmpsb
jne Instaluj
loop PorownajSymbole
; Jeśli program jest już w pamięci to wyświetla
; komunikat i wychodzi do DOS`a.
mov dx,offset Info1
mov ah,9
int 21h
mov ah,4Ch
int 21h
; Gdy programu nie ma w pamięci dokonuje jego instalacji.
Instaluj:
push ds
push es
pop ds
mov dx,bx
mov ax,25A0h ; oryginalne przerwanie 10h umieszcza pod
int 21h ; nieużywanym 0A0h
pop ds
mov dx,offset NowaProceduraPrzerwania10h
mov ax,2510h ; pod nr 10h umieszcza nową procedurę
int 21h ; obsługi tego przerwania
mov dx,offset Info2 ; wyświetla informacje, że
mov ah,9 ; program jest już zainstalowany
int 21h
mov dx,offset KoniecKodu
int 27h ; pozostawia program rezydentnym
Code ENDS
END Start
Literatura: 1. "Mikroprocesory 80286, 80386, i486"
- Ryszard Goczyński, Michał Tuszyński,
2. "Turbo Assembler Biblia użytkownika"
- Gary Syck,
3. "Jak pisać wirusy"
- Andrzej Dudek.





