Данная программа
запускается в реальном режиме в среде MS-DOS,
переключает процессор в защищённый режим,
выдаёт сообщение и через некоторое время
возвращает процессор в реальный режим,
стирает экран и завершает своё выполнение.
Программа подготовлена с
помощью транслятора Borland Turbo Assembler и
использует режим IDEAL. Для её трансляции был
использован следующий командный файл:
Вы можете
запускать эту программу на любой машине,
совместимой с IBM AT и оборудованной
процессорами i80286, i80386, i80486. Но вы не должны
запускать эту программу, если у вас
компьютер на базе процессоров i80386 или i80486 и
активны драйверы QEMM, EMM386 - отключите эти
драйверы. Кроме того, эту программу нельзя
запускать на виртуальной машине в среде
Microsoft WINDOWS в режиме Enchanced Mode или на
виртуальной машине в среде OS/2 версии 2.0.
Это связано с тем, что в
перечисленных выше случаях процессор
работает не в реальном режиме, а в так
называемом режиме виртуального процессора
8086. Этот режим возможен только для
процессоров i80386 или i80486.
Кроме того, не следует
запускать эту программу на компьютерах
серии PS/2, так как в них используется другой
способ управления линией A20 и другой способ
сброса процессора для возврата в реальный
режим.
Вы можете использовать эту
программу для первых экспериментов с
защищённым режимом.
Обратим ваше внимание на
некоторые ограничения защищённого режима.
Эти ограничения связаны в основном с
использованием прерываний и сегментных
регистров.
Вашей программе,
работающей в защищённом режиме, недоступны
ни прерывания BIOS, ни прерывания DOS. Любой
обмен данными с периферийными устройствами
(дисплеем, клавиатурой, диском или
принтером) должен выполняться на уровне
команд ввода/вывода. Т.е. программа должна
работать непосредственно с портами
аппаратуры компьютера.
Это ограничение связано с
тем, что обработчики прерываний BIOS и DOS
рассчитаны на работу в реальном режиме
процессора. Попытка вызова этих
обработчиков в защищенном режиме неизбежно
приведёт к аварийному завершению программы.
Поэтому в приведённой ниже
программе вывод текстовых строк на экран
производится при помощи непосредственной
записи в память видеоконтроллера. Что же
касается ввода данных с клавиатуры, то
здесь не обойтись без обработки прерывания
от клавиатуры. В следующей главе мы
приведём пример программы, которая не
только выводит данные на экран дисплея (также
через запись в видеопамять), но и умеет
работать с клавиатурой и таймером.
Если ваша программа
составлена на языках высокого уровня (Си
или Паскаль), ей недоступна практически вся
библиотека стандартных функций. Особенно
это касается функций ввода/вывода и
управления памятью. Причина очевидна -
большинство функций стандартных библиотек
трансляторов вызывает те или иные
прерывания BIOS или DOS.
Программы не должны
загружать в сегментные регистры значения,
которые не являются правильными
селекторами, описанными в соответствующих
дескрипторных таблицах. Несмотря на то, что
сама по себе загрузка произвольного
значения в сегментный регистр не опасна,
попытка использования неправильно
загруженного сегментного регистра вызовет
аварийное завершение работы программы.
Это ограничение связано с
механизмом преобразования логического
адреса в физический.
Итак, наша первая программа
для защищённого режима процессора 80286:
Листинг 1. Демонстрация переключения в защищённый режим
и возврата обратно в реальный режим
---------------------------------------------------------
IDEAL
RADIX 16
P286
; Используем модель памяти LARGE, при этом мы организуем
; несколько отдельных сегментов и для каждого сегмента
; создадим дескриптор в таблице GDT.
MODEL LARGE
;----------------------------------------
; Определения структур данных и констант
;----------------------------------------
STRUC desc_struc ; структура дескриптора
limit dw 0 ; предел
base_l dw 0 ; мл. слово физического адреса
base_h db 0 ; ст. байт физического адреса
access db 0 ; байт доступа
rsrv dw 0 ; зарезервировано
ENDS desc_struc
; Биты байта доступа
ACC_PRESENT EQU 10000000b ; сегмент есть в памяти
ACC_CSEG EQU 00011000b ; сегмент кода
ACC_DSEG EQU 00010000b ; сегмент данных
ACC_EXPDOWN EQU 00000100b ; сегмент расширяется вниз
ACC_CONFORM EQU 00000100b ; согласованный сегмент
ACC_DATAWR EQU 00000010b ; разрешена запись
; Типы сегментов
; сегмент данных
DATA_ACC = ACC_PRESENT OR ACC_DSEG OR ACC_DATAWR
; сегмент кода
CODE_ACC = ACC_PRESENT OR ACC_CSEG OR ACC_CONFORM
; сегмент стека
STACK_ACC = ACC_PRESENT OR ACC_DSEG OR
ACC_DATAWR OR ACC_EXPDOWN
; Константы
STACK_SIZE EQU 0400 ; размер стека
B_DATA_SIZE EQU 0300 ; размер области данных BIOS
B_DATA_ADDR EQU 0400 ; адрес области данных BIOS
MONO_SEG EQU 0b000 ; сегмент видеопамяти
; монохромного видеоадаптера
COLOR_SEG EQU 0b800 ; сегмент видеопамяти
; цветного видеоадаптера
CRT_SIZE EQU 4000 ; размер сегмента видеопамяти
; цветного видеоадаптера
MONO_SIZE EQU 1000 ; размер сегмента видеопамяти
; монохромного видеоадаптера
CRT_LOW EQU 8000 ; мл. байт физического адреса
; сегмента видеопамяти
; цветного видеоадаптера
MONO_LOW EQU 0000 ; мл. байт физического адреса
; сегмента видеопамяти
; монохромного видеоадаптера
CRT_SEG EQU 0Bh ; ст. байт физического адреса
; сегмента видеопамяти
; Селекторы, определённые в таблице GDT
DS_DESCR = (gdt_ds - gdt_0)
CS_DESCR = (gdt_cs - gdt_0)
SS_DESCR = (gdt_ss - gdt_0)
BIOS_DESCR = (gdt_bio - gdt_0)
CRT_DESCR = (gdt_crt - gdt_0)
MDA_DESCR = (gdt_mda - gdt_0)
CMOS_PORT EQU 70h ; порт для доступа к CMOS-памяти
PORT_6845 EQU 0063h; адрес области данных BIOS,
; где записано значение адреса
; порта контроллера 6845
COLOR_PORT EQU 03d4h; порт цветного видеоконтроллера
MONO_PORT EQU 03b4h; порт монохромного видеоконтроллера
STATUS_PORT EQU 64h ; порт состояния клавиатуры
SHUT_DOWN EQU 0feh ; команда сброса процессора
VIRTUAL_MODE EQU 0001h; бит перехода в защищённый режим
A20_PORT EQU 0d1h ; команда управления линией A20
A20_ON EQU 0dfh ; открыть A20
A20_OFF EQU 0ddh ; закрыть A20
KBD_PORT_A EQU 60h ; адреса клавиатурных
KBD_PORT_B EQU 61h ; портов
INT_MASK_PORT EQU 21h ; порт для маскирования прерываний
STACK STACK_SIZE ; сегмент стека
DATASEG ; начало сегмента данных
DSEG_BEG = THIS WORD
; Память для хранения регистров SS, SP, ES. Содержимое
; этих регистров будет записано здесь перед входом в
; защищённый режим и восстановлено отсюда после возврата
; из защищённого режима в реальный.
real_ss dw ?
real_sp dw ?
real_es dw ?
;Глобальная таблица дескрипторов GDT,
;содержит следующие дескрипторы:
;
;gdt_0 - дескриптор для пустого селектора
;gdt_gdt - дескриптор для GDT
;gdt_ds - дескриптор для сегмента, адресуемого DS
;gdt_cs - дескриптор для сегмента кода
;gdt_ss - дескриптор для сегмента стека
;gdt_bio - дескриптор для области данных BIOS
;gdt_crt - дескриптор для видеопамяти цветного дисплея
;gdt_mda - дескриптор для видеопамяти монохромного дисплея
GDT_BEG = $
LABEL gdtr WORD
gdt_0 desc_struc <0,0,0,0,0>
gdt_gdt desc_struc
gdt_ds desc_struc
gdt_cs desc_struc
gdt_ss desc_struc
gdt_bio desc_struc
gdt_crt desc_struc
gdt_mda desc_struc
GDT_SIZE = ($ - GDT_BEG) ; размер таблицы дескрипторов
CODESEG ; сегмент кода
PROC start
; Инициализируем регистр сегмента данных
; для реального режима
mov ax,DGROUP
mov ds,ax
; Определяем базовый адрес видеопамяти
call set_crt_base
; Стираем экран дисплея (устанавливаем серый фон)
mov bh, 77h
call clrscr
;Выполняем все подготовительные действия для перехода
;в защищённый режим и обеспечения возможности возврата
;в реальный режим
call init_protected_mode
;Переключаемся в защищённый режим
call set_protected_mode
;----- * Программа работает в защищённом режиме! * -----
call write_hello_msg ; выводим сообщение на экран
call pause ; ждём некоторое время
; Возвращаемся в реальный режим
call set_real_mode
;------ * Программа работает в реальном режиме! * ------
; Стираем экран и возвращаемся в DOS
mov bh, 07h
call clrscr
mov ah,4Ch
int 21h
ENDP start
;--------------------------------------------------
; Макрокоманда для записи в дескриптор 24-битового
; базового адреса сегмента
;--------------------------------------------------
MACRO setgdtentry
mov [(desc_struc bx).base_l],ax
mov [(desc_struc bx).base_h],dl
ENDM
;-------------------------------------------------------
;Процедура подготовки процессора к переходу в защищённый
;режим с последующим возвратом в реальный режим
;-------------------------------------------------------
PROC init_protected_mode NEAR
; Заполняем глобальную таблицу дескрипторов GDT
; Вычисляем 24-битовый базовый адрес сегмента данных
mov ax,DGROUP
mov dl,ah
shr dl,4
shl ax,4
;Регистры dl:ax содержат базовый адрес,
;сохраняем его в di:si
mov si,ax
mov di,dx
; Подготавливаем дескриптор для GDT
add ax,OFFSET gdtr
adc dl,0
mov bx,OFFSET gdt_gdt
setgdtentry
; Подготавливаем дескриптор для сегмента ds
mov bx,OFFSET gdt_ds
mov ax,si
mov dx,di
setgdtentry
; Подготавливаем дескриптор для сегмента cs
mov bx,OFFSET gdt_cs
mov ax,cs
mov dl,ah
shr dl,4
shl ax,4
setgdtentry
; Подготавливаем дескриптор для сегмента стека
mov bx,OFFSET gdt_ss
mov ax,ss
mov dl,ah
shr dl,4
shl ax,4
setgdtentry
; Записываем адрес возврата в реальный режим в область
; данных BIOS по адресу 0040h:0067h
push ds
mov ax,40
mov ds,ax
mov [WORD 67],OFFSET shutdown_return
mov [WORD 69],cs
pop ds
;Маскируем все прерывания, в том числе немаскируемые.
;Записываем в CMOS-память в ячейку 0Fh код 5,
;этот код обеспечит после выполнения сброса процессора
;передачу управления по адресу, подготовленному нами
;в области данных BIOS по адресу 0040h:0067h.
;Для того, чтобы немаскируемые прерывания были запрещены,
;устанавливаем в 1 старший бит при определении
;ячейки CMOS.
cli
mov al,8f
out CMOS_PORT,al
jmp next1 ; небольшая задержка
next1:
mov al,5
out CMOS_PORT+1,al ; код возврата
ret
ENDP init_protected_mode
;---------------------------------------------------
;Процедура переключает процессор в защищённый режим
;---------------------------------------------------
PROC set_protected_mode NEAR
mov ax,[rl_crt] ; записываем в es сегментный
mov es,ax ; адрес видеопамяти
call enable_a20 ; открываем адресную линию A20
mov [real_ss],ss ; запоминаем указатель стека
mov [real_es],es ; для реального режима
; Загружаем регистр GDTR
lgdt [QWORD gdt_gdt]
;Устанавливаем защищённый режим работы процессора
mov ax,VIRTUAL_MODE
lmsw ax
; Мы находимся в защищённом режиме
; Очищаем внутреннюю очередь команд процессора
; Выполняем команду межсегментного прерхода,
; в качестве селектора указываем селектор текущего
; сегмента кода, в качестве смещения - метку flush
; jmp far flush
db 0ea
dw OFFSET flush
dw CS_DESCR
LABEL flush FAR
; Загружаем сегментные регистры SS и DS селекторами
mov ax,SS_DESCR
mov ss,ax
mov ax,DS_DESCR
mov ds,ax
ret
ENDP set_protected_mode
;------------------------------------------------
;Процедура возвращает процессор в реальный режим
;------------------------------------------------
PROC set_real_mode NEAR
; Запоминаем содержимое указателя стека, так как после
; сброса процессора оно будет потеряно
mov [real_sp],sp
; Выполняем сброс процессора
mov al,SHUT_DOWN
out STATUS_PORT,al
; Ожидаем сброса процессора
wait_reset:
hlt
jmp wait_reset
;--->> В это место мы попадём после сброса процессора,
;теперь мы снова в реальном режиме
LABEL shutdown_return FAR
; Инициализируем ds адресом сегмента данных
mov ax,DGROUP
mov ds,ax
assume ds:DGROUP
; Восстанавливаем указатель стека
mov ss,[real_ss]
mov sp,[real_sp]
; Восстанавливаем содержимое регистра es
mov es,[real_es]
; Закрываем адресную линию A20
call disable_a20
; Разрешаем все прерывания
mov ax,000dh ; разрешаем немаскируемые прерывания
out CMOS_PORT,al
in al,INT_MASK_PORT ;разрешаем маскируемые прерывания
and al,0
out INT_MASK_PORT,al
sti
ret
ENDP set_real_mode
;---------------------------------------
;Процедура открывает адресную линию A20
;---------------------------------------
PROC enable_a20 NEAR
mov al,A20_PORT
out STATUS_PORT,al
mov al,A20_ON
out KBD_PORT_A,al
ret
ENDP enable_a20
;---------------------------------------
;Процедура закрывает адресную линию A20
;---------------------------------------
PROC disable_a20 NEAR
mov al,A20_PORT
out STATUS_PORT,al
mov al,A20_OFF
out KBD_PORT_A,al
ret
ENDP disable_a20
;-------------------------------------------------
;Процедура выполняет небольшую временную задержку
;-------------------------------------------------
PROC pause NEAR
push cx
mov cx,50
ploop0:
push cx
xor cx,cx
ploop1:
loop ploop1
pop cx
loop ploop0
pop cx
ret
ENDP pause
;-------------------------------------------------------
;Сегмент данных для процедур обслуживания видеоадаптера
;-------------------------------------------------------
DATASEG
columns db 80d ; количество столбцов на экране
rows db 25d ; количество строк на экране
rl_crt dw COLOR_SEG ; сегментный адрес видеобуфера
vir_crt dw CRT_DESCR ; селектор видеобуфера
curr_line dw 0d ; номер текущей строки
CODESEG
;----------------------------------------
;Определение базового адреса видеобуфера
;----------------------------------------
PROC set_crt_base NEAR
; Определяем количество столбцов на экране и записываем
; в переменную columns
mov ax,40
mov es,ax
mov bx,[WORD es:4a]
mov [columns],bl
; То же для количества строк, записываем в переменную rows
mov bl,[BYTE es:84]
inc bl
mov [rows],bl
; Для того чтобы определить тип видеоконтроллера (цветной
; или монохромный), считываем адрес микросхемы 6845
mov bx,[WORD es:PORT_6845]
cmp bx,COLOR_PORT
je set_crt_exit
;Если видеоконтроллер монохромный, изменяем адрес сегмента
;и селектор, заданные по умолчанию
mov [rl_crt],MONO_SEG
mov [vir_crt],MDA_DESCR
set_crt_exit:
ret
ENDP set_crt_base
;----------------------------------------------------
;Вывод строки на экран
;Параметры:
; (ax, bx) - координаты (x, y) выводимой строки
; ds:si - адрес выводимой строки
; cx - длина выводимой строки
; dh - атрибут выводимой строки
; es - сегмент или селектор видеопамяти
;----------------------------------------------------
PROC writexy NEAR
push si
push di
; Вычисляем смещение в видеобуфере для записи строки,
; используем формулу ((y * columns) + x) * 2
mov dl,[columns]
mul dl
add ax,bx
shl ax,1
mov di,ax
mov ah,dh ; записываем в ah байт атрибута
; Выполняем запись в видеобуфер
wxy_write:
lodsb ; очередной символ в al
stosw ; записываем его в видеопамять
loop wxy_write ; цикл до конца строки
pop di
pop si
ret
ENDP writexy
;-------------------------------------------------
;Процедура стирания экрана
; Параметр: bh - атрибут для заполнения экрана
;-------------------------------------------------
PROC clrscr NEAR
xor cx,cx
mov dl,[columns]
mov dh,[rows]
mov ax,0600h
int 10h
ret
ENDP clrscr
DATASEG
hello_msg db " Protected mode monitor *TINY/OS*,
v.1.0 for CPU 80286 ¦ © Frolov A.V., 1992 "
CODESEG
;------------------------------------------------
;Процедура выводит сообщение в защищённом режиме
;------------------------------------------------
PROC write_hello_msg NEAR
mov ax,[vir_crt] ; загружаем селектор видеопамяти
mov es,ax ; в регистр es
; Выводим сообщение в верхний левый угол экрана (x=y=0)
mov bx,0 ;(X,Y) = (AX,BX)
mov ax,[curr_line]
inc [curr_line] ; увеличиваем номер текущей строки
; Загружаем адрес выводимой строки и её длину
mov si,OFFSET hello_msg
mov cx,SIZE hello_msg
mov dh,30h ; аттрибут - черный текст на голубом фоне
call writexy ; выводим строку
ret
ENDP write_hello_msg
CSEG_SIZE = ($ - start) ; размер сегмента кода
DATASEG
DSEG_SIZE = ($ - DSEG_BEG) ; размер сегмента данных
END start
[ << Назад ] [ Содержание ] [ Далее >> ]