Пример простой программы переключения режима
Данная программа запускается в реальном режиме в среде 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