§ Used Physical Memory Map

Для того, чтобы обработчик страничного нарушения "знал", какие страницы физической памяти доступны и где именно они находятся, в нашем примере вводится битовая карта, которая будет называться UPMM (от англ. Used Physical Memory Map - карта использованной физической памяти). Эта аббревиатура и технология является число программным продуктом, придуманном мною лично, в процессорах Intel и их клонах нет ничего подобного; UPMM используется как один из возможных вариантов управления распределением памяти, при этом относительно простым и надёжным.
UPMM по сути представляет собой массив размером 4Кб, а по смыслу - битовую карту размером 32 Кбита. Каждый бит UPMM соответствует одной физической странице памяти; если он сброшен (т.е. равен 0), значит страница - свободна, если установлен (1) - то занята.
Работа с UPMM происходит блоками по 32 бита, т.е. если, например, нужно обратиться к 49-биту, производится чтение двойного слова по смещению 4 относительно начала UPMM, а уже в этом двойном слове обрабатывается 17-й бит.
Обработка битовой карты блоками по 32 бита позволяет максимально быстро обращаться к ней - ведь всё равно процессор тратит одинаковое число тактов при обращении к байту или двойному слову в памяти (если, конечно, процессор - не SX), а оптимизация по скорости у многих ранних моделей вообще относится только к командам, обрабатывающим 32-разрядные операнды.
Для удобства работы с UPMM, для неё создаётся отдельный сегмент данных, доступный через селектор UPMM_sel. Размер этого сегмента - 4Кб, что позволяет описать до 128Мб физической памяти. Для демонстрации страничного преобразования этого вполне достаточно, но для полнофункционального обработчика понадобится сегмент размером 128Кб, чтобы описать 4Гб памяти.
Установка сегмента, описывающего UPMM производится перед переходом в защищённый режим следующим образом:
	mov  edx, UPMM_size
	mov  eax, cr3	; UPMM создаём в странице, следующей сразу
	add  eax, 2000h	; за первой таблицей страниц.
	mov  cx, 92h    ; Сегмент данных.
	call set_descriptor
Здесь в переменной UPMM_size содержится размер сегмента (4Кб). Для .com-программы его можно менять в небольших пределах, т.к. при работе наших примеров подразумевается, что они, во-первых, являются .com-программами, а во-вторых, вместе со всеми данными, занимают не более 64Кб памяти.
Прежде, чем будет создана UPMM, необходимо определить её размер в dd-переменной UPMM_size, а также какие страницы физической памяти 1-го мегабайта уже заняты и какие - свободны. Для этого предусмотрена процедура "setup_for_UPMM":
setup_for_UPMM:
;    Установка размера UPMM (в 4Кб), определение адреса и размера свободной
; DOS-памяти и физических страниц, на которые она приходится.
; Возврат: значения двух переменных:
;	DOS_free_mem_start_page - страница, с которой начинается DOS-память,
;	DOS_free_mem_pages - число целых страниц, на которые она приходится.

	xor	ebp,ebp	;    Число страниц по умолчанию свободной
				; DOS-памяти (пока - 0).

	mov	UPMM_size,4096

; Сокращаем выделенный блок памяти этой программе до 64Кб:

	mov	ah,4ah
	push	cs
	pop	es
	mov	bx,4096		; Число параграфов, соответствующее 64Кб.
	int	21h

; Выясняем размер свободной DOS-памяти.

	mov	bx,-1
	mov	ah,48h
	int	21h

; BX = максимальный размер в параграфах

	movzx	ecx,bx
	shl	ecx,4		;    ECX = число байт свободной памяти в
				; первом мегабайте.

	mov	ah,48h
	int	21h		; Распределяем всю свободную DOS-память.

	push	ax		; Сохраняем сегмент начала блока памяти.

	mov	es,ax
	mov	ah,49h
	int	21h		; Освобождаем распределённую память.

	pop	ax
	movzx	eax,ax
	shl	eax,4		;    EAX = физический адрес начала
				; свободной DOS-памяти.
	mov	ebx,eax
	add	ebx,ecx	;    Максимальное значение адреса (+1) в
				; свободном блоке DOS-памяти.

	mov	dx,ax
	and	ax,0f000h
	sub	dx,ax
	cmp	dx,0		; EAX был кратен 4Кб?
	je	sfupmm_1	;    Если да, то свободная DOS-память
				; начинается на границе страницы.

	add	eax,1000h	;    Если нет - выравниваем блок на границу
				; следующей страницы.

sfupmm_1:
	cmp	eax,ebx	;    Если начало блока (уже выравненное на
				; границу страницы) превысило её предел,
	jae	sfupmm_end	; то - выход из процедуры.


	shr	eax,12		;    EAX = AL = номер страницы, с которой
				; начинается блок DOS-памяти.
	shr	ecx,12		; ECX = CL = число целых страниц этого блока.
	mov	ebp,ecx

	mov	DOS_free_mem_start_page,al

sfupmm_end:
	mov	ecx,ebp
	mov	DOS_free_mem_pages,cl

	ret
