   #Start Contents

                              Pamiec EMS i XMS

   Jak wiemy, programy w trybie 16-bitowym sa ograniczone do jednego
   megabajta pamieci, z ktorej moga korzystac. Dzieje sie tak ze wzgledu
   na to, ze w trybie 16-bitowym adres fizyczny otrzymuje sie, mnozac
   zawartosc rejestru segmentowego przez 16 i dodajac offset (adres w
   segmencie), co daje 16 * 65536 = 1MB. Wiecej po prostu fizycznie
   procesor nie jest w stanie zaadresowac. A jesli nie mozna podac adresu
   wyzszego niz 1MB, to nie mozna tam nic zapisac ani odczytac. O
   strukturze pamieci pisalem szerzej w czesci drugiej mojego kursu.

   Jednak na procesorach od 80386 w gore mozna adresowac wyzsze obszary
   pamieci. Robi sie to przy uzyciu menadzerow pamieci EMS i XMS. Te
   specjalne programy wprowadzaja procesor w tryb, ktory umozliwia
   adresowanie pamieci w obszarach powyzej 1MB, nadal bedac w srodowisku
   16-bitowym, takim jak DOS.

   Menadzery pamieci, po uruchomieniu, udostepniaja interfejs w postaci
   przerwan, z ktorych moga korzystac programy 16-bitowe. Tym interfejsem
   sie wlasnie zajmiemy.
     _________________________________________________________________

