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

Функции предлагаемого вашему вниманию обработчика исключения страничного нарушения просты - отобразить не присутствующую логическую страницу памяти на свободную физическую. При необходимости, обработчик должен создать таблицу страниц и очистить в ней нулевые биты всех элементов 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 на новую физическую страницу будет выглядеть так:
	mov	eax,page_address	; Какой-либо адрес страницы.
	mov	al,3		;    Устанавливаем биты 0 и 1 (зачем - см.
				; в тексте обработчика)

	mov	bx,Page_Directory_adr
	add	bx,4096 + 1024 - 4
	mov	[ bx ],eax		; Записали новый PTE

	mov	eax,cr3		; Перезагрузили CR3 (т.е. PDBR).
	mov	cr3,eax

	;    Всё, сегмент Page_Table отображён на физическую страницу,
	; находящуюся по адресу page_address.
Хотелось бы заметить, что использование всяких "трюков", "уловок", "фокусов" и прочих "магических" приёмов является нормальным явлением при написании программ на Ассемблере, причём, использование новых особенностей процессоров, призванных, вроде бы, повысить эффективность работы, порождает дополнительные "трюки", хотя, позволяет избавиться от некоторых старых (например, адресация памяти через любые 32-разрядные регистры общего назначения значительно упрощает доступ к памяти).
Итак, обработчик исключения страничного нарушения:
exeption_0e_handler:

	cli

	pushad
	push	es
	push	gs

	mov	tmp_ax, ax	;    Сохраняем значение AX, т.к. перед
				; возвратом из обработчика нужно будет
				; извлечь из стека dw-код ошибки, который
				; в данном примере не используется.

;    Это тестовый кусок кода, предназначенный для вывода на экран числа
; отображённых страниц:

	mov	dx, 0a26h
	mov	ax, pages_counter
	inc	ax
	mov	pages_counter,ax
	call	put_dw_num

; Конец тестового кода.

	mov	ax, Page_Directory	;    Page_Directory описывает
	mov	gs, ax			; каталог страниц.

;    CR2 содержит адрес страничного нарушения, т.е. тот адрес, при обращении
; к которому процессор обнаружил, что соответствующая страница или таблица
; страниц не присутствует в памяти и сгенерировал исключение.

	mov	ebx, cr2
	mov	ebp, ebx	;    Сохраняем в EBP значение CR2, т.к.
				; доступ к регистрам общего назначения
				; быстрее, чем к регистрам управления.

	shr	ebx, 20
	and	bl, 11111100b