Для создания UPMM используется процедура "create_UPMM". Она способна создать UPMM размером до 128Кб, но используется для 4 килобайтовой UPMM. Эта процедура вызывается сразу после процедуры "get_phys_mem_size" и в качестве входного параметра использует размер памяти, возвращённый ею в EAX. Процедура "create_UPMM" использует также dd-переменную UPMM_size и макрос UPMM_sel - селектор дескриптора сегмента, описывающего UPMM:
create_UPMM:
;    Создаёт UPMM (Used Physical Memory Map - битовая карта
; использованной физической памяти).
; EAX = число байт физической памяти; используеюся UPMM_sel, UPMM_size.

	push	eax
	push	ecx
	push	edx
	push	es
	push	edi

	shr	eax,12		;    Теперь в EAX - число страниц
				; физической памяти.
	mov	ecx,eax
	and	cl,11100000b
	sub	eax,ecx
	shr	ecx,5		; ECX = (число страниц физической памяти) / 32.
				; EAX = AL = остаток от этого деления.
	mov	dl,al		; Сохраняем остаток.

 ; ECX = CX = число двойных слов, из которых состоит UPMM.

; Устанавливаем все биты в UPMM

	push	cx

	mov	ax,UPMM_sel
	mov	es,ax
	xor	edi,edi

	mov	ecx,UPMM_size	;    Размер сегмента, в котором будет
					; хранится UPMM.

	shr	ecx,2	;    ECX = CX. Максимальный размер UPMM = 128Кб
			; (для 4Гб памяти), следовательно, число двойных
			; слов, из которых она состоит не превысит 65'536

	cld
	xor	eax,eax
	dec	eax	; EAX = -1 = FFFF FFFFh

	db	67h	; Атрибут размера опреранда - цикл будет по ECX.
	rep	stosd	; Заполняем таблицу.

; Сбрасываем свободные страницы 1-го мегабайта

	push	dx

	mov	al,DOS_free_mem_start_page
	mov	ah,al
	and	al,11100000b
	sub	ah,al
	shr	al,3		;    AL = AL / 8, AH = остаток от деления
				; DOS_free_mem_start_page на 32.

	movzx	edi,al		;    ES:EDI указывает в UPMM на двойное,
				; слово в котором начинается описание
				; блока свободной DOS-памяти.

	mov	edx,-1		;    EDX будет использоваться как XOR-маска
				; для сброса битов в двойном слове из UPMM.

 ; AH = число бит, которые должны остаться установленными, начиная
 ;  с 0-го разряда регистра.

	mov	cl,ah		;    CL = число бит, на которое нужно
				; сдвинуть маску в EDX.
	shr	edx,cl		; Сбрасываем биты.
	shl	edx,cl

	mov	eax,es:[ edi ]
	xor	eax,edx

	mov	ch,32
	sub	ch,cl
	mov	cl,ch		; CL = число бит, сброшенных в EDX (и в EAX).

	mov	ch,DOS_free_mem_pages
	cmp	ch,cl
	ja	cupmm_1

 ; Если свободных страниц меньше, чем сброшенных бит в EAX, то нужно
 ;  установить обратно те, которые заняты.

	sub	cl,ch
	mov	ch,32
	sub	ch,cl
	mov	cl,ch		; Число бит, на которое нужно сдвинуть EDX

	mov	edx,-1		;    Теперь EDX будет использоваться
				; как OR-маска.

	shr	edx,cl		; Сбрасываем младшие биты
	shl	edx,cl

	or	eax,edx

	stosd			; Всё, установили.
	jmp	cupmm_2

cupmm_1:
	sub	ch,cl
	stosd

	mov	eax,es:[ edi ]

	xor	eax,eax
	mov	cl,32
	cmp	ch,cl
	jae	cupmm_1	;    Очищаем нулями все двойные слова,
				; пока CH > 32

	cmp	ch,0
	je	cupmm_2

	sub	cl,ch
	shl	eax,cl
	shr	eax,cl		; Сбрасываем оставшиеся биты

	stosd			; Всё, установили.

cupmm_2:
;    Страницы, используемые как HMA (первые 64Кб 2-го мегабайта), оставляем
; как занятые.

; Теперь, используя значение в CX и DX можно записывать UPMM:

	pop	dx	;    Восстанавливаем остаток деления числа страниц
			; физической памяти на 32.
	pop	cx	;    Восстанавливаем число двойных слов, из
			; которых состоит UPMM. Значение в CX - не менее 8.

	sub	cx,8	;    Вычитаем 256 страниц, соответствующих
			; первому мегабайту (8 двойных слов
			; содержат 256 бит и описывают 256 страниц).

	jcxz	cupmm_end	; Всё? Памяти только 1Мб?

	mov	edi,32		;    ES:EDI указывает в UPMM на начало
				; 2-го мегабайта.

	mov	eax,es:[ edi ]
	shl	eax,16		;    Сбрасываем старшие 16 бит (младшие
	shr	eax,16		; описывают HMA).

	stosd

; Теперь нужно вычесть 32 из числа оставшихся физических страниц:

	dec	cx
	jcxz	cupmm_3

