к содержанию

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.

Это ограничение связано с механизмом преобразования логического адреса в физический.

Итак, наша первая программа для защищённого режима процессора 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
к содержанию
Hosted by uCoz