Главная ]
2.4.Пример простой программы переключения режима
Программирование
Базы данных



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

<< Назад ] Содержание ] Далее >> ]

Дизайн: Piton Alien
Rambler's Top100 Рейтинг@Mail.ru
Сайт создан в системе uCoz