Pamięć EMS i XMS

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.


EMS


(przeskocz EMS)

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:

  1. sprawdzić, czy sterownik EMS w ogóle jest załadowany
  2. pobrać numer segmentu zawierającego ramkę stron
  3. opcjonalnie sprawdzić, czy są jakieś wolne uchwyty pamięci (podobne do uchwytów plików)
  4. zaalokować odpowiednią liczbę logicznych stron pamięci (w obszarze powyżej 1MB)
  5. zmapować logiczne strony pamięci (w obszarze powyżej 1MB) na fizyczne strony znajdujące się w ramce stron (w obszarze poniżej 1MB)
  6. korzystać z pamięci
  7. zwolnić pamięć EMS (jeśli tego nie zrobimy, pamięć będzie zaznaczona jako niedostępna dla innych programów aż do restartu komputera)

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.

  1. Sprawdzić, czy sterownik EMS w ogóle jest załadowany.
    Robi się to w prosty sposób:

  2. Pobrać numer segmentu zawierającego ramkę stron.
    Skoro już wiemy, że sterownik jest załadowany, można korzystać z przerwania 67h. Do pobrania numeru segmentu zawierającego ramkę stron służy funkcja 41h:

  3. Opcjonalnie sprawdzić, czy są jakieś wolne uchwyty pamięci (podobne do uchwytów plików).
    Aby obliczyć liczbę wolnych uchwytów pamięci EMS, należy od łącznej liczby uchwytów odjąć liczbę uchwytów zajętych. Robi się to dwoma wywołaniami:

  4. Zaalokować odpowiednią liczbę logicznych stron pamięci (w obszarze powyżej 1MB).
    Tę czynność wykonuje się jednym wywołaniem:

  5. Zmapować logiczne strony pamięci (w obszarze powyżej 1MB) na fizyczne strony znajdujące się w ramce stron (w obszarze poniżej 1MB).
    Tę czynność również wykonuje się jednym wywołaniem:

  6. Korzystać z pamięci.
    Tu inwencja zależy tylko od twórcy programu. W większych obszarach pamięci można przechowywać obrazy i dźwięki dla gier, inne dane i cokolwiek akurat trzeba.

  7. Zwolnić pamięć EMS.
    Zwalnianie logicznych stron pamięci wykonuje się funkcją 45h:

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:


XMS

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:

  1. sprawdzić, czy sterownik XMS w ogóle jest załadowany
  2. pobrać adres głównej funkcji sterownika XMS, z której będziemy korzystać
  3. opcjonalnie sprawdzić, ile jest wolnej pamięci
  4. zaalokować odpowiednią ilość pamięci (w obszarze powyżej 1MB)
  5. korzystać z pamięci
  6. zwolnić pamięć XMS (jeśli tego nie zrobimy, pamięć będzie zaznaczona jako niedostępna dla innych programów aż do restartu komputera)

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.

  1. Sprawdzić, czy sterownik XMS w ogóle jest załadowany.
    Do tej czynności służy funkcja 4300h przerwania 2Fh:

  2. Pobrać adres głównej funkcji sterownika XMS, z której będziemy korzystać.
    W tym celu należy wywołać funkcję 4310h przerwania 2Fh:

  3. Opcjonalnie sprawdzić, ile jest wolnej pamięci.
    Do tego służy czynność numer 8 funkcji obsługującej XMS:

  4. Zaalokować odpowiednią ilość pamięci (w obszarze powyżej 1MB).
    Do alokacji pamięci służy czynność numer 9 funkcji obsługującej XMS:

  5. Korzystać z pamięci.
    W obszarach pamięci większych niż ten poniżej granicy 1MB można przechowywać obrazy i dźwięki dla gier, inne dane i cokolwiek akurat trzeba.
    Problem w tym, że skoro w XMS nie ma ramki stron, gdzie możemy sobie mapować pamięć, trzeba mieć własne bufory oraz sposób przekazania sterownikowi XMS informacji, że chcemy przenieść gdzieś zawartość pamięci.
    Do opisu pojedynczej operacji przenoszenia zawartości pamięci służy taka struktura (składnia 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 oznaczają kolejno:
    Odpowiednio wypełnioną strukturę przekazujemy do czynności 0Bh funkcji obsługującej XMS:

  6. Zwolnić pamięć XMS.
    Do dealokacji pamięci służy czynność numer 10 funkcji obsługującej XMS:

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:



Spis treści off-line (Alt+1)
Spis treści on-line (Alt+2)
Ułatwienia dla niepełnosprawnych (Alt+0)