2. ВХОДИМ В ЗАЩИЩЁННЫЙ РЕЖИМ
2.1. Подготовка к переключению в защищённый режим
2.2. Переключение в защищённый режим
2.3. Возврат в реальный режим
2.4. Пример простой программы переключения режима
Задача второй главы - показать на простом примере, как составить
программу для MS-DOS, переключающую процессор из реального режима
в защищённый и возвращающую его обратно в реальный режим. Сам
процесс описан во многих книгах, посвящённых i80286, однако при
этом обычно опускаются многие технические детали, связанные с
аппаратным обеспечением компьютера. Знание этих деталей совершенно
необходимо для успешного использования защищенного режима на реальных
компьютерах.
Сейчас мы не будем рассматривать процесс обработки прерываний
в защищённом режиме - это материал следующей главы. Там же будет
приведён и соответствующий пример программы. В программе, которую
представим в этой главе, мы запретили прерывания на всё то время,
пока процессор находится в защищённом режиме.
Перед тем, как переключить процессор в защищённый режим, надо
выполнить некоторые подготовительные действия, а именно:
- Подготовить в оперативной памяти глобальную таблицу дескрипторов
GDT. В этой таблице должны быть созданы дескрипторы для всех сегментов,
которые будут нужны программе сразу после того, как она переключится
в защищённый режим. Впоследствии, находясь в защищённом режиме,
программа может модифицировать GDT (если, разумеется, она работает
в нулевом кольце защиты). Программа может модифицировать имеющиеся
дескрипторы или добавить новые, загрузив заново регистр GDTR.
- Для обеспечения возможности возврата из защищённого режима
в реальный необходимо записать адрес возврата в реальный режим
в область данных BIOS по адресу 0040h:0067h, а также записать
в CMOS-память в ячейку 0Fh код 5. Этот код обеспечит после выполнения
сброса процессора передачу управления по адресу, подготовленному
нами в области данных BIOS по адресу 0040h:0067h.
- Запретить все маскируемые и немаскируемые прерывания.
- Открыть адресную линию A20.
- Запомнить в оперативной памяти содержимое сегментных регистров,
которые необходимо сохранить для возврата в реальный режим, в
частности, указатель стека реального режима.
- Загрузить регистр GDTR.
Первый шаг, связанный с подготовкой GDT, мы уже описали, когда
рассказывали о преобразовании адресов в защищённом режиме.
Что же касается возврата из защищённого режима в реальный, то
он выполняется сбросом процессора, инициированного выводом определённого
байта в процессор клавиатуры 8042. Это связано с тем, что разработчики
процессора i80286 не предусмотрели никакой команды для переключения
процессора из защищённого режима в реальный. Есть ещё один способ
возврата в реальный режим, основанный на переводе процессора в
состояние отключения, он будет описан в главе, посвящённой обработке
прерываний в защищённом режиме.
После выполнения сброса (или после отключения) процессор переходит
в реальный режим и управление передаётся в BIOS. BIOS анализирует
содержимое ячейки CMOS-памяти с адресом 0Fh - байта состояния
отключения. Дальнейшие действия определяются содержимым этой ячейки.
Байт состояния отключения 0Fh используется BIOS для определения
способа возврата из защищённого режима в реальный после аппаратного
сброса. В таблице 3 перечислены возможные значения для байта состояния
отключения.
Таблица 3. Значения байта состояния отключения.
Значение | Причина отключения
|
0 | Программный сброс при нажатии комбинации клавиш CTRL-ALT-DEL или неожиданный сброс. Выполняется обычный перезапуск системы, но процедуры тестирования при включении питания не выполняются.
|
|
|
|
|
|
|
|
|
|
|
6,7,8 | Сброс после выполнения теста работы процессора в защищённом режиме.
|
9 | Сброс после выполнения пересылки блока памяти из основной памяти в расширенную.
|
0Ah | После сброса управление немедленно передаётся по адресу, взятому из области данных BIOS 0040h:0067h.
|
Для обеспечения возврата в реальный режим после сброса по адресу,
записанному в области данных BIOS 0040h:0067h можно использовать
байты 5 и 0Ah.
Из за особенностей обработки прерываний в защищённом режиме, которые
мы рассмотрим в третьей главе, перед переключением в защищённый
режим необходимо перепрограммировать контроллер прерываний. Восстановить
состояние контроллера после возврата в реальный режим можно автоматически,
если использовать значение 5 для байта состояния отключения.
Если же вы не используете прерывания и, соответственно, не перепрограммируете
контроллер прерываний, можно использовать значение 0Ah, при этом
после сброса управление будет сразу передано по адресу, взятому
из области данных BIOS 0040h:0067h. В этом случае затраченное
на возврат в реальный режим время будет меньше.
В следующем фрагменте программы мы записываем в ячейку CMOS-памяти
с адресом 0Fh значение 5.
Напомним, что для записи числа в ячейку CMOS-памяти необходимо
вначале в порт с адресом 70h записать номер нужной ячейки, а затем
в порт 71h - записываемые данные.
Не удивляйтесь, что в этом фрагменте программы вместо ячейки 0Fh
указано значение 8Fh - это не ошибка. Напомним, что единственный
способ замаскировать немаскируемые прерывания в компьютере IBM AT
- это записать в порт 70h байт, в котором старший бит установлен
в 1. Поэтому наш фрагмент программы не только записывает байт
состояния отключения, но и маскирует немаскируемые прерывания
(!). Нам необходимо также замаскировать обычные прерывания, поэтому
мы выдаём команду CLI.
cli
mov al,8f
out CMOS_PORT,al
jmp next1 ; небольшая задержка
next1:
mov al,5
out CMOS_PORT+1,al
Следующий шаг - открытие адресной линии A20 - необходим в том
случае, если ваша программа будет обращаться к оперативной памяти,
лежащей за пределами первого мегабайта.
Приведём процедуру, которую можно использовать для этой цели:
; ------------------------------------------------------------
; Процедура открывает адресную линию 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_ON
клавиатурному процессору 8042, к которому подключены схемы управления
адресной линией A20. После начального сброса линия A20 закрыта,
и расширенная память за границами первого мегабайта недоступна.
Следующий этап - запоминание содержимого сегментных регистров,
которые будут нужны при возврате в реальный режим. Это сегментные
регистры SS и ES:
mov [real_ss],ss ; запоминаем указатель стека
mov [real_es],es ; для реального режима
На последнем перед переключением в защищённый режим этапе мы загружаем
регистр GDTR адресом подготовленной заранее GDT:
lgdt [QWORD gdt_gdt]
Всё! Можно переключаться в защищённый режим!
Это самый простой этап. Для перевода процессора i80286 из реального
режима в защищённый можно использовать специальную команду LMSW,
загружающую регистр состояния процессора (Mashine Status Word).
Младший бит этого регистра указывает режим работы процессора.
Значение, равное 0, соответствует реальному режиму работы, а значение
1 - защищённому.
Если установить младший бит регистра состояния процессора в 1,
процессор переключится в защищённый режим:
mov ax, 1
lmsw ax
К сожалению, с помощью команды LMSW невозможно переключить процессор
обратно в реальный режим. Для этого необходимо использовать другой
способ.
Для того, чтобы вернуть процессор 80286 из защищённого режима
в реальный, необходимо выполнить аппаратный сброс (отключение)
процессора. Это можно сделать следующим образом:
mov ax, 0FEh ; команда отключения
out 64h, ax
Перед выдачей команды отключения необходимо запомнить содержимое
регистра SP, так как после передачи управления по адресу, записанному
в области данных BIOS 0040h:0067h, регистры SS:SP будет указывать
на стек BIOS.
После выдачи команды отключения надо подождать, когда произойдёт
сброс процессора. Это можно сделать, выдавая в цикле команду HLT.
Вот фрагмент программы, возвращающий процессор в реальный режим:
; Запоминаем содержимое указателя стека, так как после
; сброса процессора оно будет потеряно
mov [real_sp],sp
; Выполняем сброс процессора
mov al,SHUT_DOWN
out STATUS_PORT,al
; Ожидаем сброса процессора
wait_reset:
hlt
jmp wait_reset
Далее необходимо восстановить содержимое сегментных регистров,
записанное в оперативную память на этапе подготовки к переключению
в защищённый режим, закрыть адресную линию A20 и размаскировать
прерывания.
Для закрытия линии 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
Следующая последовательность команд размаскирует все прерывания:
mov ax,000dh ; разрешаем немаскируемые прерывания
out CMOS_PORT,al
in al,INT_MASK_PORT ; разрешаем маскируемые прерывания
and al,0
out INT_MASK_PORT,al
sti
Теперь, когда мы знаем всё, что нужно для переключения процессора
в защищённый режим и возврата в реальный режим, можно приступить
к практической работе в защищённом режиме.
Данная программа запускается в реальном режиме в среде MS-DOS,
переключает процессор в защищённый режим, выдаёт сообщение и через
некоторое время возвращает процессор в реальный режим, стирает
экран и завершает своё выполнение.
Программа подготовлена с помощью транслятора Borland Turbo Assembler
и использует режим IDEAL. Для её трансляции был использован следующий
командный файл:
tasm %1.asm /l /zi
tlink %1.obj /v
Вы можете запускать эту программу на любой машине, совместимой
с 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_SIZE-1,,,DATA_ACC,0>
gdt_ds desc_struc <DSEG_SIZE-1,,,DATA_ACC,0>
gdt_cs desc_struc <CSEG_SIZE-1,,,CODE_ACC,0>
gdt_ss desc_struc <STACK_SIZE-1,,,DATA_ACC,0>
gdt_bio desc_struc <B_DATA_SIZE-1,B_DATA_ADDR,0,DATA_ACC,0>
gdt_crt desc_struc <CRT_SIZE-1,CRT_LOW,CRT_SEG,DATA_ACC,0>
gdt_mda desc_struc <MONO_SIZE-1,MONO_LOW,CRT_SEG,DATA_ACC,0>
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
|