Jak wiemy, programy w trybie 16-bitowym są ograniczone do jednego megabajta pamięci, z której mogą korzystać. Dzieje się tak ze względu na to, że w trybie 16-bitowym adres fizyczny otrzymuje się, mnożąc zawartość rejestru segmentowego przez 16 i dodając offset (adres w segmencie), co daje 16 * 65536 = 1MB. Więcej po prostu fizycznie procesor nie jest w stanie zaadresować. A jeśli nie można podać adresu wyższego niż 1MB, to nie można tam nic zapisać ani odczytać. O strukturze pamięci pisałem szerzej w części drugiej mojego kursu.
Jednak na procesorach od 80386 w górę można adresować wyższe obszary pamięci. Robi się to przy użyciu menadżerów pamięci EMS i XMS. Te specjalne programy wprowadzają procesor w tryb, który umożliwia adresowanie pamięci w obszarach powyżej 1MB, nadal będąc w środowisku 16-bitowym, takim jak DOS.
Menadżery pamięci, po uruchomieniu, udostępniają interfejs w postaci przerwań, z których mogą korzystać programy 16-bitowe. Tym interfejsem się właśnie zajmiemy.
Historycznie, pamięć EMS była fizycznym urządzeniem, kartą wkładaną do gniazd rozszerzeń, jak karty ISA czy PCI.
Wraz ze spadkiem kosztu pamięci RAM, pamięć EMS zaczęła być emulowana za pomocą standardowej pamięci RAM komputera i tak też pozostało - współczesne menadżery EMS (jak Jemm czy EMM386) emulują dostęp do tych kart rozszerzeń z pamięcią i zamiast wysyłać żądania do urządzenia, po prostu wykorzystują pamięć RAM. Aby to działało, potrzebny jest jednak sterownik XMS, który w ogóle umożliwia dostęp do wyższych adresów.
Skoro nie można bezpośrednio dostać się do pamięci powyżej 1MB, trzeba jakoś taki dostęp umożliwić poprzez dostępną pamięć. W EMS jest to zrealizowane za pomocą tak zwanej ramki stron. Jest to segment (czyli 64 kilobajty) pamięci w obszarze poniżej 1MB, który służy jako bufor do tymczasowego przechowywania danych kopiowanych do i z EMS.
Najmniejszą jednostką, którą można buforować, jest tak zwana strona pamięci. Strona pamięci ma wielkość 16 kilobajtów, więc ramka stron mieści 4 strony fizyczne, do których mapowane (odwzorowywane) mogą być strony logiczne EMS, znajdujące się już powyżej granicy 1MB.
Jak więc wykorzystać pamięć EMS w swoim programie? Algorytm jest następujący:
Teraz omówimy te punkty po kolei. Funkcje EMS są udostępniane przez przerwanie 67h, a zerowa wartość rejestru AH po powrocie z wywołania oznacza brak błędu.
Poniżej znajduje się przykładowy, gotowy program ilustrujący podane wyżej funkcjonalności (składnia 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 pamięci 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 ; sprawdź liczbę wolnych uchwytów pamięci 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 używanych uchwytów pamięci 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 = łączna liczba uchwytów sub bx, cx ; BX = liczba wolnych uchwytów jnz sa_uchwyty mov ah, 9 mov dx, brak_uchwytow int 21h mov ax, 4c05h int 21h sa_uchwyty: ; alokujemy jedną logiczną stronę pamięci 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 logiczną stronę pamięci na pierwszą stronę ; fizyczną w ramce pamięci EMS mov ax, 4400h ; mapuj do zerowej strony fizycznej xor bx, bx ; pierwsza strona logiczna ;mov dx, [uchwyt_pamieci] ; DX już zawiera uchwyt pamięci int 67h test ah, ah jz zapelnij_pamiec mov ah, 9 mov dx, blad_mapowanie int 21h ; dealokacja pamięci mov ah, 45h mov dx, [uchwyt_pamieci] int 67h mov ax, 4c07h int 21h zapelnij_pamiec: ; pamięć przydzielona, zapełniamy ją 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 pamięci 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, '$'
Więcej przykładów można znaleźć pod adresami:
Pamięć XMS to po prostu pamięć RAM o adresach powyżej 1MB.
Skoro pamięć XMS także jest położona powyżej granicy osiągalnej w trybie rzeczywistym, to także nie można się do niej bezpośrednio odwoływać. A skoro tak, to także potrzebny jest do niej sterownik (menadżer), taki jak HIMEM.SYS czy HimemX. Sterownik taki udostępnia swoje funkcje, z których mogą korzystać programy.
Menadżery XMS udostępniają mieszany interfejs - część funkcjonalności udostępniana jest przez przerwania, a część - poprzez bezpośrednie wywołanie funkcji w obszarze pamięci sterownika.
Dostęp do pamięci XMS nie odbywa się za pomocą ramek stron, wiec trzeba mieć przygotowane własne bufory na dane. Oto algorytm wykorzystania pamięci XMS w swoim programie:
Teraz omówimy te punkty po kolei. Funkcje XMS są udostępniane przez przerwanie 2Fh oraz przez funkcję znajdującą się wewnątrz sterownika, a wartość rejestru AX równa 1 po powrocie z większości wywołań oznacza brak błędu.
struc struk_kopia_xms dlugosc resd 1 uchwyt_zrodla resw 1 offset_zrodla resd 1 uchwyt_celu resw 1 offset_celu resd 1 endstrucPola oznaczają kolejno:
Poniżej znajduje się przykładowy, gotowy program pokazujący wymienione wyżej czynności (składnia NASM). Zauważcie, że wywołania funkcji obsługującej XMS muszą być wywołaniami dalekimi (CALL FAR), gdyż znajduje się ona w innym segmencie.
org 100h start: ; sprawdź, 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 wejścia do XMS (funkcji sterującej) mov ax, 4310h int 2fh mov [funkcja_xms], bx mov [funkcja_xms+2], es ; sprawdź ilość wolnej pamięci 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 pamięć równie dużą, co nasz bufor mov ah, 9 mov dx, bufor_dl >> 10 ; liczba kilobajtów 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 ; pamięć przydzielona, zapełniamy ją mov dword [opis_kopiowania + dlugosc], bufor_dl mov word [opis_kopiowania + uchwyt_zrodla], 0 ; pamięć 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 pamięci 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 pamięci 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
Więcej przykładów można znaleźć pod adresami: