§ Возврат в режим реальных адресов

Практически защищённый режим процессора можно использовать двумя способами:
  • Операционная система. Процессор входит в P-Mode при загрузке ОС и не возвращается в R-Mode. Примером таких ОС являются Windows 95 и старше либо специализированные ОС, управляющие контроллерами на базе 32-разрядных процессоров.
  • DOS-программа. Программа запускается в ОС, которая работает в R-Mode (например, MS-DOS), переходит в P-Mode, выполняет свою работу, возвращается обратно в R-Mode и завершает свою работу. Примеров множество: это hymem.sys, обеспечивающий через P-Mode работу с памятью выше 1-го мегабайта (XMS-память); также это игрушки, работающие через Dos4GW - предзагружаемую ОС защищённого режима; это Windows 3.xx.
Второй способ, с возвратом в режим реальных адресов, предоставляет программисту больше возможностей - ведь не все пишут полнофункциональные операционные системы...
Фактически, переход в режим реальных адресов может быть только из защищённого режима и осуществляется сбросом бита PE в CR0:
mov    eax, cr0
and    al, 0feh
mov    cr0, eax
или так:
mov    eax, cr0
btr    eax, 0
mov    cr0, eax
Вы наверное уже догадались, что на самом деле не всё так просто. Действительно, выполнение трёх вышеприведенных команд переведёт процессор в R-Mode, но дальше он повиснет либо произойдёт аппаратный сброс, потому что программная среда не будет соответствовать режиму реальных адресов.
Для корректного перехода из P-Mode в R-Mode необходимо подготовить процессор следующим образом:
  • Запретить прерывания (CLI)
  • Передать управление в читаемый сегмент кода, имеющий предел в 64Кб (FFFFh)
Загрузить в SS, DS, ES, FS и GS селекторы дескрипторов, имеющих следующие параметры
  • Предел = 64 Кб (FFFFh)
  • Байтная гранулярность (G = 0)
  • Расширяется вверх (E = 0)
  • Записываемый (W = 1)
  • Присутствующий (P = 1)
  • Базовый адрес = любое значение
Сегментные регистры должны быть загружены ненулевыми селекторами. Те сегментные регистры, в которые не будут загружены описанные выше значения, будут использоваться с атрибутами, установленными в защищённом режиме.
  • Сбросить флаг PE в CR0
  • Выполнить команду far jmp на программу режима реальных адресов
  • Загрузить в регистры SS, DS, ES, FS и GS необходимые значения или 0
  • Разрешить прерывания (STI)
В предыдущем примере мы просто перевели процессор в защищённый режим, при этом не используя особенности прерываний в P-Mode и не подключали механизм виртуальной памяти. Реализация каждой из этих технологий немного усложнит возврат в R-Mode и в разделах, описывающих эти технологии будут приведены соответствующие примеры перехода в P-mode и возврата из него.
Здесь подразумевается, что вы уже ознакомились со всеми предыдущими главами, поэтому предварительного подробного описания перехода в R-Mode не будет и мы перейдём сразу к примеру.
Этот пример является полноценной программой и в нём без комментариев повторяется переход в P-Mode. Процесс подготовки дескрипторов и GDTR зависит от предназначения каждого примера и по этой причине я не вынес его в отдельную функцию а полностью описываю каждый раз.
Пример ориентирован на использование как самостоятельная .com-программа. Это сделано по следующим причинам:
  • Пользоваться обычной программной средой - библиотеки языков высокого уровня, прерывания MS-DOS и прочими особенностями этой ОС из защищённого режима вы не сможете.
  • Размеры кода и данных в данном примере не большие и удобно использовать формат файла .com.
  • COM-программу можно просто загрузить в память как блок данных и, передав управление на смещение 100h, использовать как оверлей.
  • Вся мощь защищённого режима раскрывается при использовании 32-разрядного кода и данных. Инициализация защищённого режима должна происходить 16-разрядным кодом и 32-разрядные программы удобно использовать как отдельные модули.
Также хочу обратить ваше внимание на то, что в этом примере адреса сегментов для защищённого режима совпадают с адресами сегментов, используемых в R-Mode. В результате, обращение к памяти происходит непосредственно через метки, определённые в исходнике (в предыдущем примере для этого требовалось из адреса метки вычитать адрес начала сегмента данных).
Пример используется вместе с файлом "pmode.lib.asm".
Пример 3. Вход в защищённый режим и возврат в режим реальных адресов.
    org     100h
;------------------------------------------------------------------------
; Определяем селекторы как константы. У всех у них биты TI = 0 (выборка
;  дескрипторов производится из GDT), RPL = 00B - уровень привилегий -
;  нулевой.

    Code_selector       equ  8
    Stack_selector      equ 16
    Data_selector       equ 24
    Screen_selector     equ 32
    R_Mode_Code         equ 40    ; Селектор дескриптора сегмента кода для возврата в режим реальных адресов
    R_Mode_Data         equ 48    ; Селектор дескриптора сегментов стека и данных

;------------------------------------------------------------------------

; Сохраняем сегментные регистры, используемые в R-Mode:

    mov    [R_Mode_SS], ss
    mov    [R_Mode_DS], ds
    mov    [R_Mode_ES], es
    mov    [R_Mode_FS], fs
    mov    [R_Mode_GS], gs

; Подготавливаем адрес возврата в R-Mode:

    mov    [R_Mode_segment], cs
    lea    ax, [R_Mode_entry]
    mov    [R_Mode_offset], ax

; Подготовка к переходу в защищённый режим:

    mov    bx, GDT + 8
    xor    eax, eax
    mov    edx, eax

    push   cs
    pop    ax

    shl    eax, 4
    mov    dx, 65535
    mov    cl, 10011000b
    call   set_descriptor        ; Code

    lea    dx, [Stack_seg_start]
    add    eax, edx
    mov    dx, 1024
    mov    cl, 10010110b
    call   set_descriptor        ; Stack

    xor    eax, eax
    mov    ax, ds
    shl    eax, 4
    mov    dx,0ffffh
    mov    cl, 10010010b
    call   set_descriptor        ; Data

    mov    eax, 0b8000h
    mov    edx, 4000
    mov    cl, 10010010b
    call   set_descriptor        ; Screen

; Готовим дополнительные дескрипторы для возврата в R-Mode:

    xor    eax,eax
    push   cs
    pop    ax
    shl    eax, 4       ; EAX = физический адрес сегмента кода
                        ;  (и всех остальных сегментов, т.к.
                        ;  это .com-программа)

    mov    edx,0ffffh
    mov    cl, 10011010b       ; P=1, DPL=00b, S=1, Тип=101b, A=0
    call   set_descriptor      ; R_Mode_Code

    mov    cl, 10010010b       ; P=1, DPL=00b, S=1, Тип=001b, A=0
    call   set_descriptor      ; R_Mode_Data

; Устанавливаем GDTR:

    xor    eax, eax
    mov    edx, eax

    mov    ax, ds
    shl    eax, 4
    lea    dx, [GDT]
    add    eax, edx
    mov    [GDT_adr],eax

    mov    dx, 55           ; Предел GDT = 8 * (1 + 6) - 1
    mov    [GDT_lim], dx

    cli
    lgdt   [GDTR]
    mov    [R_Mode_SP], sp  ; Указатель на стек сохраняем в последний момент.

; Переходим в защищённый режим:

    mov    eax, cr0
    or     al, 1
    mov    cr0, eax

; Процессор в защищённом режиме

    db    0eah              ; Команда far jmp Code_selector:P_Mode_entry.
    dw    P_Mode_entry
    dw    Code_selector

    include    "pmode.lib.asm"

;------------------------------------------------------------------------
P_Mode_entry:

    mov    ax, Screen_selector
    mov    es, ax

    mov    ax, Data_selector
    mov    ds, ax

    mov    ax, Stack_selector
    mov    ss, ax
    mov    sp, 0

; Сообщаем о входе в P-Mode (выводим ZS-строку):

    lea    bx, [Start_P_Mode_ZS]
    mov    di, 480
    call   putzs

; Работа программы в защищённом режиме (здесь - только вывод строки):

    lea    bx, [P_Mode_ZS]
    add    di, 160
    call   putzs

