§ Обработчик исключения страничного нарушения
Функции предлагаемого вашему вниманию обработчика исключения страничного нарушения просты - отобразить не присутствующую логическую страницу памяти на свободную физическую. При необходимости, обработчик должен создать таблицу страниц и очистить в ней нулевые биты всех элементов PTE - в нашем примере очищается вся таблица целиком - это быстрее и проще. Также в этом примере подразумевается, что единственная причина исключения - не присутствующая страница или таблица страниц; остальные причины у нас быть не могут, к ним относятся:- Попытка записи в страницу, предназначенную только для чтения - такое может быть только, если бит R/W (1-й бит в PTE или PDE) сброшен, а у нас он везде устанавливается.
- Нарушение уровня привилегий при обращении к странице - такое может быть только при обращении программы с уровня 1, 2 или 3 к странице, бит U/S (2-й бит в PTE или PDE) в которой сброшен (страница на системном уровне), либо наоборот, в процессорах, начиная с Intel486 при установленном бите WP (Write Protect) в CR0 при попытке записи с нулевого уровня в страницу уровня пользователя. Всё это не возможно в нашем примере по причине его простоты, т.к. весь код и данные в нём находятся на уровне 0.
- Установлены зарезервированные биты в элементах PTE или PDE - в нашем примере этого не может быть, т.к. любой элемент PTE или PDE конструируется из адреса и двух установленных битов - 0-го и 1-го.
Основная проблема при написании подобной процедуры заключается в создании новой таблицы страниц и здесь эта проблема решена следующим образом: в программе определён сегмент Page_Table, который находится по адресу 1Мб - 4Кб, т.е. занимает последнюю страницу первого мегабайта. Для того, чтобы очистить новую таблицу страниц, либо добавить в неё новый элемент PTE, обработчик отображает последнюю страницу 1-го мегабайта на ту физическую страницу, в которой расположена текущая таблица страниц и после перезагрузки регистра CR3 обращение к сегменту Page_Table обеспечивает доступ к этой странице.
Для того, чтобы отображать последнюю страницу 1-го мегабайта на любую, нужную обработчику, используется следующий "трюк": в программе определена переменная Page_Directory_adr, которая хранит адрес каталога страниц относительно основного сегмента данных, доступного по селектору Data_selector. Первая таблица страниц, соответствующая элементу PDE 0, находится сразу же после каталога страниц, т.е. адрес её начала на 4096 больше адреса, хранящегося в Page_Directory_adr. Последняя страница 1-го мегабайта описывается 255-м элементом PTE, который находится в таблице страниц по смещению 1020 (для наглядности используется 1024 - 4). Таким образом, отображение сегмента Page_Table на новую физическую страницу будет выглядеть так:
1 mov eax,page_address ; Какой-либо адрес страницы. 2 mov al,3 ; Устанавливаем биты 0 и 1 (зачем - см. 3 ; в тексте обработчика) 4 5 mov bx,Page_Directory_adr 6 add bx,4096 + 1024 - 4 7 mov [ bx ],eax ; Записали новый PTE 8 9 mov eax,cr3 ; Перезагрузили CR3 (т.е. PDBR). 10 mov cr3,eax 11 12 ; Всё, сегмент Page_Table отображён на физическую страницу, 13 ; находящуюся по адресу page_address.Хотелось бы заметить, что использование всяких "трюков", "уловок", "фокусов" и прочих "магических" приёмов является нормальным явлением при написании программ на Ассемблере, причём, использование новых особенностей процессоров, призванных, вроде бы, повысить эффективность работы, порождает дополнительные "трюки", хотя, позволяет избавиться от некоторых старых (например, адресация памяти через любые 32-разрядные регистры общего назначения значительно упрощает доступ к памяти).
Итак, обработчик исключения страничного нарушения:
1exeption_0e_handler: 2 3 cli 4 5 pushad 6 push es 7 push gs 8 9 mov tmp_ax, ax ; Сохраняем значение AX, т.к. перед 10 ; возвратом из обработчика нужно будет 11 ; извлечь из стека dw-код ошибки, который 12 ; в данном примере не используется. 13 14; Это тестовый кусок кода, предназначенный для вывода на экран числа 15; отображённых страниц: 16 17 mov dx, 0a26h 18 mov ax, pages_counter 19 inc ax 20 mov pages_counter,ax 21 call put_dw_num 22 23; Конец тестового кода. 24 25 mov ax, Page_Directory ; Page_Directory описывает 26 mov gs, ax ; каталог страниц. 27 28; CR2 содержит адрес страничного нарушения, т.е. тот адрес, при обращении 29; к которому процессор обнаружил, что соответствующая страница или таблица 30; страниц не присутствует в памяти и сгенерировал исключение. 31 32 mov ebx, cr2 33 mov ebp, ebx ; Сохраняем в EBP значение CR2, т.к. 34 ; доступ к регистрам общего назначения 35 ; быстрее, чем к регистрам управления. 36 37 shr ebx, 20 38 and bl, 11111100b 39 40; EBX = PDE (мы сбросили все биты адреса страничного нарушения, кроме 10 41; старших бит, которые содержат номер элемента каталога страниц (т.е. PDE). 42; Теперь в младшей половине EBX, т.е. в BX содержится PDE, умноженный 43; на 4 - это указатель на описание соответствующего элемента PDE в каталоге 44; страниц. 45 46 mov edx, [ gs:ebx ] ; EDX = Текущий PDE. 47 test dl,1 ; Текущий PDE отображён или нет? 48 jnz e0eh_1 ; Если да, то отображаем страницу. 49 50; Иначе - устанавливаем новую таблицу страниц. 51 52 call find_free_phys_page ; EAX = адрес свободной физической 53 ; страницы. 54 jc e0eh_err ; Если свободных страниц больше 55 ; нет, то после возврата из процедуры 56 ; find_free_phys_page будет 57 ; установлен флаг CF, иначе - сброшен. 58 59 mov al, 3 ; Запись в AL 3 можно рассматривать как, 60 ; установку двух бит - 0 и 1 регистра EAX. 61 ; При записи EAX в каталог страниц, эти биты 62 ; будут соответствовать битам P и R/W в 63 ; элементе PDE и будут означать, что таблица 64 ; страниц присутствует (P=1) и для всех её 65 ; страниц разрешено считывание и 66 ; запись (R/W=1). 67 68 mov [ gs:ebx ],eax ; Записали PDE. 69 70 mov edx, eax ; Временно сохранили адрес в EDX. 71 72; Теперь мы отобразим один сегмент - Page_Table (он состоит из одной 73; страницы) на этот адрес и очистим всю страницу, потому что теперь на неё 74; будет отображена новая таблица страниц и все её элементы должны иметь 75; 0 хотя бы в бите P. 76 77 mov bx,Page_Directory_adr ; DS:BX - указатель на начало 78 ; каталога страниц. 79 add bx,4096 + 1024 - 4 ; А теперь - указатель на 80 ; последний элемент PTE первой 81 ; таблицы страниц. 82 83 mov [ bx ], edx ; Записываем в него адрес новой физической 84 ; страницы памяти. 85 86 mov eax, cr3 ; Перегружаем содержимое CR3 (т.е. PDBR), 87 mov cr3, eax ; это заставит процессор сбросить все TLB и 88 ; использовать обновлённый каталог страниц и 89 ; таблицы страниц. 90 91; Очищаем новую физическую страницу: 92 93 mov ax, Page_Table 94 mov es, ax 95 xor di, di 96 mov cx, 1024 97 cld 98 rep stosd 99 100; Теперь из адреса страничного нарушения выделяем номер страницы (т.е. 101; элемент PTE), при обращении к которой возникло исключение. Эта страница 102; может быть либо не отображена (в её PTE сброшен бит P), либо иметь запрет 103; доступа по уровню привилегий, но в данном примере подразумевается, что 104; страница может быть только не отображена. 105 106 mov eax, ebp 107 shl eax, 10 108 shr eax, 20 109 and al, 11111100b 110 111 mov di, ax ; DI = номер PTE, умноженный на 4 - 112 ; получается указатель на этот PTE в 113 ; текущей таблице страниц. 114 115 call find_free_phys_page ; Ищем новую свободную физическую 116 ; страницу, на которую уже и 117 ; отобразим неприсутствующую 118 ; логическую страницу. 119 jc e0eh_err 120 121 mov al, 3 ; Устанавливаем биты 0 и 1. 122 mov [ es:di ], eax ; Записываем новый элемент PTE. 123 124 mov eax, cr3 ; Перезагружаем PDBR. 125 mov cr3, eax 126 127 jmp e0eh_end ; Сброс конвейера команд и переход на 128 ; конец процедуры. 129 130e0eh_1: 131; EBP = адрес страничного нарушения. 132; EBX = BX - указатель на тот PDE, в котором нужно отобразить страницу. 133; Если этот указатель совпадает с Current_PDE, то сегмент Page_Table уже 134; отображён на эту таблицу страниц, иначе - нет и мы будем его отображать. 135 136 mov ax, Current_PDE 137 cmp ax, bx 138 je e0eh_2 139 140 mov Current_PDE, bx 141 142 and dx, 0f000h 143 mov dl, 3 144 145 mov bx, Page_Directory_adr 146 add bx, 4096 + 1024 - 4 147 mov [ bx ],edx 148 149 mov eax, cr3 150 mov cr3, eax 151 152 jmp e0eh_2 153 154e0eh_2: 155; Сегмент Page_Table уже отображён на ту таблицу страниц, в которой нужно 156; отобразить страницу. 157 158 mov ax, Page_Table 159 mov es, ax 160 161 call find_free_phys_page 162 jc e0eh_err 163 164; Выделяем из адреса страничного нарушения номер PTE и преобразуем его 165; в смещение в таблице страниц. 166 167 mov ebx,ebp 168 shl ebx, 10 169 shr ebx, 20 170 and bl, 11111100b 171 172 mov al,3 ; Устанавливаем биты 0 и 1 (P и R/W). 173 mov [ es:bx ], eax ; Записываем новый PTE. 174 175 mov eax, cr3 ; Перезагружаем PDBR. 176 mov cr3, eax 177 178 jmp e0eh_end ; Сброс конвейера команд. 179 180e0eh_end: 181 pop gs 182 pop es 183 popad 184 185 pop ax ; Код ошибки здесь игнорируется. 186 187 mov ax, tmp_ax 188 189 iret 190 191e0eh_err: 192; Если больше нет свободных физических страниц, обработчик генерирует 193; сообщение о нехватке памяти и аварийно завершает программу. 194 195 lea bx, _no_mem_ 196 mov dx, 0200h 197 call put_zs 198 jmp Return_to_R_Mode 199