; Сбрасываем все биты, соответствующие свободным страницам:
 ; сначала блоками по 32 страницы:

	xor	eax,eax
	rep	stosd

cupmm_3:
 ; потом - оставшееся число страниц:

	mov	cl,dl
	mov	eax,es:[ edi ]
	shr	eax,cl
	shl	eax,cl

	stosd

cupmm_end:
	pop	edi
	pop	es
	pop	edx
	pop	ecx
	pop	eax

	ret
Для поиска свободной страницы в UPMM используется процедура "find_free_phys_page", которая, по сути, ищет первый нулевой бит в UPMM. После того, как он будет найден, процедура вычисляет адрес физической страницы, которой соответствует этот бит и возвращает его в EAX.
При возврате из этой процедуры флаг переноса CF (в EFLAGS) содержит информацию о процессе поиска - если он сброшен, значит всё в порядке, страница найдена и её адрес находится в EAX. Если же CF установлен, то в UPMM больше нет нулевых бит, следовательно, вся доступная физическая память уже занята. Везде в нашем примере после вызова этой процедуры проверяется флаг CF и если он установлен, выводится сообщение о недостатке памяти и производится аварийное прекращение программы. При реализации системы виртуальной памяти, в таком случае следовало бы найти наименее используемую физическую страницу, свопнуть её на диск и использовать её далее как свободную страницу, но в нашем примере внимание концентрируется именно на обработчике исключения страничного нарушения, а не механизме виртуальной памяти.
find_free_phys_page:
; Ищет свободную физическую страницу используя битовую карту UPMM.
; Возврат:	CF = 1 - свободных страниц нет.
;		CF = 0 - EAX = физический адрес найденной страницы.

	push	ebx
	push	ecx
	push	edx
	push	es
	push	edi

	mov	ax,UPMM_sel
	mov	es,ax
	xor	edi,edi
	mov	ecx,phys_mem_size
	shr	ecx,12		;    Если разделить phys_mem_size на 4096, то мы
				; получим число страниц физической памяти.
	mov	edx,ecx
	and	cl,11100000b
	sub	edx,ecx
	shr	ecx,5	;   Если это число страниц физической памяти
			; разделить ещё на 32, то мы получим число блоков
			; по 32 страницы - его мы и будем использовать
			; в цикле просмотра UPMM. При этом в EDX = DL будет
			; остаток от этого деления.

; Цикл будет по CX, т.к. ECX был сдвинут на 17 разрядов вправо.

ffpp_1:
	mov	eax,es:[ edi ]

	cmp	eax,-1		;    -1 = FFFF FFFFh - проверяем, все ли
				; страницы заняты в данном блоке
				; из 32-х страниц.
	je	ffpp_3		;    Если все, то переходим на "ffpp_3".

	push	cx
	mov	cx,32

ffpp_2:
	shr	eax,1		;    Сдвиг помещает бит во флаг CF.
	jnc	ffpp_find	;    Если был сдвинут бит, равный 0, то мы
				; нашли свободную страницу.
	loop	ffpp_2

	pop	cx

ffpp_3:
	add	edi,4
	loop	ffpp_1

	mov	cl,dl
	jcxz	ffpp_bad_end
	push	cx

ffpp_4:
	mov	eax,es:[ edi ]
	shr	eax,1
	jnc	ffpp_find

	loop	ffpp_4

	pop	cx

ffpp_bad_end:
	stc		; CF = 1 - страница не найдена.

ffpp_end:
	pop	edi
	pop	es
	pop	edx
	pop	ecx
	pop	ebx

	ret

ffpp_find:
	mov	al,32
	sub	al,cl
	mov	cl,al		;    Теперь CL содержит номер обнаруженного
				; нулевого бита.

	pop	ax		;    Восстанавливаем сохранённое значение,
				; CX теперь оно нам не нужно.

;    Теперь нужно установить в UPMM найденный бит - теперь эта страница
; будет занята.

	mov	eax,1
	shl	eax,cl		; В EAX установлен только найденный бит.
	or	es:[ edi ],eax	; Устанавливаем его в UPMM.

; Теперь по позиции этого бита в UPMM вычисляем физический адрес страницы.

	mov	eax,edi	;    EDI используется как указатель в UPMM. Т.к.
				; каждый байт UPMM описывает 8 страниц (это - 32Кб
				; памяти), а EDI у нас увеличивался в цикле на 4
				; то умножив EDI на 32Кб мы получим физический
				; адрес первой страницы блока из 32-х страниц.
				;    Все вычисления адреса мы будем производить
				; в EAX.

	shl	eax,15		; EAX = EAX * 32K.
	movzx	ecx,cl		; CL = ECX = номер бита в блоке из 32-х бит.

	shl	ecx,12		;    Т.к. этот бит определяет страницу, то
				; умножив его позицию на 4096 мы получим
				; смещение адреса этой страницы
				; относительно блока.

	add	eax,ecx	;    Всё. EAX содержит физический адрес
				; найденной свободной страницы физической
				; памяти.

	clc		; CF = 0 - страница найдена.

	jmp	ffpp_end
24 янв, 2023
© 2007-2023 Все лоскуты ништяково переколочены