EMS

   (przeskocz EMS)

   Historycznie, pamiec EMS byla fizycznym urzadzeniem, karta wkladana do
   gniazd rozszerzen, jak karty ISA czy PCI.

   Wraz ze spadkiem kosztu pamieci RAM, pamiec EMS zaczela byc emulowana
   za pomoca standardowej pamieci RAM komputera i tak tez pozostalo -
   wspolczesne menadzery EMS (jak Jemm czy EMM386) emuluja dostep do tych
   kart rozszerzen z pamiecia i zamiast wysylac zadania do urzadzenia, po
   prostu wykorzystuja pamiec RAM. Aby to dzialalo, potrzebny jest jednak
   sterownik XMS, ktory w ogole umozliwia dostep do wyzszych adresow.

   Skoro nie mozna bezposrednio dostac sie do pamieci powyzej 1MB, trzeba
   jakos taki dostep umozliwic poprzez dostepna pamiec. W EMS jest to
   zrealizowane za pomoca tak zwanej ramki stron. Jest to segment (czyli
   64 kilobajty) pamieci w obszarze ponizej 1MB, ktory sluzy jako bufor
   do tymczasowego przechowywania danych kopiowanych do i z EMS.

   Najmniejsza jednostka, ktora mozna buforowac, jest tak zwana strona
   pamieci. Strona pamieci ma wielkosc 16 kilobajtow, wiec ramka stron
   miesci 4 strony fizyczne, do ktorych mapowane (odwzorowywane) moga byc
   strony logiczne EMS, znajdujace sie juz powyzej granicy 1MB.

   Jak wiec wykorzystac pamiec EMS w swoim programie? Algorytm jest
   nastepujacy:
    1. sprawdzic, czy sterownik EMS w ogole jest zaladowany
    2. pobrac numer segmentu zawierajacego ramke stron
    3. opcjonalnie sprawdzic, czy sa jakies wolne uchwyty pamieci
       (podobne do uchwytow plikow)
    4. zaalokowac odpowiednia liczbe logicznych stron pamieci (w obszarze
       powyzej 1MB)
    5. zmapowac logiczne strony pamieci (w obszarze powyzej 1MB) na
       fizyczne strony znajdujace sie w ramce stron (w obszarze ponizej
       1MB)
    6. korzystac z pamieci
    7. zwolnic pamiec EMS (jesli tego nie zrobimy, pamiec bedzie
       zaznaczona jako niedostepna dla innych programow az do restartu
       komputera)

   Teraz omowimy te punkty po kolei. Funkcje EMS sa udostepniane przez
   przerwanie 67h, a zerowa wartosc rejestru AH po powrocie z wywolania
   oznacza brak bledu.
    1. Sprawdzic, czy sterownik EMS w ogole jest zaladowany.
       Robi sie to w prosty sposob:
          + pobieramy numer segmentu zawierajacego procedure obslugi
            przerwania 67h (przerwania EMS)
          + sprawdzamy, czy po 10 bajtach od poczatku tego segmentu
            znajduje sie ciag znakow EMMXXXX0
    2. Pobrac numer segmentu zawierajacego ramke stron.
       Skoro juz wiemy, ze sterownik jest zaladowany, mozna korzystac z
       przerwania 67h. Do pobrania numeru segmentu zawierajacego ramke
       stron sluzy funkcja 41h:
          + ustawiamy AH=41h
          + wywolujemy przerwanie 67h
          + po pomyslnym wywolaniu w rejestrze BX bedzie numer segmentu
            zawierajacego ramke stron
    3. Opcjonalnie sprawdzic, czy sa jakies wolne uchwyty pamieci
       (podobne do uchwytow plikow).
       Aby obliczyc liczbe wolnych uchwytow pamieci EMS, nalezy od
       lacznej liczby uchwytow odjac liczbe uchwytow zajetych. Robi sie
       to dwoma wywolaniami:
          + ustawiamy AX=5402h
          + wywolujemy przerwanie 67h
          + po pomyslnym wywolaniu w rejestrze BX bedzie laczna liczba
            uchwytow
          + ustawiamy AH=4Bh
          + wywolujemy przerwanie 67h
          + po pomyslnym wywolaniu w rejestrze BX bedzie liczba uzywanych
            uchwytow
    4. Zaalokowac odpowiednia liczbe logicznych stron pamieci (w obszarze
       powyzej 1MB).
       Te czynnosc wykonuje sie jednym wywolaniem:
          + ustawiamy AH=43h
          + ustawiamy BX na liczbe logicznych stron, ktore chcemy zajac
            (zaalokowac)
          + wywolujemy przerwanie 67h
          + po pomyslnym wywolaniu w rejestrze DX bedzie uchwyt do
            przydzielonego nam obszaru
    5. Zmapowac logiczne strony pamieci (w obszarze powyzej 1MB) na
       fizyczne strony znajdujace sie w ramce stron (w obszarze ponizej
       1MB).
       Te czynnosc rowniez wykonuje sie jednym wywolaniem:
          + ustawiamy AH=44h
          + ustawiamy AL na numer fizycznej strony w ramce stron, do
            ktorej chcemy przypisac logiczna strone. Jako ze ramka stron
            miesci tylko 4 strony, AL powinien miec wartosc od 0 do 3.
          + ustawiamy BX na numer logicznej strony, ktora wczesniej
            zaalokowalismy, a ktora chcemy zmapowac do fizycznej strony.
            Strony numerujemy od zera, wiec BX powinien miec wartosc od 0
            do liczby zaalokowanych logicznych stron minus jeden.
          + wpisujemy do DX uchwyt do przydzielonego nam obszaru, z
            ktorego maja byc brane strony logiczne
          + wywolujemy przerwanie 67h
    6. Korzystac z pamieci.
       Tu inwencja zalezy tylko od tworcy programu. W wiekszych obszarach
       pamieci mozna przechowywac obrazy i dzwieki dla gier, inne dane i
       cokolwiek akurat trzeba.
    7. Zwolnic pamiec EMS.
       Zwalnianie logicznych stron pamieci wykonuje sie funkcja 45h:
          + ustawiamy AH=45h
          + wpisujemy do DX uchwyt do przydzielonego nam obszaru, ktory
            chcemy zwolnic
          + wywolujemy przerwanie 67h

   Ponizej znajduje sie przykladowy, gotowy program ilustrujacy podane
   wyzej funkcjonalnosci (skladnia NASM).
