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.
w3cw3c
automatyka przemysłowa