; ----------------------------------------------------------------------
; Возвращаемся в режим реальных адресов.
; 1. Запретить прерывания (CLI).
;    Прерывания уже запрещены при входе в P-Mode.

; 2. Передать управление в читаемый сегмент кода, имеющий предел в 64Кб.
; ----------------------------------------------------------------------

    db    0eah              ; Команда far jmp R_Mode_Code:Pre_R_Mode_entry.
    dw    Pre_R_Mode_entry
    dw    R_Mode_Code


Pre_R_Mode_entry:

; 3. Загрузить в SS, DS, ES, FS и GS селекторы дескрипторов, имеющих
;    следующие параметры:
;  1) Предел = 64 Кб (FFFFh)
;  2) Байтная гранулярность (G = 0)
;  3) Расширяется вверх (E = 0)
;  4) Записываемый (W = 1)
;  5) Присутствующий (P = 1)
;  6) Базовый адрес = любое значение

    mov    ax, R_Mode_Data    ; Селектор R_Mode_Data - "один на всех".
    mov    ss, ax
    mov    ds, ax
    mov    es, ax
    mov    fs, ax
    mov    gs, ax

; 4. Сбросить флаг PE в CR0.

    mov    eax, cr0
    and    al, 0feh        ; FEh = 1111'1110b
    mov    cr0, eax

; 5. Выполнить команду far jump на программу режима реальных адресов.

        db    0eah
        R_Mode_offset       dw    ?    ; Значения R_Mode_offset и R_Mode_segment
        R_Mode_segment      dw    ?    ; сюда прописались перед входом в
                                       ; защищённый режим (в начале программы).
;------------------------------------------------------------------------
R_Mode_entry:

; 6. Загрузить в регистры SS, DS, ES, FS и GS необходимые значения или 0
;    (восстанавливаем сохранённые значения):

    mov    ss, [R_Mode_SS]
    mov    ds, [R_Mode_DS]
    mov    es, [R_Mode_ES]
    mov    fs, [R_Mode_FS]
    mov    gs, [R_Mode_GS]
    mov    sp, [R_Mode_SP]    ; Восстанавливаем указатель стека
                              ;  непосредственно перед разрешением
                              ;  прерываний.

; 7. Разрешить прерывания (STI).

    sti

; Выводим ZS-строку "Back to real address mode..."

    lea    bx, [R_Mode_ZS]
    mov    ax, 0b800h
    mov    es, ax
    mov    di, 800
    call   putzs        ; Функция putzs универсальна и работает
                        ;  в обоих режимах.

    int    20h          ; Конец программы (здесь - выход в MS-DOS).

;------------------------------------------------------------------------
; ZS-строка для вывода при входе в P-Mode:
Start_P_Mode_ZS:    db    "Entering to protected mode...",0

; ZS-строка для вывода при работе в P-Mode:
P_Mode_ZS:    db    "Working in P-mode...",0

; ZS-строка для вывода в R-Mode:
R_Mode_ZS:    db    "Back to real address mode...",0

;------------------------------------------------------------------------
; Значения регистров, которые программа имела до перехода в P-Mode:
R_Mode_SP    dw    ?
R_Mode_SS    dw    ?
R_Mode_DS    dw    ?
R_Mode_ES    dw    ?
R_Mode_FS    dw    ?
R_Mode_GS    dw    ?
;------------------------------------------------------------------------
; Образ регистра GDTR:

GDTR:
GDT_lim        dw    ?
GDT_adr        dd    ?
;------------------------------------------------------------------------
GDT:
    dd    ?,?    ; 0-й дескриптор
    dd    ?,?    ; 1-й дескриптор (кода)
    dd    ?,?    ; 2-й дескриптор (стека)
    dd    ?,?    ; 3-й дескриптор (данных)
    dd    ?,?    ; 4-й дескриптор (видеопамяти)
    dd    ?,?    ; 5-й дескриптор (код для перехода в R-Mode)
    dd    ?,?    ; 6-й дескриптор (стек и данные для перехода в R-Mode)
;------------------------------------------------------------------------
    db    1024 dup (?)      ; Зарезервировано для стека.

Stack_seg_start:            ; Последняя метка программы - отсюда будет расти стек.