org 100h

start:
        ; pobierz segment przerwania sterownika EMS (67h)
        xor     ax, ax
        mov     es, ax
        mov     ds, [es:(67h << 2) + 2]

        ; szukaj znacznika
        mov     si, 10  ; DS:SI = adres znacznika w pamieci
        mov     cx, znacznik_ems_dl
        mov     ax, cs
        mov     es, ax
        mov     di, znacznik_ems        ; ES:DI = adres zmiennej
        repe    cmpsb
        je      jest_ems

        mov     ds, ax
        mov     dx, brak_ems
        mov     ah, 9
        int     21h

        mov     ax, 4c01h
        int     21h

jest_ems:
        mov     ds, ax

        ; pobieramy segment ramki EMS
        mov     ah, 41h
        int     67h
        test    ah, ah
        jz      mamy_segment

        mov     dx, brak_ramki
        mov     ah, 9
        int     21h

        mov     ax, 4c02h
        int     21h

mamy_segment:
        mov     [segment_ramki], bx

        ; sprawdz liczbe wolnych uchwytow pamieci EMS
        mov     ah, 4bh
        int     67h
        test    ah, ah
        jz      mamy_uzywane_uchwyty

        mov     ah, 9
        mov     dx, blad_uchwyty_u
        int     21h

        mov     ax, 4c03h
        int     21h

mamy_uzywane_uchwyty:
        mov     cx, bx  ; CX = liczba uzywanych uchwytow pamieci
        mov     ax, 5402h
        int     67h
        test    ah, ah
        jz      mamy_laczne_uchwyty

        mov     ah, 9
        mov     dx, blad_uchwyty
        int     21h

        mov     ax, 4c04h
        int     21h

mamy_laczne_uchwyty:
        ; BX = laczna liczba uchwytow
        sub     bx, cx  ; BX = liczba wolnych uchwytow
        jnz     sa_uchwyty

        mov     ah, 9
        mov     dx, brak_uchwytow
        int     21h

        mov     ax, 4c05h
        int     21h

sa_uchwyty:
        ; alokujemy jedna logiczna strone pamieci
        mov     ah, 43h
        mov     bx, 1
        int     67h
        test    ah, ah
        jz      jest_alokacja

        mov     ah, 9
        mov     dx, blad_alok
        int     21h

        mov     ax, 4c06h
        int     21h

jest_alokacja:
        mov     [uchwyt_pamieci], dx

        ; mapujemy logiczna strone pamieci na pierwsza strone
        ; fizyczna w ramce pamieci EMS
        mov     ax, 4400h       ; mapuj do zerowej strony fizycznej
        xor     bx, bx          ; pierwsza strona logiczna
        ;mov    dx, [uchwyt_pamieci]    ; DX juz zawiera uchwyt pamieci
        int     67h
        test    ah, ah
        jz      zapelnij_pamiec

        mov     ah, 9
        mov     dx, blad_mapowanie
        int     21h

        ; dealokacja pamieci
        mov     ah, 45h
        mov     dx, [uchwyt_pamieci]
        int     67h

        mov     ax, 4c07h
        int     21h

zapelnij_pamiec:
        ; pamiec przydzielona, zapelniamy ja
        mov     ax, [segment_ramki]
        mov     es, ax
        mov     ax, 5a5ah
        mov     cx, 1 << 12     ; CX = 16kB / 4 = 4kB
        xor     di, di
        rep     stosd

        mov     ah, 9
        mov     dx, wszystko_ok
        int     21h

koniec:
        ; dealokacja pamieci
        mov     ah, 45h
        mov     dx, [uchwyt_pamieci]
        int     67h

        mov     ax, 4c00h
        int     21h