;    EBX = PDE (мы сбросили все биты адреса страничного нарушения, кроме 10
; старших бит, которые содержат номер элемента каталога страниц (т.е. PDE).
; Теперь в младшей половине EBX, т.е. в BX содержится PDE, умноженный
; на 4 - это указатель на описание соответствующего элемента PDE в каталоге
; страниц.

	mov	edx, [ gs:ebx ]		; EDX = Текущий PDE.
	test	dl,1		; Текущий PDE отображён или нет?
	jnz	e0eh_1		; Если да, то отображаем страницу.

; Иначе - устанавливаем новую таблицу страниц.

	call	find_free_phys_page	;    EAX = адрес свободной физической
					; страницы.
	jc	e0eh_err		;    Если свободных страниц больше
					; нет, то после возврата из процедуры
					; find_free_phys_page будет
					; установлен флаг CF, иначе - сброшен.

	mov	al, 3		;    Запись в AL 3 можно рассматривать как,
				; установку двух бит - 0 и 1 регистра EAX.
				; При записи EAX в каталог страниц, эти биты
				; будут соответствовать битам P и R/W в
				; элементе PDE и будут означать, что таблица
				; страниц присутствует (P=1) и для всех её
				; страниц разрешено считывание и
				; запись (R/W=1).

	mov	[ gs:ebx ],eax	; Записали PDE.

	mov	edx, eax		; Временно сохранили адрес в EDX.

;    Теперь мы отобразим один сегмент - Page_Table (он состоит из одной
; страницы) на этот адрес и очистим всю страницу, потому что теперь на неё
; будет отображена новая таблица страниц и все её элементы должны иметь
; 0 хотя бы в бите P.

	mov	bx,Page_Directory_adr	;    DS:BX - указатель на начало
					; каталога страниц.
	add	bx,4096 + 1024 - 4	;    А теперь - указатель на
					; последний элемент PTE первой
					; таблицы страниц.

	mov	[ bx ], edx	;    Записываем в него адрес новой физической
				; страницы памяти.

	mov	eax, cr3	;    Перегружаем содержимое CR3 (т.е. PDBR),
	mov	cr3, eax	; это заставит процессор сбросить все TLB и
				; использовать обновлённый каталог страниц и
				; таблицы страниц.

; Очищаем новую физическую страницу:

	mov	ax, Page_Table
	mov	es, ax
	xor	di, di
	mov	cx, 1024
	cld
	rep	stosd

;    Теперь из адреса страничного нарушения выделяем номер страницы (т.е.
; элемент PTE), при обращении к которой возникло исключение. Эта страница
; может быть либо не отображена (в её PTE сброшен бит P), либо иметь запрет
; доступа по уровню привилегий, но в данном примере подразумевается, что
; страница может быть только не отображена.

	mov	eax, ebp
	shl	eax, 10
	shr	eax, 20
	and	al, 11111100b

	mov	di, ax		;    DI = номер PTE, умноженный на 4 -
				; получается указатель на этот PTE в
				; текущей таблице страниц.

	call	find_free_phys_page	;    Ищем новую свободную физическую
					; страницу, на которую уже и
					; отобразим неприсутствующую
					; логическую страницу.
	jc	e0eh_err

	mov	al, 3			; Устанавливаем биты 0 и 1.
	mov	[ es:di ], eax	; Записываем новый элемент PTE.

	mov	eax, cr3		; Перезагружаем PDBR.
	mov	cr3, eax

	jmp	e0eh_end	;    Сброс конвейера команд и переход на
				; конец процедуры.

e0eh_1:
; EBP = адрес страничного нарушения.
;    EBX = BX - указатель на тот PDE, в котором нужно отобразить страницу.
; Если этот указатель совпадает с Current_PDE, то сегмент Page_Table уже
; отображён на эту таблицу страниц, иначе - нет и мы будем его отображать.

	mov	ax, Current_PDE
	cmp	ax, bx
	je	e0eh_2

	mov	Current_PDE, bx

	and	dx, 0f000h
	mov	dl, 3

	mov	bx, Page_Directory_adr
	add	bx, 4096 + 1024 - 4
	mov	[ bx ],edx

	mov	eax, cr3
	mov	cr3, eax

	jmp	e0eh_2

e0eh_2:
;    Сегмент Page_Table уже отображён на ту таблицу страниц, в которой нужно
; отобразить страницу.

	mov	ax, Page_Table
	mov	es, ax

	call	find_free_phys_page
	jc	e0eh_err

;    Выделяем из адреса страничного нарушения номер PTE и преобразуем его
; в смещение в таблице страниц.

	mov	ebx,ebp
	shl	ebx, 10
	shr	ebx, 20
	and	bl, 11111100b

	mov	al,3			; Устанавливаем биты 0 и 1 (P и R/W).
	mov	[ es:bx ], eax	; Записываем новый PTE.

	mov	eax, cr3		; Перезагружаем PDBR.
	mov	cr3, eax

	jmp	e0eh_end	; Сброс конвейера команд.

e0eh_end:
	pop	gs
	pop	es
	popad

	pop	ax		; Код ошибки здесь игнорируется.

	mov	ax, tmp_ax

	iret

e0eh_err:
;    Если больше нет свободных физических страниц, обработчик генерирует
; сообщение о нехватке памяти и аварийно завершает программу.

	lea	bx, _no_mem_
	mov	dx, 0200h
	call	put_zs
	jmp	Return_to_R_Mode