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.