uchwyt_pamieci  dw      0
segment_ramki   dw      0
znacznik_ems    db      'EMMXXXX0'
znacznik_ems_dl equ     $ - znacznik_ems
brak_ems        db      'Brak EMS', 13, 10, '$'
brak_ramki      db      'Nie mozna pobrac ramki EMS', 13, 10, '$'
blad_uchwyty_u  db      'Nie mozna pobrac liczby uzywanych uchwytow', 13, 10, '
$'
blad_uchwyty    db      'Nie mozna pobrac lacznej liczby uchwytow', 13, 10, '$'
brak_uchwytow   db      'Brak wolnych uchwytow', 13, 10, '$'
blad_alok       db      'Blad alokacji pamieci', 13, 10, '$'
blad_mapowanie  db      'Blad mapowania pamieci', 13, 10, '$'
wszystko_ok     db      'Wszystko ukonczone prawidlowo', 13, 10, '$'

   Wiecej przykladow mozna znalezc pod adresami:
     * www.phatcode.net/articles.php?id=155
     * petesqbsite.com/sections/tutorials/zines/chronicles/3-ems.html
     _________________________________________________________________

XMS

   Pamiec XMS to po prostu pamiec RAM o adresach powyzej 1MB.

   Skoro pamiec XMS takze jest polozona powyzej granicy osiagalnej w
   trybie rzeczywistym, to takze nie mozna sie do niej bezposrednio
   odwolywac. A skoro tak, to takze potrzebny jest do niej sterownik
   (menadzer), taki jak HIMEM.SYS czy HimemX. Sterownik taki udostepnia
   swoje funkcje, z ktorych moga korzystac programy.

   Menadzery XMS udostepniaja mieszany interfejs - czesc funkcjonalnosci
   udostepniana jest przez przerwania, a czesc - poprzez bezposrednie
   wywolanie funkcji w obszarze pamieci sterownika.

   Dostep do pamieci XMS nie odbywa sie za pomoca ramek stron, wiec
   trzeba miec przygotowane wlasne bufory na dane. Oto algorytm
   wykorzystania pamieci XMS w swoim programie:
    1. sprawdzic, czy sterownik XMS w ogole jest zaladowany
    2. pobrac adres glownej funkcji sterownika XMS, z ktorej bedziemy
       korzystac
    3. opcjonalnie sprawdzic, ile jest wolnej pamieci
    4. zaalokowac odpowiednia ilosc pamieci (w obszarze powyzej 1MB)
    5. korzystac z pamieci
    6. zwolnic pamiec XMS (jesli tego nie zrobimy, pamiec bedzie
       zaznaczona jako niedostepna dla innych programow az do restartu
       komputera)

   Teraz omowimy te punkty po kolei. Funkcje XMS sa udostepniane przez
   przerwanie 2Fh oraz przez funkcje znajdujaca sie wewnatrz sterownika,
   a wartosc rejestru AX rowna 1 po powrocie z wiekszosci wywolan oznacza
   brak bledu.
    1. Sprawdzic, czy sterownik XMS w ogole jest zaladowany.
       Do tej czynnosci sluzy funkcja 4300h przerwania 2Fh:
          + ustawiamy AX=4300h
          + wywolujemy przerwanie 2Fh
          + po pomyslnym wywolaniu (jesli sterownik XMS jest dostepny) w
            rejestrze AH bedzie wartosc 80h
    2. Pobrac adres glownej funkcji sterownika XMS, z ktorej bedziemy
       korzystac.
       W tym celu nalezy wywolac funkcje 4310h przerwania 2Fh:
          + ustawiamy AX=4310h
          + wywolujemy przerwanie 2Fh
          + po pomyslnym wywolaniu w parze rejestrow ES:BX bedzie pelny
            adres (segment i offset) funkcji obslugujacej XMS, ktora
            bedziemy wywolywac
    3. Opcjonalnie sprawdzic, ile jest wolnej pamieci.
       Do tego sluzy czynnosc numer 8 funkcji obslugujacej XMS:
          + ustawiamy AH=8
          + wywolujemy funkcje obslugujaca XMS (CALL FAR!)
          + po pomyslnym wywolaniu w rejestrze DX bedzie ilosc wolnej
            pamieci XMS w kilobajtach
    4. Zaalokowac odpowiednia ilosc pamieci (w obszarze powyzej 1MB).
       Do alokacji pamieci sluzy czynnosc numer 9 funkcji obslugujacej
       XMS:
          + ustawiamy AH=9
          + ustawiamy DX na liczbe kilobajtow, ktore chcemy zaalokowac
          + wywolujemy funkcje obslugujaca XMS
          + po pomyslnym wywolaniu w rejestrze DX bedzie uchwyt do
            przydzielonego nam obszaru
    5. Korzystac z pamieci.
       W obszarach pamieci wiekszych niz ten ponizej granicy 1MB mozna
       przechowywac obrazy i dzwieki dla gier, inne dane i cokolwiek
       akurat trzeba.
       Problem w tym, ze skoro w XMS nie ma ramki stron, gdzie mozemy
       sobie mapowac pamiec, trzeba miec wlasne bufory oraz sposob
       przekazania sterownikowi XMS informacji, ze chcemy przeniesc
       gdzies zawartosc pamieci.
       Do opisu pojedynczej operacji przenoszenia zawartosci pamieci
       sluzy taka struktura (skladnia NASM):
                struc struk_kopia_xms
                        dlugosc         resd    1
                        uchwyt_zrodla   resw    1
                        offset_zrodla   resd    1
                        uchwyt_celu     resw    1
                        offset_celu     resd    1
                endstruc
       Pola oznaczaja kolejno:
          + dlugosc - liczba bajtow, ktore chcemy skopiowac w tej
            operacji
          + uchwyt zrodla - uchwyt do pamieci XMS (otrzymany z alokacji),
            z ktorego pobrac dane. Jesli dane maja byc kopiowane z
            pamieci konwencjonalnej, nalezy wstawic tu zero.
          + offset zrodla - adres poczatkowy w zrodlowym obszarze
            pamieci, z ktorego pobierac dane. Jesli dane maja byc
            kopiowane z pamieci konwencjonalnej, nalezy wstawic tu offset
            (w mlodszych dwoch bajtach) i segment (w starszych dwoch
            bajtach) bufora zrodlowego.
          + uchwyt celu - uchwyt do pamieci XMS (otrzymany z alokacji),
            do ktorego skopiowac dane. Jesli dane maja byc kopiowane do
            pamieci konwencjonalnej, nalezy wstawic tu zero.
          + offset celu - adres poczatkowy w docelowym obszarze pamieci,
            do ktorego skopiowac dane. Jesli dane maja byc kopiowane do
            pamieci konwencjonalnej, nalezy wstawic tu offset (w
            mlodszych dwoch bajtach) i segment (w starszych dwoch
            bajtach) bufora zrodlowego.
       Odpowiednio wypelniona strukture przekazujemy do czynnosci 0Bh
       funkcji obslugujacej XMS:
          + ustawiamy AH=0Bh
          + ustawiamy SI na adres wypelnionej struktury, opisanej powyzej
          + wywolujemy funkcje obslugujaca XMS
    6. Zwolnic pamiec XMS.
       Do dealokacji pamieci sluzy czynnosc numer 10 funkcji obslugujacej
       XMS:
          + ustawiamy AH=10
          + ustawiamy DX na uchwyt do przydzielonego nam obszaru, ktory
            chcemy zwolnic
          + wywolujemy funkcje obslugujaca XMS

   Ponizej znajduje sie przykladowy, gotowy program pokazujacy wymienione
   wyzej czynnosci (skladnia NASM). Zauwazcie, ze wywolania funkcji
   obslugujacej XMS musza byc wywolaniami dalekimi (CALL FAR), gdyz
   znajduje sie ona w innym segmencie.
