§ 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 производится перед переходом в защищённый режим следующим образом:
1 mov edx, UPMM_size 2 mov eax, cr3 ; UPMM создаём в странице, следующей сразу 3 add eax, 2000h ; за первой таблицей страниц. 4 mov cx, 92h ; Сегмент данных. 5 call set_descriptorЗдесь в переменной UPMM_size содержится размер сегмента (4Кб). Для .com-программы его можно менять в небольших пределах, т.к. при работе наших примеров подразумевается, что они, во-первых, являются .com-программами, а во-вторых, вместе со всеми данными, занимают не более 64Кб памяти.
Прежде, чем будет создана UPMM, необходимо определить её размер в dd-переменной UPMM_size, а также какие страницы физической памяти 1-го мегабайта уже заняты и какие - свободны. Для этого предусмотрена процедура "setup_for_UPMM":
1setup_for_UPMM: 2; Установка размера UPMM (в 4Кб), определение адреса и размера свободной 3; DOS-памяти и физических страниц, на которые она приходится. 4; Возврат: значения двух переменных: 5; DOS_free_mem_start_page - страница, с которой начинается DOS-память, 6; DOS_free_mem_pages - число целых страниц, на которые она приходится. 7 8 xor ebp,ebp ; Число страниц по умолчанию свободной 9 ; DOS-памяти (пока - 0). 10 11 mov UPMM_size,4096 12 13; Сокращаем выделенный блок памяти этой программе до 64Кб: 14 15 mov ah,4ah 16 push cs 17 pop es 18 mov bx,4096 ; Число параграфов, соответствующее 64Кб. 19 int 21h 20 21; Выясняем размер свободной DOS-памяти. 22 23 mov bx,-1 24 mov ah,48h 25 int 21h 26 27; BX = максимальный размер в параграфах 28 29 movzx ecx,bx 30 shl ecx,4 ; ECX = число байт свободной памяти в 31 ; первом мегабайте. 32 33 mov ah,48h 34 int 21h ; Распределяем всю свободную DOS-память. 35 36 push ax ; Сохраняем сегмент начала блока памяти. 37 38 mov es,ax 39 mov ah,49h 40 int 21h ; Освобождаем распределённую память. 41 42 pop ax 43 movzx eax,ax 44 shl eax,4 ; EAX = физический адрес начала 45 ; свободной DOS-памяти. 46 mov ebx,eax 47 add ebx,ecx ; Максимальное значение адреса (+1) в 48 ; свободном блоке DOS-памяти. 49 50 mov dx,ax 51 and ax,0f000h 52 sub dx,ax 53 cmp dx,0 ; EAX был кратен 4Кб? 54 je sfupmm_1 ; Если да, то свободная DOS-память 55 ; начинается на границе страницы. 56 57 add eax,1000h ; Если нет - выравниваем блок на границу 58 ; следующей страницы. 59 60sfupmm_1: 61 cmp eax,ebx ; Если начало блока (уже выравненное на 62 ; границу страницы) превысило её предел, 63 jae sfupmm_end ; то - выход из процедуры. 64 65 66 shr eax,12 ; EAX = AL = номер страницы, с которой 67 ; начинается блок DOS-памяти. 68 shr ecx,12 ; ECX = CL = число целых страниц этого блока. 69 mov ebp,ecx 70 71 mov DOS_free_mem_start_page,al 72 73sfupmm_end: 74 mov ecx,ebp 75 mov DOS_free_mem_pages,cl 76 77 retДля создания UPMM используется процедура "create_UPMM". Она способна создать UPMM размером до 128Кб, но используется для 4 килобайтовой UPMM. Эта процедура вызывается сразу после процедуры "get_phys_mem_size" и в качестве входного параметра использует размер памяти, возвращённый ею в EAX. Процедура "create_UPMM" использует также dd-переменную UPMM_size и макрос UPMM_sel - селектор дескриптора сегмента, описывающего UPMM:
1create_UPMM: 2; Создаёт UPMM (Used Physical Memory Map - битовая карта 3; использованной физической памяти). 4; EAX = число байт физической памяти; используеюся UPMM_sel, UPMM_size. 5 6 push eax 7 push ecx 8 push edx 9 push es 10 push edi 11 12 shr eax,12 ; Теперь в EAX - число страниц 13 ; физической памяти. 14 mov ecx,eax 15 and cl,11100000b 16 sub eax,ecx 17 shr ecx,5 ; ECX = (число страниц физической памяти) / 32. 18 ; EAX = AL = остаток от этого деления. 19 mov dl,al ; Сохраняем остаток. 20 21 ; ECX = CX = число двойных слов, из которых состоит UPMM. 22 23; Устанавливаем все биты в UPMM 24 25 push cx 26 27 mov ax,UPMM_sel 28 mov es,ax 29 xor edi,edi 30 31 mov ecx,UPMM_size ; Размер сегмента, в котором будет 32 ; хранится UPMM. 33 34 shr ecx,2 ; ECX = CX. Максимальный размер UPMM = 128Кб 35 ; (для 4Гб памяти), следовательно, число двойных 36 ; слов, из которых она состоит не превысит 65'536 37 38 cld 39 xor eax,eax 40 dec eax ; EAX = -1 = FFFF FFFFh 41 42 db 67h ; Атрибут размера опреранда - цикл будет по ECX. 43 rep stosd ; Заполняем таблицу. 44 45; Сбрасываем свободные страницы 1-го мегабайта 46 47 push dx 48 49 mov al,DOS_free_mem_start_page 50 mov ah,al 51 and al,11100000b 52 sub ah,al 53 shr al,3 ; AL = AL / 8, AH = остаток от деления 54 ; DOS_free_mem_start_page на 32. 55 56 movzx edi,al ; ES:EDI указывает в UPMM на двойное, 57 ; слово в котором начинается описание 58 ; блока свободной DOS-памяти. 59 60 mov edx,-1 ; EDX будет использоваться как XOR-маска 61 ; для сброса битов в двойном слове из UPMM. 62 63 ; AH = число бит, которые должны остаться установленными, начиная 64 ; с 0-го разряда регистра. 65 66 mov cl,ah ; CL = число бит, на которое нужно 67 ; сдвинуть маску в EDX. 68 shr edx,cl ; Сбрасываем биты. 69 shl edx,cl 70 71 mov eax,es:[ edi ] 72 xor eax,edx 73 74 mov ch,32 75 sub ch,cl 76 mov cl,ch ; CL = число бит, сброшенных в EDX (и в EAX). 77 78 mov ch,DOS_free_mem_pages 79 cmp ch,cl 80 ja cupmm_1 81 82 ; Если свободных страниц меньше, чем сброшенных бит в EAX, то нужно 83 ; установить обратно те, которые заняты. 84 85 sub cl,ch 86 mov ch,32 87 sub ch,cl 88 mov cl,ch ; Число бит, на которое нужно сдвинуть EDX 89 90 mov edx,-1 ; Теперь EDX будет использоваться 91 ; как OR-маска. 92 93 shr edx,cl ; Сбрасываем младшие биты 94 shl edx,cl 95 96 or eax,edx 97 98 stosd ; Всё, установили. 99 jmp cupmm_2 100 101cupmm_1: 102 sub ch,cl 103 stosd 104 105 mov eax,es:[ edi ] 106 107 xor eax,eax 108 mov cl,32 109 cmp ch,cl 110 jae cupmm_1 ; Очищаем нулями все двойные слова, 111 ; пока CH > 32 112 113 cmp ch,0 114 je cupmm_2 115 116 sub cl,ch 117 shl eax,cl 118 shr eax,cl ; Сбрасываем оставшиеся биты 119 120 stosd ; Всё, установили. 121 122cupmm_2: 123; Страницы, используемые как HMA (первые 64Кб 2-го мегабайта), оставляем 124; как занятые. 125 126; Теперь, используя значение в CX и DX можно записывать UPMM: 127 128 pop dx ; Восстанавливаем остаток деления числа страниц 129 ; физической памяти на 32. 130 pop cx ; Восстанавливаем число двойных слов, из 131 ; которых состоит UPMM. Значение в CX - не менее 8. 132 133 sub cx,8 ; Вычитаем 256 страниц, соответствующих 134 ; первому мегабайту (8 двойных слов 135 ; содержат 256 бит и описывают 256 страниц). 136 137 jcxz cupmm_end ; Всё? Памяти только 1Мб? 138 139 mov edi,32 ; ES:EDI указывает в UPMM на начало 140 ; 2-го мегабайта. 141 142 mov eax,es:[ edi ] 143 shl eax,16 ; Сбрасываем старшие 16 бит (младшие 144 shr eax,16 ; описывают HMA). 145 146 stosd 147 148; Теперь нужно вычесть 32 из числа оставшихся физических страниц: 149 150 dec cx 151 jcxz cupmm_3 152 153; Сбрасываем все биты, соответствующие свободным страницам: 154 ; сначала блоками по 32 страницы: 155 156 xor eax,eax 157 rep stosd 158 159cupmm_3: 160 ; потом - оставшееся число страниц: 161 162 mov cl,dl 163 mov eax,es:[ edi ] 164 shr eax,cl 165 shl eax,cl 166 167 stosd 168 169cupmm_end: 170 pop edi 171 pop es 172 pop edx 173 pop ecx 174 pop eax 175 176 retДля поиска свободной страницы в UPMM используется процедура "find_free_phys_page", которая, по сути, ищет первый нулевой бит в UPMM. После того, как он будет найден, процедура вычисляет адрес физической страницы, которой соответствует этот бит и возвращает его в EAX.
При возврате из этой процедуры флаг переноса CF (в EFLAGS) содержит информацию о процессе поиска - если он сброшен, значит всё в порядке, страница найдена и её адрес находится в EAX. Если же CF установлен, то в UPMM больше нет нулевых бит, следовательно, вся доступная физическая память уже занята. Везде в нашем примере после вызова этой процедуры проверяется флаг CF и если он установлен, выводится сообщение о недостатке памяти и производится аварийное прекращение программы. При реализации системы виртуальной памяти, в таком случае следовало бы найти наименее используемую физическую страницу, свопнуть её на диск и использовать её далее как свободную страницу, но в нашем примере внимание концентрируется именно на обработчике исключения страничного нарушения, а не механизме виртуальной памяти.
1find_free_phys_page: 2; Ищет свободную физическую страницу используя битовую карту UPMM. 3; Возврат: CF = 1 - свободных страниц нет. 4; CF = 0 - EAX = физический адрес найденной страницы. 5 6 push ebx 7 push ecx 8 push edx 9 push es 10 push edi 11 12 mov ax,UPMM_sel 13 mov es,ax 14 xor edi,edi 15 mov ecx,phys_mem_size 16 shr ecx,12 ; Если разделить phys_mem_size на 4096, то мы 17 ; получим число страниц физической памяти. 18 mov edx,ecx 19 and cl,11100000b 20 sub edx,ecx 21 shr ecx,5 ; Если это число страниц физической памяти 22 ; разделить ещё на 32, то мы получим число блоков 23 ; по 32 страницы - его мы и будем использовать 24 ; в цикле просмотра UPMM. При этом в EDX = DL будет 25 ; остаток от этого деления. 26 27; Цикл будет по CX, т.к. ECX был сдвинут на 17 разрядов вправо. 28 29ffpp_1: 30 mov eax,es:[ edi ] 31 32 cmp eax,-1 ; -1 = FFFF FFFFh - проверяем, все ли 33 ; страницы заняты в данном блоке 34 ; из 32-х страниц. 35 je ffpp_3 ; Если все, то переходим на "ffpp_3". 36 37 push cx 38 mov cx,32 39 40ffpp_2: 41 shr eax,1 ; Сдвиг помещает бит во флаг CF. 42 jnc ffpp_find ; Если был сдвинут бит, равный 0, то мы 43 ; нашли свободную страницу. 44 loop ffpp_2 45 46 pop cx 47 48ffpp_3: 49 add edi,4 50 loop ffpp_1 51 52 mov cl,dl 53 jcxz ffpp_bad_end 54 push cx 55 56ffpp_4: 57 mov eax,es:[ edi ] 58 shr eax,1 59 jnc ffpp_find 60 61 loop ffpp_4 62 63 pop cx 64 65ffpp_bad_end: 66 stc ; CF = 1 - страница не найдена. 67 68ffpp_end: 69 pop edi 70 pop es 71 pop edx 72 pop ecx 73 pop ebx 74 75 ret 76 77ffpp_find: 78 mov al,32 79 sub al,cl 80 mov cl,al ; Теперь CL содержит номер обнаруженного 81 ; нулевого бита. 82 83 pop ax ; Восстанавливаем сохранённое значение, 84 ; CX теперь оно нам не нужно. 85 86; Теперь нужно установить в UPMM найденный бит - теперь эта страница 87; будет занята. 88 89 mov eax,1 90 shl eax,cl ; В EAX установлен только найденный бит. 91 or es:[ edi ],eax ; Устанавливаем его в UPMM. 92 93; Теперь по позиции этого бита в UPMM вычисляем физический адрес страницы. 94 95 mov eax,edi ; EDI используется как указатель в UPMM. Т.к. 96 ; каждый байт UPMM описывает 8 страниц (это - 32Кб 97 ; памяти), а EDI у нас увеличивался в цикле на 4 98 ; то умножив EDI на 32Кб мы получим физический 99 ; адрес первой страницы блока из 32-х страниц. 100 ; Все вычисления адреса мы будем производить 101 ; в EAX. 102 103 shl eax,15 ; EAX = EAX * 32K. 104 movzx ecx,cl ; CL = ECX = номер бита в блоке из 32-х бит. 105 106 shl ecx,12 ; Т.к. этот бит определяет страницу, то 107 ; умножив его позицию на 4096 мы получим 108 ; смещение адреса этой страницы 109 ; относительно блока. 110 111 add eax,ecx ; Всё. EAX содержит физический адрес 112 ; найденной свободной страницы физической 113 ; памяти. 114 115 clc ; CF = 0 - страница найдена. 116 117 jmp ffpp_end 118