3. Листинг программы:
.model tiny
.code
.386p
org 100h
;;;;;;;;;;;;;;;;;;;;;;;
; Структура
;;;;;;;;;;;;;;;;;;;;;;;
; Сегментный дискриптор
segment_descriptor struc
limit_low dw 0 ; Младшие два байта поля Segment limit
base_low dw 0 ; Младшие два байта поля Base Address
base_high0 db 0 ; Второй байт поля Base Address
type_and_permit db 0 ; Флаги
flags db 0 ; Ещё одни флаги
base_highl db 0 ; Старший байт поля Base Address
segment_descriptor ends
;вместо дальних адресов в таблице прерываний используются
;дескрипторы специальных системных объектов, так называемых шлюзов
; Дескриптор шлюза
gate_descriptor struc
offset_low dw 0 ;Два младших байта поля Offset
selector dw 0 ; Поле Segment Selector
zero db 0
type_and_permit db 0 ;Флаги
offset_high dw 0 ; Старшие байты поля Offset
gate_descriptor ends
; Регистр, описывающий таблицу
table_register struc
limit dw 0 ; Table Limit
base dd 0 ; Linear Base Address
table_register ends
;;;;;;;;;;;;;;;;;;;;;;
; Код
;;;;;;;;;;;;;;;;;;;;;;
start:
;Подготавливаем DS
push cs
pop ds
;В es - начало видеобуфера. Можно было сделать то же
;самое средствами защищенного режима, но так проще
push 0b800h
pop es
;Устанавливаем правильный сегмент в long-jmp-to-RM
mov ax, cs
mov cs:rm_cs, ax
;Прописываем адрес начала cs в качестве базового адреса ;сегмента
call cs_to_eax
mov dsc64kb.base_low, ax
shr eax, 16
mov dsc64kb.base_high0, al
;Сохраняем IDTR реального режима
;sidt - Stores the Interrupt Descriptor Table (IDT) Register ;into the specified operand.
sidt fword ptr old_idtr
;Запретили прерывания
call disable_interrupts
;Инициализируем GDT
call initialize_gdt
;Инициализируем IDT
call initialize_idt
;Переключаем режим
call set_PE
;16-разрядный дальний переход. Переключает содержимое cs из нормального
;для реального режима (адрес) в нормальное для защищенного (селектор).
;Базовый адрес целевого сегмента совпадает с cs,
;поэтому смещение можно прописать сразу
db OEAh ;код команды дальнего перехода
dw $ + 4 ;смещение
dw 8 ;селектор
; В данный момент сегмент кода - 64 Кб, базовый адрес равен
; адресу сегмента кода до переключения в защищенный режим.
;Сохраняем состояние масок ПКП
in al, 021h
mov old_maskl, al
in al, 0Alh
mov old_mask2, al
;Инициализация ПКП, в bl и bh - базовые номера прерываний
;Ведущий ПКП ставим с 20h, ведомый с 28h
mov bl, 020h
mov bh, 028h
call initialize_pic
mov al, Ofbh ;на ведущем маскируем всё, кроме IRQ2
out 021h, al
mov al, Ofeh ;на ведомом маскируем все, кроме IRQ8 (RTC)
out OAlh, al
;Установка частоты периодического прерывания RTC
mov al, Oah
out 70h, al
in al, 71h
or al, 0fh ;2 раза в секунду
out 71h, al
;Разрешение периодического прерывания RTC
mov al, Obh
out 70h, al
in al, 71h
or al, 01000000b
out 71h, al
mov ecx, 10 ;в ecx - счётчик прерываний
call enable_interrupts
;в цикле ждём, пока прерывание произойдёт 10 раз и обнулит есх
test_end:
cmp ecx, 0
jne test_end
call disable_interrupts
;Запрещаем переодическое прерывание от RTC
mov al, 0bh
out 70h, al
in al, 71h
and al, 10111111b
out 71h, al
;Возвращаем стандартные номера прерываний
mov bl, 08h
mov bh, 70h
call initialize_pic
mov al, old_maskl ;снимаем маскировку
out 021h, al
mov al, old_mask2 ;снимаем маскировку
out 0Alh, al
call clear_PE
;Мы в реальном режиме, осталось разобраться с ; значением регистра cs
;16-разрядный дальний переход. Перключает содержимое cs из нормального
;для защищённог режима (селектор) в нормальное для реальног (адрес).
;Адрес сегмента вычисляется и прописывается во время выполнения,
db OEAh ; код команды дальнего перехода
dw $ + 4 ; смещение
rm_cs dw 0 ; сегмент
;восстанавливаем IDTR реального режима
lidt fword ptr old_idtr
;разрешаем прерывания
call enable_interrupts
ret
;;;;;;;;;;;;;;;;;;;;;;;;
; Обработчик прерывания
;;;;;;;;;;;;;;;;;;;;;;;;
;Обработчик прерывания RTC, ведёт обратный отсчёт
intRtc_handler:
push eax
push bax
dec ecx ;уменьшение ecx
mov bh, 07h ;белый текст на чёрном фоне
mov bl, '0'
add bl, cl ;если cl меньше 10, то в bl ;соответствующая цифра
mov eax, 80*24*2
mov word ptr es:[eax], bx
call upscroll_screen ; прокручивает экран на строчку вверх
;читаем из регистра 0Ch RTC, иначе прерываний больше не будет
mov al, 0ch
out 70h, al
in al, 71h
mov al, 020h ; "неспецифичное" завершение прерывания
out 020h, al ; на ведущем ПКП
out 0A0h, al ; на ведомом ПКП
pop ebx
pop eax
iretd ; 32-х разрядный возврат из прерывания
;;;;;;;;;;;;;;;;;;;;;
;Данные
;;;;;;;;;;;;;;;;;;;;;
;Глобальная таблица дескрипторов
GDT label byte
;Нулевой дескриптор
segment_descriptor <>
;Дескриптор сегмента кода, размер 64 Kb
dsc64kb segment_descriptor <0ffffh, 0, 0, 10011010b, 0, 0>
;10011010b - 1001, C/D - 1, 0, R/W - 1, 0
;0 - G - 0, 000, Limit - 0
;Данные для загрузки в GDTR
gdtr table_register <$ - GDT - 1, 0>
;Таблица дескрипторов прерываний
IDT label byte
db 32 dup ( 8 dup (0)) ; 0 - 1Fh
db 8 dup ( 8 dup (0)) ; 20h - 27h, ведущий ПКП
;Дескриптор шлюза прерывания.
;Обработчик прерывания находится в сегменте, соответствующем первому
;дескриптору GDT. Поскольку базовый адрес сегмента такой же, как
;в реальном режиме, смещение обработчика тоже совпадает.
gate_descriptor <intRtc_handler, 8, 0, 8Eh, 0>
;Данные для загрузки в IDTR
idtr table_register <$ - IDT - 1, 0>
;Место для хранения IDTR реального режима
old_idtr table_register <>
;Место для хранения старых значений масок ПКП
old_maskl db 0
old_mask2 db 0
;;;;;;;;;;;;;;;;;;;;;;
;Служебные функции
;;;;;;;;;;;;;;;;;;;;;;
;Инициализирует IDT (Interrupt Descriptor Table)
initialize_idt:
;Вычисляем линейный адрес начала массива дескрипторов
call cs_to_eax
add eax, offset IDT
;Записываем его в структуру
mov idtr.base, eax
;Загружаем IDTR
lidt fword ptr idtr
ret
;Инициализирует GDT глобальную дескрипторную таблицу
initialize_gdt:
;Вычисляем линейный адрес начала массива дескрипторов
call cs_to_eax
add eax, offset GDT
;Записываем его в структуру
mov gdtr.base, eax
;Загружаем GDTR (lgdt - Load Global Descriptor Table)
;команда выполняет загрузку 16 бит размера и 32 бит значения ;базового адреса начала таблицы GDT в памяти
;в системный регистр gdtr. Эта загрузка производится в ;соответствии с форматом этого регистра.
lgdt fword ptr gdtr
ret
;Запрещает маскируемые и немаскируемые прерывания
disable_interrupts:
cli ; запретить прерывания
in al, 70h ; индексный порт CMOS
or al, 80h ; установка бита 7 в нем запрещает NMI
out 70h, al
ret
; Разрешает маскируемые и немаскируемые прерывания
enable_interrupts:
in al, 70h ; индексный порт CMOS
and al, 7Fh ; сброс бита 7 отменяет блокирование NMI
out 70h, al
sti ; разрешить прерывания
ret
;Устанавливает флаг РЕ
set_PE:
mov eax, cr0 ; прочитать регистр CR0
or al, 1 ; установить бит РЕ,
mov cr0, eax ; с этого момента мы в защищенном режиме
ret
;Сбрасывает флаг РЕ
clear_PE:
mov eax, cr0 ;прочитать CR0
and al, 0FEh ;сбросить бит РЕ
mov cr0, eax ;с этого момента мы в реальном режиме
ret
;Вычисляет линейный адрес начала сегмента кода
cs_to_eax:
mov eax, 0
mov al, cs
shl eax, 4
ret
;Инициализация ПКП.
;bl - базовый номер прерывания ведущего ПКП
;bh - базовый номер прерывания ведомого ПКП
initialize_pic:
push eax
mov al, 00010001b ;ICW1
out 020h, al
out 0A0h, al
mov al, bl ;ICW2, ведущий
out 021h, al
mov al, bh ;ICW2, ведомый
out 0Alh, al
mov al, 00000100b ;ICW3, ведущий
out 021h, al
mov al, 2 ;ICW3, ведомый
out 0Alh, al
mov al, 00000001b
out 021h, al
out 0Alh, al
pop eax
ret
;Прокручивает экран на строчку вверх
upscroll_screen:
push eax
push ecx
push edx
mov eax, 0 ;Текущий символ
mov ecx, 80*24 ;Колическтво символов на экране
;(без последней строки, она отдельно)
screen_loop:
mov dx, word ptr es:[eax * 2 + 80*2]
mov word ptr es:[eax * 2], dx ;Меняем символ
inc eax
loop screen_loop
mov ecx, 80 ;Длинна последней строки
mov dx,0720h ;символ, которым заполняется строка
last_line_loop:
mov word ptr es:[eax * 2], dx ; Меняем символ
inc eax
loop last_line_loop
pop edx
pop ecx
pop eax
ret
end start