org 100h

start:
        ; sprawdz, czy XMS jest zainstalowane
        mov     ax, 4300h
        int     2fh
        cmp     al, 80h
        je      jest_xms

        mov     dx, brak_ster_xms
        mov     ah, 9
        int     21h

        mov     ax, 4c01h
        int     21h

jest_xms:
        ; pobierz adres punktu wejscia do XMS (funkcji sterujacej)
        mov     ax, 4310h
        int     2fh
        mov     [funkcja_xms], bx
        mov     [funkcja_xms+2], es

        ; sprawdz ilosc wolnej pamieci XMS
        mov     ah, 8
        call    far [funkcja_xms]
        test    dx, dx
        jnz     mamy_pamiec

        mov     ah, 9
        mov     dx, brak_pamieci_xms
        int     21h

        mov     ax, 4c02h
        int     21h

mamy_pamiec:
        ; alokujemy pamiec rownie duza, co nasz bufor
        mov     ah, 9
        mov     dx, bufor_dl >> 10      ; liczba kilobajtow
        call    far [funkcja_xms]
        cmp     ax, 1
        je      jest_alokacja

        mov     ah, 9
        mov     dx, blad_alok
        int     21h

        mov     ax, 4c03h
        int     21h

jest_alokacja:
        mov     [uchwyt_pamieci], dx

        ; pamiec przydzielona, zapelniamy ja
        mov     dword [opis_kopiowania + dlugosc], bufor_dl
        mov     word [opis_kopiowania + uchwyt_zrodla], 0       ; pamiec RAM
        mov     word [opis_kopiowania + offset_zrodla], bufor
        mov     word [opis_kopiowania + offset_zrodla+2], ds
        mov     [opis_kopiowania + uchwyt_celu], dx
        mov     dword [opis_kopiowania + offset_celu], 0
        mov     ah, 0bh
        mov     si, opis_kopiowania
        call    far [funkcja_xms]
        cmp     ax, 1
        je      kopia_ok

        mov     ah, 9
        mov     dx, blad_kopiowanie
        int     21h

        ; dealokacja pamieci
        mov     ah, 10
        mov     dx, [uchwyt_pamieci]
        call    far [funkcja_xms]

        mov     ax, 4c04h
        int     21h

