Глава 10. UPMM - 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 proc near ; Установка размера 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 endp
Для создания UPMM используется процедура "create_UPMM". Она способна создать UPMM размером до 128Кб, но используется для 4 килобайтовой UPMM. Эта процедура вызывается сразу после процедуры "get_phys_mem_size" и в качестве входного параметра использует размер памяти, возвращённый ею в EAX. Процедура "create_UPMM" использует также dd-переменную UPMM_size и макрос UPMM_sel - селектор дескриптора сегмента, описывающего UPMM:
create_UPMM proc near ; Создаёт 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 endp
Для поиска свободной страницы в UPMM используется процедура "find_free_phys_page", которая, по сути, ищет первый нулевой бит в UPMM. После того, как он будет найден, процедура вычисляет адрес физической страницы, которой соответствует этот бит и возвращает его в EAX.
При возврате из этой процедуры флаг переноса CF (в EFLAGS) содержит информацию о процессе поиска - если он сброшен, значит всё в порядке, страница найдена и её адрес находится в EAX. Если же CF установлен, то в UPMM больше нет нулевых бит, следовательно, вся доступная физическая память уже занята. Везде в нашем примере после вызова этой процедуры проверяется флаг CF и если он установлен, выводится сообщение о недостатке памяти и производится аварийное прекращение программы. При реализации системы виртуальной памяти, в таком случае следовало бы найти наименее используемую физическую страницу, свопнуть её на диск и использовать её далее как свободную страницу, но в нашем примере внимание концентрируется именно на обработчике исключения страничного нарушения, а не механизме виртуальной памяти.
find_free_phys_page proc near ; Ищет свободную физическую страницу используя битовую карту 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 endp
Следующая глава | Оглавление | Вопросы? Замечания? Пишите: sasm@narod.ru |
Copyright © Александр Семенко. |