§ Обработчик исключения страничного нарушения

Функции предлагаемого вашему вниманию обработчика исключения страничного нарушения просты - отобразить не присутствующую логическую страницу памяти на свободную физическую. При необходимости, обработчик должен создать таблицу страниц и очистить в ней нулевые биты всех элементов 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-го.
Из всего многообразия возможных реализаций подобных обработчиков вам предлагается следующий. Просто замените макрос "exeption_0e_handler" в файле "pmode8.lib" на приводимый ниже, а лучше - скачайте полный пример (в следующей главе). Хочу вас предупредить, что писать такой обработчик очень сложно, потому что отсутствие средств отладки совместно с чудесами в памяти делают отладку практически невозможной.
Основная проблема при написании подобной процедуры заключается в создании новой таблицы страниц и здесь эта проблема решена следующим образом: в программе определён сегмент 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