kopia_ok:
        mov     ah, 9
        mov     dx, wszystko_ok
        int     21h

        ; dealokacja pamieci
        mov     ah, 10
        mov     dx, [uchwyt_pamieci]
        call    far [funkcja_xms]

        mov     ax, 4c00h
        int     21h



funkcja_xms             dd      0
uchwyt_pamieci          dw      0
brak_ster_xms           db      'Brak sterownika XMS', 13, 10, '$'
brak_pamieci_xms        db      'Brak wolnej pamieci XMS', 13, 10, '$'
blad_alok               db      'Blad alokacji pamieci', 13, 10, '$'
blad_kopiowanie         db      'Blad kopiowania pamieci', 13, 10, '$'
wszystko_ok             db      'Wszystko ukonczone prawidlowo', 13, 10, '$'

bufor                   times   (1 << 14) db 0
bufor_dl                equ     $ - bufor

struc struk_kopia_xms
        dlugosc         resd    1
        uchwyt_zrodla   resw    1
        offset_zrodla   resd    1
        uchwyt_celu     resw    1
        offset_celu     resd    1
endstruc

opis_kopiowania         istruc struk_kopia_xms

   Wiecej przykladow mozna znalezc pod adresami:
     * petesqbsite.com/sections/tutorials/zines/chronicles/1-xms.html
     * computer-programming-forum.com/46-asm/7e08914900e20d5e.htm

   Spis tresci off-line (klawisz dostepu 1)
   Spis tresci on-line (klawisz dostepu 2)
   Ulatwienia dla niepelnosprawnych (klawisz dostepu 0)
